Android性能优化 -- 自启动管理

自启动管理简介

Android手机上安装的很多应用都会自启动,占用资源越来越多,造成系统卡顿等现象。良好的自启动管理方案管理后台自启动和开机自启动,这样就可以节约内存、优化系统流畅性等。

自启动管理流程分析

自启动管理的实现贯穿了应用APK(AutoRun.apk)以及framework的ActivityManagerService等。实现流程比较复杂,下面分阶段地介绍整个流程。

初始化

手机开机后,会发送开机广播:android.intent.action.BOOT_COMPLETED,凡是注册该广播的应用都可以接收到开机广播。(这里设计的AutoRun.apk会有一个为之提供服务apk,取名为AutoRunCore.apk)服务AutoRunCore.apk注册开机广播,接收到开机广播后,发送自定义广播(android.intent.action.security.BOOT_COMPLETED)给AutoRun.apk。

AutoRun.apk的自定义类BootBroadcastReceiver接收到AutoRunCore.apk发过来的广播后,开启线程设置禁止自启动列表和允许自启动列表;从R.array.security_boot_run_applist数组中获取允许自启动列表,定义为白名单,白名单中保存的均是非系统应用;从R.array.security_boot_forbidrun_applist数组中获取禁止自启动的列表,定义为黑名单,黑名单中保存的均是系统应用。最终调用系统接口将禁止自启动的应用(包括黑名单中的系统应用、不在白名单中的非系统应用)全部写到/data/system/forbidden_autorun_packages.xml文件中。

关键代码如下:

1、注册广播接收器

[java] view plaincopy
  1. <receiver
  2. android:name="com.android.BootBroadcastReceiver"
  3. android:exported="true"
  4. android:priority="1000"
  5. android:process="@string/process_security" >
  6. <intent-filter>
  7. <action android:name="android.intent.action.BOOT_COMPLETED" />
  8. <action android:name="android.intent.action.security.BOOT_COMPLETED" />
  9. </intent-filter>
  10. </receiver>

2、接收到广播后处理方法

