做Android 保活的背景
由于之前做一个项目的时候需要让进程一直在后台活下去保持不被杀死的状态,因此也是各种百度各种苦苦寻找,本来是想着靠Service来做保活的,因为我的手机是6.0系统的,之前试过的各种依靠Service的方式均以失败告终,因此决定站在另一个角度上来解决问题,–>Android的进程。
方案一:双进程守护
其实诸如类似360杀毒软件之类的产品本身原理是通过一个一个的遍历进程,如果存活就杀死从而达到清理软件的作用的,所以我们是可以拿到自己进程和创建新的进程的。而通过AIDL的接口则可以实现跨进程通信,因此,使用双进程并通过进程间的通信是一种可行的解决方案。因此方案一是通过双进程守护来解决这个Android应用保活的。
首先是一个AIDL接口,两边的Service都要通过继承Service_1.Stub来实现AIDL接口中的方法,这里做一个空实现,目的是为了实现进程通信。接口声明如下:
package com.ph.myservice;
interface Service_1 {
String getName();
}
然后是两个Service,为了保持连接,内部写一个内部类实现ServiceConnection的接口,当外部杀了其中一个进程的时候,会进入onDisConnection中,那么此时要做的就是start和bind另一个进程,因为Service的启动是可以多次的,所以这样是没问题的,代码如下:
public class LocalService extends Service {
private ServiceConnection conn;
private MyService myService;
@Override
public IBinder onBind(Intent intent) {
return myService;
}
@Override
public void onCreate() {
super.onCreate();
init();
}
private void init() {
if (conn == null) {
conn = new MyServiceConnection();
}
myService = new MyService();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(getApplicationContext(), "本地进程启动", Toast.LENGTH_LONG).show();
Intent intents = new Intent();
intents.setClass(this, RemoteService.class);
bindService(intents, conn, Context.BIND_IMPORTANT);
return START_STICKY;
}
class MyService extends Service_1.Stub {
@Override
public String getName() throws RemoteException {
return null;
}
}
class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
System.out.println("获取连接");
}
@Override
public void onServiceDisconnected(ComponentName name) {
Toast.makeText(LocalService.this, "远程连接被干掉了", Toast.LENGTH_SHORT).show();
LocalService.this.startService(new Intent(LocalService.this,
RemoteService.class));
LocalService.this.bindService(new Intent(LocalService.this,
RemoteService.class), conn, Context.BIND_IMPORTANT);
}
}
}
远程服务类如下:
public class RemoteService extends Service {
private MyBinder binder;
private ServiceConnection conn;
@Override
public void onCreate() {
super.onCreate();
// System.out.println("远程进程开启");
init();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(getApplicationContext(), "远程进程启动", Toast.LENGTH_LONG).show();
Intent intents = new Intent();
intents.setClass(this, LocalService.class);
bindService(intents, conn, Context.BIND_IMPORTANT);
return START_STICKY;
}
private void init() {
if (conn == null) {
conn = new MyConnection();
}
binder = new MyBinder();
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
static class MyBinder extends Service_1.Stub {
@Override
public String getName() throws RemoteException {
return "远程连接";
}
}
class MyConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
System.out.println("获取远程连接");
}
@Override
public void onServiceDisconnected(ComponentName nme) {
Toast.makeText(RemoteService.this, "本地连接被干掉了", Toast.LENGTH_SHORT).show();
RemoteService.this.startService(new Intent(RemoteService.this,
LocalService.class));
RemoteService.this.bindService(new Intent(RemoteService.this,
LocalService.class), conn, Context.BIND_IMPORTANT);
}
}
}
布局文件里要加上声明
实际情况我个人测试,在5.0以下的模拟器上是没问题的,不管多次从系统的进程里kill掉,也还是会重新启动tos,但是5.0以上这种方法是无效的,5.0以上Android应该是意识到了这种双进程守护的方式,因此修改了一下源码,让这种双进程保活应用的方式无效。因此,针对5.0以上,我们采用另一种方案。
方案二:JobScheduler执行任务调度保活
JobScheduler这个类是21版本google新出来的api,我们看他的文档可以知道大致这个类的作用如下:
这个任务其实是在设备空闲期执行的,而且系统设计的这个api不会很耗电,本意是用来执行一些任务调度的,但是我们设想一下,如果用这个类来执行我们的开启双进程,那么也是一定会在设备空闲期执行的,因此我们写一个类继承JobService,在onstart里声明创建JobScheduler对象,并设置多就执行一次和开机自启动,这样就能确保及时在息屏状态,也能够执行重启进程,所以我们在JobService的onStopJob方法里判断我们的进程是否被回收了,如果被回收了就重启进程,这样子就可以实现5.0以上的进程保活了。具体代码如下:
@SuppressLint("NewApi")
public class JobHandlerService extends JobService {
private JobScheduler mJobScheduler;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
System.out.println("服务被创建");
// startService(new Intent(this, LocalService.class));
// startService(new Intent(this, RemoteService.class));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(startId++,
new ComponentName(getPackageName(), JobHandlerService.class.getName()));
builder.setPeriodic(5000); //每隔5秒运行一次
builder.setRequiresCharging(true);
builder.setPersisted(true); //设置设备重启后,是否重新执行任务
builder.setRequiresDeviceIdle(true);
if (mJobScheduler.schedule(builder.build()) <= 0) {
//If something goes wrong
System.out.println("工作失败");
} else {
System.out.println("工作成功");
}
}
return START_STICKY;
}
@Override
public boolean onStartJob(JobParameters params) {
Toast.makeText(this, "服务启动", Toast.LENGTH_SHORT).show();
// || isServiceRunning(this, "com.ph.myservice.RemoteService") == false
System.out.println("开始工作");
// if (!isServiceRunning(getApplicationContext(), "com.ph.myservice") || !isServiceRunning(getApplicationContext(), "com.ph.myservice:remote")) {
// startService(new Intent(this, LocalService.class));
// startService(new Intent(this, RemoteService.class));
// }
/* boolean serviceRunning = isServiceRunning(getApplicationContext(), "com.ph.myservice");
System.out.println("进程一" + serviceRunning);
boolean serviceRunning2 = isServiceRunning(getApplicationContext(), "com.ph.myservice:remote");
System.out.println("进程二" + serviceRunning2);*/
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
if (!isServiceRunning(this, "com.ph.myservice.LocalService") || !isServiceRunning(this, "com.ph.myservice.RemoteService")) {
startService(new Intent(this, LocalService.class));
startService(new Intent(this, RemoteService.class));
}
return false;
}
// 服务是否运行
public boolean isServiceRunning(Context context, String serviceName) {
boolean isRunning = false;
ActivityManager am = (ActivityManager) this
.getSystemService(Context.ACTIVITY_SERVICE);
List lists = am.getRunningAppProcesses();
for (ActivityManager.RunningAppProcessInfo info : lists) {// 获取运行服务再启动
System.out.println(info.processName);
if (info.processName.equals(serviceName)) {
isRunning = true;
}
}
return isRunning;
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
openJobService();
} else {
openTwoService();
}
}
private void openTwoService() {
startService(new Intent(this, LocalService.class));
startService(new Intent(this, RemoteService.class));
}
private void openJobService() {
Intent intent = new Intent();
intent.setClass(MainActivity.this, JobHandlerService.class);
startService(intent);
}
}
经过我6.0系统的华为真机测试是没有问题的,就算处于息屏状态进程也还是活着的,不管过多久打开屏幕还是会tos,并且关机了开机也会tos。以上就是我的Android保活的两种方案啦,其实还有更深入的几种方案,但是涉及到ndk层和Linux层我目前也没做,就不在这里说啦。
方案三:1像素Activity进程保活
Android8.0马上就要发布,Google对于安卓市场上各种应用占据内存空间的问题不断进行内存完善,我们的应用进程很难再保持不死之身,以前的服务唤醒在5.0已经失效,现在我们尽可能的做到保活方式就是提高进程的优先级,本文介绍一种1像素Activity保活进程的实现(据说QQ也在用)。
其整个逻辑就是在手机屏幕黑屏时,我们启动一个1像素的Activity,其占用内存很小毕竟只有1像素嘛,无形中减小了内存的回收几率,在屏幕亮的时候就关闭该页面。
1、首先需要在MainActivity中注册一个监听手机屏幕状态的广播监听:
//注册监听屏幕的广播
mOnepxReceiver = new OnePixelReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.intent.action.SCREEN_OFF");
intentFilter.addAction("android.intent.action.SCREEN_ON");
intentFilter.addAction("android.intent.action.USER_PRESENT");
registerReceiver(mOnepxReceiver, intentFilter);
2、监听到屏幕状态后的处理:
public class OnePixelReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { //屏幕关闭启动1像素Activity
Intent it = new Intent(context, OnePiexlActivity.class);
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(it);
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { //屏幕打开 结束1像素
context.sendBroadcast(new Intent("finish"));
Intent main = new Intent(Intent.ACTION_MAIN);
main.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
main.addCategory(Intent.CATEGORY_HOME);
context.startActivity(main);
}
}
}
3、创建我们的1像素的Activity:
public class OnePiexlActivity extends Activity {
private BroadcastReceiver endReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//设置1像素
Window window = getWindow();
window.setGravity(Gravity.LEFT | Gravity.TOP);
WindowManager.LayoutParams params = window.getAttributes();
params.x = 0;
params.y = 0;
params.height = 1;
params.width = 1;
window.setAttributes(params);
//结束该页面的广播
endReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
finish();
}
};
registerReceiver(endReceiver, new IntentFilter("finish"));
//检查屏幕状态
checkScreen();
}
@Override
protected void onResume() {
super.onResume();
checkScreen();
}
/**
* 检查屏幕状态 isScreenOn为true 屏幕“亮”结束该Activity
*/
private void checkScreen() {
PowerManager pm = (PowerManager) OnePiexlActivity.this.getSystemService(Context.POWER_SERVICE);
boolean isScreenOn = pm.isScreenOn();
if (isScreenOn) {
finish();
}
}
}
4、我们还需要设置1像素activity为透明,不然可能会影响体验,但是影响不大,1像素毕竟是挑战视力的存在。
5、AndroidManifest配置