Android電池管理系統總體實現:
電池管理在Android系統中的主要作用是檢測我們的電池狀態,剩餘電量實時更新,高溫報警,低電量關機等。Android的電池管理模塊,從Android的應用層到底層內核分爲了4層來理解,從上到下依次爲,應用層,framwork層,本地框架層,內核驅動層。
一、Android 電池服務
Android電池服務BatteryService,用來監聽內核上報的電池事件,並將最新的電池數據上報給系統,系統收到新數據後會去更新電池顯示狀態、剩餘電量等信息。如果收到過溫報警和低電報警,系統會自動觸發關機流程,保護電池和機器不受到危害。
Android電池服務的源碼結構 :
frameworks/base/services/java/com/android/server/
├── SystemServer.java
創建BatteryService、PowerManagerService、ActivityManagerService
frameworks/base/services/core/java/com/android/server/
├── BatteryService.java
監聽底層上報的battery事件,廣播電池發生改變的消息 (廣播Intent.ACTION_BATTERY_CHANGED)
frameworks/base/services/core/java/com/android/server/am/
├── ActivityManagerService.java
創建BatteryStatsService
├── BatteryStatsService.java
統計和記錄電池參數的信息
frameworks/base/services/core/java/com/android/server/power/
├── PowerManagerService.java
監聽電池發生變化的廣播消息,並調節系統的電源狀態,例如亮屏
frameworks/base/core/java/com/android/internal/os/
├── BatteryStatsImpl.java
統計和記錄電池參數的信息,並通知其他模塊
二、Healthd
healthd是android4.4之後提出來的一種中介模型,安卓源碼路徑下system/core/healthd, 主要是通過binder機制去調用healthd向下監聽來自底層的電池事件,向上傳遞電池數據信息給Framework層的BatteryService用來計算電池電量相關信息,BatteryService通過傳遞來的數據來計算電池電量等信息,因此healthd在電池管理系統中起着承上啓下的作用。
主要是通過BatteryMonitor.cpp中的bool BatteryMonitor::update(void)函數上報信息,其中,內核首先會更新數據到/sys/class/power_supply/battery節點下各個屬性。
Healthd的源碼結構:
System/core/healthd/
├── healthd.cpp
創建uevent socket,監聽內核上報的內核事件
├── BatteryMonitor.cpp
初始化本地電池數據結構,將power_supply路徑下屬性節點路徑填充進去,
├── BatteryMonitor.h
├── BatteryPropertiesRegistrar.cpp
創建電池屬性監聽器,並將其註冊到Android的系統服務中
├── BatteryPropertiesRegistrar.h
health模塊的代碼位置位於/system/core/healthd/,其入口在Healthd.cpp中的main函數中:
healthd_mode_ops = &android_ops; //開機充電時初始化結構體
healthd_mode_ops = &charger_ops; //關機充電時初始化結構體
healthd_mode_ops是一個充電狀態的結構體,正常開機情況下會 將android_ops結構體賦值給healthd_mode_ops ,如果在關機情況下會將 charget_ops結構體賦值給healthd_mode_opos,就是關機充電的使用。
然後在healthd_init中,主要做一些初始化工作,並且創建了一個epoll,主要用於將文件指針掛在工作隊列中,用於輪詢查看驅動層是否發來信息,然後調用uevent_init()對uevent進行註冊,應爲從上篇文章我們明白,電池驅動是通過uevent與health進行通信的,uevent通信本質上就是socket通信。
epollfd = epoll_create(MAX_EPOLL_EVENTS);
初始化之後,執行healthd_mainloop(),開啓了一個無線循環,監聽是否有uevent事件到來,如果在for循環中檢測到uevent事件到來的時候,會直接調用 uevent_event函數
在uevent_event函數中,會判斷SSUBSYSTEM是否等於POWER_SUPPLY_SUBSYSTEM,如果是的話,說明是電池驅動傳過來的信息,會執行healthd_battery_update()函數。在該函數中會直接調用BatteryMonitor中的update()函數,對電池的各個屬性進行更新,並在最後調用在前面註冊的監聽進行上報。
在update()函數中調用 healthd_mode_ops->battery_update(&props) 進行,其中battery_update(&props)爲在health.cpp中初始化的結構體,最後返回當前的充電狀態。
battery_update()調用的函數如下,調用註冊的監聽進行上報:
void healthd_mode_android_battery_update(
struct android::BatteryProperties *props) {
if (gBatteryPropertiesRegistrar != NULL)
gBatteryPropertiesRegistrar->notifyListeners(*props); //調用監聽並傳入屬性值
return;
}
接着gBatteryPropertiesRegistrar 這個監聽就會回調在framwork層實現的函數,然後通過Binder機制向上層通信
ps:這篇文章介紹Healthd說的比較詳細:https://blog.csdn.net/zhou12314/article/details/79404348
三、驅動
驅動部分大概流程是這樣的:
Android內核中的電池驅動採取的是linux 內核驅動中的 power_supply子系統框架進行上報電池狀態。power_supply主要通過sys文件系統向用戶層提供讀取電池狀態的接口,路徑爲 /sys/class/power_supply/ , 該目錄下通常會有 ac , battery, usb 三個目錄,代表給Android系統供電的三種能源類型,其中電池的狀態就在battery的目錄下,當電池狀態變化的時候會通過uevent機制通知上層,然後上層通過讀取該目錄下相應的值來動態的顯示電池狀態。
驅動的源碼結構 :
kernel/drivers/power
1、對電源(ac , battery, usb)進行初始化(這裏說的是rk818):
對應驅動文件:drivers/power/rk818_charger.c
469 static const struct power_supply_desc rk818_ac_desc = {
470 .name = "ac",//設備名稱
471 .type = POWER_SUPPLY_TYPE_MAINS,//類型
472 .properties = rk818_ac_props,//屬性
473 .num_properties = ARRAY_SIZE(rk818_ac_props),//屬性數目
474 .get_property = rk818_cg_ac_get_property,//得到屬性的函數
475 };
476
477 static const struct power_supply_desc rk818_usb_desc = {
478 .name = "usb",
479 .type = POWER_SUPPLY_TYPE_USB,
480 .properties = rk818_usb_props,
481 .num_properties = ARRAY_SIZE(rk818_usb_props),
482 .get_property = rk818_cg_usb_get_property,
483 };
對應驅動文件:drivers/power/rk818_battery.c
1017 static const struct power_supply_desc rk818_bat_desc = {
1018 .name = "battery",
1019 .type = POWER_SUPPLY_TYPE_BATTERY,
1020 .properties = rk818_bat_props,
1021 .num_properties = ARRAY_SIZE(rk818_bat_props),
1022 .get_property = rk818_battery_get_property,
1023 };
這裏主要是實現給電源名字類型等賦初值,最主要是將get_property函數指向我們寫好的可以得到電源的屬性的函數的起始地址,以便當內核需要用到驅動的信息的時候進行回調。
2、通過power_supply_register(devm_power_supply_register最終也是調用power_supply_register)將所提供的電源進行註冊,即把他們的屬性寫到sys文件系統裏,使用戶空間可以得到有關電源的信息。
489 cg->usb_psy = devm_power_supply_register(cg->dev, &rk818_usb_desc,
490 &psy_cfg);
power_supply_register調用內核提供的函數device_create()和power_supply_create_attrs來實現電源的註冊,這裏電源類型是usb。
3、Power Supply驅動程序頭文件是kernel/include/linux/power_supply.h,註冊和註銷驅動程序的函數如下:
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; /*設備名稱*/
enum power_supply_type type; /* 類型 */
enum power_supply_property *properties; /* 屬性指針 */
size_t num_properties; /*屬性的數目*/
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);
/* ...... 省略部分內容 */
內核主要通過get_property這個函數指針來獲得驅動中的有關電池的信息,而這個函數在內核中只給出了聲明,我們在寫驅動的時候要自己實現這個函數,即將自己寫的函數賦值給這個函數指針,當內核需要驅動中電源信息的時候就回調這個get_property函數。另外,我們寫驅動程序的時候又要給用戶提供接口,內核中提供給用戶的接口就是sysfs,通過讀取sysfs文件系統中文件內容,就可以得到電源的信息。內核主要通過兩個文件power_supply_class.c 和power_supply_core.c,我們調用其中的函數就可以把電源(BATTERY,USB或AC)的信息展現給用戶,有關電源的屬性寫在/sys/class/powersupply文件夾下(此文件夾爲程序運行後所生成的)。
ac和usb只創建了一個online屬性,上層通過判斷ac和usb的online狀態(1表示設備接入,0表示設備拔出)便可知道當前系統是由什麼設備在充電了;而battery則創建瞭如status、health、present、capacity、batt_vol等等和電池相關的諸多屬性,上層通過這些電池屬性uevent便可監控電池的當前工作狀態了。下面舉例是battery,ac和usb同理。
955 static int rk818_battery_get_property(struct power_supply *psy,
956 enum power_supply_property psp,
957 union power_supply_propval *val)
958 {
959 struct rk818_battery *di = power_supply_get_drvdata(psy);
960
961 switch (psp) {
962 case POWER_SUPPLY_PROP_CURRENT_NOW:
963 val->intval = di->current_avg * 1000;/*uA*/ //獲取電池電流
964 if (di->pdata->bat_mode == MODE_VIRTUAL)
965 val->intval = VIRTUAL_CURRENT * 1000;
966 break;
967 case POWER_SUPPLY_PROP_VOLTAGE_NOW:
968 val->intval = di->voltage_avg * 1000;/*uV*/ //獲取電池電壓
969 if (di->pdata->bat_mode == MODE_VIRTUAL)
970 val->intval = VIRTUAL_VOLTAGE * 1000;
971 break;
972 case POWER_SUPPLY_PROP_PRESENT:
973 val->intval = is_rk818_bat_exist(di);
974 if (di->pdata->bat_mode == MODE_VIRTUAL)
975 val->intval = VIRTUAL_PRESET;
976 break;
977 case POWER_SUPPLY_PROP_CAPACITY:
978 val->intval = di->dsoc; //獲取電池電量
979 if (di->pdata->bat_mode == MODE_VIRTUAL)
980 val->intval = VIRTUAL_SOC;
981 DBG("<%s>. report dsoc: %d\n", __func__, val->intval);
982 break;
983 case POWER_SUPPLY_PROP_HEALTH:
984 val->intval = POWER_SUPPLY_HEALTH_GOOD;
985 break;
986 case POWER_SUPPLY_PROP_TEMP:
987 val->intval = di->temperature;
988 if (di->pdata->bat_mode == MODE_VIRTUAL)
989 val->intval = VIRTUAL_TEMPERATURE;
990 break;
各能源設備屬性概況如下(adb工具cat可以查看):
/sys/class/power_supply/ac/online AC 電源連接狀態
/sys/class/power_supply/usb/online USB電源連接狀態
/sys/class/power_supply/battery/status 充電狀態
/sys/class/power_supply/battery/health 電池狀態
/sys/class/power_supply/battery/present 使用狀態
/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 電池技術
當供電設備的狀態或者電量發生變化後,會調用power_supply_changed(&battery_data->battery)更新這些文件;
//Send uevent.
power_supply_changed(&battery_data->battery);
備註:
1、Uevent機制
Uevent是內核通知android有狀態變化的一種方法,比如USB線插入、拔出,電池電量變化等等。其本質是內核發送(可以通過socket)一個字符串,應用層(android)接收並解釋該字符串,獲取相應信息。