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:
/**
* Email: [email protected]
* 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 ,该监听器代码如下:
/**
* Email: [email protected]
* 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);
}
}
/**
* Email: [email protected]
* Created by JackChen 2018/4/13 13:57
* Version 1.0
* Params:
* Description: screen屏幕管理者
*/
public class ScreenManager {
private Context mContext;
private WeakReference 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(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中测试:
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代码如下:
/**
* Email: [email protected]
* 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代码如下:
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即可,代码如下:
/**
* Email: [email protected]
* 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
5.3>:相互唤醒
意思就是如果你手机里边安装了 淘宝、天猫、支付宝等阿里系的 app,那么你打开任意一个 阿里的app之后,有可能就把阿里系的其他 app就给唤醒了。
所以说可以让 QQ、微信、淘宝、天猫、支付宝等app可以保活自己的应用也可以,或者像友盟、信鸽这种 推送的SDK,也有唤醒app的功能。
5.4>:JobScheduler
是一种进程死后复活的一种手段,native进程缺点是费电,原因是感知主进程是否存活有两种方式。在Native进程中通过死循环或者定时器,轮训判断主进程是否存活,如果不存活就拉活,5.0以上不支持。但是JobScheduler可以代替5.0以上 Native进程的方式。这种方式即使用户强制关闭,也能被拉起来,代码如下:
/**
* Email: [email protected]
* 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都能监听到,即使用户把进程杀死,也能重启,所以我们可以把这个进程放到我们的服务中,代码如下:
/**
* Email: [email protected]
* 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) {
}
}
需要在清单文件中配置
代码已上传至github:
https://github.com/shuai999/ProcessDemo.git