android6.0系統Healthd分析及低電量自動關機流程

来源:https://www.cnblogs.com/ljin/archive/2018/12/25/10175603.html
-Advertisement-
Play Games

系統平臺:android6.0概述Healthd是android4.4之後提出來的一種中介模型,該模型向下監聽來自底層的電池事件,向上傳遞電池數據信息給Framework層的BatteryService用以計算電池電量相關狀態信息,BatteryServcie通過傳遞來的數據來計算電池電量顯示,剩餘 ...


 

系統平臺:android6.0
概述
Healthd是android4.4之後提出來的一種中介模型,該模型向下監聽來自底層的電池事件,向上傳遞電池數據信息給Framework層的BatteryService用以計算電池電量相關狀態信息,BatteryServcie通過傳遞來的數據來計算電池電量顯示,剩餘電量,電量級別等信息,如果收到過溫報警或者嚴重低電報警等信息,系統會直接關機,保護硬體。

1.主模塊處理流程
Healthd模塊代碼是在system/core/healthd/,其模塊入口在healthd的main函數,函數代碼如下:

 1 int main(int argc, char **argv) {
 2     int ch;
 3     int ret;
 4 /*代碼中開始便是解析參數,healthd_mode_ops是一個關於充電狀態結構體變數,
 5  *結構體變數里的參數是函數指針,在初始化時指向各個不同的操作函數,
 6  *當開機充電時變數賦值為&android_ops,關機充電時候變數賦值為&charger_ops。
 7  */
 8     klog_set_level(KLOG_LEVEL);
 9     healthd_mode_ops = &android_ops;    //正常開機充電
10     
11   //charger為該程式的可執行文件名,該行代碼主要用於區分是命令操作還是正常代碼啟動
12     if (!strcmp(basename(argv[0]), "charger")) {   
13         
14         healthd_mode_ops = &charger_ops;   
15     } else {
16         while ((ch = getopt(argc, argv, "cr")) != -1) {
17             switch (ch) {           //根據不同的參數賦與不同的操作方法
18             case 'c':
19                 healthd_mode_ops = &charger_ops;   
20                 break;
21             case 'r':
22                 healthd_mode_ops = &recovery_ops;
23                 break;
24             case '?':
25             default:
26                 KLOG_ERROR(LOG_TAG, "Unrecognized healthd option: %c\n",
27                            optopt);
28                 exit(1);
29             }
30         }
31     }
32     
33     //對需要數據上報的事件進行初始化,分別是healthd_mode_ops、wakealarm和uevent三個事件
34     ret = healthd_init();   
35     
36     if (ret) {
37         KLOG_ERROR("Initialization failed, exiting\n");
38         exit(2);
39     }
40     healthd_mainloop();
41     KLOG_ERROR("Main loop terminated, exiting\n");
42     return 3;
43 }

 



1.1healthd_init( )函數分析

 1 static int healthd_init() {
 2     epollfd = epoll_create(MAX_EPOLL_EVENTS);  //創建epoll用於事件觸發
 3     if (epollfd == -1) {
 4         KLOG_ERROR(LOG_TAG,
 5                    "epoll_create failed; errno=%d\n",
 6                    errno);
 7         return -1;
 8     }
 9     healthd_board_init(&healthd_config);     
10     //次處為調用android_ops->init進行初始化    
11     healthd_mode_ops->init(&healthd_config);  
12     //對wakealarm進行初始化,用於周期觸發事件上報數據  
13     wakealarm_init();             
14     //對uevent進行初始化,用於域套節字進行數據傳輸    
15     uevent_init();                   
16     
17     /*在healthd_init中最後創建BatteryMonitor的對象,並將其初始化。
18     BatteryMonitor主要接受healthd傳來的數據,做電池狀態的計算並更新。*/
19     gBatteryMonitor = new BatteryMonitor();    
20     gBatteryMonitor->init(&healthd_config);     
21     return 0;
22 }

 



