探讨一种新型的双进程守护应用保活方法
(转载请声明出处:http://blog.csdn.net/andrexpert/article/details/53485360)
APP保活系列(最高支持到Android 7.0):
(1) 探讨一种新型的双进程守护应用保活方法
(2) 探讨Android6.0及以上系统APP常驻内存(保活)实现-争宠篇
(3) 探讨Android6.0及以上系统APP常驻内存(保活)实现-复活篇
情景再现:“在高版本Android系统中,应用能否常驻内存,我想一直以来都是某些APP头疼的事情。虽然APP常驻内存对于用户来说比较”恶心”,但是在一些特殊情况来说,APP的常驻内存却有尤其重要,很多时候用户也会要求APP能够保证长久运行。因此,这里只研究APP应用在一些特殊场合的保活方法,内容仅供参考。”
1. 浅析常见应用保活方法
(1) 监听广播方式
通过监听一些全局的静态广播,比如开机广播、解锁屏广播、网络状态广播等,来启动应用的后台服务。目前,在高版本的Android系统中已经失效,因为高版本的Android系统规定应用必须在系统开机后运行一次才能监听这些系统广播,一般而言,应用被系统杀死后,基本无法接收系统广播。
(2) 提高Service的优先级
以前提高Service优先级方法很多,比如onStartCommand返回START_STICKY使系统内存足够的时候Service能够自动启动、弹出通知、配置service的优先级等,这些方式只能在一定程度上缓解service被立马回收,但只要用户一键清理或者系统回收照样无效。
(3) 全局定时器
还有一种方法就是在设置一种全局定时器,定时检测启动后台服务,但这种方法目前也已经无效,因为应用只要被系统杀死,全局定时器最后也只成了摆设。
(4) 应用中的双service拉起
经过测试,只要当前应用被杀,任何后台service都无法运行,也无法自行启动。
(5) 应用中的双进程拉起
这种方式就是传说中的使用NDK在底层fork出一个子进程,来实现与父进程之间的互拉。在Android4.x还是非常有效的,但是高版本的Android系统的系统回收策略已经改成进程组的形式了,如果系统要回收一个应用,必然会杀死同属于一个进程组的所有进程,因此最后导致双进程无法拉起。
2、 新型双进程守护应用保活原理
(1) Service特征剖析
Service的特征类似于Activity,其区别是它没有交互界面,且可长时间运行在后台,即使它所属的应用已经退出,Service仍然可以继续在后台运行。Service无法自行启动,访问者启动它的方式分为两种,即startService(绑定式)和bindService (非绑定式),相关介绍如下:
* startService:即非绑定式。访问者使用这种方式启动service后,被启动的service将不受访问者控制,也无法与访问者进行数据通信,它会无限地运行下去,必须调用stopSelf()方法或者其他组件(包括访问者)调用stopService()方法来停止。它的生命周期:onCreate->onStartCommand()->……>onDestory(),其中,onCreate用于初始化工作,多次调用startService启动同一个服务,onCreate方法只会被调用一次,onStartCommand会被调用多次,onDestory在销毁时也只会被调用一次。
* bindService:即绑定式。访问者使用这种方式启动service后,被启动的service受访问者的控制,访问者将通过一个IBinder接口的对象与被绑定的service进行通信,并且可以通过unbindService()方法随时关闭service。一个service可以同时被多个访问者绑定,只有当多个访问者都主动解除绑定关系之后,系统才会销毁service。它的生命周期:onCreate->onBind->....>onUnbind->onDestory,其中,onBind用于返回一个通信对象(IBinder)给访问者,访问者可以通过该IBinder对象调用service的相关方法。当多个访问者绑定同一个service时,onCreate只会被调用一次,onBind、unOnbind方法会被调用与访问者数目相关的次数,onDestory在销毁时只会被调用一次。
有一点需要注意的是:如果一个service被startService启动,然后其他组件再执行bindService绑定该service,service的onCreate不会再被回调,而是直接回调onBind方法;当该组件unBindService时,service的onUnbind方法被回调,但是service不会被销毁,直到自己调用stopSelf方法或者其他组件调用stopService方法时才会被销毁。
(1) AIDL和远程Service调用
bindService绑定方式启动service分为两种:本地service和远程service。本地service是指绑定启动的service实现在它所属的应用进程中,其他组件访问者与本地service之间的通信是通过IBinder接口对象实现的;远程service是指绑定启动的service实现在其他应用进程中,也就是另一个APP中,他们之间的通信则是通过IBinder接口的代理对象实现的,而这个代理对象必须通过AIDL方式来构造。
AIDL(Android Interface DefinitionLanguage,接口描述语言)使用用于约束两个进程之间通讯的规则,可以实现Android终端上两个不同应用(进程)之间的通信(IPC)。AIDL实现的步骤非常简单,阐述如下:
a) 在A应用创建IPerson.aidl接口文件(注意AIDL语言的编写规则,这里不详解);
b) 将创建的IPerson.aidl接口文件拷贝到B应用中;
packagecom.person.aidl
interfaceIPerson{
String getName();
int getAge();
}
然后,我们只需要保存.aidl文件,编译器就会自动生成所需的IPerson.java文件,保存在gen目录下,该文件包含一个内部类IPerson.Stub,它实际上继承于Binder对象,即充当通信所需的IBinder代理对象。这里有几点需要注意,将会影响两进程之间是否能够通讯成功:
※ 接口名与aidl文件名相同
※ 应用A、应用B中的.aidl文件必须相同,并且它们所属的包名也必须要一样;
(2) 单进程守护
我们先分析一下绑定方式启动Service流程,以路痴宝中的守护Service启动保活助手A的守护Service为例:当路痴宝中的Service通过bindService绑定保活助手A的Service时,保活助手A会回调onBind方法返回一个IPerson.Stub的代理对象给路痴宝,当路痴宝Service中的onServiceConnected被回调时,说明路痴宝绑定保活助手A的Service成功。当解绑时,路痴宝中的onServiceDisconnected和保活助手A中的onUnbind被调用。
有了前面的基础,接下来就分析如何在不同的应用进程中创建守护service,通过检测彼此的绑定情况来唤醒彼此。当路痴宝绑定保活助手A时(浅绿色字体),如果保活助手A被系统杀死,路痴宝的onServiceDisConnected被回调,我们可以在该方法中执行bindService方法再次尝试绑定唤醒保活助手A;当保活助手A绑定路痴宝时(橙色字体),如果路痴宝被系统杀死,保活助手A的onServiceDisconnected被回调,我们可以在该方法中执行bindService方法再次尝试绑定唤醒路痴宝。至此,经过这种双重绑定守护来到达应用保活的目的。
a)逻辑图解
a) 项目工程源码
* MainActivity.java
/**
*@decrible
*
* Create by jiangdongguo on 2016-12-6 上午9:34:10
*/
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//启动保活后台服务
Intent intent = new Intent(MainActivity.this,LcbAliveService.class);
startService(intent);
}
}
* LcbAliveService.java
/**
*@decrible 路痴宝保活后台服务,绑定启动保活助手A的服务
*
* Create by jiangdongguo on 2016-12-6 上午9:41:36
*/
public class LcbAliveService extends Service {
private final String A_PackageName = "com.alive.coreone";
private final String A_ServicePath = "com.alive.coreone.AssistantAService";
private ICat mBinderFromA;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
bindAliveA();
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderFromA = ICat.Stub.asInterface(service);
if (mBinderFromA != null) {
try {
Log.d("Debug",
"收到保活助手A的数据:name="
+ mBinderFromA.getName() + ";age="
+ mBinderFromA.getAge() + "----");
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
};
private ICat.Stub mBinderToA = new ICat.Stub() {
@Override
public String getName() throws RemoteException {
return "我是路痴宝";
}
@Override
public int getAge() throws RemoteException {
return 3;
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinderToA;
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
@Override
public void onCreate() {
super.onCreate();
if(!isApkInstalled(A_PackageName)){
Log.d("Debug","----保活助手A未安装----");
stopSelf();
return;
}
bindAliveA();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
}
private void bindAliveA() {
Intent serverIntent = new Intent();
serverIntent.setClassName(A_PackageName, A_ServicePath);
bindService(serverIntent, conn, Context.BIND_AUTO_CREATE);
}
private boolean isApkInstalled(String packageName){
PackageManager mPackageManager = getPackageManager();
//获得所有已经安装的包信息
List<PackageInfo> infos = mPackageManager.getInstalledPackages(0);
for(int i=0;i<infos.size();i++){
if(infos.get(i).packageName.equalsIgnoreCase(packageName)){
return true;
}
}
return false;
}
}
* AssistantAService.java
/**
*@decrible 保活助手A守护后台服务
*
* Create by jiangdongguo on 2016-11-23 上午10:57:59
*/
public class AssistantAService extends Service {
private final String Lcb_PackageName = "com.luchibao";
private final String Lcb_ServicePath = "com.luchibao.LcbAliveService";
private ICat mBinderFromLcb;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
bindLuChiBao();
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderFromLcb = ICat.Stub.asInterface(service);
if (mBinderFromLcb != null) {
try {
Log.d("Debug",
"收到路痴宝Service返回的数据:name="
+ mBinderFromLcb.getName() + ";age="
+ mBinderFromLcb.getAge() );
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
};
private ICat.Stub mBinderToLcb = new ICat.Stub() {
@Override
public String getName() throws RemoteException {
return "我是保活助手A";
}
@Override
public int getAge() throws RemoteException {
return 2;
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinderToLcb;
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
@Override
public void onCreate() {
super.onCreate();
//提升Service的优先级
Notification notification = new Notification();
notification.flags = Notification.FLAG_ONGOING_EVENT;
notification.flags |= Notification.FLAG_NO_CLEAR;
notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
startForeground(1, notification);
Log.d("Debug","****保活助手1onCreate:绑定启动路痴宝****");
bindLuChiBao();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
}
private void bindLuChiBao() {
Intent clientIntent = new Intent();
clientIntent.setClassName(Lcb_PackageName, Lcb_ServicePath);
bindService(clientIntent, conn, Context.BIND_AUTO_CREATE);
}
}
到这里也许你会问,为什么只提高保活助手A的service优先级,而不同时提高路痴宝的优先级?这是因为自己在测试的过程中发现,当上述两个进程长时间运行在后台时还是有可能被系统杀死,以致无法实现保活的目的。当时猜想,系统在回收进程时很可能是按顺序回收的,当这两个进程顺序比较接近,或者说内存中可能就只有这两个进程,那么系统在回收的时候一次性将其干掉了。为了缓解这种情况,我采取了一种高低优先级的方式来尽量保证系统不会同一时间回收两个进程,只要有了这个时间差,两个进程就能够实现互相启动保活的目的。单进程守护保活演示如下,测试手机为华为荣耀4X全网通(Android6.0),其一键清理、强制停止效果分别如下:
(4) 双进程守护
从程度上来说,单进程守护方式差不多可以满足应用长时间保活的要求,虽说让人感觉有
点怪异,但实现起来也不是很难,效果明显。但是,随着进一步的测试,我连续两次强杀路痴宝,路痴宝就无法启动了,这是因为当路痴宝“体积”较大,启动前需要加载诸如大量的静态变量或者Application类中的变量等,导致启动较慢,当我第一次强杀路痴宝时,保活助手A是执行绑定启动路痴宝保活服务的,但我继续第二次强杀路痴宝时,保活助手A可能还未与路痴宝绑定,最终导致保活助手A无法检查路痴宝的绑定状态而失效。
双进程守护:针对单进程守护出现的问题,当路痴宝“体积”较大时,我们可以采用双进程守护,即实现两个保活助手,它们彼此双向绑定来对路痴宝进行守护。我们采用“环”的形式来进行互拉,无论谁被杀死,只要系统杀掉剩余的任何一个进程,最后活着的进程都能够将其他被杀进程拉起来。当然,这里还有个小技巧,为了防止两个保活助手进程同时被系统杀死,我这里采取高低优先级的方式来解决。
a) 逻辑图解
* MainActivity.java:同上
* LcbAliveService.java:同上
* AssistantAService.java:/**
*@decrible 保活助手A守护后台服务,绑定保活助手B
*
* Create by jiangdongguo on 2016-11-23 上午10:57:59
*/
public class AssistantAService extends Service {
private final String B_PackageName = "com.alive.coretwo";
private final String B_ServicePath = "com.alive.coretwo.AssistantBService";
private ICat mBinderFromB;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
bindAliveB();
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderFromB = ICat.Stub.asInterface(service);
if (mBinderFromB != null) {
try {
Log.d("Debug",
"收到保活助手B Service返回的数据:name="
+ mBinderFromB.getName() + ";age="
+ mBinderFromB.getAge() );
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
};
private ICat.Stub mBinderToB = new ICat.Stub() {
@Override
public String getName() throws RemoteException {
return "我是保活助手A";
}
@Override
public int getAge() throws RemoteException {
return 2;
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinderToB;
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
@Override
public void onCreate() {
super.onCreate();
//提升Service的优先级
Notification notification = new Notification();
notification.flags = Notification.FLAG_ONGOING_EVENT;
notification.flags |= Notification.FLAG_NO_CLEAR;
notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
startForeground(1, notification);
Log.d("Debug","****保活助手AonCreate:绑定启动保活助手B****");
bindAliveB();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
}
private void bindAliveB() {
Intent clientIntent = new Intent();
clientIntent.setClassName(B_PackageName,B_ServicePath);
bindService(clientIntent, conn, Context.BIND_AUTO_CREATE);
}
}
* AssistantBService.java
/**
*@decrible 保活助手APK后台服务
*
* Create by jiangdongguo on 2016-11-23 上午10:57:59
*/
public class AssistantBService extends Service {
private final String Lcb_PackageName = "com.luchibao";
private final String Lcb_ServicePath = "com.luchibao.LcbAliveService";
private final String A_PackageName = "com.alive.coreone";
private final String A_ServicePath = "com.alive.coreone.AssistantAService";
private ICat mBinderFomAOrLcb;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
bindAliveA();
bindLuChiBao();
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderFomAOrLcb = ICat.Stub.asInterface(service);
}
};
private ICat.Stub mBinderToA = new ICat.Stub() {
@Override
public String getName() throws RemoteException {
return "我是保活助手B";
}
@Override
public int getAge() throws RemoteException {
return 1;
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinderToA;
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
@Override
public void onCreate() {
super.onCreate();
bindAliveA();
bindLuChiBao();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
}
private void bindLuChiBao() {
Intent clientIntent = new Intent();
clientIntent.setClassName(Lcb_PackageName,Lcb_ServicePath);
bindService(clientIntent, conn, Context.BIND_AUTO_CREATE);
}
private void bindAliveA() {
Intent clientIntent = new Intent();
clientIntent.setClassName(A_PackageName,A_ServicePath);
bindService(clientIntent, conn, Context.BIND_AUTO_CREATE);
}
}
最后,就是每个应用的AndroidManifest.xml配置,也许你会发现当部署在部分机型时,无法启动保活助手,并且弹出一个“关联启动”权限警告,这是由于部分机型在启动一个外部应用时需要对其进行授权。为了避免这个麻烦,我们可以在AndroidManifest.xml的<manifest ../>标签内添加android:sharedUserId,只要保证每个关联启动的android:sharedUserId的值是一致的,然后都使用同一个密钥进行签名即可无需“关联启动”权限。保活助手A的AndroidManifest.xml参考代码如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.alive.coreone"
android:sharedUserId="com.alive.app"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="21" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_logo_new_72"
android:label="@string/app_name" >
<service
android:name="com.alive.coreone.AssistantAService"
android:enabled="true"
android:exported="true"
android:process=":remote" >
<intent-filter android:priority="1000" >
</intent-filter>
</service>
</application>
</manifest>
效果演示:
转载出自:http://blog.csdn.net/andrexpert/article/details/53485360