一种提高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 //Adjustmentusedincertainplaceswherewedon'tknowityet. //(Generallythisissomethingthatisgoingtobecached,butwe //don'tknowtheexactvalueinthecachedrangetoassignyet.) staticfinalintUNKNOWN_ADJ=16; //Thisisaprocessonlyhostingactivitiesthatarenotvisible, //soitcanbekilledwithoutanydisruption. staticfinalintCACHED_APP_MAX_ADJ=15; staticfinalintCACHED_APP_MIN_ADJ=9; //TheBlistofSERVICE_ADJ--thesearetheoldanddecrepit //servicesthataren'tasshinyandinterestingastheonesintheAlist. staticfinalintSERVICE_B_ADJ=8; //Thisistheprocessofthepreviousapplicationthattheuserwasin. //Thisprocessiskeptaboveotherthings,becauseitisverycommonto //switchbacktothepreviousapp.Thisisimportantbothforrecent //taskswitch(togglingbetweenthetwotoprecentapps)aswellasnormal //UIflowsuchasclickingonaURIinthee-mailapptoviewinthebrowser, //andthenpressingbacktoreturntoe-mail. staticfinalintPREVIOUS_APP_ADJ=7; //Thisisaprocessholdingthehomeapplication--wewanttotry //avoidingkillingit,evenifitwouldnormallybeinthebackground, //becausetheuserinteractswithitsomuch. staticfinalintHOME_APP_ADJ=6; //Thisisaprocessholdinganapplicationservice--killingitwillnot //havemuchofanimpactasfarastheuserisconcerned. staticfinalintSERVICE_ADJ=5; //Thisisaprocesswithaheavy-weightapplication.Itisinthe //background,butwewanttotrytoavoidkillingit.Valuesetin //system/rootdir/init.rconstartup. staticfinalintHEAVY_WEIGHT_APP_ADJ=4; //Thisisaprocesscurrentlyhostingabackupoperation.Killingit //isnotentirelyfatalbutisgenerallyabadidea. staticfinalintBACKUP_APP_ADJ=3; //Thisisaprocessonlyhostingcomponentsthatareperceptibletothe //user,andwereallywanttoavoidkillingthem,buttheyarenot //immediatelyvisible.Anexampleisbackgroundmusicplayback. staticfinalintPERCEPTIBLE_APP_ADJ=2; //Thisisaprocessonlyhostingactivitiesthatarevisibletothe //user,sowe'dprefertheydon'tdisappear. staticfinalintVISIBLE_APP_ADJ=1; //Thisistheprocessrunningthecurrentforegroundapp.We'dreally //rathernotkillit! staticfinalintFOREGROUND_APP_ADJ=0; //Thisisaprocessthatthesystemorapersistentprocesshasboundto, //andindicateditisimportant. staticfinalintPERSISTENT_SERVICE_ADJ=-11; //Thisisasystempersistentprocess,suchastelephony.Definitely //don'twanttokillit,butdoingsoisnotcompletelyfatal. staticfinalintPERSISTENT_PROC_ADJ=-12; //Thesystemprocessrunsatthedefaultadjustment. staticfinalintSYSTEM_ADJ=-16; //Specialcodefornativeprocessesthatarenotbeingmanagedbythesystem(so //don'thaveanoomadjassignedbythesystem). staticfinalintNATIVE_ADJ=-17; Android Low Memory Killer Android系统内存不足时,系统会杀掉一部分进程以释放空间,谁生谁死的这个生死大权就是由LMK所决定的,这就是Android系统中的Low Memory Killer,其基于Linux的OOM机制,其阈值定义如下面所示的lowmemorykiller文件中,当然也可以通过系统的init.rc实现自定义。 lowmemorykiller.c staticuint32_tlowmem_debug_level=1; staticintlowmem_adj[6]={ 0, 1, 6, 12, }; staticintlowmem_adj_size=4; staticintlowmem_minfree[6]={ 3*512,/*6MB*/ 2*1024,/*8MB*/ 4*1024,/*16MB*/ 16*1024,/*64MB*/ }; staticintlowmem_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 #Setinitanditsforkedchildren'soom_adj. write/proc/1/oom_adj-16 关于Low Memory Killer的具体实现原理可参考Ref-2. 查看某个App的进程 步骤(手机与PC连接) adb shell ps | grep 进程名 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 常驻内存服务 @Override publicintonStartCommand(Intentintent,intflags,intstartId){ startForeground(R.id.notify,newNotification()); startService(newIntent(this,FakeService.class)); returnsuper.onStartCommand(intent,flags,startId); } FakeService 临时服务 publicclassFakeServiceextendsService{ @Nullable @Override publicIBinderonBind(Intentintent){ returnnull; } @Override publicintonStartCommand(Intentintent,intflags,intstartId){ startForeground(R.id.notify,newNotification()); stopSelf(); returnsuper.onStartCommand(intent,flags,startId); } @Override publicvoidonDestroy(){ stopForeground(true); super.onDestroy(); } } 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(优先级仅次于前台进程),能提高进程优先级,对比如下图 正常情况 采用AccountSyncAdapter方法后 进程被系统kill后,可以由syn拉起 风险 SyncAdapter时间进度不高,往往会因为手机处于休眠状态,而时间往后调整,同步间隔最低为1分钟 用户可以单独停止或者删除,有些手机账号默认是不同步的,需要手动开启 实现 (核心代码) 1 建立数据同步系统(ContentProvider) 通过一个ContentProvider用来作数据同步,由于并没有实际数据同步,所以此处就直接建立一个空的ContentProvider即可 publicclassXXAccountProviderextendsContentProvider{ publicstaticfinalStringAUTHORITY="包名.provider"; publicstaticfinalStringCONTENT_URI_BASE="content://"+AUTHORITY; publicstaticfinalStringTABLE_NAME="data"; publicstaticfinalUriCONTENT_URI=Uri.parse(CONTENT_URI_BASE+"/"+TABLE_NAME); @Override publicbooleanonCreate(){ returntrue; } @Nullable @Override publicCursorquery(Uriuri,String[]projection,Stringselection, String[]selectionArgs,StringsortOrder){ returnnull; } @Nullable @Override publicStringgetType(Uriuri){ returnnewString(); } @Nullable @Override publicUriinsert(Uriuri,ContentValuesvalues){ returnnull; } @Override publicintdelete(Uriuri,Stringselection,String[]selectionArgs){ return0; } @Override publicintupdate(Uriuri,ContentValuesvalues,Stringselection,String[]selectionArgs){ return0; } } 然后再Manifest中声明 <provider android:name="**.XXAccountProvider" android:authorities="@string/account_auth_provider" android:exported="false" android:syncable="true"/> 2 建立Sync系统 (SyncAdapter) 通过实现SyncAdapter这个系统服务后, 利用系统的定时器对程序数据ContentProvider进行更新,具体步骤为: 创建Sync服务 publicclassXXSyncServiceextendsService{ privatestaticfinalObjectsSyncAdapterLock=newObject(); privatestaticXXSyncAdaptersSyncAdapter=null; @Override publicvoidonCreate(){ synchronized(sSyncAdapterLock){ if(sSyncAdapter==null){ sSyncAdapter=newXXSyncAdapter(getApplicationContext(),true); } } } @Override publicIBinderonBind(Intentintent){ returnsSyncAdapter.getSyncAdapterBinder(); } staticclassXXSyncAdapterextendsAbstractThreadedSyncAdapter{ publicXXSyncAdapter(Contextcontext,booleanautoInitialize){ super(context,autoInitialize); } @Override publicvoidonPerformSync(Accountaccount,Bundleextras,Stringauthority,ContentProviderClientprovider,SyncResultsyncResult){ getContext().getContentResolver().notifyChange(XXAccountProvider.CONTENT_URI,null,false); } } } 声明Sync服务 <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为: <sync-adapterxmlns: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实现 手动更新 publicvoidtriggerRefresh(){ Bundleb=newBundle(); b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL,true); b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED,true); ContentResolver.requestSync( account, CONTENT_AUTHORITY, b); } 添加账号 Accountaccount=AccountService.GetAccount(); AccountManageraccountManager=(AccountManager)context.getSystemService(Context.ACCOUNT_SERVICE); accountManager.addAccountExplicitly(...) 同步周期设置 ContentResolver.setIsSyncable(account,CONTENT_AUTHORITY,1); ContentResolver.setSyncAutomatically(account,CONTENT_AUTHORITY,true); ContentResolver.addPeriodicSync(account,CONTENT_AUTHORITY,newBundle(),SYNC_FREQUENCY); 3 建立账号系统 (Account Authenticator) 通过建立Account账号,并关联SyncAdapter服务实现同步 接下文 本文作者:佚名 来源:51CTO