概况

Android手机上安装的很多应用都会频繁唤醒手机(唤醒系统、唤醒屏幕),造成手机耗电等现象。良好的对齐唤醒管理方案,就是对后台应用待机时不频繁唤醒,智能节省电量。

实现原理:APK作为该功能的入口,勾选应用后,将勾选的应用写入黑名单,并通知framework黑名单内容变化;framework接收到通知后,自动获取黑名单中的应用,保存到列表中;在framework调用接口中检测应用是否在黑名单中,如果在黑名单中则检测闹钟类型,如果闹钟类型是0或2,对应修改为1或3。

应用层功能实现

APK界面初始化

在ForbitAlarmLogic构造方法中初始化了数组列表listPkgs、forbitPkgs、allowPkgs、showPkgs。

listPkgs:表示需要设置对齐唤醒的应用,如果这些应用已经安装,就会显示在对齐唤醒设置的界面上。初始数据从/data/data/com.***.android.security/app_bin/forbitapplist.xml中获取,如果文件不存在,则从本地资源数组security_array_savepower_forbitalarms中获取。

forbitPkgs:表示对齐唤醒名单,即禁止唤醒的名单,界面勾选的应用。初始数据从SharedPreference数据库名ManagerUtil.PRE_NAME(com.***.android.savepowermanager_preferences)中获取键值ManagerUtil.FORBIT_ALARM_APP_LIST_KEY中保存的数据,将获取的数据保存到forbitPkgs数组中,如果没有数据则返回null。

allowPkgs:表示允许唤醒的名单,界面没有勾选的应用。初始数据从SharedPreference数据库ManagerUtil.PRE_NAME(com.***.android.savepowermanager_preferences)中获取键值为ManagerUtil.ALLOW_ALARM_APP_LIST_KEY中保存的数据,将获取的数据保存到allowPkgs数组列表中;如果没有数据则返回null。

showPkgs:表示要显示在对齐唤醒设置界面的数组应用列表,在数据初始化之前先将该数组清空。对齐唤醒方案优化之前,该数组保存的是listPkgs列表与已安装应用的交集。优化之后,同时还保存了已安装的第三方应用。

[java] view plain copy
  1. public ForbitAlarmLogic(Context ctx) {
  2. this.mCtx = ctx;
  3. pm = ctx.getPackageManager();
  4. xmlAppList = Util.getDefaultDataPath(ctx) + "/app_bin/applist.xml";
  5. String xmlFile = Util.getDefaultDataPath(ctx)+"/app_bin/forbitapplist.xml";
  6. File f = new File(xmlFile);
  7. if (!f.exists()) {
  8. Log.e("forbitapplist not exist!");
  9. String[] strs = mCtx.getResources().getStringArray(R.array.security_array_savepower_forbitalarms);
  10. for (String str : strs) {
  11. listPkgs.add(str);
  12. }
  13. } else {
  14. readFromXmlWithFilename(xmlFile, listPkgs);
  15. }
  16. readFromXml();
  17. Set<String> forbitset = (Set<String>)ManagerUtil.getPreferenceValue(mCtx, ManagerUtil.PRE_NAME,
  18. ManagerUtil.FORBIT_ALARM_APP_LIST_KEY, null, 4);
  19. if (forbitset != null) {
  20. Iterator<String> forbitir = forbitset.iterator();
  21. while(forbitir.hasNext()) {
  22. String forbit = forbitir.next();
  23. forbitPkgs.add(forbit);
  24. }
  25. }
  26. Set<String> allowset = (Set<String>)ManagerUtil.getPreferenceValue(mCtx, ManagerUtil.PRE_NAME,
  27. ManagerUtil.ALLOW_ALARM_APP_LIST_KEY, null, 4);
  28. if (allowset != null) {
  29. Iterator<String> allowir = allowset.iterator();
  30. while(allowir.hasNext()) {
  31. String allow = allowir.next();
  32. allowPkgs.add(allow);
  33. }
  34. }
  35. }
