Desarrollo de Android aprendiendo la lectura del código fuente de HelloDaemon

antecedentes

Recientemente, el proceso de mantenimiento de servicio dual se usa en el proyecto de la unidad, el propósito es garantizar que el servicio no se elimine.

Keep-alive de proceso dual en realidad significa que dos procesos se monitorean entre sí y se inician entre sí en sus respectivos métodos de devolución de llamada de destrucción. Existe un buen marco de trabajo de código abierto para mantener vivo el proceso de servicio dual en Internet, llamado HelloDaemon , dirección github:

https://github.com/xingda920813/HelloDaemon
Ahora, registra el proceso de lectura de su código fuente

Lectura de código fuente

AbsTrabajoServicio

Nuestro proceso de trabajo (servicio) debe heredar de AbsWorkService y llamar a los métodos onBind() y onStartCommand() al iniciar

    @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(intent, null) en onBind() está reservado para que las subclases implementen la lógica comercial al vincular. Entonces la clave es el método onStart()

enInicio()

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

Se puede dividir en cuatro pasos:

1. Inicie el perro guardián

2. Detenerse o iniciarse solo

3. Cuando se realiza la primera llamada, se inicia el servicio de primer plano y no se muestra la notificación.

4. Configure la aplicación donde se encuentra el perro guardián para que no se elimine

En primer lugar, se inicia WatchDogService, que es el servicio de vigilancia, que se utiliza para iniciar el proceso de trabajo a intervalos regulares. En cuanto a por qué el método DaemonEnv.startServiceMayBind() se puede llamar arbitrariamente sin temor a que se inicie repetidamente el servicio (de hecho, no teme a la vinculación repetida del servicio), puede hacer clic para ver la implementación del código fuente

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 indica que se inicializa antes de su uso (antes de iniciar el servicio de trabajo) y luego instancia una intención, llamando a startServiceSafely()

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

De hecho, el servicio se inicia.

Regrese a DaemonEnv.startServiceMayBind() para ver cómo maneja el enlace del servicio. Obviamente, DaemonEnv usa un mapeo para guardar el servicio y la conexión correspondiente. Antes de enlazar, verifique si la conexión correspondiente al servicio está vacía o no. Indica que es el primer enlace, de lo contrario no lo es; para el primer servicio de enlace, en la devolución de llamada de la nueva conexión cuando se vincula, la devolución de llamada cuando se conecta es para guardar la relación correspondiente entre el servicio y la conexión, y la devolución de llamada cuando se desconecta es para repetir el proceso startServiceMayBind() para asegurarse de que el servicio vinculado no se desvincule. (Aunque el código en onServiceDisconnected() está escrito en cuatro líneas, es obvio que el proceso se vuelve a repetir comparando con el método DaemonEnv.startServiceMayBind(), pero los datos originales se borran antes de poner el mapeo)

Parada y puesta en marcha de servicios

Volviendo al método onStart() del proceso de trabajo, después de que DaemonEnv.startServiceMayBind() inicia el mecanismo de vigilancia, el servicio se detiene y se inicia según si el servicio se detuvo o no.

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

shouldStopService() se implementa mediante subclases, y luego observamos los métodos stopService() y startService(). El código fuente del método stopService() es el siguiente:

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

stopWork() se deja a la subclase para implementar. El método cancelJobAlarmSub() finaliza la suscripción a jobScheduler, AlaramManager y desechable en forma de envío de una transmisión, que también cancela el inicio de tiempo del proceso de servicio por parte del perro guardián (la apertura no realmente termina el servicio). , pero cancela la guardia)

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

Después de enviar la difusión de la acción especificada, si se va a cancelar el servicio, el receptor llama al método cancelJobAlarmSub() en el mecanismo de vigilancia para finalizar la suscripción a jobScheduler, AlaramManager y desechable.

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

Volviendo al onStart() del proceso de trabajo, el siguiente paso es iniciar el servicio en primer plano y no mostrar la notificación.

Iniciar servicio en primer plano sin mostrar notificación

Si es la primera vez que inicia el servicio de trabajo, configure el servicio como el servicio de primer plano, pero no muestre la notificación. El método específico varía según la versión SDK: para 25 y más, no importa; 18-24, debe llamar a startForeground() dos veces seguidas; 17 y menos, puede llamarlo una vez. Sin embargo, al llamar a startForeground(), asegúrese de que la identificación no sea igual a 0, de lo contrario, no será un servicio de primer plano

Para sdk es 18-24, inicia un WorkNotificationService, de hecho, este servicio es muy 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;
        }
    }

Después de la segunda llamada a startForeground(), se detiene.

Tenga en cuenta que el método onStartCommand() de todos los servicios en HelloDaemon devuelve START_STICK, lo que indica que el servicio se puede activar o detener en cualquier momento y que la intención entrante puede ser nula.

Vuelva a onStart() del proceso de trabajo, al final del primer inicio, establece no eliminar la aplicación donde se encuentra el servicio de vigilancia.

servicio de vigilancia

Acabo de mencionar que el servicio de vigilancia se iniciará cuando se inicie el proceso de trabajo. Echemos un vistazo a los métodos onBind() y onStartCommand() del servicio de vigilancia 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;
    }

Al igual que el proceso de trabajo, llama a su propio método onStart()

enInicio()

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

Se puede dividir en cinco pasos:

1. Determinar si inicializar y si cancelar la suscripción a desechables, en caso afirmativo, volver directamente

2, abra el proceso de primer plano, no muestre la notificación

3. De acuerdo con las diferentes versiones de sdk, inicie el tiempo de jobScheduler o alarm clock e inicie el proceso de servicio regularmente

4. Use desechables para iniciar el proceso de servicio periódicamente, lo que puede indicar el estado del servicio del perro guardián

5. Configure la aplicación donde se encuentra el proceso de trabajo para que no se elimine

Con el registro de lectura del proceso de servicio, el onStart() del watchdog es muy simple, pero no entiendo por qué es necesario usar el jobScheduler y el despertador ya que hay un tiempo desechable.

Terminación manual o retiro de servicios

Cuando finalizamos manualmente el servicio de trabajo o el perro guardián (salimos de la configuración o abrimos el administrador de procesos para salir), se devolverá la llamada a los métodos onDestroy() o onTaskRemoved() respectivos, y la implementación de estos dos métodos en las dos clases es lo mismo 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);
    }


De hecho, solo los está reiniciando a ambos.

Epílogo

Al leer el código fuente de HelloDaemon, podemos encontrar:

1. El proceso de trabajo y el perro guardián se monitorean y protegen entre sí, el proceso de trabajo inicia el perro guardián y el perro guardián inicia el proceso trabajador regularmente

2. DaemonEnv garantiza que la vinculación de cada servicio solo se vinculará una vez y no se desvinculará después de la vinculación.

3. La llamada terminación del servicio en realidad solo cancela el inicio programado del proceso de trabajo y no detiene directamente el proceso.

4. Si desea cerrar el proceso manualmente, solo puede provocar el reinicio del servicio.

Se puede decir que proporciona una idea de mantenimiento de doble proceso.
———————————————

Reimpreso en: https://blog.csdn.net/qq_37475168/article/details/87921791

Supongo que te gusta

Origin blog.csdn.net/weixin_42602900/article/details/123086565
Recomendado
Clasificación