关于APP在Android上保活的问题不算新鲜,不过既然遇到了也就顺便记录一下。
只有确定要需求背景,了解技术可行性才可以决定使用哪种方案更加合适。
那么我们的APP为什么需要保活呢?
1、像即时通讯、运动app这种需要时刻在手机后台保持活跃已及时收到后台消息的
2、需要向许久没有启动App的用户推送一波广告,拉活需求等。
APP在什么情况下会被kill掉?
1、App长时间停留在后台,内存不足被kill
2、国内大部分手机在锁屏一段时间后,省电机制会kill后台进程
3、用户主动杀死APP,或者通过第三方清理工具释放内存
APP为什么会被kill掉?
Android一般的进程优先级划分:
1.前台进程 (Foreground process)
2.可见进程 (Visible process)
3.服务进程 (Service process)
4.后台进程 (Background process)
5.空进程 (Empty process)
在Android系统中,进程的优先级越低的越容易被系统回收。因此我们可以确定提高进程优先级可以有效的提高进程的存活率。
保活方案一:双进程守护
1、在AS创建一个aidl:
ProcessConnection.aidl 删掉无关方法
2、创建MessageService.java
public class MessageService extends Service {
private final int MessageId = 1;
@Override
public void onCreate() {
// new Thread(new Runnable() {
// @Override
// public void run() {
// while (true) {
// try {
// Thread.sleep(2000);
// Log.e("Message", "等待接受消息");
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }
// }).start();
Log.e("MessageService", "启动啦");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("MessageService", "onStartCommand 执行啦执行啦");
//提高进程的优先级
startForeground(MessageId, new Notification());
//绑定建立连接
bindService(new Intent(this, GuardService.class), mServiceConnection, Context.BIND_IMPORTANT);
return START_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MessageBind();
}
private class MessageBind extends ProcessConnection.Stub {
}
ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Toast.makeText(MessageService.this, "与Guard建立连接", Toast.LENGTH_SHORT).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
startService(new Intent(MessageService.this, GuardService.class));
//绑定建立连接
bindService(new Intent(MessageService.this, GuardService.class), mServiceConnection,
Context.BIND_IMPORTANT);
}
};
}
3、创建GuardService.java
public class GuardService extends Service {
private final int GuardId = 1;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//提高进程的优先级
startForeground(GuardId, new Notification());
//绑定建立连接
bindService(new Intent(this, MessageService.class), mServiceConnection, Context.BIND_IMPORTANT);
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return new ProcessConnection.Stub() {
};
}
ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Toast.makeText(GuardService.this, "与Message建立连接", Toast.LENGTH_SHORT).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
startService(new Intent(GuardService.this, MessageService.class));
//绑定建立连接
bindService(new Intent(GuardService.this, MessageService.class), mServiceConnection,
Context.BIND_IMPORTANT);
}
};
}
4、创建MainActivity.java
public class MainActivity extends AppCompatActivity {
private Button mBtn;
private static String TAG = MainActivity.class.getSimpleName();
private ScreenListenerManager mManager;
private ScreenManager mScreenManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtn = findViewById(R.id.mBtn);
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getAutoStartPermission(MainActivity.this,true,getPackageName());
// JobScheduler
startService(new Intent(MainActivity.this, JobWakeUpService.class));
// 双进程守护
startService(new Intent(MainActivity.this, MessageService.class));
startService(new Intent(MainActivity.this, GuardService.class));
}
});
/* 一像素方案
mManager = new ScreenListenerManager(this);
mManager.register();
mManager.setOnScreenListener(new ScreenListenerManager.OnScreenListener() {
@Override
public void onScreenOn() {
Log.d(TAG, "onScreenOn: ");
}
@Override
public void onScreenOff() {
Log.d(TAG, "onScreenOff: ");
// 开启1像素的Activity
mScreenManager.startSinglePixelActivity(MainActivity.this);
}
@Override
public void onUserresent() {
Toast.makeText(MainActivity.this, "onUserresent!", Toast.LENGTH_LONG).show();
Log.d(TAG, "onUserresent: ");
// 关闭1像素的Activity
mScreenManager.finishSinglePixelActivity();
}
});
mScreenManager = ScreenManager.getInstance(getApplicationContext());
*/
}
private void getAutoStartPermission(Context context, Boolean isOpen, String pkgName) {
boolean isSucc = false;
try {
Bundle bundle = new Bundle();
bundle.putLong("extra_permission", 16384);
bundle.putInt("extra_action", isOpen ? 3 : 1);
bundle.putStringArray("extra_package", new String[]{pkgName});
context.getContentResolver().call(Uri.parse("content://com.lbe.security.miui.permmgr"),
String.valueOf(6), null, bundle);
isSucc = true;
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mManager.unRegister();
}
}
但是这种双进程守护的方案在5.0以上的手机是无效的,心累...
于是有了方案二
保活方案二:JobScheduler执行任务调度保活
JobScheduler这个类是21版本google新出来的api。
这个任务其实是在设备空闲期执行的,而且系统设计的这个api不会很耗电,本意是用来执行一些任务调度的,但是我们设想一下,如果用这个类来执行我们的开启双进程,那么也是一定会在设备空闲期执行的,因此我们写一个类继承JobService,在onstart里声明创建JobScheduler对象,并设置多就执行一次和开机自启动,这样就能确保及时在息屏状态,也能够执行重启进程,所以我们在JobService的onStopJob方法里判断我们的进程是否被回收了,如果被回收了就重启进程,这样子就可以实现5.0以上的进程保活了。具体代码如下:
创建JobWakeUpService.java
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class JobWakeUpService extends JobService {
private final int jobWakeUpId = 1;
private JobScheduler jobScheduler;
private JobInfo.Builder builder;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("JobWakeUpService", "onStartCommand执行啦");
builder = new JobInfo.Builder(jobWakeUpId, new ComponentName(this, JobWakeUpService.class))
.setMinimumLatency(2000)
.setPersisted(true);
jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(builder.build());
return START_STICKY;
}
@Override
public boolean onStartJob(JobParameters params) {
// 如果杀死了启动 轮询onStartJob
Log.e("JobWakeUpService", "onStartJob 执行啦执行啦");
//判断服务有没有在运行
boolean messageServiceAlive = isServiceRunning(MessageService.class.getName());
if (!messageServiceAlive) {
startService(new Intent(this, MessageService.class));
}
//执行任务
jobScheduler.schedule(builder.build());
//第二个参数,类似于onStopJob的返回值,是否需要重新执行
jobFinished(params, false);
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
/**
* 判断某个服务是否在运行
*
* @param serviceName
* @return
*/
private boolean isServiceRunning(String serviceName) {
boolean isWork = false;
ActivityManager myAM = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> myList = myAM.getRunningServices(100);
if (myList.size() <= 0) {
return false;
}
for (int i = 0; i < myList.size(); i++) {
String mName = myList.get(i).service.getClassName().toString();
if (mName.equals(serviceName)) {
isWork = true;
break;
}
}
return isWork;
}
}
但是很难受,在小米手机上这样仍然无法让app保活,原来小米有个比较特别的权限管理,
在设置->授权管理->自启动管理,把自己的app添加到自启动列表中,上面的代码就可以愉快的保活了。
不过这里需要引导用户添加自启权限,用户成本较大。一旦以这种方式启动双进程守护,那么只有在卸载app的情况下才能杀掉服务。
网上还有另外一个,一像素的方法,无非就是把利用activity为前台进程保活。代码就不贴出来了,后面我把demo传到GitHub上
伪保活方案:第三方推送唤醒
个推测试 | 极光推送 | 小米推送 | |
app进程存活 | 正常 | 正常 | 正常 |
APP进程不存活 | 收不到 | 免费版在屏幕解锁偶尔能收到 收费版可以收到(接入小米、华为、oppo、魅族、海外用户)咨询客服得知 | 走系统级别,可收到 |
现在android在保活权限越来越严格,而且保活的定义也是不一样;
是杀死app的保活还是后台进程的保活
个人认为像即时通讯这种类型app完全可以通过第三方推送来进行伪保活,也可以降低系统压力,降低app对电量的消耗
而悦动这种运动计步的可以使用1像素这种方案。