創建一個epoll的變數將其賦值給epollfd,在healthd_board_init中未作任何事便返回了。
healthd_mode_ops->init調用有兩種情況:關機情況下調用charger_ops的init函數;開機情況下調用android_ops的init函數,這裡就開機情況來分析。android_ops的init函數指針指向healthd_mode_android_init函數
代碼如下:

 1 void healthd_mode_android_init(struct healthd_config* /*config*/) {
 2     ProcessState::self()->setThreadPoolMaxThreadCount(0);//線程池裡最大線程數
 3     IPCThreadState::self()->disableBackgroundScheduling(true);//禁用後臺調度
 4     IPCThreadState::self()->setupPolling(&gBinderFd);
 5     if (gBinderFd >= 0) {
 6         //gBinderfd加入到epoll中
 7         if (healthd_register_event(gBinderFd, binder_event))
 8                
 9             /********healthd_register_event函數分析****************
10             int healthd_register_event(int fd, void (*handler)(uint32_t)) {
11                 struct epoll_event ev;
12                 ev.events = EPOLLIN | EPOLLWAKEUP;        //設置事件類型
13                 ev.data.ptr = (void *)handler;       //加入事件處理函數
14                 //將被監聽的描述符添加到epoll
15                 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {  
16                 KLOG_ERROR(LOG_TAG,
17                        "epoll_ctl failed; errno=%d\n", errno);
18                         return -1;
19                 }
20                 eventct++;
21                 return 0;
22             }
23             */
24             KLOG_ERROR(LOG_TAG,
25                        "Register for binder events failed\n");
26     }
27     gBatteryPropertiesRegistrar = new BatteryPropertiesRegistrar();
28     //將"batteryproperties"這個Service註冊到ServiceManager中
29     gBatteryPropertiesRegistrar->publish();        
30 }



前面三條語句做初始化工作,設置線程池最大線程數,禁用後臺調度,以及將gBinderfd加入到epoll中。healthd_register_event將binder_event事件註冊到gBinderfd文件節點用以監聽Binder事件。gBatteryPropertiesRegistrar->publish將"batteryproperties"這個Service註冊到ServiceManager中

再來看看wakealarm_init函數:

 1 static void wakealarm_init(void) {
 2     //首先創建一個wakealarm_fd的定時器與之對應的文件描述符
 3     wakealarm_fd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK);   
 4     if (wakealarm_fd == -1) {
 5         KLOG_ERROR(LOG_TAG, "wakealarm_init: timerfd_create failed\n");
 6         return;
 7     }
 8 
 9     //healthd_register_event將wakealarm事件註冊到wakealarm_fd文件節點用以監聽wakealarm事件
10     if (healthd_register_event(wakealarm_fd, wakealarm_event))   
11         KLOG_ERROR(LOG_TAG,
12                    "Registration of wakealarm event failed\n");
13      //wakealarm_set_interval設置alarm喚醒的間隔
14     wakealarm_set_interval(healthd_config.periodic_chores_interval_fast);
15 }


再看看uevent_init函數:

 1 static void uevent_init(void) {
 2   //創建並打開一個64k的socket文件描述符uevent_fd
 3  uevent_fd = uevent_open_socket(64*1024, true);
 4  if (uevent_fd < 0) {
 5      KLOG_ERROR(LOG_TAG, "uevent_init: uevent_open_socket failed\n");
 6      return;
 7  }
 8  fcntl(uevent_fd, F_SETFL, O_NONBLOCK);        //設置文件狀態標誌為非阻塞模
 9    //將uevent事件註冊到uevent_fd文件節點用以監聽uevent事件
10  if (healthd_register_event(uevent_fd, uevent_event))  
11      KLOG_ERROR(LOG_TAG,
12                 "register for uevent events failed\n");
13 }



我們可以看到android利用epoll監聽了三個文件節點的改變事件,分別是:通過gBinderfd監聽線程Binder通信事件;通過wakealarm_fd監聽wakealarm事件;
通過uevent_fd監聽wakealarm事件。至於如何監聽後面做詳細分析
在healthd_init中最後創建BatteryMonitor的對象,並將其初始化。BatteryMonitor主要接受healthd傳來的數據,做電池狀態的計算並更新。

