1. 概述
根据前辈们的经验,如果没有白名单,Android系统要做一个任何情况下都不被杀死的应用几乎是不可能的,但我们可以做一个最大程度不被杀死,如果被杀死可以立即让它在第一时间内复活,那也是ok的。网上的进程常驻也是众说纷纭,这篇文章就总结下Android中进程保活的一些可行性方法,当然这些东西也不是我发明的,我只是在网上查询之后将其写成自己的文章,然后分享出来。
2. 白名单概念?
首先说下什么是白名单?白名单就是把我们自己的应用直接内置到一些手机厂商的手机中,让其手机厂商自带该应用,即就是该app软件
3. 问题?
3.1>:系统为什么会杀死进程?
3.2>:为什么会杀死我们的进程?
3.3>:根据什么规则决定?
3.4>:是一次杀掉多个,还是一个一个杀死?
那么带着这几个问题,我们就继续往下边来看。
4. 分析
4.1>:进程的划分,如下图所示
分析上图可以得知,进程被分为5种,按照优先级从高到低:
1>:活动进程
优先级最高,指的是用户正在操作的程序,是前台进程,并且可以操作的;
2>:可见进程
次高优先级,指的是看的见,摸不着,不能直接操作的进程;
3>:服务进程
第三优先级,没有界面,一直运行在后台,优先级不高,当系统内存不足的时会被杀死,当内存再次充裕时会重新再次开启;
4>:后台进程
低优先级,用户按下home或者back键后,程序本身看不到了,但是其实还是在运行的程序;比如Activity调用 onPause()方法,系统可能随时终止它们,回收内存;
5>:空进程
优先级最低,某个进程不包含任何活跃的组件,该进程就会被置为空进程,完全没用,系统会第一个回收空进程。
4.2>:内存阈值
app在返回到后台时,系统并不会kill这个进程的,而是将其缓存起来。打开的进程越多,后台缓存的进程就越多,系统在内存不足时,会根据自身的一套进程回收机制来判断需要kill掉哪些进程,来腾出内存给有需要的app,这套杀死进程回收内存的机制就叫做 Low Memory Killer。怎样去规定内存不足,就是内存阈值,可以使用 cat /sys/module/lowmemorykiller/parameters/minfree 来查看手机的内存阈值;
可以看出上边的6个数字,这些数字的单位就是 page。 1page=4kb,上边的6个数字对应的就是 MB,及对应的是 72,90,108,126,144,180,这些数字就对应的是内存阈值,内存阈值在不同手机上不一样,一旦低于该值,Android便开始顺序关闭进程,也就是说结束优先级最低的进程,即就是最小可用内存小于 180MB (46080*4/1024)。
注意:
进程是有它的优先级,该优先级是根据进程的adj值来反映,它是 Linux内核分配给每个进程的一个值,进程回收机制是根据优先级决定的。
oom_adj越大,占用的物理内存越大,会被最先kill掉,也就是说现在可以 把 进程如何保活变成 如何减小 oom_adj的值,来使应用内存占用最小。
5. 进程保活使用方法
5.1>:开启一个像素的Activity
思想就是:系统一般不会杀死前台进程,所以要使进程常驻,只需要:
打开屏幕的时候关闭 一个像素的Activity;
锁屏的时候开启一个 一个像素的Activity,透明并且没有动画;
做法就是 只需要监听系统的锁屏广播即可,代码如下:
1>:一个像素的Activity:
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 |
/** * Email: 2185134304@qq.com * Created by JackChen 2018/4/13 13:57 * Version 1.0 * Params: * Description: 开启一个像素的Activity */ public class SinglePixelActivity extends Activity{ public static final String TAG = SinglePixelActivity.class.getSimpleName(); public static void actionToSinglePixelActivity(Context pContext){ Intent intent = new Intent(pContext, SinglePixelActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); pContext.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); Log.d(TAG, "onCreate"); setContentView(R.layout.activity_singlepixel); Window window = getWindow(); //放在左上角 window.setGravity(Gravity.START | Gravity.TOP); WindowManager.LayoutParams attributes = window.getAttributes(); //宽高设计为1个像素 attributes.width = 1; attributes.height = 1; //起始坐标 attributes.x = 0; attributes.y = 0; window.setAttributes(attributes); ScreenManager.getInstance(this).setActivity(this); } @Override protected void onDestroy(){ super.onDestroy(); } } |
2>:在屏幕关闭的时候启动 一个像素的SinglePixelActivity ,打开屏幕的时候 finish掉 SinglePixelActivity ,所以需要监听系统锁屏的广播,使用接口形式通知 MainActivity 来 打开或者关闭 SinglePixelActivity ,该监听器代码如下:
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 |
/** * Email: 2185134304@qq.com * Created by JackChen 2018/4/13 13:58 * Version 1.0 * Params: * Description: 监听 系统锁屏的广播监听器 */ public class ScreenBroadcastListener{ private Context mContext; private ScreenBroadcastReceiver mScreenReceiver; private ScreenStateListener mListener; public ScreenBroadcastListener(Context context){ mContext = context.getApplicationContext(); mScreenReceiver = new ScreenBroadcastReceiver(); } interface ScreenStateListener{ void onScreenOn(); // 屏幕打开 void onScreenOff(); // 锁屏 } /** * screen状态广播接收者 */ private class ScreenBroadcastReceiver extends BroadcastReceiver{ private String action = null; @Override public void onReceive(Context context, Intent intent){ action = intent.getAction(); if (Intent.ACTION_SCREEN_ON.equals(action)) { // 开屏 mListener.onScreenOn(); } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // 锁屏 mListener.onScreenOff(); } } } public void registerListener(ScreenStateListener listener){ mListener = listener; registerListener(); } private void registerListener(){ IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); mContext.registerReceiver(mScreenReceiver, filter); } } |
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 |
/** * Email: 2185134304@qq.com * Created by JackChen 2018/4/13 13:57 * Version 1.0 * Params: * Description: screen屏幕管理者 */ public class ScreenManager{ private Context mContext; private WeakReference<Activity> mActivityWref; public static ScreenManager gDefualt; public static ScreenManager getInstance(Context pContext){ if (gDefualt == null) { gDefualt = new ScreenManager(pContext.getApplicationContext()); } return gDefualt; } private ScreenManager(Context pContext){ this.mContext = pContext; } public void setActivity(Activity pActivity){ mActivityWref = new WeakReference<Activity>(pActivity); } public void startActivity(){ // 开启一个像素的SinglePixelActivity SinglePixelActivity.actionToSinglePixelActivity(mContext); } public void finishActivity(){ //结束掉SinglePixelActivity if (mActivityWref != null) { Activity activity = mActivityWref.get(); if (activity != null) { activity.finish(); } } } } |
3>:在MainActivity中测试:
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 MainActivity extends AppCompatActivity{ @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 屏幕管理者的单例 final ScreenManager screenManager = ScreenManager.getInstance(MainActivity.this); // 屏幕广播监听器 ScreenBroadcastListener listener = new ScreenBroadcastListener(this); listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() { @Override public void onScreenOn(){ // 屏幕打开时候,关闭一个像素的Activity screenManager.finishActivity(); } @Override public void onScreenOff(){ // 屏幕锁屏的时候,开启一个像素的SinglePixelActivity screenManager.startActivity(); } }); } } |
经过测试,按下back返回键之后,然后锁屏,测试一下 oom_adj的值,发现进程的优先级提高了。
由于需要考虑内存的问题,因为内存占用越多的进程会被最先杀掉,所以需要把上边的 MainActivity中的业务逻辑放在Service中,然后直接在 MainActivity 中开启这个服务就ok,这样进程会更加轻量。
4>:LiveService代码如下:
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 |
/** * Email: 2185134304@qq.com * Created by JackChen 2018/4/13 15:10 * Version 1.0 * Params: * Description: 把MainActivity的业务逻辑放到Service中,使得进程更加轻量 */ public class LiveService extends Service{ public static void toLiveService(Context pContext){ Intent intent=new Intent(pContext,LiveService.class); pContext.startService(intent); } @Nullable @Override public IBinder onBind(Intent intent){ return null; } @Override public int onStartCommand(Intent intent, int flags, int startId){ // 屏幕管理者的单例 final ScreenManager screenManager = ScreenManager.getInstance(this); // 屏幕广播监听器 ScreenBroadcastListener listener = new ScreenBroadcastListener(this); listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() { @Override public void onScreenOn(){ // 屏幕打开时候,关闭一个像素的Activity screenManager.finishActivity(); } @Override public void onScreenOff(){ // 屏幕锁屏的时候,开启一个像素的SinglePixelActivity screenManager.startActivity(); } }); return START_REDELIVER_INTENT; } } |
5>:修改后的MainActivity代码如下:
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 |
public class MainActivity extends AppCompatActivity{ @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // // 屏幕管理者的单例 // final ScreenManager screenManager = ScreenManager.getInstance(MainActivity.this); // // 屏幕广播监听器 // ScreenBroadcastListener listener = new ScreenBroadcastListener(this); // listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() { // @Override // public void onScreenOn() { // // 屏幕打开时候,关闭一个像素的Activity // screenManager.finishActivity(); // } // // @Override // public void onScreenOff() { // // 屏幕锁屏的时候,开启一个像素的SinglePixelActivity // screenManager.startActivity(); // } // }); // 不用上边的写法,把上边的逻辑放到 Service中,直接从 MainActivity中开启Service即可 LiveService.toLiveService(this) ; } } |
5.2>:前台服务
微信的进程保活之前也是采用这种方式,其实就是利用 前台Service的漏洞
原理如下:
对于 API level < 18 ,调用 startForeground(ID, new Notification()),发送的是空的 Nofication,图标则不会显示;
对于 API level >= 18 ,在优先级的 KeepLiveService中启动 一个 InnerService,让两个服务同时启动 startFroeground(),且绑定同样的 Id,然后再 停掉 InnerService即可,代码如下:
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 |
/** * Email: 2185134304@qq.com * Created by JackChen 2018/4/13 15:36 * Version 1.0 * Params: * Description: 方法2:前台进程 */ public class KeepLiveService extends Service{ public static final int NOTIFICATION_ID=0x11; public KeepLiveService(){ } @Override public IBinder onBind(Intent intent){ throw new UnsupportedOperationException("Not yet implemented"); } @Override public void onCreate(){ super.onCreate(); //API 18以下,直接发送Notification并将其置为前台 if (Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN_MR2) { startForeground(NOTIFICATION_ID, new Notification()); } else { //API 18以上,发送Notification并将其置为前台后,启动InnerService Notification.Builder builder = new Notification.Builder(this); builder.setSmallIcon(R.mipmap.ic_launcher); startForeground(NOTIFICATION_ID, builder.build()); startService(new Intent(this, InnerService.class)); } } public class InnerService extends Service{ @Override public IBinder onBind(Intent intent){ return null; } @Override public void onCreate(){ super.onCreate(); //发送与KeepLiveService中ID相同的Notification,然后将其取消并取消自己的前台显示 Notification.Builder builder = new Notification.Builder(this); builder.setSmallIcon(R.mipmap.ic_launcher); startForeground(NOTIFICATION_ID, builder.build()); new Handler().postDelayed(new Runnable() { @Override public void run(){ stopForeground(true); NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); manager.cancel(NOTIFICATION_ID); stopSelf(); } },100); } } } |
5.3>:相互唤醒
意思就是如果你手机里边安装了 淘宝、天猫、支付宝等阿里系的 app,那么你打开任意一个 阿里的app之后,有可能就把阿里系的其他 app就给唤醒了。
所以说可以让 QQ、微信、淘宝、天猫、支付宝等app可以保活自己的应用也可以,或者像友盟、信鸽这种 推送的SDK,也有唤醒app的功能。
5.4>:JobScheduler
是一种进程死后复活的一种手段,native进程缺点是费电,原因是感知主进程是否存活有两种方式。在Native进程中通过死循环或者定时器,轮训判断主进程是否存活,如果不存活就拉活,5.0以上不支持。但是JobScheduler可以代替5.0以上 Native进程的方式。这种方式即使用户强制关闭,也能被拉起来,代码如下:
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 |
/** * Email: 2185134304@qq.com * Created by JackChen 2018/4/14 8:34 * Version 1.0 * Params: * Description: 方法4:JobScheduler */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) //API 21 5.0 public class MyJobService extends JobService{ @Override public void onCreate(){ super.onCreate(); startJobSheduler(); } public void startJobSheduler(){ try { JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(getPackageName(), MyJobService.class.getName())); builder.setPeriodic(5); builder.setPersisted(true); JobScheduler jobScheduler = (JobScheduler) this.getSystemService(Context.JOB_SCHEDULER_SERVICE); jobScheduler.schedule(builder.build()); } catch (Exception ex) { ex.printStackTrace(); } } @Override public boolean onStartJob(JobParameters jobParameters){ return false; } @Override public boolean onStopJob(JobParameters jobParameters){ return false; } } |
5.5>:粘性服务与系统捆绑服务
捆绑服务很好理解,比如NotificationListenerService,用来监听手机通知的,只要手机收到了通知,NotificationListenerService都能监听到,即使用户把进程杀死,也能重启,所以我们可以把这个进程放到我们的服务中,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/** * Email: 2185134304@qq.com * Created by JackChen 2018/4/14 8:50 * Version 1.0 * Params: * Description: 方法5:粘性服务与捆绑进程 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public class NotificationLiveService extends NotificationListenerService{ public NotificationLiveService(){ } @Override public void onNotificationPosted(StatusBarNotification sbn){ } @Override public void onNotificationRemoved(StatusBarNotification sbn){ } } |
需要在清单文件中配置
1 2 3 4 5 6 7 8 |
<service android:name=".NotificationLiveService" android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> <intent-filter> <action android:name="android.service.notification.NotificationListenerService" /> </intent-filter> </service> |
代码已上传至github:
https://github.com/shuai999/ProcessDemo.git