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


    【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;
    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 @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    }
    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     @Override
    16     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     @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 }
    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     @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 }
    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-data
    10             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     @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 }
    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-data
    10         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 

  • 相关阅读:
    七、文件的排序、合并和分割
    六、awk编程
    五、sed命令
    四、grep命令
    三、正则表达式
    二、Linux文件系统和文本编辑器
    一、shell基础知识点
    mysql实现交易编码生成(代替oracle的序列)
    新安装Centos无法访问网络
    uiview 动画
  • 原文地址:https://www.cnblogs.com/skyseraph/p/5511612.html
Copyright © 2020-2023  润新知