[java] view plaincopy
  1. @Override
  2. public void onReceive(final Context context, Intent intent) {
  3. action = intent.getAction();
  4. if((action != null)&&(action.equals("android.intent.action.security.BOOT_COMPLETED"))){
  5. action = ACTION_BOOT_COMPLETED;
  6. }
  7. ......
  8. if(BOOT_AUTO_RUN && ACTION_BOOT_COMPLETED.equals(action)) {
  9. Log.d("forbid auto run");
  10. new Thread(new Runnable() {
  11. @Override
  12. public void run() {
  13. // TODO Auto-generated method stub
  14. SharedPreferences preferences = context.getSharedPreferences(
  15. "forbidrun_appList", Context.MODE_PRIVATE);
  16. boolean isInit = preferences.getBoolean("initflag", false);
  17. long lastModif = preferences.getLong("fileModif", 0);
  18. long fileModif = 0;
  19. File config = new File("data/system/seccenter/"
  20. + APPAUTORUN_CONFIG_FILE);//APPAUTORUN_CONFIG_FILE = "seccenter_appautorun_applist.xml"
  21. pm = context.getPackageManager();
  22. List<String> autorun_appList = new ArrayList<String>();
  23. if (config.exists()) {
  24. fileModif = config.lastModified();
  25. }
  26. if (!isInit || (fileModif > lastModif)) {
  27. if (config.exists()) {
  28. try {
  29. autorun_appList = <span style="color:#009900;">parseXML</span>(config);
  30. } catch (Exception e) {
  31. // TODO Auto-generated catch block
  32. e.printStackTrace();
  33. }
  34. } else {
  35. autorun_appList = Arrays.asList(context.getResources()
  36. .getStringArray(R.array.security_boot_run_applist));
  37. }
  38. List<String> forbidrun_appList = Arrays.asList(context.getResources()
  39. .getStringArray(R.array.security_boot_forbidrun_applist));
  40. List<ApplicationInfo> allAppInfo = pm.getInstalledApplications(0);
  41. for (ApplicationInfo appInfo : allAppInfo) {
  42. if (!Util.<span style="color:#009900;">isSystemApp</span>(appInfo)
  43. && !autorun_appList.contains(appInfo.packageName)) {
  44. SystemApiUtil.<span style="color:#009900;">fobidAutoRun</span>(context,appInfo.packageName, true);
  45. } else if (Util.isSystemApp(appInfo)
  46. && forbidrun_appList.contains(appInfo.packageName)) {
  47. SystemApiUtil.fobidAutoRun(context,appInfo.packageName, true);
  48. }
  49. }
  50. SharedPreferences preference = context.getSharedPreferences("forbidrun_appList",
  51. Context.MODE_PRIVATE);
  52. SharedPreferences.Editor editor = preference.edit();
  53. editor.putBoolean("initflag", true);
  54. editor.putLong("fileModif", fileModif);
  55. editor.commit();
  56. }
  57. }
  58. }, "btReForbidRun").start();
  59. }

在上面的处理方法中使用的几个封装的方法,下面逐一看下。先看parseXML()方法,

[java] view plaincopy
  1. public List<String> parseXML(File xmlFile) throws Exception {
  2. List<String> appList = new ArrayList<String>();
  3. DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  4. DocumentBuilder db = dbf.newDocumentBuilder();
  5. Document doc = db.parse(xmlFile);
  6. NodeList nodeList = doc.getElementsByTagName("appname");
  7. Node fatherNode = nodeList.item(0);
  8. NodeList childNodes = fatherNode.getChildNodes();
  9. for (int j = 0; j < childNodes.getLength(); j++) {
  10. Node childNode = childNodes.item(j);
  11. if (childNode instanceof Element) {
  12. appList.add(childNode.getFirstChild().getNodeValue());
  13. }
  14. }
  15. return appList;
  16. }

接着看下Util.isSystemApp()方法,

[java] view plaincopy
  1. public static boolean isSystemApp(ApplicationInfo appInfo) {
  2. boolean flag = false;
  3. if (appInfo != null
  4. && ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 || (appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) {
  5. flag = true;
  6. }
  7. return flag;
  8. }

再接着看下forbitAutorun()方法,这里是通过反射方式调用ActivityManager中的方法。

[java] view plaincopy
  1. private static Method mSetStopAutoStart = null;
  2. private static Method mgetStopAutoStart = null;
[java] view plaincopy
  1. public static void fobidAutoRun(Context context, String pkg, boolean isFobid) {
  2. if (isForceStopAutoStartMethodExist(context)) {
  3. try {
  4. ActivityManager am = (ActivityManager) context
  5. .getSystemService(Context.ACTIVITY_SERVICE);
  6. mSetStopAutoStart.invoke(am, pkg,
  7. isFobid);
  8. } catch (IllegalArgumentException e) {
  9. e.printStackTrace();
  10. } catch (IllegalAccessException e) {
  11. e.printStackTrace();
  12. } catch (InvocationTargetException e) {
  13. e.printStackTrace();
  14. }
  15. }
  16. }
  17. public static boolean isForceStopAutoStartMethodExist(Context context) {
  18. synchronized (SystemApiUtil.class) {
  19. if (mSetStopAutoStart == null) {
  20. try {
  21. ActivityManager am = (ActivityManager) context
  22. .getSystemService(Context.ACTIVITY_SERVICE);
  23. mSetStopAutoStart = am.getClass().getMethod(
  24. "setForbiddenAutorunPackages", String.class,
  25. boolean.class);
  26. mgetStopAutoStart = am.getClass()
  27. .getMethod("getForbiddenAutorunPackages");
  28. } catch (SecurityException e) {
  29. // TODO Auto-generated catch block
  30. e.printStackTrace();
  31. } catch (NoSuchMethodException e) {
  32. // TODO Auto-generated catch block
  33. e.printStackTrace();
  34. }
  35. }
  36. if (mSetStopAutoStart == null || mgetStopAutoStart == null) {
  37. return false;
  38. } else {
  39. return true;
  40. }
  41. }
  42. }

总结:在AutoRun接收到广播后,根据数组security_boot_run_applist(允许自启动白名单)和数组security_boot_forbidrun_applist(禁止自启动黑名单)设置好了禁止自启动的xml文件(forbidden_autorun_packages.xml)。

流程图如下:

AutoRun界面初始化

AutoRun在启动之前就已经设置好了黑白名单,界面初始化过程就是界面加载的过程。

(1)、AutoRun通过调用ActivityManagerService封装的getForbiddenAutoRunPackages()方法获取禁止自启动的应用列表。该方法从/data/system/forbidden_autorun_packages.xml文件中读取应用,保存到mPackagesForbiddenAutoRun全局变量中,同时也把应用包含的服务加入mServicesForbiddenAutoRun中。

(2)、security_autorun_notdisplayed_whitelist 数组表示允许自启动不显示白名单,即不显示在自启动管理界面上的应用白名单。

如果这个白名单中的应用包含getForbiddenAutoRunPackages方法返回的数组中的应用,该应用取消禁止自启动(允许自启动),并且不会显示在自启动管理界面。

取消禁止自启动的处理方法就是通过ActivityManagerService封装的setForbiddenAutoRunPackages()方法实现的。

注意:

1、第三方应用默认禁止自启动,会设置一个自启动的白名单(R.array.security_boot_run_applist),白名单里的应用就会允许自启动。

2、系统应用默认允许自启动,会设置一个禁止自启动的黑名单(R.array.security_boot_forbidrun_applist),黑名单里的应用会被禁止自启动。

具体代码如下:

[java] view plaincopy
  1. public void onResume() {
  2. super.onResume();
  3. checkedItemCount = 0;//设置选中的个数
  4. refreshList();
  5. }
[java] view plaincopy
  1. private void refreshList() {
  2. ......
  3. new Thread(new InitViewRunable()).start();
  4. }
[java] view plaincopy
  1. class InitViewRunable implements Runnable {
  2. @Override
  3. public void run() {
  4. initData();
  5. Message.obtain(mHandler, MSG_INIT_DATA_COMPLETED).sendToTarget();
  6. }
  7. }
[java] view plaincopy
  1. private synchronized void initData() {
  2. applicationList.clear();//清空列表
  3. forbidAutoRunList = speedLogic.<span style="color:#009900;">getForbidAutoRunList</span>(true);//获取禁止自启动应用列表
  4. SharedPreferences preferences = mContext.getSharedPreferences(
  5. "forbidrun_appList", Context.MODE_PRIVATE);
  6. boolean isInit = preferences.getBoolean("isInit", false);
  7. // remove the package from the forbidAutoRunList whith need to autorun
  8. if (!isInit) {//第一次初始化
  9. if (isAdded() && forbidAutoRunList != null) {
  10. List<String> autorun_whiteList = Arrays.asList(mContext
  11. .getResources().getStringArray(
  12. R.array.<span style="color:#009900;">security_autorun_applist</span>));//从数组中读取允许自启动的列表
  13. for (String string : autorun_whiteList) {//security_autorun_applist数组中包含禁止自启动的应用,设置为允许自启动
  14. if (forbidAutoRunList.contains(string)) {
  15. forbidAutoRunList.remove(string);
  16. SystemApiUtil.fobidAutoRun(mContext, string, false);//更新禁止自启动数据,重新设置下xml文件
  17. }
  18. }
  19. }
  20. SharedPreferences.Editor editor = preferences.edit();
  21. editor.putBoolean("isInit", true);
  22. editor.commit();
  23. }
  24. canAutoRunMap = speedLogic.<span style="color:#009900;">getCanAutoRunList</span>(false);//获得允许自启动应用列表,返回已封装的对象
  25. if (canAutoRunMap.size() != 0) {
  26. Map<String, Boolean> tmpAutoRunMap = new HashMap<String, Boolean>();
  27. tmpAutoRunMap.putAll(canAutoRunMap);
  28. Iterator iter = tmpAutoRunMap.entrySet().iterator();//将canAutoRunMap保存到临时Map对象中,并迭代
  29. ApplicationInfo appInfo;
  30. while (iter.hasNext()) {
  31. Map.Entry entry = (Map.Entry) iter.next();
  32. String pkgName = (String) entry.getKey();//取出包名
  33. ListBean bean = new ListBean();
  34. bean.setPkgName(pkgName);//设置ListBean对象的包名
  35. try {
  36. appInfo = pm.getApplicationInfo(pkgName, 0);//根据包名获取对应的ApplicationInfo对象
  37. String packageName = appInfo.packageName;
  38. Bitmap icon = Util.<span style="color:#009900;">getIcon</span>(mContext, packageName, null);//获取图标
  39. if (isAdded()) {//设置ListBean对象的icon
  40. if (null == icon) {
  41. bean.setListIcon(appInfo.loadIcon(pm));
  42. } else {
  43. bean.setListIcon(new BitmapDrawable(icon));
  44. }
  45. }
  46. bean.setListTitle((String) appInfo.loadLabel(pm));
  47. bean.setBoot((Boolean) entry.getValue());//设置是否禁止自启动
  48. if (forbidAutoRunList != null
  49. && forbidAutoRunList.contains(pkgName)) {
  50. bean.setChecked(true);//如果包含在禁止自启动名单里,设置勾选状态
  51. }
  52. if (type == SYSTEM && Util.isSystemApp(appInfo)) {
  53. if (bean.isChecked) {
  54. checkedItemCount++;
  55. }
  56. applicationList.add(bean);
  57. Log.d("forbidAutoRunList of SystemApp" + pkgName);
  58. } else if (type == PERSONAL && !Util.isSystemApp(appInfo)) {
  59. if (bean.isChecked) {
  60. checkedItemCount++;
  61. }
  62. applicationList.add(bean);
  63. Log.d("forbidAutoRunList of PersonalApp" + pkgName);
  64. }
  65. } catch (NameNotFoundException e) {
  66. e.printStackTrace();
  67. }
  68. }
  69. }
  70. }

initData()完毕后,发送消息更新界面。在initData()方法中用到了几个封装的方法,分别看下。

[java] view plaincopy
  1. public List<String> getForbidAutoRunList(boolean forceFresh) {
  2. if (forbidAutoRunList == null || forceFresh) {
  3. forbidAutoRunList = SystemApiUtil.getForbiddenAutorunPackages(mContext);//调用系统接口
  4. if (forbidAutoRunList != null) {
  5. List<String> autorun_whiteList = Arrays.asList(mContext.getResources()
  6. .getStringArray(R.array.security_autorun_notdisplayed_whitelist));
  7. for (String string : autorun_whiteList) {//如果从xml文件中获取的禁止自启动列表中,包含允许自启动不显示数组中的应用,则设置该应用允许自启动
  8. if (forbidAutoRunList.contains(string)) {
  9. Log.d("set autorun packageName:" + string);
  10. SystemApiUtil.fobidAutoRun(mContext, string, false);//设置允许自启动,并自动更新xml文件中的应用
  11. }
  12. }
  13. }
  14. }
  15. return forbidAutoRunList;
  16. }

getAutoRunList()方法获取允许自启动列表。

[java] view plaincopy
  1. public Map<String, Boolean> getCanAutoRunList(boolean forceFresh) {
  2. SharedPreferences pref = mContext.getSharedPreferences(PRE_NAME, Context.MODE_PRIVATE);
  3. Editor editor = pref.edit();
  4. boolean hasApkInstalled = pref.getBoolean("hasApkInstalled", false);
  5. if (autoRunMap.size() == 0 || forceFresh || hasApkInstalled) {
  6. editor.putBoolean("hasApkInstalled",false);
  7. editor.commit();
  8. autoRunMap = new HashMap<String, Boolean>();
  9. List<String> forbidList = getForbidAutoRunList(forceFresh);//获取最新状态的禁止自启动列表
  10. List<ApplicationInfo> allAppInfo = null;
  11. try {
  12. allAppInfo = mPm.getInstalledApplications(0);//获取安装的第三方应用
  13. } catch (Exception e) {
  14. e.printStackTrace();
  15. }
  16. if (allAppInfo != null) {
  17. for (ApplicationInfo appInfo : allAppInfo) {
  18. boolean isContains = false;
  19. if (forbidList != null
  20. && forbidList.contains(appInfo.packageName)) {
  21. // That is in forbidden list
  22. isContains = true;//如果在禁止自启动列表中,设置isContains为true。
  23. }
  24. if (<span style="color:#009900;">isExcludeForAutoBoot</span>(appInfo, isContains)) {//过滤掉launcher、白名单(security_autorun_notdisplayed_whitelist)中的应用。
  25. continue;
  26. }
  27. if (<span style="color:#009900;">isPackageHasReceivers</span>(appInfo.packageName)) {//判断是否注册了广播接收器
  28. Boolean isBoot = false;
  29. if (<span style="color:#009900;">isPackageHasReceiverPermission</span>(appInfo.packageName,
  30. Manifest.permission.RECEIVE_BOOT_COMPLETED)) {
  31. isBoot = true;//注册了开机广播
  32. }
  33. autoRunMap.put(appInfo.packageName, isBoot);
  34. }
  35. }
  36. }
  37. }
  38. return autoRunMap;
  39. }

我们再看下isExcludeForAutoBoot()方法,该方法排除掉launcher应用,排除掉白名单(security_autorun_notdisplayed_whitelist)中的应用。

[java] view plaincopy
  1. private boolean isExcludeForAutoBoot(ApplicationInfo appInfo, Boolean isContains) {
  2. if (appInfo == null) {
  3. return true;
  4. }
  5. if (Util.isSystemApp(appInfo)) {//系统应用
  6. List<String> launcherPkgs = SpeedUtil.<span style="color:#009900;">getLauncherPkgs</span>(mContext);//获取launcher应用
  7. // not contain launcher icon ,not list it
  8. if (!launcherPkgs.contains(appInfo.packageName)) {
  9. return true;
  10. }//过滤掉launcher应用。
  11. }
  12. // judge whether contain in white list,if it is ,do not list it
  13. if (<span style="color:#009900;">isInAutoBootWhiteList</span>(appInfo)) {//这里再过滤一次是否在白名单中
  14. if (isContains) {//如果设置了禁止自启动,则设置为允许自启动
  15. Log.d("i donot think will come in.");
  16. SystemApiUtil.fobidAutoRun(mContext, appInfo.packageName, false);
  17. }
  18. return true;
  19. } else {
  20. return false;
  21. }
  22. }

再看下isPackageHasReceivers()方法,判断是否注册了广播接收器。

[java] view plaincopy
  1. private boolean isPackageHasReceivers(String packageName) {
  2. if (mPm == null) {
  3. mPm = mContext.getPackageManager();
  4. }
  5. boolean hasReceivers = true;
  6. try {
  7. PackageInfo packinfo = mPm.getPackageInfo(packageName, PackageManager.GET_RECEIVERS);
  8. if (packinfo.receivers == null || packinfo.receivers.length <= 0) {
  9. hasReceivers = false;
  10. }
  11. } catch (NameNotFoundException e) {
  12. hasReceivers = false;
  13. e.printStackTrace();
  14. }
  15. return hasReceivers;
  16. }

看下封装的isPackageHasReceiverPermission()方法,判断是否有接收开机广播的权限。

[java] view plaincopy
  1. public boolean isPackageHasReceiverPermission(String packageName, String permissionName) {
  2. boolean isReceiverFunctionEnable = true;
  3. if (mPm == null) {
  4. mPm = mContext.getPackageManager();
  5. }
  6. if (permissionName != null
  7. && PackageManager.PERMISSION_GRANTED != mPm.checkPermission(permissionName,
  8. packageName)) {
  9. isReceiverFunctionEnable = false;
  10. }
  11. return isReceiverFunctionEnable;
  12. }

流程图如下:

AutoRun处理机制

1、禁止自启动

禁止自启动:调用系统接口ActivityManagerService类封装的setForbiddenAutorunPackages(final StringpackageName, boolean bAdd),此时bAdd参数为true,将指定的包名添加到禁止自启动列表mPackagesForbiddenAutoRun中,将应用包含的服务添加到禁止自启动服务列表mServicesForbiddenAutoRun中,最后将禁止自启动列表中的数据重新更新到/data/system/forbidden_autorun_packages.xml文件中。

具体代码如下:

[java] view plaincopy
  1. /**
  2. * @hide
  3. */
  4. public boolean setForbiddenAutorunPackages(final String packageName,
  5. boolean bAdd) {
  6. boolean bResult = false;
  7. if (Feature.FEATURE_FORBID_APP_AUTORUN) {
  8. // Slog.v(TAG, "ctl packagename=" + packageName + "bAdd=" + bAdd);
  9. synchronized (this) {
  10. if ((null != packageName) && (null != mPackagesForbiddenAutoRun)
  11. && (null != mServicesForbiddenAutoRun)) {
  12. final PackageManager pm = mContext.getPackageManager();
  13. PackageInfo pi = null;
  14. try {
  15. pi = pm.getPackageInfo(packageName,
  16. PackageManager.GET_SERVICES);
  17. } catch (NameNotFoundException e) {
  18. // TODO Auto-generated catch block
  19. e.printStackTrace();
  20. }
  21. int temp_size = mPackagesForbiddenAutoRun.size();
  22. if (bAdd) {
  23. if (mPackagesForbiddenAutoRun.indexOfKey(packageName.hashCode())<0) {
  24. mPackagesForbiddenAutoRun.append(packageName.hashCode(),packageName);
  25. bResult = temp_size!=mPackagesForbiddenAutoRun.size();
  26. if (pi != null && null != pi.services) {
  27. for (ServiceInfo service : pi.services) {
  28. if (0>mServicesForbiddenAutoRun.indexOfKey(service.processName.hashCode()))  {
  29. Slog.i(TAG, "ADD forbid autorun service: "+ service.processName);
  30. mServicesForbiddenAutoRun.append(service.processName.hashCode(), service.processName);
  31. }
  32. }
  33. }
  34. }
  35. } else {
  36. mPackagesForbiddenAutoRun.delete(packageName.hashCode());
  37. bResult = mPackagesForbiddenAutoRun.size()!=temp_size;
  38. if (pi != null && pi.services != null) {
  39. for (ServiceInfo service : pi.services) {
  40. mServicesForbiddenAutoRun.delete(service.processName.hashCode());
  41. }
  42. }
  43. }
  44. }
  45. writeFilterPackages();
  46. }
  47. }
  48. return bResult;
  49. }
[java] view plaincopy
  1. void writeFilterPackages(){
  2. if ( mFileFilter == null) {
  3. return ;
  4. }
  5. if (mFileFilter.exists()) {
  6. mFileFilter.delete();
  7. }
  8. try {
  9. FileOutputStream fstr = new FileOutputStream(mFileFilter);
  10. BufferedOutputStream str = new BufferedOutputStream(fstr);
  11. //XmlSerializer serializer = XmlUtils.serializerInstance();
  12. XmlSerializer serializer = new FastXmlSerializer();
  13. serializer.setOutput(str, "utf-8");
  14. serializer.startDocument(null, true);
  15. //serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
  16. serializer.startTag(null, "packages");
  17. int numPackages = mPackagesForbiddenAutoRun.size();
  18. //Slog.i(TAG, " forbid autorun numPackages := "+ numPackages);
  19. for( int i = 0; i < numPackages; ++i){
  20. serializer.startTag(null, "package");
  21. String apkPackageName = mPackagesForbiddenAutoRun.valueAt(i);
  22. //Slog.i(TAG, "forbid autorun package: "+apkPackageName);
  23. serializer.attribute(null, "name", apkPackageName);
  24. serializer.endTag(null, "package");
  25. }
  26. serializer.endTag(null, "packages");
  27. serializer.endDocument();
  28. str.flush();
  29. FileUtils.sync(fstr);
  30. str.close();
  31. FileUtils.setPermissions(mFileFilter.toString(),
  32. FileUtils.S_IRUSR|FileUtils.S_IWUSR
  33. |FileUtils.S_IRGRP|FileUtils.S_IWGRP
  34. |FileUtils.S_IROTH,
  35. -1, -1);
  36. } catch(java.io.IOException e) {
  37. Slog.w(TAG, "Unable to write broadcast filter, current changes will be lost at reboot", e);
  38. }
  39. }
[java] view plaincopy
  1. public List<String> getForbiddenAutorunPackages( ) {
  2. if (Feature.FEATURE_FORBID_APP_AUTORUN) {
  3. // Slog.v(TAG, "ctl packagename YLGetForbiddenAutorunPackages" );
  4. synchronized (this) {
  5. ArrayList<String> res = new ArrayList<String>();
  6. for (int i = 0; i < mPackagesForbiddenAutoRun.size(); i++) {
  7. res.add(mPackagesForbiddenAutoRun.valueAt(i));
  8. }
  9. return res;
  10. }
  11. }
  12. }

流程图如下:

2、允许自启动

允许自启动:调用系统接口ActivityManagerService类封装的setForbiddenAutorunPackages( final String packageName, boolean bAdd)方法,此时flag参数为false,将设置的应用的包名从全局变量mPackagesForbiddenAutoRun中移除,包括该应用包含的服务从全局变量mServicesForbiddenAutoRun中移除,最后将mPackagesForbiddenAutoRun列表中的数据重新更新到/data/system/forbidden_autorun_packages.xml文件。

流程图如下:

3、动态安装

当一个应用安装时,AppInstallReceiver接收到android.intent.action.PACKAGE_ADDED广播,如果该应用不在security_autorun_displayed_whitelist(允许自启动显示白名单)及security_autorun_notdisplayed_whitelist(允许自启动不显示白名单)中,并且该应用又是第三方应用,则将该应用加入禁止自启动列表。

4、AMS实现机制

1.开机自启动

(1)、开机启动过程中,会调用到ActivityManagerService的systemReady()方法,在该方法中读取/data/system/forbidden_autorun_packages.xml文件中的数据,并将其保存到全局数组变量mPackagesForbiddenAutoRun中。

具体代码如下:

[java] view plaincopy
  1. if (Feature.FEATURE_FORBID_APP_AUTORUN){
  2. mFileFilter = new File(PATH_PACKAGES_FILTER);
  3. if(!mFileFilter.exists()){
  4. try {
  5. mFileFilter.createNewFile();
  6. } catch (IOException e) {
  7. Slog.d(TAG, "Failed to creat black list file!!!");
  8. }
  9. }
  10. readFilterPackages();
  11. }

readFilterPackages()方法读取禁止自启动列表,完成mPackagesForbiddenAutoRun、mServicesForbiddenAutoRun数组的初始化。

[java] view plaincopy
  1. private static final String PATH_PACKAGES_FILTER = "/data/system/forbidden_autorun_packages.xml";
  2. private boolean readFilterPackages(){
  3. FileInputStream str = null;
  4. if ( mFileFilter == null) {
  5. return false;
  6. }
  7. if (!mFileFilter.exists()) {
  8. Log.d(TAG, PATH_PACKAGES_FILTER + "does not exist" );
  9. return false;
  10. }
  11. try {
  12. str = new FileInputStream(mFileFilter);
  13. XmlPullParser parser = Xml.newPullParser();
  14. parser.setInput(str, null);
  15. int type;
  16. while ((type=parser.next()) != XmlPullParser.START_TAG
  17. && type != XmlPullParser.END_DOCUMENT) {
  18. ;
  19. }
  20. if (type != XmlPullParser.START_TAG) {
  21. Log.d(TAG, "No start tag found in in" + PATH_PACKAGES_FILTER  );
  22. return false;
  23. }
  24. int outerDepth = parser.getDepth();
  25. while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
  26. && (type != XmlPullParser.END_TAG
  27. || parser.getDepth() > outerDepth)) {
  28. if (type == XmlPullParser.END_TAG
  29. || type == XmlPullParser.TEXT) {
  30. continue;
  31. }
  32. String tagName = parser.getName();
  33. if (tagName.equals("package")) {
  34. String delPackageName = parser.getAttributeValue(null,"name");
  35. final PackageManager pm = mContext.getPackageManager();
  36. PackageInfo pi = null;
  37. try {
  38. pi = pm.getPackageInfo(delPackageName, PackageManager.GET_SERVICES);
  39. } catch (NameNotFoundException e) {
  40. // TODO Auto-generated catch block
  41. e.printStackTrace();
  42. }
  43. Slog.i(TAG, "forbid autorun pakacge: "+ delPackageName);
  44. mPackagesForbiddenAutoRun.append(delPackageName.hashCode(),delPackageName);
  45. if (pi != null && null != pi.services) {
  46. for (ServiceInfo service : pi.services) {
  47. if (0>mServicesForbiddenAutoRun.indexOfKey(service.processName.hashCode())) {
  48. Slog.i(TAG, "forbid autorun service: "+ service.processName);
  49. mServicesForbiddenAutoRun.append(service.processName.hashCode(),service.processName);
  50. }
  51. }
  52. }
  53. }else {
  54. Slog.w(TAG, "Unknown element under <packages>: "
  55. + parser.getName());
  56. XmlUtils.skipCurrentTag(parser);
  57. }
  58. }
  59. str.close();
  60. } catch(XmlPullParserException e) {
  61. Slog.e(TAG, "Error reading "+ PATH_PACKAGES_FILTER, e);
  62. } catch(java.io.IOException e) {
  63. Slog.e(TAG, "Error reading "+ PATH_PACKAGES_FILTER, e);
  64. }
  65. return true;
  66. }

(2)、系统在启动过程中会拉起一些重要的应用,而大多数应用是在启动完成之后拉起的。这里解释下mProcessesOnHold,这是一个数组列表,保存ProcessRecord对象,表示暂时挂起的进程列表,这些进程因尝试在系统启动(systemReady)完成之前启动,而被暂时挂起,当系统启动完成之后,会启动该列表中的进程。

我们看下源码解释:

[cpp] view plaincopy
  1. /**
  2. * List of records for processes that someone had tried to start before the
  3. * system was ready.  We don't start them at that point, but ensure they
  4. * are started by the time booting is complete.
  5. */
  6. final ArrayList<ProcessRecord> mProcessesOnHold = new ArrayList<ProcessRecord>();

这部分在启动过程中不能拉起的应用就暂存在mProcessesOnHold列表中。因此,自启动管理在系统启动完成之前将mProcessesOnHold列表中包含的mPackagesForbiddenAutoRun中的应用排除,这样在系统启动完成后,就不会启动mPackagesForbiddenAutoRun中的应用。

在startProcessLocked方法中会调用Process.start方法开启线程,我们要做的就是从mProcessesOnHold中排除禁止自启动的应用,这样就实现开机禁止自启动了。

[java] view plaincopy
  1. // If the system is not ready yet, then hold off on starting this
  2. // process until it is.
  3. if (!mProcessesReady
  4. && !isAllowedWhileBooting(info)
  5. && !allowWhileBooting
  6. && !isInBlackList(info)) {
  7. if (!mProcessesOnHold.contains(app)) {
  8. mProcessesOnHold.add(app);
  9. }
  10. if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES,
  11. "System not ready, putting on hold: " + app);
  12. checkTime(startTime, "startProcess: returning with proc on hold");
  13. return app;
  14. }
