• android功耗优化(2)--对齐唤醒


    概况

    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目录下的应用不可以加入对齐唤醒名单;

  • 相关阅读:
    Regex Expression的资料和笔记整理
    27个提升效率的iOS开源库推荐
    IOS-Swift、Objective-C、C++混合编程
    《极客学院 --NSAttributedString 使用详解-4-UITextKit 简介》学习笔记(待处理)
    前端开发学习网站
    为Xcode添加和备份快捷代码
    Swift语言与Objective-C语言混合编程
    《慕客网:IOS动画案例之会跳动的登入界面(下)》学习笔记 -Sketch的使用
    《慕客网:IOS动画案例之会跳动的登入界面(上)》学习笔记 -Sketch的使用
    关于移动端架构的好的学习网站
  • 原文地址:https://www.cnblogs.com/linhaostudy/p/13616070.html
Copyright © 2020-2023  润新知