【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 decrepit12     // 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 to17     // switch back to the previous app.  This is important both for recent18     // task switch (toggling between the two top recent apps) as well as normal19     // 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 try24     // 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 not29     // 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 the33     // background, but we want to try to avoid killing it.  Value set in34     // 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 it38     // 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 the42     // user, and we really want to avoid killing them, but they are not43     // 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 the47     // 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 really51     // 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.  Definitely59     // 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 (so66     // don't have an oom adj assigned by the system).67     static final int NATIVE_ADJ = -17;
View Code

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 = 1; 2 static int lowmem_adj[6] = { 3     0, 4     1, 5     6, 6     12, 7 }; 8 static int lowmem_adj_size = 4; 9 static int lowmem_minfree[6] = {10     3 * 512,    /* 6MB */11     2 * 1024,   /* 8MB */12     4 * 1024,   /* 16MB */13     16 * 1024,  /* 64MB */14 };15 static int lowmem_minfree_size = 4;
View Code

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

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

init.rc

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

关于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 @Override2    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    }
View Code

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     @Override16     public void onDestroy() {17         stopForeground(true);18         super.onDestroy();19     }20 }
View Code

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     @Nullable13     @Override14     public Cursor query(Uri uri, String[] projection, String selection,15                         String[] selectionArgs, String sortOrder) {16         return null;17     }18 19     @Nullable20     @Override21     public String getType(Uri uri) {22         return new String();23     }24 25     @Nullable26     @Override27     public Uri insert(Uri uri, ContentValues values) {28         return null;29     }30 31     @Override32     public int delete(Uri uri, String selection, String[] selectionArgs) {33         return 0;34     }35 36     @Override37     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {38         return 0;39     }40 }
View Code

然后再Manifest中声明

    <provider        android:name="**.XXAccountProvider"        android:authorities="@string/account_auth_provider"        android:exported="false"        android:syncable="true"/>
View Code

② 建立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     @Override14     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         @Override23         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 }
View Code

- 声明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-data10             android:name="android.content.SyncAdapter"11             android:resource="@xml/sync_adapter"/>12     </service>
View Code

其中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"/>
View Code

参数说明:
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 }
View Code

添加账号

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

同步周期设置

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

③ 建立账号系统 (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     @Override16     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         @Override30         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         @Override41         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 again46             if (TextUtils.isEmpty(authToken)) {47                 final String password = accountManager.getPassword(account);48                 if (password != null) {49                     //get new token50                     authToken = account.name + password;51                 }52             }53             //without password, need to sign again54             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 sign63             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         @Override71         public String getAuthTokenLabel(String authTokenType) {72 //            throw new UnsupportedOperationException();73             return null;74         }75 76         @Override77         public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {78             return null;79         }80 81         @Override82         public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)83                 throws NetworkErrorException {84             return null;85         }86 87         @Override88         public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)89                 throws NetworkErrorException {90             return null;91         }92 93         @Override94         public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)95                 throws NetworkErrorException {96             return null;97         }98     }99 }
View Code

- 声明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-data10         android:name="android.accounts.AccountAuthenticator"11         android:resource="@xml/authenticator"/>12 </service>
View Code

其中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 />
View Code

- 使用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

更多相关文章

  1. Android的进程优先级与进程回收详解
  2. android:process=":remote" .
  3. android:process=":remote"
  4. Android(安卓)IPC 机制【1】--简介
  5. android:process=":remote"
  6. 《Android和PHP开发最佳实践》一2.2 Android系统框架
  7. Android中线程的应用
  8. Android的ps命令介绍和技巧
  9. 守护进程通信之Socket

随机推荐

  1. Android(安卓)获取控件宽高的3种方法
  2. android ImageView scaleType属性
  3. android 面试题一
  4. android 常用小知识点 tips (二)
  5. Android发送短信
  6. Android开发:设置widget大小为 4x1
  7. Android底层和中间层共同学习系列之andro
  8. Android上MediaScanner是如何工作的
  9. 取WiFi MAC地址
  10. 反抗金山毒霸的代码