[java] view plaincopy
  1. public boolean isInBlackList(final ApplicationInfo info)
  2. {
  3. boolean ret = (info.flags & ApplicationInfo.FLAG_PERSISTENT) != ApplicationInfo.FLAG_PERSISTENT//revert for debugging crash
  4. || (info.flags & ApplicationInfo.FLAG_SYSTEM) != ApplicationInfo.FLAG_SYSTEM;
  5. String packageName = info.packageName;
  6. ret = ((mPackagesForbiddenAutoRun.indexOfKey(packageName.hashCode())>=0 ||
  7. mServicesForbiddenAutoRun.indexOfKey(packageName.hashCode())>=0) &&
  8. (mPackagesHasWidgetRun.indexOfKey(packageName.hashCode())<0));
  9. if(ret) Slog.v(TAG, "check isInBlackList:" + packageName + " ret=" + ret);
  10. return ret;
  11. }

2、应用后台自启动

自启动管理同样包含一个应用死掉后后台重新启动的过程。Service运行在前后台都可以,即表示可以运行在当前进程也可以运行在其他进程中,Service可以为多个App共享使用,是通过binder机制来实现的。当我kill掉一个带有服务的进程(没有调用stopService()),过一会该应用会自动重启。下面是代码的调用顺序,自下往上看一下:

