Développement Android apprenant à lire le code source de HelloDaemon

Contexte

Depuis peu, le processus de double service keep alive est utilisé dans le projet unitaire, le but est de s'assurer que le service n'est pas tué.

Le maintien en vie à double processus signifie en fait que deux processus se surveillent et démarrent l'un l'autre dans leurs méthodes de rappel de destruction respectives. Il existe sur Internet un bon framework open source keep-alive de processus à double service, appelé HelloDaemon , adresse github :

https://github.com/xingda920813/HelloDaemon
Maintenant, enregistrez le processus de lecture de son code source

Lecture de code source

AbsWorkService

Notre processus de travail (service) doit hériter d'AbsWorkService et appeler les méthodes onBind() et onStartCommand() au démarrage

    @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);
    }


Le onBind(intent, null) dans onBind() est réservé aux sous-classes pour implémenter la logique métier lors de la liaison. Donc la clé est la méthode 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;
    }

Elle peut être divisée en quatre étapes :

1. Démarrer le chien de garde

2. Arrêter ou démarrer lui-même

3. Lorsque le premier appel est passé, le service de premier plan est démarré et la notification ne s'affiche pas.

4. Définissez l'application où se trouve le chien de garde pour qu'elle ne soit pas tuée

Tout d'abord, un WatchDogService est démarré, qui est le service de surveillance, qui est utilisé pour démarrer le processus de travail à intervalles réguliers. Quant à savoir pourquoi la méthode DaemonEnv.startServiceMayBind() peut être appelée arbitrairement sans crainte d'un démarrage répété du service (en fait, elle n'a pas peur de la liaison répétée du service), vous pouvez cliquer pour voir l'implémentation du code source

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 indique qu'il est initialisé avant utilisation (avant de démarrer le service de travail), puis instancie une intention, en appelant startServiceSafely()

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

En fait, le service est lancé.

Retournez à DaemonEnv.startServiceMayBind() pour voir comment il gère la liaison de service. De toute évidence, DaemonEnv utilise un mappage pour enregistrer le service et la connexion correspondante. Avant la liaison, vérifiez si la connexion correspondant au service est vide ou non. Cela indique qu'il est la première liaison, sinon ce n'est pas le cas ; pour le premier service de liaison, dans le rappel de la nouvelle connexion lors de la liaison, le rappel lors de la connexion consiste à enregistrer la relation correspondante entre le service et la connexion, et le rappel lors de la déconnexion C'est pour répéter le processus startServiceMayBind() pour s'assurer que le service lié ne sera pas non lié. (Bien que le code dans onServiceDisconnected() soit écrit en quatre lignes, il est évident que le processus est répété à nouveau en comparant avec la méthode DaemonEnv.startServiceMayBind(), mais les données d'origine sont supprimées avant de mettre le mappage)

Arrêt et démarrage des services

Revenons à la méthode onStart () du processus de travail, après que DaemonEnv.startServiceMayBind () ait démarré le chien de garde, le service est arrêté et démarré selon qu'il est arrêté ou non.

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

shouldStopService() est implémenté par des sous-classes, puis nous examinons les méthodes stopService() et startService(). Le code source de la méthode stopService() est le suivant :

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

stopWork() est laissé à la sous-classe à implémenter. La méthode cancelJobAlarmSub() met fin à l'abonnement à jobScheduler, AlaramManager et jetable sous la forme d'envoi d'une diffusion, qui annule également le démarrage du processus de service par le chien de garde (l'ouverture n'est pas met vraiment fin au service). , mais annule la garde)

    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,暂时没有用到
}

Après avoir envoyé la diffusion de l'action spécifiée, si le service doit être annulé, le récepteur appelle la méthode cancelJobAlarmSub() dans le chien de garde pour mettre fin à l'abonnement à jobScheduler, AlaramManager et jetable.

    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();
    }

De retour au onStart () du processus de travail, l'étape suivante consiste à démarrer le service de premier plan et à ne pas afficher la notification

Démarrer le service de premier plan sans afficher de notification

Si c'est la première fois que vous démarrez le service de travail, définissez le service comme service de premier plan, mais n'affichez pas la notification. La méthode spécifique varie selon la version du sdk : pour 25 et plus, ne vous en souciez pas ; 18-24, vous devez appeler startForeground() deux fois de suite ; 17 et moins, vous pouvez l'appeler une fois. Cependant, lors de l'appel de startForeground(), assurez-vous que l'id n'est pas égal à 0, sinon ce ne sera pas un service de premier plan

Pour sdk est 18-24, il démarre un WorkNotificationService, en fait, ce service est très simple

    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;
        }
    }

Après le deuxième appel à startForeground(), il s'arrête.

Notez que la méthode onStartCommand() de tous les services dans HelloDaemon renvoie START_STICK, indiquant que le service peut être activé ou arrêté à tout moment, et l'intention entrante peut être nulle

Revenez au onStart() du processus de travail, à la fin du premier démarrage, il définit de ne pas tuer l'application où se trouve le service de surveillance

service de surveillance

Je viens de mentionner que le service de surveillance sera lancé au démarrage du processus de travail. Jetons un coup d'œil aux méthodes onBind() et onStartCommand() du chien de garde WatchDogService.

    @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;
    }

Comme le processus de travail, il appelle sa propre méthode 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;
    }

Elle peut être divisée en cinq étapes :

1. Déterminez s'il faut initialiser et s'il faut annuler l'abonnement à jetable, si oui, revenez directement

2, ouvrez le processus de premier plan, n'affichez pas la notification

3. Selon les différentes versions de sdk, démarrez le minutage de jobScheduler ou du réveil, et démarrez régulièrement le processus de service

4. Utilisez jetable pour démarrer le processus de service périodiquement, ce qui peut indiquer l'état de service du chien de garde

5. Définissez l'application où se trouve le processus de travail pour qu'il ne soit pas tué

Avec l'enregistrement de lecture du processus de service, le onStart () du chien de garde est très simple, mais je ne comprends pas pourquoi il est nécessaire d'utiliser le jobScheduler et le réveil puisqu'il y a un timing jetable.

Résiliation manuelle ou retrait des services

Lorsque nous terminons manuellement le service de travail ou le chien de garde (quittez dans les paramètres ou ouvrez le gestionnaire de processus pour quitter), les méthodes respectives onDestroy () ou onTaskRemoved () seront rappelées, et l'implémentation de ces deux méthodes dans les deux classes est le même de

   /**
     * 设置-正在运行中停止服务时回调
     */
    @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);
    }


En fait, il ne fait que les redémarrer tous les deux.

Épilogue

En lisant le code source de HelloDaemon, on peut trouver :

1. Le processus de travail et le chien de garde se surveillent et se protègent mutuellement, le processus de travail démarre le chien de garde et le chien de garde démarre le processus de travail régulièrement

2. DaemonEnv garantit que la liaison de chaque service ne sera liée qu'une seule fois et ne sera pas déliée après la liaison

3. La soi-disant résiliation du service n'annule en fait que le début prévu du processus de travail et n'arrête pas directement le processus

4. Si vous souhaitez fermer le processus manuellement, cela ne peut que provoquer le redémarrage du service.

On peut dire qu'il fournit une idée de maintien en vie à double processus.
———————————————

Réimprimé dans : https://blog.csdn.net/qq_37475168/article/details/87921791

Je suppose que tu aimes

Origine blog.csdn.net/weixin_42602900/article/details/123086565
conseillé
Classement