【Android】一种提高Android应用进程存活率新方法

SkySeraph Jun. 19st 2016

Email:skyseraph00@163.com

更多精彩请直接访问SkySeraph个人站点www.skyseraph.com

一、基础知识

1.Android 进程优先级

1.1 进程优先级等级一般分法:
- Activte process
- Visible Process
- Service process
- Background process
- Empty process

1.2 进程优先级号

ProcessList.java

  1. // Adjustment used in certain places where we don't know it yet.
  2. // (Generally this is something that is going to be cached, but we
  3. // don't know the exact value in the cached range to assign yet.)
  4. static final int UNKNOWN_ADJ = 16;
  5.  
  6. // This is a process only hosting activities that are not visible,
  7. // so it can be killed without any disruption.
  8. static final int CACHED_APP_MAX_ADJ = 15;
  9. static final int CACHED_APP_MIN_ADJ = 9;
  10.  
  11. // The B list of SERVICE_ADJ -- these are the old and decrepit
  12. // services that aren't as shiny and interesting as the ones in the A list.
  13. static final int SERVICE_B_ADJ = 8;
  14.  
  15. // This is the process of the previous application that the user was in.
  16. // This process is kept above other things, because it is very common to
  17. // switch back to the previous app. This is important both for recent
  18. // task switch (toggling between the two top recent apps) as well as normal
  19. // UI flow such as clicking on a URI in the e-mail app to view in the browser,
  20. // and then pressing back to return to e-mail.
  21. static final int PREVIOUS_APP_ADJ = 7;
  22.  
  23. // This is a process holding the home application -- we want to try
  24. // avoiding killing it, even if it would normally be in the background,
  25. // because the user interacts with it so much.
  26. static final int HOME_APP_ADJ = 6;
  27.  
  28. // This is a process holding an application service -- killing it will not
  29. // have much of an impact as far as the user is concerned.
  30. static final int SERVICE_ADJ = 5;
  31.  
  32. // This is a process with a heavy-weight application. It is in the
  33. // background, but we want to try to avoid killing it. Value set in
  34. // system/rootdir/init.rc on startup.
  35. static final int HEAVY_WEIGHT_APP_ADJ = 4;
  36.  
  37. // This is a process currently hosting a backup operation. Killing it
  38. // is not entirely fatal but is generally a bad idea.
  39. static final int BACKUP_APP_ADJ = 3;
  40.  
  41. // This is a process only hosting components that are perceptible to the
  42. // user, and we really want to avoid killing them, but they are not
  43. // immediately visible. An example is background music playback.
  44. static final int PERCEPTIBLE_APP_ADJ = 2;
  45.  
  46. // This is a process only hosting activities that are visible to the
  47. // user, so we'd prefer they don't disappear.
  48. static final int VISIBLE_APP_ADJ = 1;
  49.  
  50. // This is the process running the current foreground app. We'd really
  51. // rather not kill it!
  52. static final int FOREGROUND_APP_ADJ = 0;
  53.  
  54. // This is a process that the system or a persistent process has bound to,
  55. // and indicated it is important.
  56. static final int PERSISTENT_SERVICE_ADJ = -11;
  57.  
  58. // This is a system persistent process, such as telephony. Definitely
  59. // don't want to kill it, but doing so is not completely fatal.
  60. static final int PERSISTENT_PROC_ADJ = -12;
  61.  
  62. // The system process runs at the default adjustment.
  63. static final int SYSTEM_ADJ = -16;
  64.  
  65. // Special code for native processes that are not being managed by the system (so
  66. // don't have an oom adj assigned by the system).
  67. static final int NATIVE_ADJ = -17;

2. Android Low Memory Killer

  Android系统内存不足时,系统会杀掉一部分进程以释放空间,谁生谁死的这个生死大权就是由LMK所决定的,这就是Android系统中的Low Memory Killer,其基于Linux的OOM机制,其阈值定义如下面所示的lowmemorykiller文件中,当然也可以通过系统的init.rc实现自定义。

lowmemorykiller.c

  1. static uint32_t lowmem_debug_level = ;
  2. static int lowmem_adj[] = {
  3. ,
  4. ,
  5. ,
  6. ,
  7. };
  8. static int lowmem_adj_size = ;
  9. static int lowmem_minfree[] = {
  10. * , /* 6MB */
  11. * , /* 8MB */
  12. * , /* 16MB */
  13. * , /* 64MB */
  14. };
  15. static int lowmem_minfree_size = ;

