Android APP保活

425 阅读4分钟

一、1像素一直在前台

    private Handler mHandler;
    private boolean isScreenOn = true;
    private PendingIntent  pendingIntent;
    private List<ScreenStateListener> screenStateListeners = null;
    @Override
    public void onReceive(final Context context, Intent intent) {
        String action = intent.getAction();
      
        if (action.equals(Intent.ACTION_SCREEN_OFF)) {
           //标记屏幕为锁屏状态
            isScreenOn = false;
            //开启一像素的Activity
            startOnePixelActivity(context);
 
        } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
            //标记屏幕为解屏状态
            isScreenOn = true;     
            if(pendingIntent!=null){
                pendingIntent.cancel();
            }
     
        }
    }
 
  //开启一像素的Activity
 private void startOnePActivity(final Context context){
        if(mHandler==null){
            mHandler = new Handler(Looper.myLooper());
        }
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
              //如果屏幕此时已经打开,则不执行
                if(isScreenOn){
                    return;
                }
                if(pendingIntent!=null){
                    pendingIntent.cancel();
                }
                
                Intent startOnePixelActivity = new Intent(context, OnePixelActivity.class);
                startOnePixelActivity.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
 
                //启动一像素包活activity
                pendingIntent = PendingIntent.getActivity(context, 0, startOnePixelActivity, 0);
                try {
                    pendingIntent.send();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                notifyScreenOff();
            }
        },1000);
    }

1、启动App(比如进入MainActivity),按home键让app返回后台

2、锁定屏幕,此时注册好的监听广播会启动OnePixelActivity

3、解锁屏幕,此时广播接受到此广播后会finish 掉OnePixelActivity.

4、因为OnePixelActivity是非singleInstance,所以此时本来已经进入后台的MainActivity的页面会自动打开。给用户造成干扰:我明明已经按下home键了,怎么此页面又自动打开了?而换成OnePixelActivity的启动模式改成singleInstance的话就可以很好的避免此问题。

<activity android:name=".OnePActivity"
       
            android:launchMode="singleInstance"
            android:excludeFromRecents="true"/>

5、另外需要注意的是,当屏幕解锁的时候,OnePixelActivity的onResume得到执行,所以在该Activity的onResume方法执行finish效果最好:

   protected void onResume() {
        super.onResume();
        if (DeviceUtils.isScreenOn(this)) {//如果屏幕已经打开
            finish();
        }
    }

二、前台服务

<service
    android:name=".service.BluetoothService"
    android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|layoutDirection|fontScale"
    android:enabled="true"
    android:exported="false"
    android:foregroundServiceType="dataSync"
    android:permission="TODO">
    <intent-filter android:priority="1000">
        <action android:name="com.ispring2.action.MYSERVICE" />

        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>

1、这里有些手机不使用前台服务会造成网络请求失败的情况(红米 K50),

2、Android 要求前台服务要绑定一个通知;

3、START_STICKY粘性返回, 第一次死掉会自己唤醒, 但是往后唤醒时间变长, 最后唤醒会失效.

Intent t1 = new Intent(MainActivity.this, BluetoothService.class);
startForegroundService(t1);
...
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    super.onStartCommand(intent, flags, startId);
    MyLog.e("onStartCommand");
    startNotification();
    startConnThread();//onStartCommand
    return START_STICKY;
}

 
public void startNotification() {
    NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    notificationManager.cancel(InputADBUtils.getInstance().notificationIdInt);
    NotificationChannel channel = new NotificationChannel(InputADBUtils.getInstance().notificationId, getApplication().getString(R.string.app_name), NotificationManager.IMPORTANCE_MIN);
    notificationManager.createNotificationChannel(channel);
    startForeground(InputADBUtils.getInstance().notificationIdInt, getNotification());
}

private Notification getNotification() {
    Notification.Builder builder = null;
    builder = new Notification.Builder(this).setSmallIcon(R.mipmap.ic_launcher_round).setContentText(getString(R.string.notification_tips));
    builder.setChannelId(InputADBUtils.getInstance().notificationId);
    return builder.build();
}
 