com.android.server.am.ActiveServices.bringDownServiceLocked(ActiveServices.java)
com.android.server.am.ActiveServices.killServicesLocked(ActiveServices.java)
com.android.server.am.ActivityManagerService.cleanUpApplicationRecordLocked(ActivityManagerService.java)
com.android.server.am.ActivityManagerService.handleAppDiedLocked(ActivityManagerService.java)
com.android.server.am.ActivityManagerService.appDiedLocked(ActivityManagerService.java)
com.android.server.am.ActivityManagerService$AppDeathRecipient.binderDied(ActivityManagerService.java)

ActivityManagerService.handleAppDiedLocked方法中对于在黑名单中的应用调用PackageManagerService.setPackageStoppedState方法,将应用对应的Package设置为stop状态(如果一个应用正在灭亡,或已经灭亡,就要将其对应的package置于stop状态)。

如果服务重启,则在ActiveServices.killServicesLocked方法中调用bringDownServiceLocked方法,不允许重启,直接挂掉。

具体代码如下:

[java] view plaincopy
  1. /**
  2. * Main function for removing an existing process from the activity manager
  3. * as a result of that process going away.  Clears out all connections
  4. * to the process.哈哈
  5. */
  6. private final void handleAppDiedLocked(ProcessRecord app,
  7. boolean restarting, boolean allowRestart) {
  8. int pid = app.pid;
  9. boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1);
  10. if (!kept && !restarting) {
  11. removeLruProcessLocked(app);
  12. if (pid > 0) {
  13. ProcessList.remove(pid);
  14. }
  15. }
  16. if (mProfileProc == app) {
  17. clearProfilerLocked();
  18. }
  19. if (Feature.FEATURE_FORBID_APP_AUTORUN ) {
  20. if (app != null && app.info != null &&
  21. isInBlackList(app.info)) {
  22. IPackageManager pm = AppGlobals.getPackageManager();
  23. try {
  24. pm.setPackageStoppedState(app.info.packageName, true, UserHandle.getUserId(app.uid));
  25. Slog.i(TAG, "forbid restart this app that is contained in forbidden list: "  + app.processName + " and remove its alarms & jobs.");
  26. mContext.sendBroadcastAsUser(new Intent(ACTION_APP_KILL).putExtra(Intent.EXTRA_PACKAGES, new String[]{ app.info.packageName }),
  27. UserHandle.ALL, "android.permission.DEVICE_POWER");
  28. } catch (RemoteException e) {
  29. // TODO Auto-generated catch block
  30. e.printStackTrace();
  31. } catch (IllegalArgumentException e) {
  32. Slog.w(TAG, "Failed trying to unstop package "
  33. + app.info.packageName + ": " + e);
  34. e.printStackTrace();
  35. }
  36. }
  37. }
  38. ......
  39. }
