知其然也要知其所以然---Kernel上报电量UEvent事件流程分析

本文深入探讨了Linux内核如何通过工作队列和uevent机制更新电量信息,并详细讲解了这一过程如何被用户空间的healthd服务捕捉,最终触发电量广播。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

 

 

1,

kernel-4.4\drivers\power\power_supply_core.c

当电量信息需要更新的时候,kernel会调用power_supply_changed_work这个工作队列,使用kobject_uevent函数往上发送uevent事件,action是KOBJ_CHANGE;

static void power_supply_changed_work(struct work_struct *work)
{
	unsigned long flags;
	struct power_supply *psy = container_of(work, struct power_supply,
						changed_work);

	dev_dbg(&psy->dev, "%s\n", __func__);

	spin_lock_irqsave(&psy->changed_lock, flags);
	/*
	 * Check 'changed' here to avoid issues due to race between
	 * power_supply_changed() and this routine. In worst case
	 * power_supply_changed() can be called again just before we take above
	 * lock. During the first call of this routine we will mark 'changed' as
	 * false and it will stay false for the next call as well.
	 */
	if (likely(psy->changed)) {
		printk("power_supply_changed_work 1111 \n" );
		psy->changed = false;
		spin_unlock_irqrestore(&psy->changed_lock, flags);
		class_for_each_device(power_supply_class, NULL, psy,
				      __power_supply_changed_work);
		power_supply_update_leds(psy);
		atomic_notifier_call_chain(&power_supply_notifier,
				PSY_EVENT_PROP_CHANGED, psy);
		kobject_uevent(&psy->dev.kobj, KOBJ_CHANGE);
		spin_lock_irqsave(&psy->changed_lock, flags);
	}

	/*
	 * Hold the wakeup_source until all events are processed.
	 * power_supply_changed() might have called again and have set 'changed'
	 * to true.
	 */
	if (likely(!psy->changed))
		pm_relax(&psy->dev);
	spin_unlock_irqrestore(&psy->changed_lock, flags);
}

 

来看看kobject_uevent(&psy->dev.kobj, KOBJ_CHANGE);

/kernel-4.4/lib/kobject_uevent.c

372  int kobject_uevent(struct kobject *kobj, enum kobject_action action)
373  {
374  	return kobject_uevent_env(kobj, action, NULL);
375  }

kobject_uevent_env代码相当长,由于已经配置了CONFIG_NET,在这里就只需要看netlink部分。