[java] view plain copy
  1. public ArrayList<DroidApp> getListApps() {
  2. if (forbitPkgs.size() == 0) {
  3. Set<String> forbitset= (Set<String>)ManagerUtil.getPreferenceValue(mCtx, ManagerUtil.PRE_NAME,
  4. ManagerUtil.FORBIT_ALARM_APP_LIST_KEY, null, 4);
  5. if (forbitset == null) {
  6. readFromXml();
  7. HashSet<String> forbitPkgsSet = new HashSet<String>();
  8. for (String pkg : forbitPkgs) {
  9. forbitPkgsSet.add(pkg);
  10. }
  11. ManagerUtil.savePreferenceValue(mCtx, ManagerUtil.PRE_NAME,
  12. ManagerUtil.FORBIT_ALARM_APP_LIST_KEY, forbitPkgsSet, 4);
  13. } else {
  14. Iterator<String> forbitir = forbitset.iterator();
  15. while(forbitir.hasNext()) {
  16. String forbit = forbitir.next();
  17. forbitPkgs.add(forbit);
  18. }
  19. }
  20. }
  21. showPkgs.clear();
  22. ArrayList<DroidApp> apps = new ArrayList<DroidApp>();
  23. final List<PackageInfo> installed = pm.getInstalledPackages(0);
  24. String name = null;
  25. for (final PackageInfo appInfo : installed){
  26. String pkg = appInfo.packageName;
  27. if (listPkgs.contains(pkg)) {
  28. DroidApp app = new DroidApp();
  29. name = pm.getApplicationLabel(appInfo.applicationInfo).toString();
  30. app.name = name;
  31. app.icon = appInfo.applicationInfo.loadIcon(pm);
  32. if (forbitPkgs.contains(pkg)) {
  33. app.online_switch = true;
  34. } else if (allowPkgs.contains(pkg)) {
  35. app.online_switch = false;
  36. } else {
  37. app.online_switch = true;
  38. }
  39. app.pkg = pkg;
  40. apps.add(app);
  41. showPkgs.add(pkg);
  42. Log.d("in white list and installed package is : "+pkg);
  43. } else {
  44. 已经安装的第三方应用
  45. if ((appInfo.applicationInfo.uid > 10000)
  46. && (appInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
  47. && (appInfo.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) {
  48. String pkgName = appInfo.packageName;
  49. DroidApp app = new DroidApp();
  50. app.name = pm.getApplicationLabel(appInfo.applicationInfo).toString();
  51. app.icon = appInfo.applicationInfo.loadIcon(pm);
  52. app.online_switch = true;
  53. if (forbitPkgs.contains(pkg)) {
  54. app.online_switch = true;
  55. } else if (allowPkgs.contains(pkg)) {
  56. app.online_switch = false;
  57. } else {
  58. app.online_switch = true;
  59. }
  60. app.pkg = pkgName;
  61. apps.add(app);
  62. showPkgs.add(pkgName);
  63. Log.d("not in white list and installed third package is : "+pkgName);
  64. }
  65. }
  66. }
  67. return apps;
  68. }
[java] view plain copy
  1. private class GetListDataThread implements Runnable {
  2. @Override
  3. public void run() {
  4. // TODO Auto-generated method stub
  5. appList = mFbAmLogic.getListApps();
  6. resultList.clear();
  7. for (DroidApp app : appList) {
  8. Log.d("getListApps appname = " + app.pkg);
  9. if (app.online_switch) {
  10. if (app.pkg != null && app.pkg.length() > 0) {
  11. resultList.add(app.pkg);
  12. saveList.add(app.pkg);
  13. }
  14. }
  15. }
  16. Message msg = Message.obtain();
  17. msg.what = MSG_SHOWLIST;
  18. handler.sendMessage(msg);
  19. }
  20. }

ForbitAlarmLogic类的getListApps()方法中重新为forbitPkgs数组赋值

如果forbitPkgs为空,即在构造方法中没有获取到数据,重新从上面数据库中获取数据;如果仍然是空,则从/data/data/com.***.android.security/app_bin/applist.xml文件中获取,保存到forbitPkgs数组中。

手机管家中显示的对齐唤醒名单主要有:

(1)、forbitapplist.xml文件与已安装应用的交集应用;

(2)、已安装的第三方应用。

流程图如下:

APK响应机制

APK在启动之后,就已经设置好了黑白名单,初始化过程就是加载界面的过程。

响应点击事件

界面初始化完毕之后,将处于勾选状态的应用保存到两个数组列表:resultList、saveList。响应点击事件时,将应用移除resultList列表,或添加到resultList列表中。

界面退出机制

在onPause()方法中判断resultList与saveList是否相同,如果不相同则重新保存对齐唤醒名单,并通知AlarmManagerService。

[java] view plain copy
  1. public void onPause() {
  2. // TODO Auto-generated method stub
  3. super.onPause();
  4. new Thread(new Runnable() {
  5. @Override
  6. public void run() {
  7. // TODO Auto-generated method stub
  8. boolean isSameContent = true;
  9. for (int i = 0; i < saveList.size(); i++) {
  10. Log.d("saveList "+ i + " = "+saveList.get(i));
  11. }
  12. for (int j = 0; j < resultList.size(); j++) {
  13. Log.d("resultList "+ j + " = "+resultList.get(j));
  14. }
  15. if (saveList.size() == resultList.size()) {
  16. Log.i("saveList == resultList");
  17. for (String result : resultList) {
  18. String xmlAppList = "/data/data/com.***.android.security/app_bin/applist.xml";
  19. ArrayList<String> forbitPkgs = new ArrayList<String>();
  20. ForbitAlarmLogic.readFromXmlWithFilename(xmlAppList, forbitPkgs);
  21. if (!forbitPkgs.contains(result)) {
  22. Log.i(result + "Not In applist.xml");
  23. isSameContent = false;
  24. break;
  25. }
  26. if (!saveList.contains(result)) {
  27. Log.i(result + "Not In SaveList");
  28. isSameContent = false;
  29. break;
  30. }
  31. }
  32. } else {
  33. Log.i("saveList Changed");
  34. isSameContent = false;
  35. }
  36. if (!isSameContent) {
  37. Log.i("ForbitAlarmSetting save Data");
  38. mFbAmLogic.saveAlarmAppMap(resultList);
  39. }
  40. }
  41. }).start();
  42. }

(1)、如何重新保存名单?

首先,清空allowPkgs和forbitPkgs,即先清空允许启动的应用列表和禁止启动的应用列表。

其次,将禁止唤醒的应用(即界面上处于勾选状态的应用)添加到forbitPkgs中,并写入/data/data/com.***.android.security/app_bin/applist.xml文件中。同时写入对应键值为ManagerUtil.FORBIT_ALARM_APP_LIST_KEY数据库中。

再次,将允许唤醒的应用(界面上没有勾选的应用)添加到allowPkgs中,并写入对应键值为ManagerUtil.ALLOW_ALARM_APP_LIST_KEY数据库中。

最后,通知AlarmManagerService。

(2)、如何通知AlarmManagerService?

上面数据保存完毕后,发送广播:com.***.android.savepower.forbitalarmapplistchanged,通知AlarmManagerService。

[java] view plain copy
  1. public static void notifyFramework(final Context ctx) {
  2. new Thread(){
  3. public void run() {
  4. try {
  5. Thread.sleep(200);
  6. Intent intent = new Intent();
  7. intent.setAction(ManagerUtil.INTENT_FORBITALARM_LIST_CHANGED);
  8. ctx.sendBroadcast(intent);
  9. } catch (InterruptedException e) {
  10. Log.e("applist.xml send broadcast error");
  11. }
  12. };
  13. }.start();
  14. }

流程图如下:

安装第三方应用

在PackageReceiver类中接收到包安装的广播后,将第三方应用添加到白名单,重新获取对齐唤醒数据。

[java] view plain copy
  1. new Thread(new Runnable() {
  2. @Override
  3. public void run() {
  4. // TODO Auto-generated method stub
  5. Log.d("automatically add newly installed applications into blacklist."
  6. + " packageName = " + packageName);
  7. synchronized (PackageReceiver.this) {
  8. mForbitAlarmLogic = ForbitAlarmLogic
  9. .getInstance(mCtx);
  10. mForbitAlarmLogic
  11. .packageReceiverApkAdded(packageName);
  12. }
  13. }
  14. }).start();

AlarmManagerService实现机制

接收广播

当对齐唤醒名单发生变化时,会发送forbitalarmapplistchanged 广播。AlarmManagerService定义了该广播的接收器,用来接收APK发送的广播。从applist.xml(/data/data/com.***.android.security/app_bin/applist.xml)文件中读取应用保存到全局变量mHashtable中。

[java] view plain copy
  1. class UpdateXmlReceiver extends BroadcastReceiver {
  2. public UpdateXmlReceiver() {
  3. IntentFilter filter = new IntentFilter();
  4. filter.addAction(ACTION_SAVEPOWER_UPDATEXML);
  5. getContext().registerReceiver(this, filter);
  6. }
  7. @Override
  8. public void onReceive(Context context, Intent intent) {
  9. synchronized (mLock) {
  10. // TODO Auto-generated method stub
  11. if(YulongFeature.FEATURE_REDUCE_RTC_WAKEUP){
  12. mHashtable.clear();
  13. Slog.d(TAG, "Receive savepower broadcast, read xml again.");
  14. getPackageNameFromXml();
  15. }
  16. }
  17. }
  18. }
[java] view plain copy
  1. private void getPackageNameFromXml() {
  2. FileReader permReader = null;
  3. try {
  4. permReader = new FileReader(xmlNewFile);
  5. Slog.d(TAG, "getPackageNameFromXml : read xmlNewFile ");
  6. } catch (FileNotFoundException e) {
  7. try {
  8. permReader = new FileReader(xmlFile);
  9. Slog.d(TAG, "getPackageNameFromXml : read xmlFile ");
  10. } catch (FileNotFoundException e1) {
  11. // TODO Auto-generated catch block
  12. Slog.d(TAG, "getPackageNameFromXml, can not find config xml ");
  13. return;
  14. }
  15. }
  16. try {
  17. XmlPullParser parser = Xml.newPullParser();
  18. parser.setInput(permReader);
  19. XmlUtils.beginDocument(parser, "channel");
  20. while (true) {
  21. XmlUtils.nextElement(parser);
  22. if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
  23. break;
  24. }
  25. String name = parser.getName();
  26. if ("item".equals(name)) {
  27. int id = Integer.parseInt(parser.getAttributeValue(null, "id"));
  28. if (id <= 0) {
  29. Slog.w(TAG, "<item> without name at "
  30. + parser.getPositionDescription());
  31. XmlUtils.skipCurrentTag(parser);
  32. continue;
  33. }
  34. String packagename = parser.getAttributeValue(null, "name");
  35. if (packagename == null) {
  36. Slog.w(TAG, "<item> without name at "
  37. + parser.getPositionDescription());
  38. XmlUtils.skipCurrentTag(parser);
  39. continue;
  40. }
  41. Slog.d(TAG, "getPackageNameFromXml : id is " + id + "  name is " + packagename);
  42. mHashtable.put(id, packagename);
  43. XmlUtils.skipCurrentTag(parser);
  44. } else {
  45. XmlUtils.skipCurrentTag(parser);
  46. continue;
  47. }
  48. }
  49. permReader.close();
  50. } catch (XmlPullParserException e) {
  51. Slog.w(TAG, "Got execption parsing permissions.", e);
  52. } catch (IOException e) {
  53. Slog.w(TAG, "Got execption parsing permissions.", e);
  54. }
  55. }

修改闹钟类型

在调用setImpl方法设置闹钟时,我们通过修改闹钟的类型来实现对齐唤醒功能。

[java] view plain copy
  1. if (type == AlarmManager.RTC_WAKEUP || type == AlarmManager.ELAPSED_REALTIME_WAKEUP) {
  2. if(mHashtable.containsValue(callingPackage)){
  3. if (AlarmManager.RTC_WAKEUP == type) {
  4. type = AlarmManager.RTC;
  5. Slog.v(TAG, "change alarm type RTC_WAKEUP to RTC for " + callingPackage);
  6. }
  7. if (AlarmManager.ELAPSED_REALTIME_WAKEUP == type) {
  8. type = AlarmManager.ELAPSED_REALTIME;
  9. Slog.v(TAG, "change alarm type ELAPSED_REALTIME_WAKEUP to ELAPSED_REALTIME for " + callingPackage);
  10. }
  11. }
  12. }

对齐唤醒添加机制

(1)、第三方应用全部添加到对齐唤醒名单;

(2)、禁止系统应用验证前添加到对齐唤醒名单,避免导致系统异常。

A.       系统核心应用不允许加入对齐唤醒名单,即位于system/priv-app目录下的应用不可以加入对齐唤醒名单;

android功耗优化(2)--对齐唤醒相关推荐

  1. Android功耗优化(7)---如何分析wakelock(wakeup source)持锁问题

    如何分析wakelock(wakeup source)持锁问题 锁一般分为:APP透过PowerManager拿锁,以及kernel wakelock. 分析上层持锁的问题: 目前PowerManag ...

  2. Android 功耗优化(4)---android 7.0低电耗Doze模式

    android 7.0低电耗Doze模式 从 Android 6.0(API 级别 23)开始,Android 引入了两个省电功能,可通过管理应用在设备未连接至电源时的行为方式为用户延长电池寿命. 低 ...

  3. android功耗优化,Android功耗测试小工具集锦

    {,](cpGs,uQ0电池时间一直是移动无线设备设计中的关键因素.随着移动设备变得越来越小.越来越轻,使用大电池并不是好的选择.分析电池耗电量可以帮助优化这些设备的电池工作时间.功耗,指的是终端各个 ...

  4. Android 功耗优化(3)---Android后台调度与省电

    Android后台调度与省电 I. Handler: 在进程存活的期间有效使用, Google官方推荐使用. 相关机制可以参见: Android Handler Looper机制 简单易用. 稳定高效 ...

  5. Android 功耗优化(1)----使用Battery Historian生成电量消耗报告

    性能优化之电量优化-使用Battery Historian生成电量消耗报告 1 数据准备 1.1 先断开adb服务,然后开启adb服务 (a)adb kill-server 1 这一步很重要,因为当我 ...

  6. 唤醒时间过长 Android,关于android:功耗问题之过多唤醒源-wakeups

    和你一起一生学习,这里是程序员 Android 本篇文章次要介绍 Android 开发中的 性能 局部知识点,通过浏览本篇文章,您将播种以下内容: 过多的唤醒源wakeups 如何fix 过多唤醒源问 ...

  7. Android 功耗优化(13)---功耗基础知识

    SODI (screen on deep idle) 背景知识---SODI SODI跟deep idle类似,是SPM的另外一种工作模式 SODI:Screen On Deep Idle SODI的 ...

  8. Android功耗优化(10)---功耗基础知识

    SPM SPM以及时钟请求信号控制流程 因为整个系统不只是AP(MCU),还包括modem.connectivity等子系统: CPU进入WFI后,整个系统就依靠一颗SCP:SPM来控制睡眠/唤醒的流 ...

  9. Android功耗优化(6)---如何确定阻止进入suspend的原因

    如何确定阻止进入suspend的原因 系统没有进入suspend,主要的原因是因为系统有锁导致. 锁一般分为:APP透过PowerManager拿锁,以及kernel wakelock. 分析上层持锁 ...

最新文章

  1. So many interfaces!
  2. 活体检测很复杂?仅使用opencv就能实现!(附源码)
  3. 动态内表及动态ALV显示
  4. 日常问题———安装新版zookeeper 出现Starting zookeeper ... FAILED TO START
  5. 案例集锦|科技赋能,华为云GaussDB助千行百业数字化转型
  6. 2.6 谷歌 Inception 网络简介
  7. 腾讯CDC:用户流失原因调研四步经
  8. 19-7/8作业:模拟实现用户密码管理
  9. 高亮显示不区分大小写的关键字——ASP
  10. Dell R410 BIOS 升级方法
  11. 回头看看中国互联网二十年,未来很清晰
  12. Spring的加密工具类---DigestUtils
  13. 功率放大芯片IR2184介绍
  14. oracle中is not null,oracle之is null和is not null的优化
  15. 2021绍兴市大学生程序设计竞赛邀请赛题解
  16. 如何将PDF格式转换成Excel格式?
  17. 关于Apple ID相关设置
  18. 高校或企业开源软件镜像站【汇总】2022.5.8
  19. haproxy中的Proxies段的配置
  20. BZOJ1455: 罗马游戏

热门文章

  1. [计算机网络] - TCP半连接队列和全连接队列
  2. socket android用法,Android NDK中socket的用法以及注意事项分析
  3. linux登录后自动打开终端,linux登录信息/打开终端信息
  4. 《RabbitMQ 实战指南》第五章 RabbitMQ 进阶(下)
  5. RocketMQ知识点整理
  6. MySql单表的curd-02
  7. sql server 事务与try catch
  8. [MVC.NET] Asp.Net MVC3 简单入门第一季
  9. css网页练习-3视觉
  10. Postgresql 帐号密码修改方法