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目錄下的應用不能夠加入對齊喚醒名單;