[java] view plaincopy
  1. /* optimize memory */
  2. else if (Feature.FEATURE_FORBID_APP_AUTORUN &&
  3. app != null && app.info != null && !isCTSMode() &&
  4. mAm.isInBlackList(app.info)) {
  5. Slog.w(TAG, "Service crashed " + sr.crashCount
  6. + " times, stopping: " + sr + "forbid restart this app that is contained in forbidden list: " + app.processName);
  7. EventLog.writeEvent(EventLogTags.AM_DESTROY_SERVICE ,
  8. sr.crashCount, sr.shortName, app.pid,  app.processName);
  9. bringDownServiceLocked(sr);
  10. }

[java] view plaincopy
  1. private class QuickBootReceiver extends BroadcastReceiver {//AlarmManagerService中注册该广播接收器
  2. static final String ACTION_APP_KILL = "org.codeaurora.quickboot.appkilled";
  3. public QuickBootReceiver() {
  4. IntentFilter filter = new IntentFilter();
  5. filter.addAction(ACTION_APP_KILL);
  6. getContext().registerReceiver(this, filter,
  7. "android.permission.DEVICE_POWER", null);
  8. }
  9. @Override
  10. public void onReceive(Context context, Intent intent) {
  11. if (Feature.FEATURE_FORBID_APP_AUTORUN) {
  12. Message msg = new Message();
  13. msg.what = ScheduleHandler.REMOVE_DEAD_APP_ALARM_REVEIVE;
  14. msg.obj = intent;
  15. mScheduleHandler.sendMessage(msg);
  16. return  ;
  17. }
  18. String action = intent.getAction();
  19. String pkgList[] = null;
  20. if (ACTION_APP_KILL.equals(action)) {
  21. pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
  22. if (pkgList != null && (pkgList.length > 0)) {
  23. for (String pkg : pkgList) {
  24. removeLocked(pkg);
  25. for (int i=mBroadcastStats.size()-1; i>=0; i--) {
  26. ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(i);
  27. if (uidStats.remove(pkg) != null) {
  28. if (uidStats.size() <= 0) {
  29. mBroadcastStats.removeAt(i);
  30. }
  31. }
  32. }
  33. }
  34. }
  35. }
  36. }
  37. }
