Android電池管理系統(tǒng)總體實現(xiàn):
電池管理在Android系統(tǒng)中的主要作用是檢測我們的電池狀態(tài),剩余電量實時更新,高溫報警,低電量關(guān)機等。Android的電池管理模塊,從Android的應(yīng)用層到底層內(nèi)核分為了4層來理解,從上到下依次為,應(yīng)用層,framwork層,本地框架層,內(nèi)核驅(qū)動層。
一、Android 電池服務(wù)
Android電池服務(wù)BatteryService,用來監(jiān)聽內(nèi)核上報的電池事件,并將最新的電池數(shù)據(jù)上報給系統(tǒng),系統(tǒng)收到新數(shù)據(jù)后會去更新電池顯示狀態(tài)、剩余電量等信息。如果收到過溫報警和低電報警,系統(tǒng)會自動觸發(fā)關(guān)機流程,保護電池和機器不受到危害。
Android電池服務(wù)的源碼結(jié)構(gòu) :
frameworks/base/services/java/com/android/server/
├── SystemServer.java
創(chuàng)建BatteryService、PowerManagerService、ActivityManagerService
frameworks/base/services/core/java/com/android/server/
├── BatteryService.java
監(jiān)聽底層上報的battery事件,廣播電池發(fā)生改變的消息 (廣播Intent.ACTION_BATTERY_CHANGED)
frameworks/base/services/core/java/com/android/server/am/
├── ActivityManagerService.java
創(chuàng)建BatteryStatsService
├── BatteryStatsService.java
統(tǒng)計和記錄電池參數(shù)的信息
frameworks/base/services/core/java/com/android/server/power/
├── PowerManagerService.java
監(jiān)聽電池發(fā)生變化的廣播消息,并調(diào)節(jié)系統(tǒng)的電源狀態(tài),例如亮屏
frameworks/base/core/java/com/android/internal/os/
├── BatteryStatsImpl.java
統(tǒng)計和記錄電池參數(shù)的信息,并通知其他模塊
二、Healthd
healthd是android4.4之后提出來的一種中介模型,安卓源碼路徑下system/core/healthd, 主要是通過binder機制去調(diào)用healthd向下監(jiān)聽來自底層的電池事件,向上傳遞電池數(shù)據(jù)信息給Framework層的BatteryService用來計算電池電量相關(guān)信息,BatteryService通過傳遞來的數(shù)據(jù)來計算電池電量等信息,因此healthd在電池管理系統(tǒng)中起著承上啟下的作用。
主要是通過BatteryMonitor.cpp中的bool BatteryMonitor::update(void)函數(shù)上報信息,其中,內(nèi)核首先會更新數(shù)據(jù)到/sys/class/power_supply/battery節(jié)點下各個屬性。
Healthd的源碼結(jié)構(gòu):
System/core/healthd/
├── healthd.cpp
創(chuàng)建uevent socket,監(jiān)聽內(nèi)核上報的內(nèi)核事件
├── BatteryMonitor.cpp
初始化本地電池數(shù)據(jù)結(jié)構(gòu),將power_supply路徑下屬性節(jié)點路徑填充進去,
├── BatteryMonitor.h
├── BatteryPropertiesRegistrar.cpp
創(chuàng)建電池屬性監(jiān)聽器,并將其注冊到Android的系統(tǒng)服務(wù)中
├── BatteryPropertiesRegistrar.h
health模塊的代碼位置位于/system/core/healthd/,其入口在Healthd.cpp中的main函數(shù)中:
healthd_mode_ops = &android_ops; //開機充電時初始化結(jié)構(gòu)體
healthd_mode_ops = &charger_ops; //關(guān)機充電時初始化結(jié)構(gòu)體
healthd_mode_ops是一個充電狀態(tài)的結(jié)構(gòu)體,正常開機情況下會 將android_ops結(jié)構(gòu)體賦值給healthd_mode_ops ,如果在關(guān)機情況下會將 charget_ops結(jié)構(gòu)體賦值給healthd_mode_opos,就是關(guān)機充電的使用。
然后在healthd_init中,主要做一些初始化工作,并且創(chuàng)建了一個epoll,主要用于將文件指針掛在工作隊列中,用于輪詢查看驅(qū)動層是否發(fā)來信息,然后調(diào)用uevent_init()對uevent進行注冊,應(yīng)為從上篇文章我們明白,電池驅(qū)動是通過uevent與health進行通信的,uevent通信本質(zhì)上就是socket通信。
epollfd = epoll_create(MAX_EPOLL_EVENTS);
初始化之后,執(zhí)行healthd_mainloop(),開啟了一個無線循環(huán),監(jiān)聽是否有uevent事件到來,如果在for循環(huán)中檢測到uevent事件到來的時候,會直接調(diào)用 uevent_event函數(shù)
在uevent_event函數(shù)中,會判斷SSUBSYSTEM是否等于POWER_SUPPLY_SUBSYSTEM,如果是的話,說明是電池驅(qū)動傳過來的信息,會執(zhí)行healthd_battery_update()函數(shù)。在該函數(shù)中會直接調(diào)用BatteryMonitor中的update()函數(shù),對電池的各個屬性進行更新,并在最后調(diào)用在前面注冊的監(jiān)聽進行上報。
在update()函數(shù)中調(diào)用 healthd_mode_ops->battery_update(&props) 進行,其中battery_update(&props)為在health.cpp中初始化的結(jié)構(gòu)體,最后返回當(dāng)前的充電狀態(tài)。
battery_update()調(diào)用的函數(shù)如下,調(diào)用注冊的監(jiān)聽進行上報:
void healthd_mode_android_battery_update(
struct android::BatteryProperties *props) {
if (gBatteryPropertiesRegistrar != NULL)
gBatteryPropertiesRegistrar->notifyListeners(*props); //調(diào)用監(jiān)聽并傳入屬性值
return;
}
接著gBatteryPropertiesRegistrar 這個監(jiān)聽就會回調(diào)在framwork層實現(xiàn)的函數(shù),然后通過Binder機制向上層通信
三、驅(qū)動
驅(qū)動部分大概流程是這樣的:
Android內(nèi)核中的電池驅(qū)動采取的是linux 內(nèi)核驅(qū)動中的 power_supply子系統(tǒng)框架進行上報電池狀態(tài)。power_supply主要通過sys文件系統(tǒng)向用戶層提供讀取電池狀態(tài)的接口,路徑為 /sys/class/power_supply/ , 該目錄下通常會有 ac , battery, usb 三個目錄,代表給Android系統(tǒng)供電的三種能源類型,其中電池的狀態(tài)就在battery的目錄下,當(dāng)電池狀態(tài)變化的時候會通過uevent機制通知上層,然后上層通過讀取該目錄下相應(yīng)的值來動態(tài)的顯示電池狀態(tài)。
驅(qū)動的源碼結(jié)構(gòu) :
kernel/drivers/power
1、對電源(ac , battery, usb)進行初始化(這里說的是rk818):
對應(yīng)驅(qū)動文件:drivers/power/rk818_charger.c
static const struct power_supply_desc rk818_ac_desc = {
.name = "ac",//設(shè)備名稱
.type = POWER_SUPPLY_TYPE_MAINS,//類型
.properties = rk818_ac_props,//屬性
.num_properties = ARRAY_SIZE(rk818_ac_props),//屬性數(shù)目
.get_property = rk818_cg_ac_get_property,//得到屬性的函數(shù)
};
static const struct power_supply_desc rk818_usb_desc = {
.name = "usb",
.type = POWER_SUPPLY_TYPE_USB,
.properties = rk818_usb_props,
.num_properties = ARRAY_SIZE(rk818_usb_props),
.get_property = rk818_cg_usb_get_property,
};
對應(yīng)驅(qū)動文件:drivers/power/rk818_battery.c
static const struct power_supply_desc rk818_bat_desc = {
.name = "battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = rk818_bat_props,
.num_properties = ARRAY_SIZE(rk818_bat_props),
.get_property = rk818_battery_get_property,
};
這里主要是實現(xiàn)給電源名字類型等賦初值,最主要是將get_property函數(shù)指向我們寫好的可以得到電源的屬性的函數(shù)的起始地址,以便當(dāng)內(nèi)核需要用到驅(qū)動的信息的時候進行回調(diào)。
2、通過power_supply_register(devm_power_supply_register最終也是調(diào)用power_supply_register)將所提供的電源進行注冊,即把他們的屬性寫到sys文件系統(tǒng)里,使用戶空間可以得到有關(guān)電源的信息。
cg->usb_psy = devm_power_supply_register(cg->dev, &rk818_usb_desc,
&psy_cfg);
power_supply_register調(diào)用內(nèi)核提供的函數(shù)device_create()和power_supply_create_attrs來實現(xiàn)電源的注冊,這里電源類型是usb。
3、Power Supply驅(qū)動程序頭文件是kernel/include/linux/power_supply.h,注冊和注銷驅(qū)動程序的函數(shù)如下:
int power_supply_register(struct device *parent,struct power_supply *psy);
void power_supply_unregister(struct power_supply *psy);
struct power_supply {
const char *name; /*設(shè)備名稱*/
enum power_supply_type type; /* 類型 */
enum power_supply_property *properties; /* 屬性指針 */
size_t num_properties; /*屬性的數(shù)目*/
char* *supplied_to;
size_t num_supplicants;
int (*get_property)(struct power_supply *psy, /*獲得屬性*/
enum power_supply_property psp,
union power_supply_propval *val);
void (*external_power_changed)(struct power_supply *psy);
/* ...... 省略部分內(nèi)容 */
內(nèi)核主要通過get_property這個函數(shù)指針來獲得驅(qū)動中的有關(guān)電池的信息,而這個函數(shù)在內(nèi)核中只給出了聲明,我們在寫驅(qū)動的時候要自己實現(xiàn)這個函數(shù),即將自己寫的函數(shù)賦值給這個函數(shù)指針,當(dāng)內(nèi)核需要驅(qū)動中電源信息的時候就回調(diào)這個get_property函數(shù)。另外,我們寫驅(qū)動程序的時候又要給用戶提供接口,內(nèi)核中提供給用戶的接口就是sysfs,通過讀取sysfs文件系統(tǒng)中文件內(nèi)容,就可以得到電源的信息。內(nèi)核主要通過兩個文件power_supply_class.c 和power_supply_core.c,我們調(diào)用其中的函數(shù)就可以把電源(BATTERY,USB或AC)的信息展現(xiàn)給用戶,有關(guān)電源的屬性寫在/sys/class/powersupply文件夾下(此文件夾為程序運行后所生成的)。
ac和usb只創(chuàng)建了一個online屬性,上層通過判斷ac和usb的online狀態(tài)(1表示設(shè)備接入,0表示設(shè)備拔出)便可知道當(dāng)前系統(tǒng)是由什么設(shè)備在充電了;而battery則創(chuàng)建了如status、health、present、capacity、batt_vol等等和電池相關(guān)的諸多屬性,上層通過這些電池屬性uevent便可監(jiān)控電池的當(dāng)前工作狀態(tài)了。下面舉例是battery,ac和usb同理。
static int rk818_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct rk818_battery *di = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = di->current_avg * 1000;/*uA*/ //獲取電池電流
if (di->pdata->bat_mode == MODE_VIRTUAL)
val->intval = VIRTUAL_CURRENT * 1000;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = di->voltage_avg * 1000;/*uV*/ //獲取電池電壓
if (di->pdata->bat_mode == MODE_VIRTUAL)
val->intval = VIRTUAL_VOLTAGE * 1000;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = is_rk818_bat_exist(di);
if (di->pdata->bat_mode == MODE_VIRTUAL)
val->intval = VIRTUAL_PRESET;
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = di->dsoc; //獲取電池電量
if (di->pdata->bat_mode == MODE_VIRTUAL)
val->intval = VIRTUAL_SOC;
DBG("<%s>. report dsoc: %d\n", __func__, val->intval);
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = POWER_SUPPLY_HEALTH_GOOD;
break;
case POWER_SUPPLY_PROP_TEMP:
val->intval = di->temperature;
if (di->pdata->bat_mode == MODE_VIRTUAL)
val->intval = VIRTUAL_TEMPERATURE;
break;
各能源設(shè)備屬性概況如下(adb工具cat可以查看):
/sys/class/power_supply/ac/online AC 電源連接狀態(tài)
/sys/class/power_supply/usb/online USB電源連接狀態(tài)
/sys/class/power_supply/battery/status 充電狀態(tài)
/sys/class/power_supply/battery/health 電池狀態(tài)
/sys/class/power_supply/battery/present 使用狀態(tài)
/sys/class/power_supply/battery/capacity 電池 level
/sys/class/power_supply/battery/batt_vol 電池電壓
/sys/class/power_supply/battery/batt_temp 電池溫度
/sys/class/power_supply/battery/technology 電池技術(shù)
當(dāng)供電設(shè)備的狀態(tài)或者電量發(fā)生變化后,會調(diào)用power_supply_changed(&battery_data->battery)更新這些文件;
/Send uevent.
power_supply_changed(&battery_data->battery);
備注:
1、Uevent機制
Uevent是內(nèi)核通知android有狀態(tài)變化的一種方法,比如USB線插入、拔出,電池電量變化等等。其本質(zhì)是內(nèi)核發(fā)送(可以通過socket)一個字符串,應(yīng)用層(android)接收并解釋該字符串,獲取相應(yīng)信息。