三、双进程守护

两个进程的Service,相互守护;当一个进程的Service挂的时候, 另一个进程的Service负责重启挂掉的Service

manifest.xml

<service android:name=".KeepAliveService"/>
//将RemoteService和KeepAliveServcie处于不同的进程
<service android:name=".RemoteService" android:process=":remote"/>

定义一个 AIDL

  interface GuardAidl {
      void notifyAlive();
  }

KeepAliveService.java

/**
 * 播放无声音乐,来保持进程包活
 */
//播放无声音乐,来保持进程保活
public class KeepAliveService extends Service {
    private boolean isScreenON = true;//控制暂停
    private MediaPlayer mediaPlayer;
    //锁屏广播监听
    private ScreenStateReceiver screenStateReceiver;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e("KeepAliveService", "---KeepAliveService 启动---");
        //注册锁屏广播
        registerScreenStateReceiver();
        //开启前台Service
        startForeground(this);
        //start HideNotifactionService
        startHideNotificationService();
        //绑定守护进程
        bindRemoteService();
        return START_STICKY;
    }

    private void bindRemoteService(){
        Intent intent = new Intent(this, RemoteService.class);
        bindService(intent,connection,Context.BIND_ABOVE_CLIENT);
    }
    private void startHideNotificationService() {
        try {
//            if(Build.VERSION.SDK_INT < 25){
            startService(new Intent(this, HideNotificationService.class));
//            }
        } catch (Exception e) {
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    //将keepAliveBinder交给RemoteService
    public IBinder onBind(Intent intent) {
        return keepAliveBinder;
    }

    private GuardAidl.Stub keepAliveBinder = new GuardAidl.Stub() {
        @Override
        public void notifyAlive() throws RemoteException {
            Log.i(null, "Hello RemoteService!");
        }
    };

    private ServiceConnection connection = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Intent remoteService = new Intent(KeepAliveService.this,
                    RemoteService.class);
            KeepAliveService.this.startService(remoteService);

            Intent intent = new Intent(KeepAliveService.this, RemoteService.class);
            //将KeepAliveService和RemoteService进行绑定
            KeepAliveService.this.bindService(intent, connection,
                    Context.BIND_ABOVE_CLIENT);
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            try {
                //与RemoteService绑定成功
                GuardAidl remoteBinder = GuardAidl.Stub.asInterface(service);
                remoteBinder.notifyAlive();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    };

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e("KeepAliveService", "---KeepAliveService onDestroy---");
        if (screenStateReceiver != null) {
            unregisterReceiver(screenStateReceiver);
        }
    }

    public static void startForeground(Service service) {
        Intent intent = new Intent(service.getApplicationContext(), com.fanjun.keeplive.receiver.NotificationClickReceiver.class);
        intent.setAction(com.fanjun.keeplive.receiver.NotificationClickReceiver.CLICK_NOTIFICATION);
        Notification notification = NotificationUtils.createNotification(service, "1", "2", R.drawable.ic_launcher_background, intent);
        service.startForeground(13691, notification);
    }

    private void registerScreenStateReceiver() {
        screenStateReceiver = new ScreenStateReceiver();
        screenStateReceiver.addListener(new ScreenStateReceiver.ScreenStateListener() {
            @Override
            public void screenOn() {
                isScreenON = true;
                Log.e("ScreenStateReceiver", "---音乐关闭---");
                pause();
            }

            @Override
            public void screenOff() {
                isScreenON = false;
                Log.e("ScreenStateReceiver", "---音乐开启---");
                play();
            }
        });
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
        registerReceiver(screenStateReceiver, intentFilter);
    }

    private void initMediaPlayer() {
        if (mediaPlayer == null) {
            mediaPlayer = MediaPlayer.create(this, R.raw.novioce);
            mediaPlayer.setVolume(0f, 0f);
            mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mediaPlayer) {
                    if (isScreenON) {
                        return;
                    }
                    //循环播放
                    play();
                }
            });
        }
        //防止Service启动的时候屏幕处于解锁状态
        if (!DeviceUtils.isScreenOn(this)) {
            play();
        }
    }

    private void play() {
        if (mediaPlayer != null && !mediaPlayer.isPlaying()) {
            mediaPlayer.start();
        }
    }

    private void pause() {
        if (mediaPlayer != null && mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
        }
    }
}
 

