Android后台单进程保活处理实践

0x00、如何实现一个后台进程

  1. 自定义的 Service,实现相应的业务逻辑

  2. Manifest 文件中配置 Service 组件,需配置 process 属性,然后指定 intent-filterpriority 属性,如下示例:

    <service
    	android:name="com.stfl.SSClientService"
    	android:process=":ssclient">
    			<intent-filter android:priority="1000">
    		<action android:name="io.telebox.ssclient.service" />
    	</intent-filter>
    </service>
    

    priority 属性值越大,优先级越高。

0x01、如何重启进程

  1. 重写 ServiceOnDestroy 方法。主要逻辑是监听到 Service 被杀掉后,发送一个 Broadcast 通知,重新启动 Service 进程。根据不同的业务逻辑,可以选择是否调用 killProcess 方法,彻底清理所有进程资源。可参考如下:

    	@Override
    	public void onDestroy() {
            super.onDestroy();
            logger.info("SSClient is stop!");
            // 根据业务需要决定是否调用killProcess,这里我是推荐调用此方法,清理资源。
            Process.killProcess(Process.myPid());
            // 服务关掉通知
            Intent intent = new Intent(SSClientReceiver.ACTION_DISCONNECTED);
            intent.setComponent(new ComponentName(getPackageName(), SSClientReceiver.class.getName()));
            sendBroadcast(intent);
    	}
       
    

    需要注意的是,需要设置 component 否则在8.0手机会出现 Background execution not allowed的错误。

0x02、实现监听进程通知

  1. 继承 BroadcastReceiver 并实现 onReceive()方法。实现参考:

    	@Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action == null) {
                action = "";
            }
            switch (action) {
                case ACTION_START:
                    Log.d("SSClient", "start by receiver.");
                    SSClientService.startSService(context);
                    break;
                case ACTION_DISCONNECTED:
                    Log.d("SSClient", "disconnected,restart by receiver.");
                    SSClientService.startSService(context);
                    onSSClientDisconnected();
                    break;
                case ACTION_CONNECTED:
                    onSSClientConnected();
                    break;
            }
        }
    

    在接收到ACTION_STARTACTION_DISCONNECTED时,就调用启动Service的方法。

  2. Manifest 文件中配置 Broadcast ,示例参考:

    	<receiver
    		android:name="com.stfl.SSClientReceiver"
    		android:enabled="true">
    		<intent-filter>
    			<action android:name="io.telebox.ssclient.start" />
    		</intent-filter>
    	</receiver>
    

0x03、需要注意的问题

  1. 配置Service优先级priority属性,尽量提高Service的优先级

  2. Android 8.0手机发送通知时会出现 Background execution not allowed的错误,intent要设置component。

    Intent intent = new Intent("receiver-action-string");
    intent.setComponent(new ComponentName(getPackageName(),"receiver-class-name"));
    sendBroadcast(intent);
    
  3. Android 8.0后台服务启动时出现java.lang.IllegalStateException: Not allowed to start service Intent错误。

    // 1.启动Service的方法中修改为
    Intent service = new Intent(SSClientService.ACTION);
    service.setPackage(context.getPackageName());
    // kill:java.lang.IllegalStateException: Not allowed to start service Intent
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    	context.startForegroundService(service);
    } else {
        context.startService(service);
    }
    // 2.在Service类中调用startForeground方法
    public void onCreate() {
    	super.onCreate();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        	startForeground(Integer.MAX_VALUE, new Notification());
           	//这个id不要和应用内的其他同志id一样,不行就写 int.maxValue()
          	//context.startForeground(SERVICE_ID, builder.getNotification());
        }
    }
    
  4. Android 8.1出现android.app.RemoteServiceException: Bad notification for startForeground

    修改startForeground方法。

    	@TargetApi(Build.VERSION_CODES.O)
        private void startForeground() {
            int nid = 1010;
            String channelId = createNotificationChannel();
       
            Notification.Builder builder = new Notification.Builder(this, channelId);
            Notification notification = builder.setOngoing(false)
                    .setSmallIcon(R.mipmap.ic_small_logo)
                    .setPriority(Notification.PRIORITY_MIN)
       
                    .setCategory(Notification.CATEGORY_SERVICE)
                    .build();
            startForeground(nid, notification);
        }
       
        @TargetApi(Build.VERSION_CODES.O)
        private String createNotificationChannel() {
            String channelId = "my_service";
            String channelName = "My Background Service";
            NotificationChannel chan = new NotificationChannel(channelId,
                    channelName, NotificationManager.IMPORTANCE_NONE);
            chan.setLightColor(Color.BLUE);
            chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
            NotificationManager service = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            service.createNotificationChannel(chan);
            return channelId;
        }
    

Powered by Jekyll and Theme by solid