Android PowerManagerService省电模式策略控制
前言
初识Android PowerManagerService省电模式 让我们省电模式的概念有了初步的认识,
Android PowerManagerService 打开省电模式 对打开省电模式的代码进行了分析。
有了前面两篇文章的基础,现在我们开始分析如何控制省电模式策略,请读者务必仔细。
本文涉及的文件如下:
- frameworks/base/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
- frameworks/base/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
监听策略改变
// BatterySaverPolicy.java public void systemReady() { ConcurrentUtils.wtfIfLockHeld(TAG, mLock); // 1. 监听 Global 数据 // 当数据改变,回调 onChange() mContentResolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.BATTERY_SAVER_CONSTANTS), false, this); mContentResolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS), false, this); // 无障碍模式相关 final AccessibilityManager acm = mContext.getSystemService(AccessibilityManager.class); acm.addAccessibilityStateChangeListener(enabled -> mAccessibilityEnabled.update(enabled)); mAccessibilityEnabled.initialize(acm.isEnabled()); // 车载相关 UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class); uiModeManager.addOnProjectionStateChangedListener(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE, mContext.getMainExecutor(), mOnProjectionStateChangedListener); mAutomotiveProjectionActive.initialize( uiModeManager.getActiveProjectionTypes() != UiModeManager.PROJECTION_TYPE_NONE); // 2. 监听 Config 表中,命名空间DeviceConfig.NAMESPACE_BATTERY_SAVER下的所有数据 // 当数据改变时,回调 onPropertiesChanged() DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BATTERY_SAVER, mContext.getMainExecutor(), this); // 3. 读取 Config 表中,命名空间DeviceConfig.NAMESPACE_BATTERY_SAVER下的所有数据 mLastDeviceConfigProperties = DeviceConfig.getProperties(DeviceConfig.NAMESPACE_BATTERY_SAVER); // 4. 获取 Global 表中的数据,并执行更新操作 onChange(true, null); }
前两步是监听数据,只不过回调的方式不同,但是最终都是根据数据更新省电模式策略,然后通知监听者。
因此本文只分析其中一个回调 onChange(),而另外一个回调 onPropertiesChanged() 请读者自行分析。
DeviceConfig 就是获取 SettingsProvider 中 Config 表中的数据,这些数据的 KEY 以命名空间开头,然后把所有这些数据封装成一个 DeviceConfig.Properties 对象。
后两步,是主动获取一次数据,然后主动触发一次 onChange() 回调。
// BatterySaverPolicy.java public void onChange(boolean selfChange, Uri uri) { refreshSettings(); } private void refreshSettings() { synchronized (mLock) { // 1. 获取与设备无关的省电模式策略 // 例如,vibration_disabled=true,adjust_brightness_factor=0.5 final String setting = getGlobalSetting(Settings.Global.BATTERY_SAVER_CONSTANTS); // 2. 获取与设备相关的省电模式策略 // 格式为, cpufreq-i=core-number:frequency/...,cpufreq-n=core-number:frequency/... String deviceSpecificSetting = getGlobalSetting( Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS); // 保存与设备相关的省电模式策略的KEY值 mDeviceSpecificSettingsSource = Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS; // 3. 如果与设备相关的省电策略为空,那么加载 framework-res 的 config.xml 中的配置 config_batterySaverDeviceSpecificConfig if (TextUtils.isEmpty(deviceSpecificSetting) || "null".equals(deviceSpecificSetting)) { // 配置默认也为空 deviceSpecificSetting = mContext.getString(getDeviceSpecificConfigResId()); // 表示是从配置文件中配置的 mDeviceSpecificSettingsSource = "(overlay)"; } // 4. 更新策略 if (!updateConstantsLocked(setting, deviceSpecificSetting)) { // 没有变化,就不去执行后面的通知监听者的操作 return; } } // 5. 如果策略改变,通知监听者 maybeNotifyListenersOfPolicyChange(); }
Settings.Global.BATTERY_SAVER_CONSTANTS 保存的是与设备无关的省电策略。例如,这个字段的值可以为 vibration_disabled=true,adjust_brightness_factor=0.5
,不同的策略通过逗号进行分隔。从名字可以猜测出,前一个省电策略表示关闭振动,后一个省电策略表示屏幕亮度降低一半。
什么叫与设备无关策略?如果这个省电策略不会因设备不同而不同的话,那这个策略就是与设备无关,反之就是与设备有关策略。
Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS 保存的是与设备相关的省点策略,目前只保存 CPU 限频策略。 这个字段的值可能为 cpufreq-i=core-number:frequency,cpufreq-n=core-number:frequency
,其中 cpufreq-i 表示交互状态下的CPU限频策略,cpufreq-n 表示非交互状态下的CPU限频策略,core-number 表示 CPU 编号,frequency 表示需要限制的频率。交互状态和非交互状态的限频策略以逗号进行分隔。
当然,在省电模式下,不一定只限制一个CPU的频率,我们可以使用 / 来分隔不同的 CPU 限频策略,例如 cpufreq-i=core-number:frequency/core-number:frequency/core-number:frequency
.
通常,亮屏状态下为交互模式,灭屏状态下为非交互模式。
从第三步中可以看出,可以在 framework-res 模块的 config.xml 中配置 CPU 限频策略。记住这里,不要看完了我一系列的省电模式的文章,最终连 CPU 限频策略还不会配置哦!
第四步,会根据这些数据来更新省电模式策略。并且,如果省电模式策略改变了,那么还会执行第五步,通知监听者。
下面,重点分析第四步和第五步。
更新策略
现在,假设 Settings.Global.BATTERY_SAVER_CONSTANTS 和 Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS 保存的数据改变了,那么会调用 updateConstantsLocked() 更新省电模式策略
// BatterySaverPolicy.java boolean updateConstantsLocked(String setting, String deviceSpecificSetting) { // 如果是null,返回"" setting = TextUtils.emptyIfNull(setting); deviceSpecificSetting = TextUtils.emptyIfNull(deviceSpecificSetting); // 没有变化,直接返回 if (setting.equals(mSettings) && deviceSpecificSetting.equals(mDeviceSpecificSettings)) { return false; } // 1. 保存设置 mSettings = setting; mDeviceSpecificSettings = deviceSpecificSetting; // 2. 根据配置,创建新的策略 Poilcy p = Policy.fromSettings(setting, deviceSpecificSetting, mLastDeviceConfigProperties, null, DEFAULT_FULL_POLICY); // 3. 更新默认的省电模式策略 boolean changed = maybeUpdateDefaultFullPolicy(p); // 忽略 adaptive battery save 功能 mDefaultAdaptivePolicy = Policy.fromSettings("", "", mLastDeviceConfigProperties, KEY_SUFFIX_ADAPTIVE, DEFAULT_ADAPTIVE_POLICY); if (mPolicyLevel == POLICY_LEVEL_ADAPTIVE && !mAdaptivePolicy.equals(mDefaultAdaptivePolicy)) { // adaptive policy changed changed = true; } mAdaptivePolicy = mDefaultAdaptivePolicy; // 4. 更新有效的省电模式策略 updatePolicyDependenciesLocked(); // 5. 返回状态,表示省电模式策略是否改变 return changed; }
第二步,根据配置的数据创建一个策略,注意最后一个参数 DEFAULT_FULL_POLICY,它表示默认的省电模式策略
// BatterySaverPolicy.java private static Policy fromSettings(String settings, String deviceSpecificSettings, DeviceConfig.Properties properties, String configSuffix, Policy defaultPolicy) { // 以逗号为分隔符解析字符串 final KeyValueListParser parser = new KeyValueListParser(','); configSuffix = TextUtils.emptyIfNull(configSuffix); // 1. 首先解析设备相关的策略参数 try { parser.setString(deviceSpecificSettings == null ? "" : deviceSpecificSettings); } catch (IllegalArgumentException e) { Slog.wtf(TAG, "Bad device specific battery saver constants: " + deviceSpecificSettings); } // 读取的值的格式为 core-number:frequency/core-number:frequency/... final String cpuFreqInteractive = parser.getString(KEY_CPU_FREQ_INTERACTIVE, ""); final String cpuFreqNoninteractive = parser.getString(KEY_CPU_FREQ_NONINTERACTIVE, ""); // 2. 再解析设备无关的策略参数 try { parser.setString(settings == null ? "" : settings); } catch (IllegalArgumentException e) { Slog.wtf(TAG, "Bad battery saver constants: " + settings); } // 策略参数取值的优先级为: Settings > DeviceConfig > 默认省电策略 final float adjustBrightnessFactor = parser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, properties.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR + configSuffix, defaultPolicy.adjustBrightnessFactor)); final boolean advertiseIsEnabled = parser.getBoolean(KEY_ADVERTISE_IS_ENABLED, properties.getBoolean(KEY_ADVERTISE_IS_ENABLED + configSuffix, defaultPolicy.advertiseIsEnabled)); final boolean deferFullBackup = parser.getBoolean(KEY_DEFER_FULL_BACKUP, properties.getBoolean(KEY_DEFER_FULL_BACKUP + configSuffix, defaultPolicy.deferFullBackup)); final boolean deferKeyValueBackup = parser.getBoolean(KEY_DEFER_KEYVALUE_BACKUP, properties.getBoolean(KEY_DEFER_KEYVALUE_BACKUP + configSuffix, defaultPolicy.deferKeyValueBackup)); final boolean disableAnimation = parser.getBoolean(KEY_DISABLE_ANIMATION, properties.getBoolean(KEY_DISABLE_ANIMATION + configSuffix, defaultPolicy.disableAnimation)); final boolean disableAod = parser.getBoolean(KEY_DISABLE_AOD, properties.getBoolean(KEY_DISABLE_AOD + configSuffix, defaultPolicy.disableAod)); final boolean disableLaunchBoost = parser.getBoolean(KEY_DISABLE_LAUNCH_BOOST, properties.getBoolean(KEY_DISABLE_LAUNCH_BOOST + configSuffix, defaultPolicy.disableLaunchBoost)); final boolean disableOptionalSensors = parser.getBoolean(KEY_DISABLE_OPTIONAL_SENSORS, properties.getBoolean(KEY_DISABLE_OPTIONAL_SENSORS + configSuffix, defaultPolicy.disableOptionalSensors)); final boolean disableVibrationConfig = parser.getBoolean(KEY_DISABLE_VIBRATION, properties.getBoolean(KEY_DISABLE_VIBRATION + configSuffix, defaultPolicy.disableVibration)); final boolean enableBrightnessAdjustment = parser.getBoolean( KEY_ENABLE_BRIGHTNESS_ADJUSTMENT, properties.getBoolean(KEY_ENABLE_BRIGHTNESS_ADJUSTMENT + configSuffix, defaultPolicy.enableAdjustBrightness)); final boolean enableDataSaver = parser.getBoolean(KEY_ENABLE_DATASAVER, properties.getBoolean(KEY_ENABLE_DATASAVER + configSuffix, defaultPolicy.enableDataSaver)); final boolean enableFirewall = parser.getBoolean(KEY_ENABLE_FIREWALL, properties.getBoolean(KEY_ENABLE_FIREWALL + configSuffix, defaultPolicy.enableFirewall)); final boolean enableNightMode = parser.getBoolean(KEY_ENABLE_NIGHT_MODE, properties.getBoolean(KEY_ENABLE_NIGHT_MODE + configSuffix, defaultPolicy.enableNightMode)); final boolean enableQuickDoze = parser.getBoolean(KEY_ENABLE_QUICK_DOZE, properties.getBoolean(KEY_ENABLE_QUICK_DOZE + configSuffix, defaultPolicy.enableQuickDoze)); final boolean forceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY, properties.getBoolean(KEY_FORCE_ALL_APPS_STANDBY + configSuffix, defaultPolicy.forceAllAppsStandby)); final boolean forceBackgroundCheck = parser.getBoolean(KEY_FORCE_BACKGROUND_CHECK, properties.getBoolean(KEY_FORCE_BACKGROUND_CHECK + configSuffix, defaultPolicy.forceBackgroundCheck)); final int locationMode = parser.getInt(KEY_LOCATION_MODE, properties.getInt(KEY_LOCATION_MODE + configSuffix, defaultPolicy.locationMode)); final int soundTriggerMode = parser.getInt(KEY_SOUNDTRIGGER_MODE, properties.getInt(KEY_SOUNDTRIGGER_MODE + configSuffix, defaultPolicy.soundTriggerMode)); // 3. 创建一个新的策略 return new Policy( adjustBrightnessFactor, advertiseIsEnabled, (new CpuFrequencies()).parseString(cpuFreqInteractive), (new CpuFrequencies()).parseString(cpuFreqNoninteractive), deferFullBackup, deferKeyValueBackup, disableAnimation, disableAod, disableLaunchBoost, disableOptionalSensors, /* disableVibration */ disableVibrationConfig, enableBrightnessAdjustment, enableDataSaver, enableFirewall, enableNightMode, enableQuickDoze, forceAllAppsStandby, forceBackgroundCheck, locationMode, soundTriggerMode ); }
与设备相关的省电策略,也就是 CPU 限频策略,如果空缺,也不会被任何配置取代。
与设备无关的省电策略,如果某一项空缺,会依次被 DeviceConfig 和 默认的省电策略 DEFAULT_FULL_POLICY 取代。
最终通过解析的数据,创建一个策略 Policy 对象。
新的策略已经创建出来,之后调用 maybeUpdateDefaultFullPolicy() 更新默认的省电策略。
// BatterySaverPolicy.java private boolean maybeUpdateDefaultFullPolicy(Policy p) { boolean fullPolicyChanged = false; if (!mDefaultFullPolicy.equals(p)) { // mFullPolicy 会被 setFullPolicyLocked() 修改 // 如果 mFullPolicy 与 mDefaultFullPolicy 不同, 那么表示 mFullPolicy 被覆盖 // 如果相同,表示没有被覆盖 boolean isDefaultFullPolicyOverridden = !mDefaultFullPolicy.equals(mFullPolicy); if (!isDefaultFullPolicyOverridden) { // mFullPolicy 没有被覆盖,就要同步进行更新 mFullPolicy = p; // 现在处于省电模式中,需要通知监听者 fullPolicyChanged = (mPolicyLevel == POLICY_LEVEL_FULL); } // 更新默认的省电模式策略 mDefaultFullPolicy = p; } return fullPolicyChanged; }
mDefaultFullPolicy 代表的就是默认的省电策略,这里会更新它,但是同时,如果 mFullPolicy 没有被 setFullPolicyLocked() 修改(源码设计中称之为 overridden),那么也会同步更新它。
现在默认的省点策略已经更新,但是要应用的最终策略还不是它,需要调用 updatePolicyDependenciesLocked() 来根据情况,更新一个有效的省点策略
// BatterySaverPolicy.java private void updatePolicyDependenciesLocked() { final Policy rawPolicy = getCurrentRawPolicyLocked(); final int locationMode; invalidatePowerSaveModeCaches(); if (mAutomotiveProjectionActive.get() && rawPolicy.locationMode != PowerManager.LOCATION_MODE_NO_CHANGE && rawPolicy.locationMode != PowerManager.LOCATION_MODE_FOREGROUND_ONLY) { // If car projection is enabled, ensure that navigation works. locationMode = PowerManager.LOCATION_MODE_FOREGROUND_ONLY; } else { locationMode = rawPolicy.locationMode; } mEffectivePolicyRaw = new Policy( rawPolicy.adjustBrightnessFactor, rawPolicy.advertiseIsEnabled, rawPolicy.cpuFrequenciesForInteractive, rawPolicy.cpuFrequenciesForNoninteractive, rawPolicy.deferFullBackup, rawPolicy.deferKeyValueBackup, rawPolicy.disableAnimation, rawPolicy.disableAod, rawPolicy.disableLaunchBoost, rawPolicy.disableOptionalSensors, // Don't disable vibration when accessibility is on. rawPolicy.disableVibration && !mAccessibilityEnabled.get(), rawPolicy.enableAdjustBrightness, rawPolicy.enableDataSaver, rawPolicy.enableFirewall, // Don't force night mode when car projection is enabled. rawPolicy.enableNightMode && !mAutomotiveProjectionActive.get(), rawPolicy.enableQuickDoze, rawPolicy.forceAllAppsStandby, rawPolicy.forceBackgroundCheck, locationMode, rawPolicy.soundTriggerMode ); // ... }
很简单,根据是否是车载项目,以及是否打开无障碍模式,更新有效的省电模式策略 mEffectivePolicyRaw。
通知监听者
现在省电策略已经更新完毕,如果策略改变了,那么需要通知监听者
// BatterySaverPolicy.java private void maybeNotifyListenersOfPolicyChange() { final BatterySaverPolicyListener[] listeners; synchronized (mLock) { if (mPolicyLevel == POLICY_LEVEL_OFF) { // 省电模式没有打开 return; } // Don't call out to listeners with the lock held. listeners = mListeners.toArray(new BatterySaverPolicyListener[mListeners.size()]); } mHandler.post(() -> { for (BatterySaverPolicyListener listener : listeners) { // 通知监听者 listener.onBatterySaverPolicyChanged(this); } }); }
目前,省电策略的监听者只有一个 BatterySaverController
// BatterySaverController.java public void onBatterySaverPolicyChanged(BatterySaverPolicy policy) { if (!isPolicyEnabled()) { return; // No need to send it if not enabled. } mHandler.postStateChanged(/*sendBroadcast=*/ true, REASON_POLICY_CHANGED); }
最终会调用 handleBatterySaverStateChanged()
// BatterySaverController.java void handleBatterySaverStateChanged(boolean sendBroadcast, int reason) { final LowPowerModeListener[] listeners; final boolean enabled; final boolean isInteractive = getPowerManager().isInteractive(); final ArrayMap<String, String> fileValues; synchronized (mLock) { enabled = getFullEnabledLocked() || getAdaptiveEnabledLocked(); mFullPreviouslyEnabled = getFullEnabledLocked(); mAdaptivePreviouslyEnabled = getAdaptiveEnabledLocked(); listeners = mListeners.toArray(new LowPowerModeListener[0]); mIsInteractive = isInteractive; // 1. 获取CPU限频策略 if (enabled) { fileValues = mBatterySaverPolicy.getFileValues(isInteractive); } else { fileValues = null; } } final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); if (pmi != null) { pmi.setPowerMode(Mode.LOW_POWER, isEnabled()); } updateBatterySavingStats(); // 2. 应用CPU限频策略 if (ArrayUtils.isEmpty(fileValues)) { // 如果策略为空,那肯定是关闭了省电模式,此时需要恢复正常的CPU频率 mFileUpdater.restoreDefault(); } else { // 策略不为空,首先保存节点中原本的值到 /data/system/battery-saver/default-values.xml,然后向节点写值 mFileUpdater.writeFiles(fileValues); } if (sendBroadcast) { // 3. 发送广播 Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); mContext.sendBroadcastAsUser(intent, UserHandle.ALL); // Send the broadcast to a manifest-registered receiver that is specified in the config. if (getPowerSaveModeChangedListenerPackage().isPresent()) { intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED) .setPackage(getPowerSaveModeChangedListenerPackage().get()) .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND | Intent.FLAG_RECEIVER_FOREGROUND); mContext.sendBroadcastAsUser(intent, UserHandle.ALL); } // Send internal version that requires signature permission. intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); mContext.sendBroadcastAsUser(intent, UserHandle.ALL, Manifest.permission.DEVICE_POWER); // 4. 通知监听者,省电策略已经改变 for (LowPowerModeListener listener : listeners) { final PowerSaveState result = mBatterySaverPolicy.getBatterySaverPolicy(listener.getServiceType()); listener.onLowPowerModeChanged(result); } } }
这段代码,我只分析如何应用省电策略。
第一步,是获取 CPU 限频策略,以一个 Map 表示,其中 KEY 为 CPU 限频的节点路径,VALUE 为被限制的频率。
然后第二步是应用这个 CPU 限频策略,其实就是向节点写值。 不过在写值之前,会先保存节点中原本的值到 /data/system/battery-saver/default-values.xml 文件中,这个文件的作用是,当关闭省电模式,会恢复 CPU 原本的频率。
最后,通知监听者,策略已经改变,你们需要做相应的适配。这些监听者都是系统服务,例如WindowManagerService 就是一个监听者,它会根据省电模式策略,决定是否关闭动画,关键代码如下:
case NEW_ANIMATOR_SCALE: { // 省电模式下,scale 为 0 float scale = getCurrentAnimatorScale(); // 关闭 system_server 进程的动画 ValueAnimator.setDurationScale(scale); Session session = (Session)msg.obj; if (session != null) { try { session.mCallback.onAnimatorScaleChanged(scale); } catch (RemoteException e) { } } else { ArrayList<IWindowSessionCallback> callbacks = new ArrayList<IWindowSessionCallback>(); synchronized (mGlobalLock) { for (int i=0; i<mSessions.size(); i++) { callbacks.add(mSessions.valueAt(i).mCallback); } } for (int i=0; i<callbacks.size(); i++) { try { // 关闭 app 进程的动画 callbacks.get(i).onAnimatorScaleChanged(scale); } catch (RemoteException e) { } } } break; }
如何配置策略
看完了策略控制的源码分析,总结下如何控制策略
- 修改 Settings.Global.BATTERY_SAVER_CONSTANTS 字段值,可以设置的字参考注释文档,或者参考源码的解析部分。例如
vibration_disabled=true,adjust_brightness_factor=0.5
- 修改 Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS字段值,但是这个只能修改 CPU 限频策略。例如
cpufreq-i=0:1804810/1:1804900,cpufreq-n=0:1804700/1:1804600
。 - 可以通过 framework-res 的 config.xml 的config_batterySaverDeviceSpecificConfig 配置默认的 CPU 限频策略。但是这个会被 Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS 覆盖,在有值的情况下。
结束
省电模式的文章,到此就结束了。本来我还准备分析省电模式影响的功能,但是由于影响的功能有点多,但是我又无法精通所有的功能,因此我就不献丑了去分析了。
在工作中,如果你熟悉的某个功能模块,例如 WindowManagerService,它受省电模式影响,我相信,如果你看完我的文章,应该能自行分析受影响的功能。
到此这篇关于Android PowerManagerService省电模式策略控制的文章就介绍到这了,更多相关Android PowerManagerService 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Android利用Flutter path绘制粽子的示例代码
端午将至,作为中华民族的非常重要的传统节日,粽子那是必不可少的。今天跟随本篇文章用Flutter path画一个会科普节日的的粽子吧2022-05-05Android学习笔记-保存文件(Saving Files)
这篇文章主要介绍了Android中保存文件(Saving Files)的方法,需要的朋友可以参考下2014-10-10源码解析Android Jetpack组件之ViewModel的使用
Jetpack 是一个丰富的组件库,它的组件库按类别分为 4 类,分别是架构(Architecture)、界面(UI)、 行为(behavior)和基础(foundation)。本文将从源码和大家讲讲Jetpack组件中ViewModel的使用2023-04-04详解Android App中的AsyncTask异步任务执行方式
这篇文章主要介绍了Android App中的AsyncTask异步任务执行方式,文中举了一个打开网络图片的例子帮助大家直观理解,需要的朋友可以参考下2016-04-04Android Handle原理(Looper,Handler和Message)三者关系案例详解
这篇文章主要介绍了Android Handle原理(Looper,Handler和Message三者关系案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下2021-08-08
最新评论