安卓开发学习之HelloDaemon源码阅读

背景

最近单位项目里要用到双服务进程保活,目的是要保证服务不被杀死。

双进程保活实际是两个进程相互监听,在各自的销毁回调方法里,启动对方。网上有一个不错的双服务进程保活开源框架,叫做HelloDaemon,github地址:

https://github.com/xingda920813/HelloDaemon

现在,记录一下对于其的源码阅读过程

 

源码阅读

AbsWorkService

我们的工作(服务)进程要继承于AbsWorkService,启动时要调用onBind()和onStartCommand()方法

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return onStart(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        onStart(intent, 0, 0);
        return onBind(intent, null);
    }

其中onBind()里的onBind(intent, null)是留给子类实现的,用于bind时的业务逻辑。因此关键就是onStart()方法

onStart()

    /**
     * 1.防止重复启动,可以任意调用 DaemonEnv.startServiceMayBind(Class serviceClass);
     * 2.利用漏洞启动前台服务而不显示通知;
     * 3.在子线程中运行定时任务,处理了运行前检查和销毁时保存的问题;
     * 4.启动守护服务;
     * 5.守护 Service 组件的启用状态, 使其不被 MAT 等工具禁用.
     */
    protected int onStart(Intent intent, int flags, int startId) {

        //启动守护服务,运行在:watch子进程中
        DaemonEnv.startServiceMayBind(WatchDogService.class);

        //业务逻辑: 实际使用时,根据需求,将这里更改为自定义的条件,判定服务应当启动还是停止 (任务是否需要运行)
        Boolean shouldStopService = shouldStopService(intent, flags, startId);
        if (shouldStopService != null) {
            if (shouldStopService) stopService(intent, flags, startId); else startService(intent, flags, startId);
        }

        if (mFirstStarted) {
            mFirstStarted = false;
            //启动前台服务而不显示通知的漏洞已在 API Level 25 修复,大快人心!
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
                //利用漏洞在 API Level 17 及以下的 Android 系统中,启动前台服务而不显示通知
                startForeground(HASH_CODE, new Notification());
                //利用漏洞在 API Level 18 及以上的 Android 系统中,启动前台服务而不显示通知
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
                    DaemonEnv.startServiceSafely(new Intent(getApplication(), WorkNotificationService.class));
            }
            getPackageManager().setComponentEnabledSetting(new ComponentName(getPackageName(), WatchDogService.class.getName()),
                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
        }

        return START_STICKY;
    }

可分四步:

1、启动看门狗

2、停止或启动自身

3、初次调用时,启动前台服务并不显示通知

4、设置看门狗所在应用不被杀死

 

首先启动了一个WatchDogService,也就是看门狗服务,它就是用来每隔一段时间启动工作进程的。至于为何说DaemonEnv.startServiceMayBind()方法可以任意调用而不怕服务重复启动(实则时不怕服务重复绑定),可以点进去看看源码的实现

DaemonEnv#startServiceMayBind()

    public static void startServiceMayBind(@NonNull final Class<? extends Service> serviceClass) {
        if (!sInitialized) return;
        final Intent i = new Intent(sApp, serviceClass);
        startServiceSafely(i);
        ServiceConnection bound = BIND_STATE_MAP.get(serviceClass);
        if (bound == null) sApp.bindService(i, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                BIND_STATE_MAP.put(serviceClass, this);
            }

            @Override
            public void onServiceDisconnected(ComponentName componentName) {
                BIND_STATE_MAP.remove(serviceClass);
                startServiceSafely(i);
                if (!sInitialized) return;
                sApp.bindService(i, this, Context.BIND_AUTO_CREATE);
            }
        }, Context.BIND_AUTO_CREATE);
    }

sInitialized说明在使用前(启动工作服务前要初始化),而后实例化一个意图,调用startServiceSafely()

    static void startServiceSafely(Intent i) {
        if (!sInitialized) return;
        try { sApp.startService(i); } catch (Exception ignored) {}
    }

其实就是启动了服务。

返回DaemonEnv.startServiceMayBind(),看它对服务绑定是怎么处理的,显然DaemonEnv内部用了一个映射来保存service和对应的connection,在绑定前,先看service对应的connection是不是空,是空说明是头一次绑定,否则就不是;对于头一次绑定的服务,在绑定时new出来的connection的回调里,连接时的回调就是把service和connection的对应关系保存,断开时的回调则是重复startServiceMayBind()过程,保证已绑定的服务不会解绑。(尽管onServiceDisconnected()里代码是写了四行,但通过和DaemonEnv.startServiceMayBind()方法比较,显然是又重复了一遍过程,只不过在给映射put前,先删除了原数据)

