说到进程保活,忍不住吐槽一下市场上除了微信这样的白名单大佬,其他的应用很难在后台保持长链接,保活只能是使用一些歪门邪道来延长进程的持续时间。总的来说也就这两种方法:
(1)提供进程优先级,降低进程被杀死的概率;(2)在进程被杀死后,进行拉活.
今天就先来说降低被杀死的概率方法:一像素Activity,双进程守护Service。
1像素Activity
1像素Activity的特点: 需要设置该activity的style设置透明,在手机锁屏时start;在屏幕解锁时finish,主要作用就是在App退到后台之后且锁屏的时候启动一个看不见的Activity,造成一个该App没有回退到后台的假象,从而降低被杀的几率。
代码:
在onCreate方法里设定一像素的activity
window.setGravity(Gravity.START | Gravity.TOP);
WindowManager.LayoutParams params = window.getAttributes();
params.x = 0;
params.y = 0;
params.height = 1;
params.width = 1;
window.setAttributes(params);
在onReceive方法里进行操作
public void onReceive(final Context context, Intent intent) {
Log.i("ScreenStateReceiver", "---屏幕锁屏监听---");
if (action.equals(Intent.ACTION_SCREEN_OFF)) {
//屏幕锁定,开启OnePixelActivity
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
//屏幕解锁,finish OnePixelActivity
}
}
说一下详细步骤
1.需要注册一个监听屏幕锁屏和解屏的BroadcastReceiver
关于这一点有一个细节需要处理,为了放置用户快速进行锁屏和解屏的切换操作,而导致OnePixelActivity频繁开关。需要用一个Handler发送一个延迟消息处理最佳:
private Handler mHandler;
private boolean isScreenOn = true;
private PendingIntent pendingIntent;
private List screenStateListeners = null;
@Override
public void onReceive(final Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_SCREEN_OFF)) {
//标记屏幕为锁屏状态
isScreenOn = false;
//开启一像素的Activity
startOnePixelActivity(context);
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
//标记屏幕为解屏状态
isScreenOn = true;
if(pendingIntent!=null){
pendingIntent.cancel();
}
}
}
//开启一像素的Activity
private void startOnePixelActivity(final Context context){
if(mHandler==null){
mHandler = new Handler(Looper.myLooper());
}
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
//如果屏幕此时已经打开,则不执行
if(isScreenOn){
return;
}
if(pendingIntent!=null){
pendingIntent.cancel();
}
Intent startOnePixelActivity = new Intent(context, OnePixelActivity.class);
startOnePixelActivity.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
//启动一像素包活activity
pendingIntent = PendingIntent.getActivity(context, 0, startOnePixelActivity, 0);
try {
pendingIntent.send();
} catch (Exception e) {
e.printStackTrace();
}
notifyScreenOff();
}
},1000);
}
2、需要将该activity的 android:excludeFromRecents设置为true
3、需要将该Activity设置为singleInstance启动模式
为什么需要将此Activity的启动模式设置为singleInstance呢?原因是因为如果设置成其他模式,如果按照如下步骤操作的话会出现不友好的状况:
1、启动App(比如进入MainActivity),按home键让app返回后台
2、锁定屏幕,此时注册好的监听广播会启动OnePixelActivity
3、解锁屏幕,此时广播接受到此广播后会finish 掉OnePixelActivity.
也就是说,当你的app在后台的时候,你解锁屏幕,创建销毁OnePixelActvity的同时会把整个栈的activity弹出来,然会用户就会莫名其妙的看到你的app自己打开了。
另外需要注意的是,当屏幕解锁的时候,OnePixelActivity的onResume得到执行,所以在该Activity的onResume方法执行finish效果最好:
//OnePixelActivity的onResume
protected void onResume() {
super.onResume();
if (DeviceUtils.isScreenOn(this)) {//如果屏幕已经打开
finish();
}
}
双进程守护
所谓双进程守护原理就是很简单:两个进程的Service,相互守护;当一个进程的Service挂的时候,另一个进程的Service负责重启挂掉的Service.因为系统的进程回收机制是一个个回收的,利用这个时间差来相互唤起,当一个进程被磨灭掉,另一个马上重启,缺点是现在大部分机型只要一键清理就玩完了。文中KeepAliveService是跟我们要保活的App处于一个进程的Service,RemoteService是另外一个进程的Service;将RemoteService设置成单独的进程很方便,在AndroidMainfest.xml里面配置 android:process即可。
//将RemoteService和KeepAliveServcie处于不同的进程
守护进程其实是一个双向守护的过程,比如KeepAliveService挂了,那么RemoteService负责将KeepAliveService重启;同理,如果RemoteService挂了的话,KeepAliveService负责将RemoteService重启!那么二者是怎么知道彼此是否挂了呢?因为是位于两个进程,所以我们可以通过AIDL来确保两个不同进程的Service可以通信。
定义一个AIDL接口
interface GuardAidl {
void notifyAlive();
}
接着我们重写KeepAliveSerivice的onBind方法,该方法返回一个IBinder给RemoteService使用:
//将keepAliveBinder 交给RemoteService
public IBinder onBind(Intent intent) {
return keepAliveBinder;
}
private GuardAidl.Stub keepAliveBinder = new GuardAidl.Stub(){
@Override
public void notifyAlive() throws RemoteException {
Log.i(null,"Hello RemoteService!");
}
};
通过KeepAliveService的onBind方法返回一个IBinder对象交给RemoteService使用,这样RemoteService就可以通过keepAliveBinder对象跟KeepAliveServcie进行通信!同样的RemoteService也是一样的代码逻辑:
public class RemoteService extends Service {
public IBinder onBind(Intent intent) {
return remoteBinder;
}
private GuardAidl.Stub remoteBinder = new GuardAidl.Stub(){
@Override
public void notifyAlive() throws RemoteException {
Log.i(null,"Hello KeepAliveService!");
}
};
}
两个Servcie相互绑定
现在两个Service都重写onBind返回了自己的IBinder,但是怎么将自己创建的IBinder 交给对象使用呢?答案是双方调用bindSerice方法相互绑定对方,该方法是Service的一个方法,其签名如下:
bindService(Intent service, ServiceConnection conn, int flags)
所以我们绑定的时候除了传一个intent之外,还要传一个ServiceConnection。看看KeepAliveService绑定RemoteService的代码:
//KeepAliveService的onStartCommand方法
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("KeepAliveService", "---KeepAliveService 启动---");
//注册锁屏广播
registerScreenStateReceiver();
//初始化播放器
initMediaPlayer();
//开启前台Service
startForeground(this);
//start HideNotifactionService
startHideNotificationService();
//绑定守护进程
bindRemoteService();
return START_STICKY;
}
//绑定守护进程
private void bindRemoteService(){
Intent intent = new Intent(this,RemoteService.class);
bindService(intent,connection,Context.BIND_ABOVE_CLIENT);
}
private ServiceConnection connection = new ServiceConnection() {
//调用此方法说明RemoteServcie已经挂掉,需要重新启动
public void onServiceDisconnected(ComponentName name) {
Intent remoteService = new Intent(KeepAliveService.this,
RemoteService.class);
KeepAliveService.this.startService(remoteService);
Intent intent = new Intent(KeepAliveService.this, RemoteService.class);
//将KeepAliveService和RemoteService进行绑定
KeepAliveService.this.bindService(intent, connection,
Context.BIND_ABOVE_CLIENT);
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
//与RemoteService绑定成功
GuardAidl remoteBinder = GuardAidl.Stub.asInterface(service);
remoteBinder.notifyAlive();
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
其中ServiceConnection有两个方法onServiceDisconnected和onServiceConnected,当RemoteService被异常销毁挂掉的时候,onServiceDisconnected会被调用。此时需要在该方法里面重新启动RemoteService;而onServiceConnected方法则是系统调用它来传送在RemoteService的onBind()中返回的IBinder!同理,RemoteService绑定KeepAlivce的代码跟上面雷同,再此就不在贴出来了,
源码可以点击此处查阅