Android手机上安装的不少应用都会频繁唤醒手机(唤醒系统、唤醒屏幕),形成手机耗电等现象。良好的对齐唤醒管理方案,就是对后台应用待机时不频繁唤醒,智能节省电量。android
实现原理:APK做为该功能的入口,勾选应用后,将勾选的应用写入黑名单,并通知framework黑名单内容变化;framework接收到通知后,自动获取黑名单中的应用,保存到列表中;在framework调用接口中检测应用是否在黑名单中,若是在黑名单中则检测闹钟类型,若是闹钟类型是0或2,对应修改成1或3。数据库
在ForbitAlarmLogic构造方法中初始化了数组列表listPkgs、forbitPkgs、allowPkgs、showPkgs。数组
listPkgs:表示须要设置对齐唤醒的应用,若是这些应用已经安装,就会显示在对齐唤醒设置的界面上。初始数据从/data/data/com.***.android.security/app_bin/forbitapplist.xml中获取,若是文件不存在,则从本地资源数组security_array_savepower_forbitalarms中获取。app
forbitPkgs:表示对齐唤醒名单,即禁止唤醒的名单,界面勾选的应用。初始数据从SharedPreference数据库名ManagerUtil.PRE_NAME(com.***.android.savepowermanager_preferences)中获取键值ManagerUtil.FORBIT_ALARM_APP_LIST_KEY中保存的数据,将获取的数据保存到forbitPkgs数组中,若是没有数据则返回null。ide
allowPkgs:表示容许唤醒的名单,界面没有勾选的应用。初始数据从SharedPreference数据库ManagerUtil.PRE_NAME(com.***.android.savepowermanager_preferences)中获取键值为ManagerUtil.ALLOW_ALARM_APP_LIST_KEY中保存的数据,将获取的数据保存到allowPkgs数组列表中;若是没有数据则返回null。优化
showPkgs:表示要显示在对齐唤醒设置界面的数组应用列表,在数据初始化以前先将该数组清空。对齐唤醒方案优化以前,该数组保存的是listPkgs列表与已安装应用的交集。优化以后,同时还保存了已安装的第三方应用。ui
public ForbitAlarmLogic(Context ctx) { this.mCtx = ctx; pm = ctx.getPackageManager(); xmlAppList = Util.getDefaultDataPath(ctx) + "/app_bin/applist.xml"; String xmlFile = Util.getDefaultDataPath(ctx)+"/app_bin/forbitapplist.xml"; File f = new File(xmlFile); if (!f.exists()) { Log.e("forbitapplist not exist!"); String[] strs = mCtx.getResources().getStringArray(R.array.security_array_savepower_forbitalarms); for (String str : strs) { listPkgs.add(str); } } else { readFromXmlWithFilename(xmlFile, listPkgs); } // readFromXml(); Set<String> forbitset = (Set<String>)ManagerUtil.getPreferenceValue(mCtx, ManagerUtil.PRE_NAME, ManagerUtil.FORBIT_ALARM_APP_LIST_KEY, null, 4); if (forbitset != null) { Iterator<String> forbitir = forbitset.iterator(); while(forbitir.hasNext()) { String forbit = forbitir.next(); forbitPkgs.add(forbit); } } Set<String> allowset = (Set<String>)ManagerUtil.getPreferenceValue(mCtx, ManagerUtil.PRE_NAME, ManagerUtil.ALLOW_ALARM_APP_LIST_KEY, null, 4); if (allowset != null) { Iterator<String> allowir = allowset.iterator(); while(allowir.hasNext()) { String allow = allowir.next(); allowPkgs.add(allow); } } }
public ArrayList<DroidApp> getListApps() { if (forbitPkgs.size() == 0) { Set<String> forbitset= (Set<String>)ManagerUtil.getPreferenceValue(mCtx, ManagerUtil.PRE_NAME, ManagerUtil.FORBIT_ALARM_APP_LIST_KEY, null, 4); if (forbitset == null) { readFromXml(); HashSet<String> forbitPkgsSet = new HashSet<String>(); for (String pkg : forbitPkgs) { forbitPkgsSet.add(pkg); } ManagerUtil.savePreferenceValue(mCtx, ManagerUtil.PRE_NAME, ManagerUtil.FORBIT_ALARM_APP_LIST_KEY, forbitPkgsSet, 4); } else { Iterator<String> forbitir = forbitset.iterator(); while(forbitir.hasNext()) { String forbit = forbitir.next(); forbitPkgs.add(forbit); } } } showPkgs.clear(); ArrayList<DroidApp> apps = new ArrayList<DroidApp>(); final List<PackageInfo> installed = pm.getInstalledPackages(0); String name = null; for (final PackageInfo appInfo : installed){ String pkg = appInfo.packageName; if (listPkgs.contains(pkg)) { DroidApp app = new DroidApp(); name = pm.getApplicationLabel(appInfo.applicationInfo).toString(); app.name = name; app.icon = appInfo.applicationInfo.loadIcon(pm); if (forbitPkgs.contains(pkg)) { app.online_switch = true; } else if (allowPkgs.contains(pkg)) { app.online_switch = false; } else { app.online_switch = true; } app.pkg = pkg; apps.add(app); showPkgs.add(pkg); Log.d("in white list and installed package is : "+pkg); } else { // 已经安装的第三方应用 if ((appInfo.applicationInfo.uid > 10000) && (appInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 && (appInfo.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0) { String pkgName = appInfo.packageName; DroidApp app = new DroidApp(); app.name = pm.getApplicationLabel(appInfo.applicationInfo).toString(); app.icon = appInfo.applicationInfo.loadIcon(pm); // app.online_switch = true; if (forbitPkgs.contains(pkg)) { app.online_switch = true; } else if (allowPkgs.contains(pkg)) { app.online_switch = false; } else { app.online_switch = true; } app.pkg = pkgName; apps.add(app); showPkgs.add(pkgName); Log.d("not in white list and installed third package is : "+pkgName); } } } return apps; }
private class GetListDataThread implements Runnable { @Override public void run() { // TODO Auto-generated method stub appList = mFbAmLogic.getListApps(); resultList.clear(); for (DroidApp app : appList) { Log.d("getListApps appname = " + app.pkg); if (app.online_switch) { if (app.pkg != null && app.pkg.length() > 0) { resultList.add(app.pkg); saveList.add(app.pkg); } } } Message msg = Message.obtain(); msg.what = MSG_SHOWLIST; handler.sendMessage(msg); } }
ForbitAlarmLogic类的getListApps()方法中从新为forbitPkgs数组赋值this
若是forbitPkgs为空,即在构造方法中没有获取到数据,从新从上面数据库中获取数据;若是仍然是空,则从/data/data/com.***.android.security/app_bin/applist.xml文件中获取,保存到forbitPkgs数组中。code
手机管家中显示的对齐唤醒名单主要有:
(1)、forbitapplist.xml文件与已安装应用的交集应用;xml
(2)、已安装的第三方应用。
APK在启动以后,就已经设置好了黑白名单,初始化过程就是加载界面的过程。
界面初始化完毕以后,将处于勾选状态的应用保存到两个数组列表:resultList、saveList。响应点击事件时,将应用移除resultList列表,或添加到resultList列表中。
在onPause()方法中判断resultList与saveList是否相同,若是不相同则从新保存对齐唤醒名单,并通知AlarmManagerService。
public void onPause() { // TODO Auto-generated method stub super.onPause(); new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub boolean isSameContent = true; for (int i = 0; i < saveList.size(); i++) { Log.d("saveList "+ i + " = "+saveList.get(i)); } for (int j = 0; j < resultList.size(); j++) { Log.d("resultList "+ j + " = "+resultList.get(j)); } if (saveList.size() == resultList.size()) { Log.i("saveList == resultList"); for (String result : resultList) { String xmlAppList = "/data/data/com.***.android.security/app_bin/applist.xml"; ArrayList<String> forbitPkgs = new ArrayList<String>(); ForbitAlarmLogic.readFromXmlWithFilename(xmlAppList, forbitPkgs); if (!forbitPkgs.contains(result)) { Log.i(result + "Not In applist.xml"); isSameContent = false; break; } if (!saveList.contains(result)) { Log.i(result + "Not In SaveList"); isSameContent = false; break; } } } else { Log.i("saveList Changed"); isSameContent = false; } if (!isSameContent) { Log.i("ForbitAlarmSetting save Data"); mFbAmLogic.saveAlarmAppMap(resultList); } } }).start(); }
(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。
public static void notifyFramework(final Context ctx) { new Thread(){ public void run() { try { Thread.sleep(200); Intent intent = new Intent(); intent.setAction(ManagerUtil.INTENT_FORBITALARM_LIST_CHANGED); ctx.sendBroadcast(intent); } catch (InterruptedException e) { Log.e("applist.xml send broadcast error"); } }; }.start(); }
流程图以下:
在PackageReceiver类中接收到包安装的广播后,将第三方应用添加到白名单,从新获取对齐唤醒数据。
new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub Log.d("automatically add newly installed applications into blacklist." + " packageName = " + packageName); synchronized (PackageReceiver.this) { mForbitAlarmLogic = ForbitAlarmLogic .getInstance(mCtx); mForbitAlarmLogic .packageReceiverApkAdded(packageName); } } }).start();
当对齐唤醒名单发生变化时,会发送forbitalarmapplistchanged 广播。AlarmManagerService定义了该广播的接收器,用来接收APK发送的广播。从applist.xml(/data/data/com.***.android.security/app_bin/applist.xml)文件中读取应用保存到全局变量mHashtable中。
class UpdateXmlReceiver extends BroadcastReceiver { public UpdateXmlReceiver() { IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_SAVEPOWER_UPDATEXML); getContext().registerReceiver(this, filter); } @Override public void onReceive(Context context, Intent intent) { synchronized (mLock) { // TODO Auto-generated method stub if(YulongFeature.FEATURE_REDUCE_RTC_WAKEUP){ mHashtable.clear(); Slog.d(TAG, "Receive savepower broadcast, read xml again."); getPackageNameFromXml(); } } } }
private void getPackageNameFromXml() { FileReader permReader = null; try { permReader = new FileReader(xmlNewFile); Slog.d(TAG, "getPackageNameFromXml : read xmlNewFile "); } catch (FileNotFoundException e) { try { permReader = new FileReader(xmlFile); Slog.d(TAG, "getPackageNameFromXml : read xmlFile "); } catch (FileNotFoundException e1) { // TODO Auto-generated catch block Slog.d(TAG, "getPackageNameFromXml, can not find config xml "); return; } } try { XmlPullParser parser = Xml.newPullParser(); parser.setInput(permReader); XmlUtils.beginDocument(parser, "channel"); while (true) { XmlUtils.nextElement(parser); if (parser.getEventType() == XmlPullParser.END_DOCUMENT) { break; } String name = parser.getName(); if ("item".equals(name)) { int id = Integer.parseInt(parser.getAttributeValue(null, "id")); if (id <= 0) { Slog.w(TAG, "<item> without name at " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } String packagename = parser.getAttributeValue(null, "name"); if (packagename == null) { Slog.w(TAG, "<item> without name at " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } Slog.d(TAG, "getPackageNameFromXml : id is " + id + " name is " + packagename); mHashtable.put(id, packagename); XmlUtils.skipCurrentTag(parser); } else { XmlUtils.skipCurrentTag(parser); continue; } } permReader.close(); } catch (XmlPullParserException e) { Slog.w(TAG, "Got execption parsing permissions.", e); } catch (IOException e) { Slog.w(TAG, "Got execption parsing permissions.", e); } }
在调用setImpl方法设置闹钟时,咱们经过修改闹钟的类型来实现对齐唤醒功能。
if (type == AlarmManager.RTC_WAKEUP || type == AlarmManager.ELAPSED_REALTIME_WAKEUP) { if(mHashtable.containsValue(callingPackage)){ if (AlarmManager.RTC_WAKEUP == type) { type = AlarmManager.RTC; Slog.v(TAG, "change alarm type RTC_WAKEUP to RTC for " + callingPackage); } if (AlarmManager.ELAPSED_REALTIME_WAKEUP == type) { type = AlarmManager.ELAPSED_REALTIME; Slog.v(TAG, "change alarm type ELAPSED_REALTIME_WAKEUP to ELAPSED_REALTIME for " + callingPackage); } } }
(1)、第三方应用所有添加到对齐唤醒名单;
(2)、禁止系统应用验证前添加到对齐唤醒名单,避免致使系统异常。
A. 系统核心应用不容许加入对齐唤醒名单,即位于system/priv-app目录下的应用不能够加入对齐唤醒名单;