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

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

基础知识

Android 进程优先级

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

  • Activte process
  • Visible Process
  • Service process
  • Background process
  • Empty process

2 Service技巧

  • onStartCommand返回START_STICKY
  • onDestroy中startself
  • Service后台变前置,setForground(true)
  • android:persistent = “true”

3 进程优先级号

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. // userand 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;  

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;  

① 在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  

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

查看某个App的进程

步骤(手机与PC连接)

  1. adb shell
  2. ps | grep 进程名
  3. cat /proc/pid/oom_adj //其中pid是上述grep得到的进程号 

Linux AM命令

am命令:在Android系统中通过adb shell 启动某个Activity、Service、拨打电话、启动浏览器等操作Android的命令.其源码在Am.java中,在shell环境下执行am命令实际是启动一个线程执行Am.java中的主函数(main方法),am命令后跟的参数都会当做运行时参数传递到主函数中,主要实现在Am.java的run方法中。

拨打电话

命令:am start -a android.intent.action.CALL -d tel:电话号码

示例:am start -a android.intent.action.CALL -d tel:10086

打开一个网页

命令:am start -a android.intent.action.VIEW -d 网址

示例:am start -a android.intent.action.VIEW -d http://www.skyseraph.com

启动一个服务

命令:am startservice <服务名称>

示例:am startservice -n com.android.music/ com.android.music.MediaPlaybackService

NotificationListenerService

“A service that receives calls from the system when new notifications are posted or removed, or their ranking changed.” From Google

用来监听到通知的发送以及移除和排名位置变化,如果我们注册了这个服务,当系统任何一条通知到来或者被移除掉,我们都能通过这个service来监听到,甚至可以做一些管理工作。

Android账号和同步机制

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

Android多进程

  • 实现:android:process
  • 好处:一个独立的进程可以充分利用自己的RAM预算,使其主进程拥有更多的空间处理资源。此外,操作系统对待运行在不同组件中的进程是不一样的。这意味着,当系统运行在低可用内存的条件时,并不是所有的进程都会被杀死
  • 大坑:每一个进程将有自己的Dalvik VM实例,意味着你不能通过这些实例共享数据,至少不是传统意义上的。例如,静态字段在每个进程都有自己的值,而不是你倾向于相信的只有一个值。
  • 更多详细请参考Ref 9

现有方法

网络连接保活方法

A. GCM

B. 公共的第三方push通道(信鸽等)

C. 自身跟服务器通过轮询,或者长连接

具体实现请参考 微信架构师杨干荣的”微信Android客户端后台保活经验分享” (Ref-1).

双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.  

Service及时拉起

AlarmReceiver, ConnectReceiver,BootReceiver等

  • Service设置(见上面基础部分)
  • 通过监听系统广播,如开机,锁屏,亮屏等重新启动服务
  • 通过alarm定时器,启动服务

守护进程/进程互拉

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

守护进程一般有这样两种方式:

  • 多个java进程守护互拉
  • 底层C守护进程拉起App上层/java进程

Linux Am命令开启后台进程

一种底层实现让进程不被杀死的方法,在Android4.4以上可能有兼容性问题,具体参考Ref-7

NotificationListenerService通知

一种需要用户允许特定权限的系统拉起方式,4.3以上系统

前台浮窗

有朋友提出一种应用退出后启动一个不可交互的浮窗,个人觉得这种方法是无效的,读者有兴趣可以一试

新方法(AccountSync)

思路

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

效果

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

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

正常情况

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

采用AccountSyncAdapter方法后

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

风险

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

实现 (核心代码)

1 建立数据同步系统(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"/>  

2 建立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, nullfalse); 
  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);  

3 建立账号系统 (Account Authenticator)

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

接下文







本文作者:佚名
来源:51CTO
上一篇:linux进程管理笔记


下一篇:Servlet第一篇【介绍Servlet、HTTP协议、WEB目录结构、编写入门Servlet程序、Servlet生命周期】(上)