[java] view plaincopy
  1. case REMOVE_DEAD_APP_ALARM_REVEIVE:
  2. synchronized (mLock) {
  3. removeDeadAppAlarmReceiveLocked((Intent)msg.obj);
  4. }
  5. break;
[java] view plaincopy
  1. void removeDeadAppAlarmReceiveLocked(Intent intent) {
  2. Slog.d(TAG, "Receive for remove dead app alarm: " + intent.getAction());
  3. String pkgList[] = null;
  4. pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
  5. if (pkgList != null && (pkgList.length > 0)) {
  6. for (String pkg : pkgList) {
  7. removeLocked(pkg);
  8. for (int i=mBroadcastStats.size()-1; i>=0; i--) {
  9. ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(i);
  10. if (uidStats.remove(pkg) != null) {
  11. if (uidStats.size() <= 0) {
  12. mBroadcastStats.removeAt(i);
  13. }
  14. }
  15. }
  16. }
  17. }
  18. }

有些应用进程起来后,会在native层产生一个守护进程,应用进程挂掉后,native层进程又会把应用层进程拉起来,因此在ActivityManagerS.forceStopPackage方法中调用SystemProperties.set("ctl.start","cleandaemon");执行清理脚本。

3、广播启动场景

在Android系统中,系统发出一个广播,如果应用注册了这个广播,那么都会收到这个广播。自启动管理正是采用这种广播机制来实现禁止自启动的。系统在发送广播的时候,通过判断这个广播接收者是否在禁止自启动列表中,并且该应用没有被拉起来,就不给该应用发送广播。

