微信号:jszj2014215

介绍:主要分享移动互联网的相关产品和资讯,关注你将学习到更多,在互联网的当下你会赚更多的钱...

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

2016-05-29 11:48 点这里

一、基础知识

1.Android 进程优先级

1.1 进程优先级等级一般分法

  • Activte process

  • Visible Process

  • Service process

  • Background process

  • Empty process

1.2 进程优先级号

ProcessList.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// Adjustment used in certain places where we don't know it yet.
// (Generally this is something that is going to be cached, but we
// don't know the exact value in the cached range to assign yet.)
static final int UNKNOWN_ADJ = 16;

// This is a process only hosting activities that are not visible,
// so it can be killed without any disruption.
static final int CACHED_APP_MAX_ADJ = 15;
static final int CACHED_APP_MIN_ADJ = 9;

// The B list of SERVICE_ADJ -- these are the old and decrepit
// services that aren't as shiny and interesting as the ones in the A list.
static final int SERVICE_B_ADJ = 8;

// This is the process of the previous application that the user was in.
// This process is kept above other things, because it is very common to
// switch back to the previous app.  This is important both for recent
// task switch (toggling between the two top recent apps) as well as normal
// UI flow such as clicking on a URI in the e-mail app to view in the browser,
// and then pressing back to return to e-mail.
static final int PREVIOUS_APP_ADJ = 7;

// This is a process holding the home application -- we want to try
// avoiding killing it, even if it would normally be in the background,
// because the user interacts with it so much.
static final int HOME_APP_ADJ = 6;

// This is a process holding an application service -- killing it will not
// have much of an impact as far as the user is concerned.
static final int SERVICE_ADJ = 5;

// This is a process with a heavy-weight application.  It is in the
// background, but we want to try to avoid killing it.  Value set in
// system/rootdir/init.rc on startup.
static final int HEAVY_WEIGHT_APP_ADJ = 4;

// This is a process currently hosting a backup operation.  Killing it
// is not entirely fatal but is generally a bad idea.
static final int BACKUP_APP_ADJ = 3;

// This is a process only hosting components that are perceptible to the
// user, and we really want to avoid killing them, but they are not
// immediately visible. An example is background music playback.
static final int PERCEPTIBLE_APP_ADJ = 2;

// This is a process only hosting activities that are visible to the
// user, so we'd prefer they don't disappear.
static final int VISIBLE_APP_ADJ = 1;

// This is the process running the current foreground app.  We'd really
// rather not kill it!
static final int FOREGROUND_APP_ADJ = 0;

// This is a process that the system or a persistent process has bound to,
// and indicated it is important.
static final int PERSISTENT_SERVICE_ADJ = -11;

// This is a system persistent process, such as telephony.  Definitely
// don't want to kill it, but doing so is not completely fatal.
static final int PERSISTENT_PROC_ADJ = -12;

// The system process runs at the default adjustment.
static final int SYSTEM_ADJ = -16;

// Special code for native processes that are not being managed by the system (so
// don't have an oom adj assigned by the system).
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static uint32_t lowmem_debug_level = 1;
static int lowmem_adj[6] = {
   0,
   1,
   6,
   12,
};
static int lowmem_adj_size = 4;
static int lowmem_minfree[6] = {
   3 * 512,    /* 6MB */
   2 * 1024,   /* 8MB */
   4 * 1024,   /* 16MB */
   16 * 1024,  /* 64MB */
};
static int lowmem_minfree_size = 4;


 在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
2
# Set init and its forked children's oom_adj.
   write /proc/1/oom_adj -16


关于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
2
3
4
5
6
@Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      startForeground(R.id.notify, new Notification());
      startService(new Intent(this, FakeService.class));
      return super.onStartCommand(intent, flags, startId);
  }


  • FakeService 临时服务


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class FakeService extends Service {	
   @Nullable
   @Override
   public IBinder onBind(Intent intent) {
       return null;
   }

   @Override
   public int onStartCommand(Intent intent, int flags, int startId) {
       startForeground(R.id.notify, new Notification());
       stopSelf();
       return super.onStartCommand(intent, flags, startId);
   }

   @Override
   public void onDestroy() {
       stopForeground(true);
       super.onDestroy();
   }
}


3. 守护进程及时拉起

AlarmReceiver, ConnectReceiver,BootReceiver等

4. 进程互拉

在分析360手机助手app时,发现其拥有N多个进程,一个进程kill后会被其它未kill的进程拉起,这也是一种思路吧,虽然有点流氓~

三、新方法(AccountSyncAdapter)

1. 思路:

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

2. 效果:

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

正常情况

采用AccountSyncAdapter方法后

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

3. 风险:

  • SyncAdapter时间进度不高,往往会因为手机处于休眠状态,而时间往后调整,同步间隔最低为1分钟

  • 用户可以单独停止或者删除,有些手机账号默认是不同步的,需要手动开启

4. 实现:核心代码如下

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

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class XXAccountProvider extends ContentProvider {
   public static final String AUTHORITY = "包名.provider";
   public static final String CONTENT_URI_BASE = "content://" + AUTHORITY;
   public static final String TABLE_NAME = "data";
   public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_BASE + "/" + TABLE_NAME);

   @Override
   public boolean onCreate() {
       return true;
   }

   @Nullable
   @Override
   public Cursor query(Uri uri, String[] projection, String selection,
                       String[] selectionArgs, String sortOrder)
{

       return null;
   }

   @Nullable
   @Override
   public String getType(Uri uri) {
       return new String();
   }

   @Nullable
   @Override
   public Uri insert(Uri uri, ContentValues values) {
       return null;
   }

   @Override
   public int delete(Uri uri, String selection, String[] selectionArgs) {
       return 0;
   }

   @Override
   public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
       return 0;
   }
}