我們可以看到在BatterMonitor中的init函數中有以下語句:

 1 /*POWER_SUPPLY_SYSFS_PATH定義為"/sys/class/power_supply",
 2 在init函數中打開系統該文件夾,然後一一讀取該文件夾下的文件內容*/
 3 DIR* dir = opendir(POWER_SUPPLY_SYSFS_PATH);    
 4 struct dirent* entry;
 5 。。。。。。。
 6  //在while迴圈中判斷該文件夾下各個文件節點的內容,並將其初始化給相關的參數
 7  while ((entry = readdir(dir))) {      
 8      const char* name = entry->d_name;
 9 。。。。。。
10 }


至此,healthd_init函數就分析完了,其主要工作就是:創建了三個文件節點用來監聽相應的三種事件改變;創建BatteryMonitor對象,並通過讀取/sys/class/power_supply將其初始化。

Healthd_init走完之後,接著就是調用healthd_mainloop函數,該函數維持了一個死迴圈,代碼如下:

static void healthd_mainloop(void) {
 while (1) {
     struct epoll_event events[eventct];
     int nevents;
     int timeout = awake_poll_interval;
     int mode_timeout;
     mode_timeout = healthd_mode_ops->preparetowait();
     if (timeout < 0 || (mode_timeout > 0 && mode_timeout < timeout))
         timeout = mode_timeout;
     
     /*這裡介紹一下輪詢機制中重要函數epoll_waite().
       epoll_wait運行的道理是:等侍註冊在epfd上的socket fd的事務的產生,
       若是產生則將產生的sokct fd和事務類型放入到events數組中。
       且timeout如果為-1則為阻塞式,timeowout為0則表示非阻塞式。
       可以看到代碼中timeout為-1,故為阻塞式輪詢,當epollfd上有事件發生,
       則會走到下麵的處理邏輯。
      */
     nevents = epoll_wait(epollfd, events, eventct, timeout);
     if (nevents == -1) {
         if (errno == EINTR)
             continue;
         KLOG_ERROR(LOG_TAG, "healthd_mainloop: epoll_wait failed\n");
         break;
     }
     for (int n = 0; n < nevents; ++n) {
         if (events[n].data.ptr)
             (*(void (*)(int))events[n].data.ptr)(events[n].events);    //處理事件
     }
     if (!nevents)
         periodic_chores();
     healthd_mode_ops->heartbeat();
 }
 return;
}


Healthd_mainloop中維持了一個死迴圈,死迴圈中變數nevents 表示從epollfd中輪循中監聽得到的事件數目,這裡介紹一下輪詢機制中重要函數epoll_waite().
epoll_wait運行的道理是:等侍註冊在epfd上的socket fd的事務的產生,若是產生則將產生的sokct fd和事務類型放入到events數組中。且timeout如果為-1則為阻塞式,timeowout為0則表示非阻塞式。可以看到代碼中timeout為-1,故為阻塞式輪詢,當epollfd上有事件發生,則會走到下麵的處理邏輯。事件處理主要在for迴圈中:

在periodic_chores()中調用到healthd_battery_update()更新電池狀態。

 1 void healthd_battery_update(void) {
 2  // Fast wake interval when on charger (watch for overheat);
 3  // slow wake interval when on battery (watch for drained battery).
 4 int new_wake_interval = gBatteryMonitor->update() ? //更新,調用BatteryMonitor的update函數,根據不同充電狀態設置不同的定時器喚醒周期
 5     healthd_config.periodic_chores_interval_fast :
 6         healthd_config.periodic_chores_interval_slow;    
 7  if (new_wake_interval != wakealarm_wake_interval)     
 8          wakealarm_set_interval(new_wake_interval);     //new_wake_interval表示新的wakealarm喚醒間隔
 9  // During awake periods poll at fast rate.  If wake alarm is set at fast
10  // rate then just use the alarm; if wake alarm is set at slow rate then
11  // poll at fast rate while awake and let alarm wake up at slow rate when
12  // asleep.
13  if (healthd_config.periodic_chores_interval_fast == -1)
14      awake_poll_interval = -1;
15  else
16      awake_poll_interval =
17          new_wake_interval == healthd_config.periodic_chores_interval_fast ?
18              -1 : healthd_config.periodic_chores_interval_fast * 1000;
19 }