具体处理方法在processNextBroadcast方法中处理
[java] view plaincopy
  1. /* For do not send broadcast to the APP in blacklist that not running */
  2. if (Feature.FEATURE_FORBID_APP_AUTORUN &&
  3. info != null && info.activityInfo != null &&
  4. info.activityInfo.packageName!="com.yulong.android.dualmmsetting"&&
  5. mService.isInBlackList(info.activityInfo.applicationInfo)) {
  6. Slog.v(TAG, "Skipping delivery of static ["
  7. + mQueueName + "] " + r + " for forbiden auto run");
  8. r.receiver = null;
  9. r.curFilter = null;
  10. r.state = BroadcastRecord.IDLE;
  11. scheduleBroadcastsLocked();
  12. return;
  13. }

4、AMS垃圾回收机制

打开程序或者有程序进入后台时都会执行updateOomAdjLocked()函数。可以在该方法中进行处理。

[java] view plaincopy
  1. if (Feature.FEATURE_FORBID_APP_AUTORUN) {
  2. if (isInBlackList(app.info)) {
  3. app.kill("Mem less than " + ProcessList.MIN_MEM_LEVEL/(1024*1024) + "M, kill background!!", true);
  4. }
  5. }

Android功耗(16)---自启动管理相关推荐

  1. Android性能优化 ---(6)自启动管理

    自启动管理简介 Android手机上安装的很多应用都会自启动,占用资源越来越多,造成系统卡顿等现象.良好的自启动管理方案管理后台自启动和开机自启动,这样就可以节约内存.优化系统流畅性等. 自启动管理流 ...

  2. android管理自启动,Android自启动管理原理

    垃圾电话,我无法在互联网上使用优化软件的许,它占用了过多的内存. 我要编写一个自启动管理软件. 通常在Internet上进行搜索,您可以使用PackageManager软件包获取所有应用程序的列表,然 ...

  3. android 自启动列表,Android 机型设置自启动的跳转界面

    Android 机型设置自启动的跳转界面 由于之前版本号把23,6.0误写成26,8.0了,导致一些判断是错的,并且在catch中没有重新对intent更新,导致会有崩溃问题,现已修复. 简书怎么传附 ...

  4. Android 功耗(12)---省电续航-相互唤醒

    省电续航-相互唤醒 相互唤醒现象描述 安装100个APP,不小心点开了一个唤醒类型的APP,过一会儿,后台偷偷自启动了30~40个APP. 怪不得雷布斯之前说国内的应用市场环境太乱了,很多情况下用户都 ...

  5. android 功耗(1)---android 功耗分析方法和优化

    android 功耗 高通平台   分类:功耗 1.底电流调试(Rock Bottom Current Optimization) 底电流在手机飞行模式下调试.每个平台的底电流数据可能不一样,具体可以 ...

  6. Android 引导页开发管理2

    <Android 引导页开发管理1>说明的是连续页面的闪烁效果,本次文章显示的是,一张效果效果图等待3s后进入主页面. 后续开发可以显示一段flash或者其他效果图看看. 1.splash ...

  7. 【转载】Android功耗改进

    原文地址:<Android功耗改进> by 保罗的酒吧 最近几年中,Google在一直极力的改进Android系统的续航能力.在本文中,我们将看到Andrdoi自5.0到8.0这几个版本中 ...

  8. Android内核驱动——电源管理

    Android内核驱动--电源管理 13.1 基本原理 Android 中定义了几种低功耗状态:earlysuspend,suspend,hibernation.  earlysuspend是一种低 ...

  9. android应用的自启动 和 相互关联启动

    Android手机APP常见后台服务  2015年1月26日  小恐龙  应用技巧 前言简述 Android生态系统原本提供了类似于Apple iOS推送服务APNS的GCM(Google Cloud ...

最新文章

  1. uint8_t uint16_t uint32_t uint64_t 解析
  2. JQuery中each()的使用方法说明
  3. python trim函数_python strip()函数 介绍
  4. cmd10与16进制的相互转换
  5. boost::mp11::mp_back相关用法的测试程序
  6. 学爬虫,需要掌握哪些Python基础?
  7. SQL入侵恢复XP_CMDSHLL与开3389
  8. SMPP Java示例(客户端)
  9. php 正则mysql语句_MySQL正则表达式搜索 - mysql数据库栏目 - 自学php
  10. Source Insight 教程
  11. (项目)生鲜超市(六)
  12. 肺结节圆形边界光滑_肺结节读片(16):再谈肺磨玻璃结节边界的CT形态特点
  13. 程序员从入门到放弃,书籍推荐
  14. 低延时极简RTMP播放器
  15. 细化迭代三文档-2.1,2.2,2.3
  16. C++11 packaged_task
  17. 如何使用Sketch绘制半个圆角矩形
  18. vue和微信小程序的基本使用区别
  19. 可塑造攻击_指导如何帮助塑造我的职业
  20. Lintcode 4 Ugly Number II

热门文章

  1. linux多线程学习(一)
  2. python跳转下一页_Python网页浏览转到下一页
  3. 云端服务器怎么维护,云端服务器怎么维护
  4. java httpresponse headres属性,Java HttpHeaders.CONTENT_TYPE属性代码示例
  5. 【重难点】【Redis 01】为什么使用 Redis、Redis 的线程模型、Redis 的数据类型及其底层数据结构
  6. 【Java数据结构与算法】第十四章 红黑树
  7. linux常用命令小结(二)
  8. 深入浅出mybatis之入门使用
  9. bzoj 3679: 数字之积
  10. 【.net 深呼吸】项目中是否有必要删去多余的引用