RemoteService.java

    public class RemoteService extends Service {
        public IBinder onBind(Intent intent) {
            return remoteBinder;
        }


        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            bindService(new Intent(RemoteService.this, LocalService.class),
                    connection, Context.BIND_ABOVE_CLIENT);
            return super.onStartCommand(intent, flags, startId);
        }


        private ServiceConnection connection = new ServiceConnection() {

            @Override
            public void onServiceDisconnected(ComponentName name) {
                Intent intent = new Intent(RemoteService.this, KeepAliveService.class);
                //将KeepAliveService和RemoteService进行绑定
                RemoteService.this.bindService(intent, connection,
                        Context.BIND_ABOVE_CLIENT);
            }
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                try {
                    //与RemoteService绑定成功
                    GuardAidl remoteBinder = GuardAidl.Stub.asInterface(service);
                    remoteBinder.notifyAlive();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        };
        private GuardAidl.Stub remoteBinder = new GuardAidl.Stub(){
            @Override
            public void notifyAlive() throws RemoteException {
                Log.i(null,"Hello KeepAliveService!");
            }
        };
    }

四、JobService

系统服务 BindService 的方式把应用内 Manifest中 配置的 JobService 启动起来,并通过进程间通信 Binder 方式调用JobService的onStartJob、onStopJob等方法来进行Job的管理。

即便在执行任务之前应用程序进程被杀,也不会导致任务中断,Jobservic e不会因应用退出而退出; 当然 JobScheduler 可以利用这种机制做很多后台定时任务

public class KeepAliveJobService extends JobService {
    private JobScheduler mJobScheduler;
    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        startService(this);
        return false;
    }
 
    @Override
    public boolean onStopJob(JobParameters jobParameters) {
        startService(this);
        return false;
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //启动Servcie 
        startService(this);
        //初始化定时任务
        scheduleJob(startId);
        return START_STICKY;
    }
    
    private void startService(Context context) {
        //设置为前台服务
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Intent intent2 = new Intent(getApplicationContext(), com.fanjun.keeplive.receiver.NotificationClickReceiver.class);
            intent2.setAction(NotificationClickReceiver.CLICK_NOTIFICATION);
            Notification notification = NotificationUtils.createNotification(this, "1", "2", R.drawable.ic_launcher_background, intent2);
            startForeground(13691, notification);
        }
        //启动本地服务
        Intent keepAliveIntent= new Intent(context, KeepAliveService.class);
        //启动守护进程
        Intent guardIntent = new Intent(context, RemoteService.class);
        startService(keepAliveIntent);
        startService(guardIntent);
    }
    
    private void scheduleJob(int startId) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
            JobInfo.Builder builder = new JobInfo.Builder(startId++,
                    new ComponentName(getPackageName(), KeepAliveJobService.class.getName()));
            if (Build.VERSION.SDK_INT >= 24) {
                builder.setMinimumLatency(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS); //执行的最小延迟时间
                builder.setOverrideDeadline(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS);  //执行的最长延时时间
                builder.setMinimumLatency(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS);
                builder.setBackoffCriteria(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS, JobInfo.BACKOFF_POLICY_LINEAR);//线性重试方案
            } else {
                builder.setPeriodic(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS);
            }
            builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
            builder.setRequiresCharging(true); // 当插入充电器,执行该任务
            mJobScheduler.schedule(builder.build());
        }
    }

启动方式:

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                //启动定时器,在定时器中启动本地服务和守护进程
                Intent intent = new Intent(application, JobHandlerService.class);
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    application.startForegroundService(intent);
                } else {
                    application.startService(intent);
                }
            } else {
                //启动本地服务
                Intent localIntent = new Intent(application, LocalService.class);
                //启动守护进程
                Intent guardIntent = new Intent(application, RemoteService.class);
                application.startService(localIntent);
                application.startService(guardIntent);
            }