可以看出該函數並不長,new_wake_interval表示新的wakealarm喚醒間隔,通過調用BatteryMonitor的update函數(後面詳細分析如何更新),其返回值為是否處於充電狀態,當處於充電狀態,則喚醒間隔為healthd_config.periodic_chores_interval_fast(短間隔),當不再充電狀態時喚醒間隔為healthd_config.periodic_chores_interval_slow(長間隔)
當新的間隔變數new_wake_interval與舊的變數wakealarm_wake_interval不一樣,則將新的喚醒間隔設置成wakealarm的喚醒間隔;
awake_poll_internal作為下一次epoll_waite的timeout參數,在這裡將其更新,在充電狀態下awake_poll_internal為-1,沒有充電的狀態下awake_poll_internal為60000ms

healthd主流程都是在main函數中處理,至此main已經分析完成,其簡要流程圖如下


Healthd處理邏輯
初始化處理
前面將healthd模塊中main函數分析完了,其主要工作流程有個大概的瞭解,但是其詳細處理邏輯並未做分析,在此之後,對Healthd的初始化,事件處理,狀態更新將做一個詳細的分析。
前面已經說過在healthd_init中創建了三個文件節點gBinderfd,uevent_fd,wakealarm_fd,並用以註冊監聽三種事件,註冊監聽都是通過healthd_register_event函數實現的。

healthd_register_event(gBinderFd, binder_event);
healthd_register_event(wakealarm_fd, wakealarm_event);
healthd_register_event(uevent_fd, uevent_event);

其healthd_register_event實現代碼如下:

 1 int healthd_register_event(int fd, void (*handler)(uint32_t)) {
 2  struct epoll_event ev;
 3  ev.events = EPOLLIN | EPOLLWAKEUP;
 4  ev.data.ptr = (void *)handler;
 5  if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
 6      KLOG_ERROR(LOG_TAG,
 7                 "epoll_ctl failed; errno=%d\n", errno);
 8      return -1;
 9  }
10  eventct++;
11  return 0;
12 }


函數將相應的文件節點事件賦值為函數的第二個形參,也就是說相應的gBinderfd的事件處理函數為binder_event函數,同理wakealarm_fd,ueven_fd的事件事件處理分別為wakealarm_event,uevent_event函數。然後將其三個文件節點加入到epollfd中。

事件獲取與處理
Healthd中維持了一個阻塞式的死迴圈healthd_mainloop,在該函數中提供阻塞式的監聽已發送的事件函數epoll_wait(),healthd_mainloop中有如下代碼

1      nevents = epoll_wait(epollfd, events, eventct, timeout);
2      if (nevents == -1) {
3          if (errno == EINTR)
4              continue;
5          KLOG_ERROR(LOG_TAG, "healthd_mainloop: epoll_wait failed\n");
6          break;
7      }


當epoll_waite接受到gBinderfd,wakealarm_fd,uevent_fd其中的事件,便會將監聽到的事件加入到event數組中。在for迴圈中做處理,for迴圈中代碼看起來非常難懂,其實if判斷的便是event有沒有相應的處理函數,在前面註冊事件時候已經提到,三種句柄上的事件都有對應的處理函數,也就是當收到gBinderfd上的事件,便用binder_event函數處理,當收到uevent_fd上的事件便用uevent_event處理,當收到wakealarm_fd上的事件便用wakealarm_event處理。
這裡以較為重要的uevent_event事件處理為例:

 1 #define UEVENT_MSG_LEN 2048
 2 static void uevent_event(uint32_t /*epevents*/) {
 3  char msg[UEVENT_MSG_LEN+2];
 4  char *cp;
 5  int n;
 6  n = uevent_kernel_multicast_recv(uevent_fd, msg, UEVENT_MSG_LEN);
 7  if (n <= 0)
 8      return;
 9  if (n >= UEVENT_MSG_LEN)   /* overflow -- discard */
10      return;
11  msg[n] = '\0';
12  msg[n+1] = '\0';
13  cp = msg;
14  while (*cp) {
15      if (!strcmp(cp, "SUBSYSTEM=" POWER_SUPPLY_SUBSYSTEM)) {
16          healthd_battery_update();
17          break;
18      }
19      /* advance to after the next \0 */
20      while (*cp++)
21          ;
22  }
23 }