服务的停止和启动

回到工作进程的onStart()方法,DaemonEnv.startServiceMayBind()启动看门狗后,根据是否停止服务来进行服务的停止和启动

        Boolean shouldStopService = shouldStopService(intent, flags, startId);
        if (shouldStopService != null) {
            if (shouldStopService) stopService(intent, flags, startId); else startService(intent, flags, startId);
        }

shouldStopService()要子类实现,而后我们看看stopService()和startService()方法。stopService()方法源码如下:

    void stopService(Intent intent, int flags, int startId) {
        //取消对任务的订阅
        stopWork(intent, flags, startId);
        //取消 Job / Alarm / Subscription
        cancelJobAlarmSub();
    }

stopWork()留给子类实现,cancelJobAlarmSub()方法以发送广播的形式结束对jobScheduler、AlaramManager和disposable的订阅,也就取消了看门狗对服务进程的定时启动(开来并不是真正结束了服务,而是取消了守护)

    public static void cancelJobAlarmSub() {
        if (!DaemonEnv.sInitialized) return;
        DaemonEnv.sApp.sendBroadcast(new Intent(WakeUpReceiver.ACTION_CANCEL_JOB_ALARM_SUB));
    }
public class WakeUpReceiver extends BroadcastReceiver {
    /**
     * 向 WakeUpReceiver 发送带有此 Action 的广播, 即可在不需要服务运行的时候取消 Job / Alarm / Subscription.
     *
     * 监听 8 种系统广播 :
     * CONNECTIVITY\_CHANGE, USER\_PRESENT, ACTION\_POWER\_CONNECTED, ACTION\_POWER\_DISCONNECTED,
     * BOOT\_COMPLETED, MEDIA\_MOUNTED, PACKAGE\_ADDED, PACKAGE\_REMOVED.
     * 在网络连接改变, 用户屏幕解锁, 电源连接 / 断开, 系统启动完成, 挂载 SD 卡, 安装 / 卸载软件包时拉起 Service.
     * Service 内部做了判断,若 Service 已在运行,不会重复启动.
     * 运行在:watch子进程中.
     */
    protected static final String ACTION_CANCEL_JOB_ALARM_SUB =  "com.xdandroid.hellodaemon.CANCEL_JOB_ALARM_SUB";

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent != null && ACTION_CANCEL_JOB_ALARM_SUB.equals(intent.getAction())) {
            WatchDogService.cancelJobAlarmSub();
            return;
        }
        if (!DaemonEnv.sInitialized) return;
        DaemonEnv.startServiceMayBind(DaemonEnv.sServiceClass);
    }

    .. // WakeUpAutoStartReceiver,暂时没有用到
}

发送指定action的广播后,如果是要取消服务,receiver就调用看门狗里的cancelJobAlarmSub()方法,结束对jobScheduler、AlaramManager和disposable的订阅。

    public static void cancelJobAlarmSub() {
        if (!DaemonEnv.sInitialized) return;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            JobScheduler scheduler = (JobScheduler) DaemonEnv.sApp.getSystemService(JOB_SCHEDULER_SERVICE);
            scheduler.cancel(HASH_CODE);
        } else {
            AlarmManager am = (AlarmManager) DaemonEnv.sApp.getSystemService(ALARM_SERVICE);
            if (sPendingIntent != null) am.cancel(sPendingIntent);
        }
        if (sDisposable != null) sDisposable.dispose();
    }

回到工作进程的onStart(),下一步就是启动前台服务并不显示通知了

启动前台服务并不显示通知

如果是首次启动工作服务,就把这个服务设置为前台服务,但不显示通知。具体做法根据sdk版本不同而不同:对于25及以上,就不用管;18-24,要连续调用两次startForeground();17及以下,调用一次就可以。不过在调用startForeground()时,要保证id不等于0,否则就不是前台服务了

对于sdk是18-24,它启动了一个WorkNotificationService,其实这个service很简单

    public static class WorkNotificationService extends Service {

        
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            startForeground(AbsWorkService.HASH_CODE, new Notification());
            stopSelf();
            return START_STICKY;
        }

        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    }

第二次调用startForeground()后就把自己停掉了。

注意HelloDaemon里所有服务的onStartCommand()方法都是返回START_STICK,表示此服务可以在任何时候被调起或被停止,所传入的intent可以为null

回到工作进程的onStart(),在头一次启动最后,他设置了不要杀死看门狗服务所在的应用

 

看门狗服务

