概況
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列表與已安裝應用的交集。優化之后,同時還保存了已安裝的第三方應用。
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數組賦值
如果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。
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();
AlarmManagerService實現機制
接收廣播
當對齊喚醒名單發生變化時,會發送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目錄下的應用不可以加入對齊喚醒名單;