154  /**
155   * kobject_uevent_env - send an uevent with environmental data
156   *
157   * @action: action that is happening
158   * @kobj: struct kobject that the action is happening to
159   * @envp_ext: pointer to environmental data
160   *
161   * Returns 0 if kobject_uevent_env() is completed with success or the
162   * corresponding error when it fails.
163   */
164  int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
165  		       char *envp_ext[])
166  {
167  	struct kobj_uevent_env *env;
168  	const char *action_string = kobject_actions[action];
169  	const char *devpath = NULL;
170  	const char *subsystem;
171  	struct kobject *top_kobj;
172  	struct kset *kset;
173  	const struct kset_uevent_ops *uevent_ops;
174  	int i = 0;
175  	int retval = 0;
176  #ifdef CONFIG_NET
177  	struct uevent_sock *ue_sk;
178  #endif
179  
180  	pr_debug("kobject: '%s' (%p): %s\n",
181  		 kobject_name(kobj), kobj, __func__);
182  
183  	/* search the kset we belong to */
184  	top_kobj = kobj;
185  	while (!top_kobj->kset && top_kobj->parent)
186  		top_kobj = top_kobj->parent;
187  
188  	if (!top_kobj->kset) {
189  		pr_debug("kobject: '%s' (%p): %s: attempted to send uevent "
190  			 "without kset!\n", kobject_name(kobj), kobj,
191  			 __func__);
192  		return -EINVAL;
193  	}
194  
195  	kset = top_kobj->kset;
196  	uevent_ops = kset->uevent_ops;
197  
198  	/* skip the event, if uevent_suppress is set*/
199  	if (kobj->uevent_suppress) {
200  		pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
201  				 "caused the event to drop!\n",
202  				 kobject_name(kobj), kobj, __func__);
203  		return 0;
204  	}
205  	/* skip the event, if the filter returns zero. */
206  	if (uevent_ops && uevent_ops->filter)
207  		if (!uevent_ops->filter(kset, kobj)) {
208  			pr_debug("kobject: '%s' (%p): %s: filter function "
209  				 "caused the event to drop!\n",
210  				 kobject_name(kobj), kobj, __func__);
211  			return 0;
212  		}
213  
214  	/* originating subsystem */
215  	if (uevent_ops && uevent_ops->name)
216  		subsystem = uevent_ops->name(kset, kobj);
217  	else
218  		subsystem = kobject_name(&kset->kobj);
219  	if (!subsystem) {
220  		pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
221  			 "event to drop!\n", kobject_name(kobj), kobj,
222  			 __func__);
223  		return 0;
224  	}
225  
226  	/* environment buffer */
227  	env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
228  	if (!env)
229  		return -ENOMEM;
230  
231  	/* complete object path */
232  	devpath = kobject_get_path(kobj, GFP_KERNEL);
233  	if (!devpath) {
234  		retval = -ENOENT;
235  		goto exit;
236  	}
237  
238  	/* default keys */
239  	retval = add_uevent_var(env, "ACTION=%s", action_string);
240  	if (retval)
241  		goto exit;
242  	retval = add_uevent_var(env, "DEVPATH=%s", devpath);
243  	if (retval)
244  		goto exit;
245  	retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
246  	if (retval)
247  		goto exit;
248  
249  	/* keys passed in from the caller */
250  	if (envp_ext) {
251  		for (i = 0; envp_ext[i]; i++) {
252  			retval = add_uevent_var(env, "%s", envp_ext[i]);
253  			if (retval)
254  				goto exit;
255  		}
256  	}
257  
258  	/* let the kset specific function add its stuff */
259  	if (uevent_ops && uevent_ops->uevent) {
260  		retval = uevent_ops->uevent(kset, kobj, env);
261  		if (retval) {
262  			pr_debug("kobject: '%s' (%p): %s: uevent() returned "
263  				 "%d\n", kobject_name(kobj), kobj,
264  				 __func__, retval);
265  			goto exit;
266  		}
267  	}
268  
269  	/*
270  	 * Mark "add" and "remove" events in the object to ensure proper
271  	 * events to userspace during automatic cleanup. If the object did
272  	 * send an "add" event, "remove" will automatically generated by
273  	 * the core, if not already done by the caller.
274  	 */
275  	if (action == KOBJ_ADD)
276  		kobj->state_add_uevent_sent = 1;
277  	else if (action == KOBJ_REMOVE)
278  		kobj->state_remove_uevent_sent = 1;
279  
280  	mutex_lock(&uevent_sock_mutex);
281  	/* we will send an event, so request a new sequence number */
282  	retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++uevent_seqnum);
283  	if (retval) {
284  		mutex_unlock(&uevent_sock_mutex);
285  		goto exit;
286  	}
287  
288  #if defined(CONFIG_NET)
289  	/* send netlink message */
290  	list_for_each_entry(ue_sk, &uevent_sock_list, list) {
291  		struct sock *uevent_sock = ue_sk->sk;
292  		struct sk_buff *skb;
293  		size_t len;
294  
295  		if (!netlink_has_listeners(uevent_sock, 1))
296  			continue;
297  
298  		/* allocate message with the maximum possible size */
299  		len = strlen(action_string) + strlen(devpath) + 2;
300  		skb = alloc_skb(len + env->buflen, GFP_KERNEL);
301  		if (skb) {
302  			char *scratch;
303  
304  			/* add header */
305  			scratch = skb_put(skb, len);
306  			sprintf(scratch, "%s@%s", action_string, devpath);
307  
308  			/* copy keys to our continuous event payload buffer */
309  			for (i = 0; i < env->envp_idx; i++) {
310  				len = strlen(env->envp[i]) + 1;
311  				scratch = skb_put(skb, len);
312  				strcpy(scratch, env->envp[i]);
313  			}
314  
315  			NETLINK_CB(skb).dst_group = 1;
316  			retval = netlink_broadcast_filtered(uevent_sock, skb,
317  							    0, 1, GFP_KERNEL,
318  							    kobj_bcast_filter,
319  							    kobj);
320  			/* ENOBUFS should be handled in userspace */
321  			if (retval == -ENOBUFS || retval == -ESRCH)
322  				retval = 0;
323  		} else
324  			retval = -ENOMEM;
325  	}
326  #endif
327  	mutex_unlock(&uevent_sock_mutex);
328  
329  #ifdef CONFIG_UEVENT_HELPER
330  	/* call uevent_helper, usually only enabled during early boot */
331  	if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
332  		struct subprocess_info *info;
333  
334  		retval = add_uevent_var(env, "HOME=/");
335  		if (retval)
336  			goto exit;
337  		retval = add_uevent_var(env,
338  					"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
339  		if (retval)
340  			goto exit;
341  		retval = init_uevent_argv(env, subsystem);
342  		if (retval)
343  			goto exit;
344  
345  		retval = -ENOMEM;
346  		info = call_usermodehelper_setup(env->argv[0], env->argv,
347  						 env->envp, GFP_KERNEL,
348  						 NULL, cleanup_uevent_env, env);
349  		if (info) {
350  			retval = call_usermodehelper_exec(info, UMH_NO_WAIT);
351  			env = NULL;	/* freed by cleanup_uevent_env */
352  		}
353  	}
354  #endif
355  
356  exit:
357  	kfree(devpath);
358  	kfree(env);
359  	return retval;
360  }