方才说到,工作进程启动时会启动看门狗服务,我们就看一下看门狗WatchDogService的onBind()和onStartCommand()方法

    @Override
    public final int onStartCommand(Intent intent, int flags, int startId) {
        return onStart(intent, flags, startId);
    }

    @Override
    public final IBinder onBind(Intent intent) {
        onStart(intent, 0, 0);
        return null;
    }

和工作进程一样,都是调用了自己的onStart()方法

onStart()

    protected final int onStart(Intent intent, int flags, int startId) {

        if (!DaemonEnv.sInitialized) return START_STICKY;

        if (sDisposable != null && !sDisposable.isDisposed()) return START_STICKY;

        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
            startForeground(HASH_CODE, new Notification());
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
                DaemonEnv.startServiceSafely(new Intent(DaemonEnv.sApp, WatchDogNotificationService.class));
        }

        //定时检查 AbsWorkService 是否在运行,如果不在运行就把它拉起来
        //Android 5.0+ 使用 JobScheduler,效果比 AlarmManager 好
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            JobInfo.Builder builder = new JobInfo.Builder(HASH_CODE, new ComponentName(DaemonEnv.sApp, JobSchedulerService.class));
            builder.setPeriodic(DaemonEnv.getWakeUpInterval());
            //Android 7.0+ 增加了一项针对 JobScheduler 的新限制,最小间隔只能是下面设定的数字
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) builder.setPeriodic(JobInfo.getMinPeriodMillis(), JobInfo.getMinFlexMillis());
            builder.setPersisted(true);
            JobScheduler scheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
            scheduler.schedule(builder.build());
        } else {
            //Android 4.4- 使用 AlarmManager
            AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
            Intent i = new Intent(DaemonEnv.sApp, DaemonEnv.sServiceClass);
            sPendingIntent = PendingIntent.getService(DaemonEnv.sApp, HASH_CODE, i, PendingIntent.FLAG_UPDATE_CURRENT);
            am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + DaemonEnv.getWakeUpInterval(), DaemonEnv.getWakeUpInterval(), sPendingIntent);
        }

        //使用定时 Observable,避免 Android 定制系统 JobScheduler / AlarmManager 唤醒间隔不稳定的情况
        sDisposable = Flowable
                .interval(DaemonEnv.getWakeUpInterval(), TimeUnit.MILLISECONDS)
                .subscribe(new Consumer<Long>() {
                    @Override
                    public void accept(Long aLong) throws Exception {
                        DaemonEnv.startServiceMayBind(DaemonEnv.sServiceClass);
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        throwable.printStackTrace();
                    }
                });

        //守护 Service 组件的启用状态, 使其不被 MAT 等工具禁用
        getPackageManager().setComponentEnabledSetting(new ComponentName(getPackageName(), DaemonEnv.sServiceClass.getName()),
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);

        return START_STICKY;
    }

可分五步:

1、判断是否初始化,以及是否取消了对disposable的订阅,如果是的话,直接返回

2、开启前台进程,不显示通知

3、根据sdk的版本不同,启动jobScheduler或闹钟的定时,定时启动服务进程

4、用disposable再次实现定时启动服务进程,它可以表示看门狗的服务状态

5、设置工作进程所在的应用不被杀死

有了服务进程的阅读记录,看门狗的onStart()就很简单了,但我不明白既然有了disposable定时,为何还要用jobScheduler和闹钟的定时。

 

服务的手动结束或退出

当我们手动结束工作服务或看门狗时(设置里退出或打开进程管理器退出),会回调各自的onDestroy()或onTaskRemoved()方法,而这两个方法在两个类里的实现是一样的

   /**
     * 设置-正在运行中停止服务时回调
     */
    @Override
    public void onDestroy() {
        onEnd(null);
    }

   /**
     * 最近任务列表中划掉卡片时回调
     */
    @Override
    public void onTaskRemoved(Intent rootIntent) {
        onEnd(rootIntent);
    }

    protected void onEnd(Intent rootIntent) {
        if (!DaemonEnv.sInitialized) return;
        DaemonEnv.startServiceMayBind(DaemonEnv.sServiceClass);
        DaemonEnv.startServiceMayBind(WatchDogService.class);
    }

其实就是把他们俩重新启动一回

 

结语

通过对HelloDaemon源码的阅读,我们可以发现:

1、工作进程和看门狗相互监督守护,工作进程启动看门狗,看门狗定时启动工作进程

2、DaemonEnv则保证每个服务的绑定只会绑定一次,而且绑定后就不会解绑

3、所谓终止服务,其实只是取消了工作进程的定时启动,并没有直接停止进程

4、如果要手动关闭进程的话,只能导致服务的重启。

可以说提供了一个双进程保活的思路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值