android功耗優化(2)--對齊喚醒

概況

Android手機上安裝的不少應用都會頻繁喚醒手機(喚醒系統、喚醒屏幕),形成手機耗電等現象。良好的對齊喚醒管理方案,就是對後臺應用待機時不頻繁喚醒,智能節省電量。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中獲取。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響應機制

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

相關文章
相關標籤/搜索