處理函數首先從uevent_fd 獲取事件數目,然後迴圈判斷是否是來自與power_supply目錄下的事件,如果是,則調用到healthd_battery_update中去更新電池狀態。

更新電池狀態
當收到事件,做一些判斷工作便需要更新電池狀態,其更新函數為healthd.cpp下的healthd_battery_update函數,但是主要更新並不在heathd中完成的,而是在BatteryMonitor中的update函數:

  1 bool BatteryMonitor::update(void) {
  2  bool logthis;
  3  //清除原有屬性值
  4  props.chargerAcOnline = false;                        
  5  props.chargerUsbOnline = false;
  6  props.chargerWirelessOnline = false;
  7  props.batteryStatus = BATTERY_STATUS_UNKNOWN;
  8  props.batteryHealth = BATTERY_HEALTH_UNKNOWN;
  9  props.maxChargingCurrent = 0;
 10  if (!mHealthdConfig->batteryPresentPath.isEmpty())
 11      props.batteryPresent = getBooleanField(mHealthdConfig->batteryPresentPath);
 12  else
 13      props.batteryPresent = mBatteryDevicePresent;
 14  props.batteryLevel = mBatteryFixedCapacity ?
 15      mBatteryFixedCapacity :
 16      getIntField(mHealthdConfig->batteryCapacityPath);
 17  props.batteryVoltage = getIntField(mHealthdConfig->batteryVoltagePath) / 1000;
 18  props.batteryTemperature = mBatteryFixedTemperature ?
 19      mBatteryFixedTemperature :
 20      getIntField(mHealthdConfig->batteryTemperaturePath);
 21  // For devices which do not have battery and are always plugged
 22  // into power souce.
 23  if (mAlwaysPluggedDevice) {
 24      props.chargerAcOnline = true;
 25      props.batteryPresent = true;
 26      props.batteryStatus = BATTERY_STATUS_CHARGING;
 27      props.batteryHealth = BATTERY_HEALTH_GOOD;
 28  }
 29  
 30  const int SIZE = 128;
 31  char buf[SIZE];
 32  String8 btech;
 33  //讀取/sys/class/power_supply下文件節點信息
 34  if (readFromFile(mHealthdConfig->batteryStatusPath, buf, SIZE) > 0)     
 35      props.batteryStatus = getBatteryStatus(buf);
 36  if (readFromFile(mHealthdConfig->batteryHealthPath, buf, SIZE) > 0)
 37      props.batteryHealth = getBatteryHealth(buf);
 38  if (readFromFile(mHealthdConfig->batteryTechnologyPath, buf, SIZE) > 0)
 39      props.batteryTechnology = String8(buf);
 40  
 41  /*在init函數中將healthd_config 對象傳入,並且將裡面的成員的一些地址信息去初始化保存起來。
 42  主要是保存一些地址信息,以及充電方式。在BatteryMonitor初始化中,heathd_config傳入init函數中,
 43  賦值為mHealthdConfig,上面一段主要是讀取/sys/class/power_supply下的
 44  文件節點信息初更新電池數據屬性值
 45  */
 46  
 47  unsigned int i;
 48  //遍歷/sys/class/power_supply下的文件節點獲取信息
 49  for (i = 0; i < mChargerNames.size(); i++) {     
 50      String8 path;
 51      path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH,    
 52                        mChargerNames[i].string());
 53        //讀取子目錄文件online的值,online值為1使用,0值沒使用
 54      if (readFromFile(path, buf, SIZE) > 0) {  
 55          if (buf[0] != '0') {
 56              path.clear();
 57              path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH,
 58                                mChargerNames[i].string());
 59              switch(readPowerSupplyType(path)) {     //根據讀取的值將相應的屬性值置true
 60              case ANDROID_POWER_SUPPLY_TYPE_AC:
 61                  props.chargerAcOnline = true;     //true,false在framework層用於充電狀態判讀
 62                  break;
 63              case ANDROID_POWER_SUPPLY_TYPE_USB:
 64                  props.chargerUsbOnline = true;
 65                  break;
 66              case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
 67                  props.chargerWirelessOnline = true;
 68                  break;
 69              default:
 70                  KLOG_WARNING(LOG_TAG, "%s: Unknown power supply type\n",
 71                               mChargerNames[i].string());
 72              }
 73              path.clear();
 74              path.appendFormat("%s/%s/current_max", POWER_SUPPLY_SYSFS_PATH,
 75                                mChargerNames[i].string());
 76              if (access(path.string(), R_OK) == 0) {
 77                  int maxChargingCurrent = getIntField(path);
 78                  if (props.maxChargingCurrent < maxChargingCurrent) {
 79                      props.maxChargingCurrent = maxChargingCurrent;
 80                  }
 81              }
 82          }
 83      }
 84  }
 85  logthis = !healthd_board_battery_update(&props);    //此處必返回0
 86  if (logthis) {    //此處必執行
 87      char dmesgline[256];
 88      //更新props的各個屬性值,props將會被傳到framework層進行數據傳輸
 89      if (props.batteryPresent) {  
 90          snprintf(dmesgline, sizeof(dmesgline),
 91               "battery l=%d v=%d t=%s%d.%d h=%d st=%d",
 92               props.batteryLevel, props.batteryVoltage,
 93               props.batteryTemperature < 0 ? "-" : "",
 94               abs(props.batteryTemperature / 10),
 95               abs(props.batteryTemperature % 10), props.batteryHealth,
 96               props.batteryStatus);
 97          if (!mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
 98              int c = getIntField(mHealthdConfig->batteryCurrentNowPath);
 99              char b[20];
100              snprintf(b, sizeof(b), " c=%d", c / 1000);
101              strlcat(dmesgline, b, sizeof(dmesgline));
102          }
103      } else {
104          snprintf(dmesgline, sizeof(dmesgline),
105               "battery none");
106      }
107      size_t len = strlen(dmesgline);
108      snprintf(dmesgline + len, sizeof(dmesgline) - len, " chg=%s%s%s",
109               props.chargerAcOnline ? "a" : "",
110               props.chargerUsbOnline ? "u" : "",
111               props.chargerWirelessOnline ? "w" : "");
112      log_time realtime(CLOCK_REALTIME);
113      time_t t = realtime.tv_sec;
114      struct tm *tmp = gmtime(&t);
115      if (tmp) {
116          static const char fmt[] = " %Y-%m-%d %H:%M:%S.XXXXXXXXX UTC";
117          //前的電量級別,電壓,溫度,健康狀況,電池狀態以及充放電倍率存入dmesgline變數中,
118          len = strlen(dmesgline);    
119          if ((len < (sizeof(dmesgline) - sizeof(fmt) - 8)) // margin
120                  && strftime(dmesgline + len, sizeof(dmesgline) - len,
121                              fmt, tmp)) {
122              char *usec = strchr(dmesgline + len, 'X');
123              if (usec) {
124                  len = usec - dmesgline;
125                  snprintf(dmesgline + len, sizeof(dmesgline) - len,
126                           "%09u", realtime.tv_nsec);
127                  usec[9] = ' ';
128              }
129          }
130      }
131      KLOG_WARNING(LOG_TAG, "%s\n", dmesgline); //向log記錄電池當前各種狀態信息
132  }
133  healthd_mode_ops->battery_update(&props);  //更新電池
134  
135  /*回是否在充電狀態個update函數做完更新數據,記錄數據到log之後,
136  然後調用到BatteryPropertiesRegistrar的update函數繼續更新電池狀態,
137  最後返回值為是否處於充電狀態。*/
138  return props.chargerAcOnline | props.chargerUsbOnline |  
139          props.chargerWirelessOnline;
140 }