retval = add_uevent_var(env, "%s", envp_ext[i]);//将kernel想要发送的状态信息存储起来。

最终是通过netlink_broadcast_filtered向用户空间发送了uevent信息。

那这个信息,接收者是谁呢?

那就在我们health hidl里面了。

hardware\interfaces\health\2.0\default\healthd_common.cpp

static void uevent_event(uint32_t /*epevents*/) {
    char msg[UEVENT_MSG_LEN + 2];
    char* cp;
    int n;

    n = uevent_kernel_multicast_recv(uevent_fd, msg, UEVENT_MSG_LEN);
    if (n <= 0) return;
    if (n >= UEVENT_MSG_LEN) /* overflow -- discard */
        return;

    msg[n] = '\0';
    msg[n + 1] = '\0';
    cp = msg;

	LOGD("healthd_common.cpp uevent_event msg=%s\n", msg);//lgy
	LOGD("healthd_common.cpp uevent_event cp=%s\n", cp);//lgy
	
    while (*cp) {
        if (!strcmp(cp, "SUBSYSTEM=" POWER_SUPPLY_SUBSYSTEM)) {
			LOGD("healthd_common.cpp 111111111 SUBSYSTEM cp=%s\n", cp);//lgy
            healthd_battery_update();
            break;
        }

        /* advance to after the next \0 */
        while (*cp++)
            ;
    }
}

通过uevent_kernel_multicast_recv接收kernel广播上来的信息.