在Low Memory Killer中通过进程的oom_adj与占用内存的大小决定要杀死的进程,oom_adj值越小越不容易被杀死。其中,lowmem_minfree是杀进程的时机,谁被杀,则取决于lowmem_adj,具体值得含义参考上面 Android进程优先级 所述.

在init.rc中定义了init进程(系统进程)的oom_adj为-16,其不可能会被杀死(init的PID是),而前台进程是(这里的前台进程是指用户正在使用的Activity所在的进程),用户按Home键回到桌面时的优先级是6,普通的Service的进程是8.

init.rc

  1. # Set init and its forked children's oom_adj.
  2. write /proc//oom_adj -

关于Low Memory Killer的具体实现原理可参考Ref-2.

3. 查看某个App的进程

步骤(手机与PC连接)
1. adb shell
2. ps | grep 进程名
3. cat /proc/pid/oom_adj //其中pid是上述grep得到的进程号

4. Android账号和同步机制

属于Android中较偏冷的知识,具体参考 Ref 3/4/5

二、现有方法

1. 网络连接保活方法

a. GCM
b. 公共的第三方push通道(信鸽等)
c. 自身跟服务器通过轮询,或者长连接
具体实现请参考 微信架构师杨干荣的"微信Android客户端后台保活经验分享" (Ref-1).

2. 双service 提高进程优先级

思路:(API level > 18 )
① 应用启动时启动一个假的Service(FakeService), startForeground(),传一个空的Notification
② 启动真正的Service(AlwaysLiveService),startForeground(),注意必须相同Notification ID
③ FakeService stopForeground()

效果:通过adb查看,运行在后台的服务其进程号变成了1(优先级仅次于前台进程)

风险:Android系统前台service的一个漏洞,可能在6.0以上系统中修复

实现:核心代码如下

AlwaysLiveService 常驻内存服务

  1. @Override
  2. public int onStartCommand(Intent intent, int flags, int startId) {
  3. startForeground(R.id.notify, new Notification());
  4. startService(new Intent(this, FakeService.class));
  5. return super.onStartCommand(intent, flags, startId);
  6. }

FakeService 临时服务

  1. public class FakeService extends Service {
  2. @Nullable
  3. @Override
  4. public IBinder onBind(Intent intent) {
  5. return null;
  6. }
  7.  
  8. @Override
  9. public int onStartCommand(Intent intent, int flags, int startId) {
  10. startForeground(R.id.notify, new Notification());
  11. stopSelf();
  12. return super.onStartCommand(intent, flags, startId);
  13. }
  14.  
  15. @Override
  16. public void onDestroy() {
  17. stopForeground(true);
  18. super.onDestroy();
  19. }
  20. }

3. 守护进程及时拉起

AlarmReceiver, ConnectReceiver,BootReceiver等

三、新方法(AccountSyncAdapter)

1. 思路:

利用Android系统提供的账号和同步机制实现

2. 效果:

① 通过adb查看,运行在后台的服务其进程号变成了1(优先级仅次于前台进程),能提高进程优先级,对比如下图

正常情况

采用AccountSyncAdapter方法后

② 进程被系统kill后,可以由syn拉起

3. 风险:

① SyncAdapter时间进度不高,往往会因为手机处于休眠状态,而时间往后调整,同步间隔最低为1分钟
 用户可以单独停止或者删除,有些手机账号默认是不同步的,需要手动开启

4. 实现:核心代码如下

① 建立数据同步系统(ContentProvider)

通过一个ContentProvider用来作数据同步,由于并没有实际数据同步,所以此处就直接建立一个空的ContentProvider即可

  1. public class XXAccountProvider extends ContentProvider {
  2. public static final String AUTHORITY = "包名.provider";
  3. public static final String CONTENT_URI_BASE = "content://" + AUTHORITY;
  4. public static final String TABLE_NAME = "data";
  5. public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_BASE + "/" + TABLE_NAME);
  6.  
  7. @Override
  8. public boolean onCreate() {
  9. return true;
  10. }
  11.  
  12. @Nullable
  13. @Override
  14. public Cursor query(Uri uri, String[] projection, String selection,
  15. String[] selectionArgs, String sortOrder) {
  16. return null;
  17. }
  18.  
  19. @Nullable
  20. @Override
  21. public String getType(Uri uri) {
  22. return new String();
  23. }
  24.  
  25. @Nullable
  26. @Override
  27. public Uri insert(Uri uri, ContentValues values) {
  28. return null;
  29. }
  30.  
  31. @Override
  32. public int delete(Uri uri, String selection, String[] selectionArgs) {
  33. return 0;
  34. }
  35.  
  36. @Override
  37. public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
  38. return 0;
  39. }
  40. }