然后再Manifest中声明


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


4.2 建立Sync系统 (SyncAdapter)

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

  • 创建Sync服务


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class XXSyncService extends Service {
   private static final Object sSyncAdapterLock = new Object();
   private static XXSyncAdapter sSyncAdapter = null;
   @Override
   public void onCreate() {
       synchronized (sSyncAdapterLock) {
           if (sSyncAdapter == null) {
               sSyncAdapter = new XXSyncAdapter(getApplicationContext(), true);
           }
       }
   }

   @Override
   public IBinder onBind(Intent intent) {
       return sSyncAdapter.getSyncAdapterBinder();
   }

   static class XXSyncAdapter extends AbstractThreadedSyncAdapter {
       public XXSyncAdapter(Context context, boolean autoInitialize) {
           super(context, autoInitialize);
       }
       @Override
       public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
           getContext().getContentResolver().notifyChange(XXAccountProvider.CONTENT_URI, null, false);
       }
   }
}


  • 声明Sync服务


1
2
3
4
5
6
7
8
9
10
11
12
<service
android:name="**.XXSyncService"
android:exported="true"
android:process=":core">

<intent-filter>
<action
android:name="android.content.SyncAdapter"/>

</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_adapter"/>

</service>


其中sync_adapter为:


1
2
3
4
5
6
7
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
             android:accountType="@string/account_auth_type"
             android:allowParallelSyncs="false"
             android:contentAuthority="@string/account_auth_provide"
             android:isAlwaysSyncable="true"
             android:supportsUploading="false"
             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
2
3
4
5
6
7
8
9
public void triggerRefresh() {
Bundle b = new Bundle();
b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
ContentResolver.requestSync(
account,
CONTENT_AUTHORITY,
b);
}


添加账号


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


同步周期设置


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


4.3 建立账号系统 (Account Authenticator)

通过建立Account账号,并关联SyncAdapter服务实现同步

  • 创建Account服务


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
public class XXAuthService extends Service {
   private XXAuthenticator mAuthenticator;

   @Override
   public void onCreate() {
       mAuthenticator = new XXAuthenticator(this);
   }

   private XXAuthenticator getAuthenticator() {
       if (mAuthenticator == null)
           mAuthenticator = new XXAuthenticator(this);
       return mAuthenticator;
   }

   @Override
   public IBinder onBind(Intent intent) {
       return getAuthenticator().getIBinder();
   }

   class XXAuthenticator extends AbstractAccountAuthenticator {
       private final Context context;
       private AccountManager accountManager;
       public XXAuthenticator(Context context) {
           super(context);
           this.context = context;
           accountManager = AccountManager.get(context);
       }

       @Override
       public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)
               throws NetworkErrorException {

// 添加账号 示例代码
           final Bundle bundle = new Bundle();
           final Intent intent = new Intent(context, AuthActivity.class);
           intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
           bundle.putParcelable(AccountManager.KEY_INTENT, intent);
           return bundle;
       }

       @Override
       public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)
               throws NetworkErrorException {

// 认证 示例代码
           String authToken = accountManager.peekAuthToken(account, getString(R.string.account_token_type));
           //if not, might be expired, register again
           if (TextUtils.isEmpty(authToken)) {
               final String password = accountManager.getPassword(account);
               if (password != null) {
                   //get new token
authToken = account.name + password;
               }
           }
           //without password, need to sign again
           final Bundle bundle = new Bundle();
           if (!TextUtils.isEmpty(authToken)) {
               bundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
               bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
               bundle.putString(AccountManager.KEY_AUTHTOKEN, authToken);
               return bundle;
           }

           //no account data at all, need to do a sign
           final Intent intent = new Intent(context, AuthActivity.class);
           intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
           intent.putExtra(AuthActivity.ARG_ACCOUNT_NAME, account.name);
           bundle.putParcelable(AccountManager.KEY_INTENT, intent);
           return bundle;
       }

       @Override
       public String getAuthTokenLabel(String authTokenType) {
//            throw new UnsupportedOperationException();
           return null;
       }

       @Override
       public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
           return null;
       }

       @Override
       public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)
               throws NetworkErrorException {

           return null;
       }

       @Override
       public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)
               throws NetworkErrorException {

           return null;
       }

       @Override
       public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)
               throws NetworkErrorException {

           return null;
       }
   }
}


  • 声明Account服务


1
2
3
4
5
6
7
8
9
10
11
12
<service
android:name="**.XXAuthService"
android:exported="true"
android:process=":core">

<intent-filter>
<action
android:name="android.accounts.AccountAuthenticator"/>

</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator"/>

</service>


其中authenticator为:


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



 
Android技术之家 更多文章 自动展开标题通知栏,兼容各个版本,欢饮大家关注,并参与讨论。 minSdkVersion、targetSdkVersion、targetApiLevel的区别 ndk调用实战 GreenDao数据库操作 大家来找茬 看看下面这个单例模式有什么不妥的
猜您喜欢 .Net 分布式云平台基础服务建设说明概要 【玩转领英】精华全掌握,领英微信自定义菜单介绍 明晚公开课:需求分析浅谈 WAAPI属性 Android相机开发那些坑