再通过判断是不是POWER_SUPPLY_SUBSYSTEM(#define POWER_SUPPLY_SUBSYSTEM "power_supply")这个子系统中的信息,如果是那就执行healthd_battery_update()

static void healthd_battery_update(void) {
	LOGD("healthd_common.cpp healthd_battery_update lgy 1111\n");//lgy
    Health::getImplementation()->update();
}

然后调用health.cpp中的update()

Return<Result> Health::update() {
	
	LOGD("health.cpp update lgy 11111");
    if (!healthd_mode_ops || !healthd_mode_ops->battery_update) {
        LOG(WARNING) << "health@2.0: update: not initialized. "
                     << "update() should not be called in charger / recovery.";
        return Result::UNKNOWN;
    }

    // Retrieve all information and call healthd_mode_ops->battery_update, which calls
    // notifyListeners.
    bool chargerOnline = battery_monitor_->update();

    // adjust uevent / wakealarm periods
    healthd_battery_update_internal(chargerOnline);

    return Result::SUCCESS;
}

bool chargerOnline = battery_monitor_->update();

201  bool BatteryMonitor::update(void) {
202      bool logthis;
203  
204      initBatteryProperties(&props);
205  
206      if (!mHealthdConfig->batteryPresentPath.isEmpty())
207          props.batteryPresent = getBooleanField(mHealthdConfig->batteryPresentPath);
208      else
209          props.batteryPresent = mBatteryDevicePresent;
210  
211      props.batteryLevel = mBatteryFixedCapacity ?
212          mBatteryFixedCapacity :
213          getIntField(mHealthdConfig->batteryCapacityPath);
214      props.batteryVoltage = getIntField(mHealthdConfig->batteryVoltagePath) / 1000;
215  
216      if (!mHealthdConfig->batteryCurrentNowPath.isEmpty())
217          props.batteryCurrent = getIntField(mHealthdConfig->batteryCurrentNowPath) / 1000;
218  
219      if (!mHealthdConfig->batteryFullChargePath.isEmpty())
220          props.batteryFullCharge = getIntField(mHealthdConfig->batteryFullChargePath);
221  
222      if (!mHealthdConfig->batteryCycleCountPath.isEmpty())
223          props.batteryCycleCount = getIntField(mHealthdConfig->batteryCycleCountPath);
224  
225      if (!mHealthdConfig->batteryChargeCounterPath.isEmpty())
226          props.batteryChargeCounter = getIntField(mHealthdConfig->batteryChargeCounterPath);
227  
228      props.batteryTemperature = mBatteryFixedTemperature ?
229          mBatteryFixedTemperature :
230          getIntField(mHealthdConfig->batteryTemperaturePath);
231  
232      std::string buf;
233  
234      if (readFromFile(mHealthdConfig->batteryStatusPath, &buf) > 0)
235          props.batteryStatus = getBatteryStatus(buf.c_str());
236  
237      if (readFromFile(mHealthdConfig->batteryHealthPath, &buf) > 0)
238          props.batteryHealth = getBatteryHealth(buf.c_str());
239  
240      if (readFromFile(mHealthdConfig->batteryTechnologyPath, &buf) > 0)
241          props.batteryTechnology = String8(buf.c_str());
242  
243      unsigned int i;
244      double MaxPower = 0;
245  
246      for (i = 0; i < mChargerNames.size(); i++) {
247          String8 path;
248          path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH,
249                            mChargerNames[i].string());
250          if (getIntField(path)) {
251              path.clear();
252              path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH,
253                                mChargerNames[i].string());
254              switch(readPowerSupplyType(path)) {
255              case ANDROID_POWER_SUPPLY_TYPE_AC:
256                  props.chargerAcOnline = true;
257                  break;
258              case ANDROID_POWER_SUPPLY_TYPE_USB:
259                  props.chargerUsbOnline = true;
260                  break;
261              case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
262                  props.chargerWirelessOnline = true;
263                  break;
264              default:
265                  KLOG_WARNING(LOG_TAG, "%s: Unknown power supply type\n",
266                               mChargerNames[i].string());
267              }
268              path.clear();
269              path.appendFormat("%s/%s/current_max", POWER_SUPPLY_SYSFS_PATH,
270                                mChargerNames[i].string());
271              int ChargingCurrent =
272                      (access(path.string(), R_OK) == 0) ? getIntField(path) : 0;
273  
274              path.clear();
275              path.appendFormat("%s/%s/voltage_max", POWER_SUPPLY_SYSFS_PATH,
276                                mChargerNames[i].string());
277  
278              int ChargingVoltage =
279                  (access(path.string(), R_OK) == 0) ? getIntField(path) :
280                  DEFAULT_VBUS_VOLTAGE;
281  
282              double power = ((double)ChargingCurrent / MILLION) *
283                             ((double)ChargingVoltage / MILLION);
284              if (MaxPower < power) {
285                  props.maxChargingCurrent = ChargingCurrent;
286                  props.maxChargingVoltage = ChargingVoltage;
287                  MaxPower = power;
288              }
289          }
290      }
291  
292      logthis = !healthd_board_battery_update(&props);
293  
294      if (logthis) {
295          char dmesgline[256];
296          size_t len;
297          if (props.batteryPresent) {
298              snprintf(dmesgline, sizeof(dmesgline),
299                   "battery l=%d v=%d t=%s%d.%d h=%d st=%d",
300                   props.batteryLevel, props.batteryVoltage,
301                   props.batteryTemperature < 0 ? "-" : "",
302                   abs(props.batteryTemperature / 10),
303                   abs(props.batteryTemperature % 10), props.batteryHealth,
304                   props.batteryStatus);
305  
306              len = strlen(dmesgline);
307              if (!mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
308                  len += snprintf(dmesgline + len, sizeof(dmesgline) - len,
309                                  " c=%d", props.batteryCurrent);
310              }
311  
312              if (!mHealthdConfig->batteryFullChargePath.isEmpty()) {
313                  len += snprintf(dmesgline + len, sizeof(dmesgline) - len,
314                                  " fc=%d", props.batteryFullCharge);
315              }
316  
317              if (!mHealthdConfig->batteryCycleCountPath.isEmpty()) {
318                  len += snprintf(dmesgline + len, sizeof(dmesgline) - len,
319                                  " cc=%d", props.batteryCycleCount);
320              }
321          } else {
322              len = snprintf(dmesgline, sizeof(dmesgline),
323                   "battery none");
324          }
325  
326          snprintf(dmesgline + len, sizeof(dmesgline) - len, " chg=%s%s%s",
327                   props.chargerAcOnline ? "a" : "",
328                   props.chargerUsbOnline ? "u" : "",
329                   props.chargerWirelessOnline ? "w" : "");
330  
331          KLOG_WARNING(LOG_TAG, "%s\n", dmesgline);
332      }
333  
334      healthd_mode_ops->battery_update(&props);
335      return props.chargerAcOnline | props.chargerUsbOnline |
336              props.chargerWirelessOnline;
337  }