然后再Manifest中声明

  1. <provider
  2. android:name="**.XXAccountProvider"
  3. android:authorities="@string/account_auth_provider"
  4. android:exported="false"
  5. android:syncable="true"/>

② 建立Sync系统 (SyncAdapter)
通过实现SyncAdapter这个系统服务后, 利用系统的定时器对程序数据ContentProvider进行更新,具体步骤为:
- 创建Sync服务

  1. public class XXSyncService extends Service {
  2. private static final Object sSyncAdapterLock = new Object();
  3. private static XXSyncAdapter sSyncAdapter = null;
  4. @Override
  5. public void onCreate() {
  6. synchronized (sSyncAdapterLock) {
  7. if (sSyncAdapter == null) {
  8. sSyncAdapter = new XXSyncAdapter(getApplicationContext(), true);
  9. }
  10. }
  11. }
  12.  
  13. @Override
  14. public IBinder onBind(Intent intent) {
  15. return sSyncAdapter.getSyncAdapterBinder();
  16. }
  17.  
  18. static class XXSyncAdapter extends AbstractThreadedSyncAdapter {
  19. public XXSyncAdapter(Context context, boolean autoInitialize) {
  20. super(context, autoInitialize);
  21. }
  22. @Override
  23. public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
  24. getContext().getContentResolver().notifyChange(XXAccountProvider.CONTENT_URI, null, false);
  25. }
  26. }
  27. }

- 声明Sync服务

  1. <service
  2. android:name="**.XXSyncService"
  3. android:exported="true"
  4. android:process=":core">
  5. <intent-filter>
  6. <action
  7. android:name="android.content.SyncAdapter"/>
  8. </intent-filter>
  9. <meta-data
  10. android:name="android.content.SyncAdapter"
  11. android:resource="@xml/sync_adapter"/>
  12. </service>

其中sync_adapter为:

  1. <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:accountType="@string/account_auth_type"
  3. android:allowParallelSyncs="false"
  4. android:contentAuthority="@string/account_auth_provide"
  5. android:isAlwaysSyncable="true"
  6. android:supportsUploading="false"
  7. android:userVisible="true"/>

参数说明:
android:contentAuthority 指定要同步的ContentProvider在其AndroidManifest.xml文件中有个android:authorities属性。
android:accountType 表示进行同步的账号的类型。
android:userVisible 设置是否在“设置”中显示
android:supportsUploading 设置是否必须notifyChange通知才能同步
android:allowParallelSyncs 是否支持多账号同时同步
android:isAlwaysSyncable 设置所有账号的isSyncable为1
android:syncAdapterSettingsAction 指定一个可以设置同步的activity的Action。

- 账户调用Sync服务
首先配置好Account(第三步),然后再通过ContentProvider实现
手动更新

  1. public void triggerRefresh() {
  2. Bundle b = new Bundle();
  3. b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
  4. b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
  5. ContentResolver.requestSync(
  6. account,
  7. CONTENT_AUTHORITY,
  8. b);
  9. }

添加账号

  1. Account account = AccountService.GetAccount();
  2. AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
  3. accountManager.addAccountExplicitly(...)

同步周期设置

  1. ContentResolver.setIsSyncable(account, CONTENT_AUTHORITY, 1);
  2. ContentResolver.setSyncAutomatically(account, CONTENT_AUTHORITY, true);
  3. ContentResolver.addPeriodicSync(account, CONTENT_AUTHORITY, new Bundle(), SYNC_FREQUENCY);

