下载APK,看效果
https://qulian-apk.oss-cn-beijing.aliyuncs.com/apk/KeepAlivePro.apk
介绍
我们团队研究安卓保活有几年的时间了,从Android4.0到Android12,经历了很多坎坷,写这个系列文章不是鼓励大家去做流氓软件,而是为了让大家一起研究安卓底层技术,更深入理解app运行机制,写出更健壮体验更好的安卓应用,同时促进厂商对漏洞进行修复。
让APP活得更久,是很多产品团队孜孜不倦追求的目标,如果APP很容易在后台资源不足时被杀掉,会导致无法给用户推送重要消息,虽然厂商push通道能解决离线消息推送的需求,但是还远远不够。普通APP无法像微信、QQ等应用一样,被手机厂商加白,所以还是需要一些保活黑科技来达到保活的目的。
这个系列文章分享的目的,是如何让应用达到永生不死,希望能给各位开发者带来撸码灵感,解决产品实际业务中遇到的问题。
在进入正题之前,我们先分享几种常见的APP保活方案。
常见的4种方案
1像素保活 在侦听到广播事件(解锁屏幕、熄灭屏幕、切换网络、卸载APP、发送/接收短信)的时候,拉起一个仅有1像素的透明Activity,让APP变成前台服务提升进程的优先级,从而避免被系统杀死。1像素保活是一种比较“古老的”保活手段了,已经没办法对抗高版本的安卓系统,所以逐渐被废弃,但是在某下特殊场景下依然被使。。
// 重要的配置,防止被最近的任务列表发现
<activity
android:excludeFromRecents="true" // 不在最近任务列表中展示
android:finishOnTaskLaunch="false" // 在launcher点击图标会进主Activity同时销毁此Activity
android:launchMode="singleInstance"// 独立堆栈
android:theme="@style/TranslucentTheme"> // 使用自定义透明style
</activity>
public class OnePixelActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_1);
Window window = getWindow();
window.setGravity(Gravity.LEFT|Gravity.TOP);
WindowManager.LayoutParams layoutParams = window.getAttributes();
layoutParams.width = 1;
layoutParams.height = 1;
layoutParams.x = 1;
layoutParams.y = 1;
window.setAttributes(layoutParams);
}
}
前台Service保活
前台Service保活是保活手段里最常用最简单的一种,就像音乐播放软件(网易云、QQ音乐)一样,用户切到了后台,仍然可以播放音乐。这种保活方案通常是播放一段无声音频,从而达到保活的目的。
// 前台服务的权限
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
public class ForegroundService extends Service {
private final static int SERVICE_ID = 1;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT<Build.VERSION_CODES.JELLY_BEAN_MR2){
//4.3以下
startForeground(SERVICE_ID,new Notification());
}else if (Build.VERSION.SDK_INT<Build.VERSION_CODES.O){
//7.0以下
startForeground(SERVICE_ID,new Notification());
//删除通知栏
startService(new Intent(this,InnerService.class));
}else {
//8.0以上
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//NotificationManager.IMPORTANCE_MIN 通知栏消息的重要级别 最低,不让弹出
//IMPORTANCE_MIN 前台时,在阴影区能看到,后台时 阴影区不消失,增加显示 IMPORTANCE_NONE时 一样的提示
//IMPORTANCE_NONE app在前台没有通知显示,后台时有
NotificationChannel channel = new NotificationChannel("channel", "keep", NotificationManager.IMPORTANCE_NONE);
if (notificationManager!=null){
notificationManager.createNotificationChannel(channel);
Notification notification = new Notification.Builder(this, "channel").build();
startForeground(SERVICE_ID,notification);
}
}
}
private static class InnerService extends Service{
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.d(KeepAliveApp.TAG, "onCreate: ");
startForeground(SERVICE_ID,new Notification());
stopSelf();
}
}
}
Jobscheduler定时任务唤醒
原理很简单,就是定个闹钟,一段时间拉起一次后台 service,防止被杀掉,但是有可能注册的闹钟自己被杀了。好在 Android 5.0 提供了一个 Jobscheduler 的 API,它可以在各种情况(网络状况、电池状态、设备空闲状态、磁盘非低状态等)被触发,最重要的是和闹钟比起来它不会被强制杀死!这样我们就可以启动一个 Service 来执行任务了。
示例:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(context.getPackageName(), MyJobService.class.getName()));
/**
* 5分钟后执行,可能一直延后
* 这个设置不能和周期性任务一起设置
*/
builder.setMinimumLatency(5 * 60 * 1000);
/**
* 截止到20分钟之前执行;设置了这个Deadline,requirements相关条件不满足也会执行
* 这个设置不能和周期性任务一起设置
*/
builder.setOverrideDeadline(20 * 60 * 1000);
/**
* 设置需要的网络条件
*
* 如果设置了这个requirements又设置了setOverrideDeadline可以通过
* JobService中的onStartJob(JobParameters params)中的isOverrideDeadlineExpired()判断是不是由Deadline触发
*/
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
JobInfo jobInfo = builder.build();
scheduler.schedule(jobInfo);
}
双进程守护
android 保活的终极方案:双进程守护保活。就是在一个 app 中,启用双进程,如果有一个进程被杀了,另一个进程马上重新拉起这个进程,两个进程互相守护以达到保活的目的。
基础配置就是两个保活的逻辑 Service 和一个用于进程间通讯的 ADIL 文件。
<service
android:name=".LocalService" // 默认的进程
android:enabled="true"
android:exported="true" />
<service
android:name=".RemoteService"
android:enabled="true"
android:exported="true"
android:process=":RemoteProcess"/> // 开启一个新的进程
// 基本逻辑就是在一个Service断开的时候通知另一个服务,而另一个服务在接收到断开的通知的时候,把断开的服务重新拉起来
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i("RemoteService", "断开连接");
startService(new Intent(RemoteService.this,LocalService.class));
bindService(new Intent(RemoteService.this,LocalService.class),connection, Context.BIND_IMPORTANT);
}
};