从文件节点中拿到数据,然后调用healthd_mode_ops->battery_update(&props);

healthd_mode_ops是什么呢?

hardware/interfaces/health/2.0/utils/libhealthservice/HealthServiceCommon.cpp

healthd_mode_service_2_0_ops

1  void healthd_mode_service_2_0_battery_update(struct android::BatteryProperties* prop) {
72      HealthInfo info;
73      convertToHealthInfo(prop, info.legacy);
74      Health::getImplementation()->notifyListeners(&info);
75  }
76  
77  static struct healthd_mode_ops healthd_mode_service_2_0_ops = {
78      .init = healthd_mode_service_2_0_init,
79      .preparetowait = healthd_mode_service_2_0_preparetowait,
80      .heartbeat = healthd_mode_service_2_0_heartbeat,
81      .battery_update = healthd_mode_service_2_0_battery_update,
82  };
83  
84  int health_service_main(const char* instance) {
85      gInstanceName = instance;
86      if (gInstanceName.empty()) {
87          gInstanceName = "default";
88      }
89      healthd_mode_ops = &healthd_mode_service_2_0_ops;
90      LOG(INFO) << LOG_TAG << gInstanceName << ": Hal starting main loop...";
91      return healthd_main();
92  }

所以healthd_mode_ops->battery_update(&props)其实调用的就是healthd_mode_service_2_0_battery_update();

Health::getImplementation()->notifyListeners(&info);这个notifyListeners又是谁呢?

void Health::notifyListeners(HealthInfo* healthInfo) {
	LOGD("Health.cpp notifyListeners lgy ");
    std::vector<StorageInfo> info;
    get_storage_info(info);

    std::vector<DiskStats> stats;
    get_disk_stats(stats);

    int32_t currentAvg = 0;

    struct BatteryProperty prop;
    status_t ret = battery_monitor_->getProperty(BATTERY_PROP_CURRENT_AVG, &prop);
    if (ret == OK) {
        currentAvg = static_cast<int32_t>(prop.valueInt64);
    }

    healthInfo->batteryCurrentAverage = currentAvg;
    healthInfo->diskStats = stats;
    healthInfo->storageInfos = info;

    std::lock_guard<std::mutex> _lock(callbacks_lock_);
    for (auto it = callbacks_.begin(); it != callbacks_.end();) {
        auto ret = (*it)->healthInfoChanged(*healthInfo);
        if (!ret.isOk() && ret.isDeadObject()) {
            it = callbacks_.erase(it);
        } else {
            ++it;
        }
    }
}