③ 建立账号系统 (Account Authenticator)
通过建立Account账号,并关联SyncAdapter服务实现同步
- 创建Account服务

  1. public class XXAuthService extends Service {
  2. private XXAuthenticator mAuthenticator;
  3.  
  4. @Override
  5. public void onCreate() {
  6. mAuthenticator = new XXAuthenticator(this);
  7. }
  8.  
  9. private XXAuthenticator getAuthenticator() {
  10. if (mAuthenticator == null)
  11. mAuthenticator = new XXAuthenticator(this);
  12. return mAuthenticator;
  13. }
  14.  
  15. @Override
  16. public IBinder onBind(Intent intent) {
  17. return getAuthenticator().getIBinder();
  18. }
  19.  
  20. class XXAuthenticator extends AbstractAccountAuthenticator {
  21. private final Context context;
  22. private AccountManager accountManager;
  23. public XXAuthenticator(Context context) {
  24. super(context);
  25. this.context = context;
  26. accountManager = AccountManager.get(context);
  27. }
  28.  
  29. @Override
  30. public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)
  31. throws NetworkErrorException {
  32. // 添加账号 示例代码
  33. final Bundle bundle = new Bundle();
  34. final Intent intent = new Intent(context, AuthActivity.class);
  35. intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
  36. bundle.putParcelable(AccountManager.KEY_INTENT, intent);
  37. return bundle;
  38. }
  39.  
  40. @Override
  41. public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)
  42. throws NetworkErrorException {
  43. // 认证 示例代码
  44. String authToken = accountManager.peekAuthToken(account, getString(R.string.account_token_type));
  45. //if not, might be expired, register again
  46. if (TextUtils.isEmpty(authToken)) {
  47. final String password = accountManager.getPassword(account);
  48. if (password != null) {
  49. //get new token
  50. authToken = account.name + password;
  51. }
  52. }
  53. //without password, need to sign again
  54. final Bundle bundle = new Bundle();
  55. if (!TextUtils.isEmpty(authToken)) {
  56. bundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
  57. bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
  58. bundle.putString(AccountManager.KEY_AUTHTOKEN, authToken);
  59. return bundle;
  60. }
  61.  
  62. //no account data at all, need to do a sign
  63. final Intent intent = new Intent(context, AuthActivity.class);
  64. intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
  65. intent.putExtra(AuthActivity.ARG_ACCOUNT_NAME, account.name);
  66. bundle.putParcelable(AccountManager.KEY_INTENT, intent);
  67. return bundle;
  68. }
  69.  
  70. @Override
  71. public String getAuthTokenLabel(String authTokenType) {
  72. // throw new UnsupportedOperationException();
  73. return null;
  74. }
  75.  
  76. @Override
  77. public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
  78. return null;
  79. }
  80.  
  81. @Override
  82. public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)
  83. throws NetworkErrorException {
  84. return null;
  85. }
  86.  
  87. @Override
  88. public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)
  89. throws NetworkErrorException {
  90. return null;
  91. }
  92.  
  93. @Override
  94. public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)
  95. throws NetworkErrorException {
  96. return null;
  97. }
  98. }
  99. }

- 声明Account服务

  1. <service
  2. android:name="**.XXAuthService"
  3. android:exported="true"
  4. android:process=":core">
  5. <intent-filter>
  6. <action
  7. android:name="android.accounts.AccountAuthenticator"/>
  8. </intent-filter>
  9. <meta-data
  10. android:name="android.accounts.AccountAuthenticator"
  11. android:resource="@xml/authenticator"/>
  12. </service>

其中authenticator为:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:accountType="@string/account_auth_type"
  4. android:icon="@drawable/icon"
  5. android:smallIcon="@drawable/icon"
  6. android:label="@string/app_name"
  7. />

- 使用Account服务
同SyncAdapter,通过AccountManager使用

  - 申请Token主要是通过 [AccountManager.getAuthToken]系列方法

  - 添加账号则通过 [AccountManager.addAccount]

  - 查看是否存在账号通过 [AccountManager.getAccountsByType]

Refs

1. [微信Android客户端后台保活经验分享]

2. [Android Low Memory Killer原理]

3. [stackOverflow 上介绍的双Service方法]

4. [Write your own Android Sync Adapter]

5. [Write your own Android Authenticator]

6. Android developer
- [android.accounts]
- [AccountManager]
- [AbstractAccountAuthenticator]
- [AccountAuthenticatorActivity]
- [Creating a Sync Adapter]

========

By SkySeraph-2016  www.skyseraph.com

