让应用永生不死之常见保活手段(一)-Android4.0-Android12.0完美APP保活解决方案

2,328 阅读3分钟

下载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);
    }
};