在这里调用回调函数 auto ret = (*it)->healthInfoChanged(*healthInfo);通知上层了。

23 interface IHealthInfoCallback {
24     /**
25      * An implementation of IHealthInfoBus must call healthInfoChanged on all
26      * registered callbacks after health info changes.
27      * @param info the updated HealthInfo
28      */
29     oneway healthInfoChanged(HealthInfo info);
30 };

在framework里使用health这个hidl服务 和 HealthHalCallback 

frameworks/base/services/core/java/com/android/server/BatteryService.java

  import android.hardware.health.V2_0.IHealth;

 import android.hardware.health.V2_0.IHealthInfoCallback;

1096      private final class HealthHalCallback extends IHealthInfoCallback.Stub
1097              implements HealthServiceWrapper.Callback {
1098          @Override public void healthInfoChanged(android.hardware.health.V2_0.HealthInfo props) {
1099              BatteryService.this.update(props);
1100          }
1101          // on new service registered
1102          @Override public void onRegistration(IHealth oldService, IHealth newService,
1103                  String instance) {
1104              if (newService == null) return;
1105  
1106              traceBegin("HealthUnregisterCallback");
1107              try {
1108                  if (oldService != null) {
1109                      int r = oldService.unregisterCallback(this);
1110                      if (r != Result.SUCCESS) {
1111                          Slog.w(TAG, "health: cannot unregister previous callback: " +
1112                                  Result.toString(r));
1113                      }
1114                  }
1115              } catch (RemoteException ex) {
1116                  Slog.w(TAG, "health: cannot unregister previous callback (transaction error): "
1117                              + ex.getMessage());
1118              } finally {
1119                  traceEnd();
1120              }
1121  
1122              traceBegin("HealthRegisterCallback");
1123              try {
1124                  int r = newService.registerCallback(this);
1125                  if (r != Result.SUCCESS) {
1126                      Slog.w(TAG, "health: cannot register callback: " + Result.toString(r));
1127                      return;
1128                  }
1129                  // registerCallback does NOT guarantee that update is called
1130                  // immediately, so request a manual update here.
1131                  newService.update();
1132              } catch (RemoteException ex) {
1133                  Slog.e(TAG, "health: cannot register callback (transaction error): "
1134                          + ex.getMessage());
1135              } finally {
1136                  traceEnd();
1137              }
1138          }
1139      }

最后调用的是 BatteryService.this.update(props)更新电量并发送ACTION_BATTERY_CHANGED广播。

update()-->processValuesLocked()-->sendBatteryChangedIntentLocked()

 final Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);

mHandler.post(() -> ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL));

 以下是debug整个流程的log;

2019-07-29 10:39:47.047 358-358/? D/LGY_HAL: healthd_common.cpp uevent_event msg=change@/devices/platform/battery/power_supply/battery
2019-07-29 10:39:47.047 358-358/? D/LGY_HAL: healthd_common.cpp uevent_event cp=change@/devices/platform/battery/power_supply/battery
2019-07-29 10:39:47.047 358-358/? D/LGY_HAL: healthd_common.cpp 111111111 SUBSYSTEM cp=SUBSYSTEM=power_supply
2019-07-29 10:39:47.047 358-358/? D/LGY_HAL: healthd_common.cpp healthd_battery_update lgy 1111
2019-07-29 10:39:47.047 358-358/? D/LGY_HAL: health.cpp update lgy 11111
2019-07-29 10:39:47.047 358-358/? D/LGY_HAL: BatteryMonitor.cpp BatteryMonitor::update  
2019-07-29 10:39:47.048 338-342/? D/LGY_HAL: NetlinkHandler::onEvent  subsys = p
2019-07-29 10:39:47.056 358-358/? D/LGY_HAL: HealthServiceCommon.cpp healthd_mode_service_2_0_battery_update lgy 111
2019-07-29 10:39:47.056 358-358/? D/LGY_HAL: Health.cpp notifyListeners lgy 
2019-07-29 10:39:47.057 861-1080/system_process D/lgy_debug: HealthHalCallback 1111111111111
2019-07-29 10:39:47.058 861-1080/system_process D/lgy_debug: HealthInfoUpdate  update 222222222222

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值