BatteryPropertiesRegistrar的update函數未作任何操作調用Healthd_mode_android.cpp中的healthd_mode_android_battery_update函數,我們可以看看該函數

1 void healthd_mode_android_battery_update(
2  struct android::BatteryProperties *props) {    //props攜帶各種需要傳輸的數據
3  if (gBatteryPropertiesRegistrar != NULL)
4    //調用BatteryPropertiesRegistrar的notifyListeners去通知props改變了
5      gBatteryPropertiesRegistrar->notifyListeners(*props);
6  return;
7 }


這裡這裡直接調用到BatteryPropertiesRegistrar的notifyListeners去通知props改變了,props是什麼呢?props是定義的一個BatteryProperties屬性集,裡面的成員變數包含了所有的電池狀態信息,在update開始便通過讀取各個文件節點的實時數據更新電池屬性props,更新完成後通過BatteryPropertiesRegistrar通知其屬性監聽者去更新狀態,但是誰是監聽呢?

我們可以看到framework層中的BatteryService.java的onStart函數中有如下代碼:

 1  public void onStart() {
 2      IBinder b = ServiceManager.getService("batteryproperties");
 3      final IBatteryPropertiesRegistrar batteryPropertiesRegistrar =
 4              IBatteryPropertiesRegistrar.Stub.asInterface(b);
 5      try {
 6          batteryPropertiesRegistrar.registerListener(new BatteryListener());  //監聽事件
 7      } catch (RemoteException e) {
 8          // Should never happen.
 9      }
10      publishBinderService("battery", new BinderServic

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一 Harbor主從介紹 harbor官方預設提供主從複製的方案來解決鏡像同步問題,通過複製的方式,我們可以實時將測試環境harbor倉庫的鏡像同步到生產環境harbor,類似於如下流程: Harbor以“項目”為中心,通過對項目配置“複製策略”,標明需要複製的項目以及鏡像。管理員在複製策略中指明目 ...
  • UTC(Coordinated Universal Time)時間:協調世界時,即世界標準時間 GMT(Greenwich Mean Time):格林威治/格林尼治時間 GMT=UTC,均使用秒數來計算。 UTC+時區差=本地時間。 時區差東為正,西為負。 Unix時間戳:電腦中看到的UTC時間都 ...
  • Mac下的date命令是BSD(Berkeley Software Distribution)系的,Linux下date命令是GNU(GNU's Not Unix)系,二者用法有一些區別。 BSD並不特指任何一個BSD衍生版本,而是類UNIX操作系統中的一個分支的總稱。 Mac OS X和iOS實際 ...
  • Redis官網:https://redis.io Redis是完全開源免費的,遵守 "BSD協議" . Redis是一個高性能的 資料庫. @[TOC] Redis具有以下特點 1. 支持數據持久化,可將記憶體中的數據保存至磁碟,重啟後可以再次載入進行使用. 2. 支持五種數據類型. 3. 支持資料庫 ...
  • ArchLinux關機、重啟時出現ACPI錯誤: ACPI中類似 錯誤是因為雙顯卡而導致的,所以解決思路如下: 1. 啟動Archlinux時關閉獨立顯卡,使用集顯。 2. 屏蔽nouveau,安裝nvidia閉源驅動。 ...
  • 查看所有運行中的進程:ps aux | less顯示所有進程: ps -A / ps -e顯示進程的樹狀圖:pstree ...
  • 示範一下如何透過Docker安裝GitLab,也順便將一些常用的東西紀錄一下 作業系統: CentOS 7 安裝Docker CE 1. 先移除系統上預先安裝的Docker舊版本 2. 安裝相關套件 3. 新增Docker 官方的stable 套件庫(repository) 4. 更新yum 的套件 ...
  • 今天使用docker部署asp.net core應用程式時,發現當我們做好基礎鏡像之後需要把鏡像導出到正式環境,因此學習了一下如何從docker中導出鏡像: 1.首先通過docker images命令查看需要導出的鏡像信息 我們要導出容器id為: 47c4890b7bd1的鏡像, 使用 docker ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...