【Android】一种提高Android应用进程存活率新方法的更多相关文章

  1. 一种提高Android应用进程存活率新方法

    一.基础知识 1.Android 进程优先级 1.1 进程优先级等级一般分法:- Activte process- Visible Process- Service process- Backgrou ...

  2. android 8种对话框(Dialog)使用方法汇总

    1.写在前面 Android提供了丰富的Dialog函数,本文介绍最常用的8种对话框的使用方法,包括普通(包含提示消息和按钮).列表.单选.多选.等待.进度条.编辑.自定义等多种形式,将在第2部分介绍 ...

  3. 关于如何提高SRAM存储器的新方法

    SRAM是当今处理器上最普遍的内存.当芯片制造商宣布他们已经成功地将更多的电路封装到芯片上时,通常是较小的晶体管引起了人们的注意.但是连接晶体管形成电路的互连也必须收缩.IMEC的研究人员提出了一个方 ...

  4. [Android]GOF23种设计模式 & Android中的设计模式

    GOF23种设计模式 设计原则: 1. 单一职责原则(SRP):就一个类而言,应该仅有一个引起它变化的原因 2. 开放-封闭原则(OCP):软件实体(类.模块.函数等)应该可以扩展,但是不可修改.即对 ...

  5. 一种部署 Python 代码的新方法

    在Nylas,我们喜欢使用Python进行开发.它的语法简单并富有表现力,拥有大量可用的开源模块和框架,而且这个社区既受欢迎又有多样性.我们的后台是纯用 Python 写的,团队也经常在 PyCon ...

  6. NFV一种提高进程消息高可用性的方法

    1.背景及概述 1.1 背景 在做NFV的过程中,由于控制面进程被放置到不同虚拟机中,中间可能跨越路由器,因此期间网络有可能震荡,这种情况下保证高可用性就必须有保护机制,本文正是在这种背景下的考虑. ...

  7. 重点:怎样正确的使用QThread类(注:包括推荐使用QThread线程的新方法QObject::moveToThread)

    背景描述: 以前,继承 QThread 重新实现 run() 函数是使用 QThread唯一推荐的使用方法.这是相当直观和易于使用的.但是在工作线程中使用槽机制和Qt事件循环时,一些用户使用错了.Qt ...

  8. Android 三种动画详解

    [工匠若水 http://blog.csdn.net/yanbober 转载请注明出处.点我开始Android技术交流] 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让 ...

  9. 【Android 复习】:Android五种布局的使用方法

    ---恢复内容开始--- 在Android布局中,有五种常用的布局,下面我们就来学习一下这几种布局的使用方式 1) 线性布局:LinearLayout 2) 帧布局:  FrameLayout 3)  ...

随机推荐

  1. oracle 序列中cache 有什么用途

    create sequence name increment by x //x为增长间隔 start with x //x为初始值 maxvalue x //x为最大值 minvalue x //x为 ...

  2. db2、Oracle存储过程引号用法

      在存储过程中,单引号有两个作用,一是字符串是由单引号引用,二是转义.单引号的使用是就近配对,即就近原则.而在单引号充当转义角色时相对不好理解     1.从第二个单引号开始被视为转义符,如果第二个 ...

  3. 爬取https页面遇到“SSLError: hostname 'xxx' doesn't match either of”的解决方法

    使用python requests 框架包访问https://itunes.apple.com 页面是遇到 SSLError: hostname 'itunes.apple.com' doesn't ...

  4. centos7 打开mysql 3306端口并 设置外部访问

    mysql安装后默认是localhost访问,如果需要外部访问可以设置一个新的账号把host改为%,意味着所有ip均可以访问 grant all privileges on *.* to 'outUs ...

  5. javascript实用技巧、javascript高级技巧

    字号+作者:H5之家 来源:H5之家 2016-10-31 11:00 我要评论( ) 三零网提供网络编程. JavaScript 的技术文章javascript实用技巧.javascript高级技巧 ...

  6. CSS属性小结之--半透明处理

    项目中经常有遇到需求半透明的情况,如图片.文字.容器.背景等等,每次都要去翻以前的项目,不甚其烦.现在一次性做个小结,方便自己查阅,也同时分享给大家: 一. 元素容器透明 .div{ opacity: ...

  7. [ActiveMQ]初识ActiveMQ

    初识ActiveMQ ActiveMQ介绍 官方网站:http://activemq.apache.org/ 最新版本:ActiveMQ 5.14.1(2016-10-28) 最新版本下载链接:htt ...

  8. 使用spring的AOP时产生的异常

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userService' ...

  9. 爬虫 htmlUnit遇到Cannot locate declared field class org.apache.http.impl.client.HttpClientBuilder.dnsResolve错误

    当在使用htmlUnit时遇到无法定位org.apache.http.impl.client.HttpClientBuilder.dnsResolver类时,此时所需要的依赖包为: <depen ...

  10. Mosquitto搭建Android推送服务(一)MQTT简介

    总体概要: MQTT系列文章分为4部分 1.MQTT简介 2.mosquitto服务器搭建 3.编写Mosquitto的可视化工具 4.使用Mosquitto完成Android推送服务 文章钢要: 对 ...