Android:后台保活

1. 进程是怎么死的

  1. 系统资源不足回收
  2. 第三方安全软件杀死
  3. 用户在设置页面强制结束
  4. 用户在设置页面的正在运行中结束服务
  5. 一键清理最近任务列表

2. 进程保活的手段

这里有一个非常不错的博客+github开源项目,里面详细介绍了6.0以下能确保不死的方法 博客地址

目前比较流行的方案有:

  1. 将服务设置为前台进程。这样可以大大得提高进程的优先级,可以大大的降低被系统回收的概率。缺点是前台服务会有一个去不掉通知出现在通知栏,不过在7.0以下可以利用BUG去掉这个通知。
  2. 后台播放一段无声的音乐,在程序从后台切入前台时停止播放,从前台切入后台时开始播放。提高进程的优先级
  3. 定时器定时唤醒,在5.0以上使用JobService,5.0以下使用AlarmManager。这两种方式都可以自动获取到唤醒锁。
  4. 双进程互相守护,两个进程互相绑定,当某一个进程判断与对方连接断开时,立马开启对方并且继续绑定
  5. 锁屏时启动一像素Activity,解锁时关闭这个Activity。用于提高锁屏状态下的进程优先级
  6. SDK互相拉活,比如集成了个推SDK的应用,只要有一个还存活,那么会立马拉起其他所有集成了个推SDK的应用
  7. 监听触发频繁的系统静态广播,这个在高版本直接无效,很多广播无法静态注册,并且被杀死的程序不会收到静态广播
  8. 设置厂商的白名单,可以确保不会被杀死

3. 目前采用的几种方案

目前采用了前台服务进程、后台音乐、定时器唤醒、双进程守护、锁屏唤起一像素Activity 五种保活手段

A. 首先给出一些常量的定义,最重要的就是这三个定义

  1. OPEN_FOREGROUND_SERVICE:是否开启前台服务,因为在7.0以下可以通过BUG来去掉前台服务的通知,所以这里就把开关设置为7.0以下。如果你的程序能够容忍这个通知,可以把这个值设置为常量true
  2. OPEN_JOB_SCHEDULER_OR_ALARM_MANAGER:是开启JobService定时器还是开启AlarmManager定时器。因为5.0以上JobService的效果比AlarmManager好,所以这里就把开关设置为5.0以上
  3. MATCH_JOB_SCHEDULER_7:是否手动完成JobService的定时器效果,7.0以上默认定时器的间隔时间最短为15分钟,所以这里需要手动地处理这种情况
public class KeepAliveConstants {

    public static final int HIDE_FOREGROUND_SERVICE_STOP_DELAY = 2000; // 隐藏前台通知的延迟时间
    public static final int FOREGROUND_NOTIFICATION_ID = 13691; // 前台通知ID
    public static final String REMOTE_PROCESS_NAME = ":keepalive"; // 远程保活进程名
    public static final long INTERVAL_WAKEUP = 30000L;

    public static final String ACTION_PLAY_MUSIC_ON = "ACTION_PLAY_MUSIC_ON"; // 音乐开启广播
    public static final String ACTION_PLAY_MUSIC_OFF = "ACTION_PLAY_MUSIC_OFF"; // 音乐关闭广播
    public static final String ACTION_FINISH_ONE_PIXEL_ACTIVITY = "ACTION_FINISH_ONE_PIXEL_ACTIVITY"; // 一像素界面关闭广播

    public static String FOREGROUND_NOTIFICATION_TITLE = ""; // 前台通知的标题
    public static String FOREGROUND_NOTIFICATION_DESCRIPTION = ""; // 前台通知的描述
    public static int FOREGROUND_NOTIFICATION_ICON_ID = 0; // 前台通知的图标

    // 7.0以下才开启前台服务
    public static boolean OPEN_FOREGROUND_SERVICE = Build.VERSION.SDK_INT <= Build.VERSION_CODES.N;
    // 5.0以上开启 job scheduler,5.0以下开启 alarm manager
    public static boolean OPEN_JOB_SCHEDULER_OR_ALARM_MANAGER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
    // 7.0以上需要单独适配 job scheduler
    public static boolean MATCH_JOB_SCHEDULER_7 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;

    public static final boolean MEDIA_PLAYER_POWER = true; // 后台音乐播放省电模式
    public static final int MEDIA_PLAYER_DELAY = 10000; // 后台音乐播放间隔时间

}

B. 启用定时器唤醒

程序的入口非常简单,就是根据不同的版本起不同的服务。特别注意这个方法只能被调用一次

public class JuMeiStrategy implements IKeepAliveStrategy {

    private static boolean isInit = false;

    @Override
    public void keepAlive(Context context) {
        if (isInit) {
            return;
        }

        try {
            if (KeepAliveConstants.OPEN_JOB_SCHEDULER_OR_ALARM_MANAGER) {
                LogUtils.i("JuMeiStrategy 大于等于5.0,开启 JobScheduler");
                // 用 job scheduler 的方式
                Intent intent = new Intent(context, JobHandlerService.class);
                Utils.startServiceSafety(context, intent);
            } else {
                LogUtils.i("JuMeiStrategy 小于5.0,开启 AlarmManager");
                // 用 alarm manager 的方式
                Intent intent = new Intent(context, AlarmHandlerService.class);
                Utils.startServiceSafety(context, intent);
            }
            isInit = true;
        } catch (Exception e) {
            e.printStackTrace();
            isInit = false;
        }
    }

}

大于5.0,则启动 JobHandlerService 服务,采用的是 JobService 的定时方式。

  1. 首先调用 startService,开启双进程守护。这个后面再说
  2. 开启 JobService,定时调用 startService,如果本地服务或远程服务被杀掉了,则立马启动起来
  3. 内部针对7.0以上做了手动定时的效果
  4. 开启前台服务,这个后面再说
@SuppressWarnings(value = {"unchecked", "deprecation"})
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public final class JobHandlerService extends JobService {

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        LogUtils.i("JobHandlerService 启动");
        startService(this);
        startJobSchedulerSafety();
        return START_STICKY;
    }

    private void startJobSchedulerSafety() {
        try {
            JobScheduler mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
            if (mJobScheduler == null) {
                return;
            }
            JobInfo.Builder builder = new JobInfo.Builder(new Random().nextInt(),
                    new ComponentName(getPackageName(), JobHandlerService.class.getName()));
            // 7.0 以上设置 setPeriodic 默认最短时间间隔是 15分钟,因此用 setMinimumLatency 手动实现定时效果
            if (KeepAliveConstants.MATCH_JOB_SCHEDULER_7) {
                LogUtils.i("JobHandlerService 大于等于7.0,手动实现定时器效果");
                builder.setMinimumLatency(KeepAliveConstants.INTERVAL_WAKEUP); //执行的最小延迟时间
                builder.setOverrideDeadline(KeepAliveConstants.INTERVAL_WAKEUP);  //执行的最长延时时间
                builder.setMinimumLatency(KeepAliveConstants.INTERVAL_WAKEUP);
                builder.setBackoffCriteria(KeepAliveConstants.INTERVAL_WAKEUP, JobInfo.BACKOFF_POLICY_LINEAR);//线性重试方案
            } else {
                LogUtils.i("JobHandlerService 大于5.0小于7.0,自动实现定时器效果");
                builder.setPeriodic(KeepAliveConstants.INTERVAL_WAKEUP);
            }
            builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
            builder.setRequiresCharging(true); // 当插入充电器,执行该任务
            mJobScheduler.schedule(builder.build());
            LogUtils.i("JobHandlerService 开启Job计划");
        } catch (Exception e) {
            e.printStackTrace();
            LogUtils.i("JobHandlerService 开启Job计划失败");
        }
    }

    private void startService(Context context) {
        if (Utils.isServiceRunning(getApplicationContext(), LocalService.class.getName())
                && Utils.isRunningTaskExist(getApplicationContext(), getPackageName() + KeepAliveConstants.REMOTE_PROCESS_NAME)) {
            return;
        }
        if (KeepAliveConstants.OPEN_FOREGROUND_SERVICE) {
            LogUtils.i("JobHandlerService 开启前台服务");
            Utils.startForegroundSafety(this);
        }
        //启动本地服务
        Intent localIntent = new Intent(context, LocalService.class);
        Utils.startServiceSafety(context, localIntent);
        //启动守护进程
        Intent guardIntent = new Intent(context, RemoteService.class);
        Utils.startServiceSafety(context, guardIntent);
        LogUtils.i("JobHandlerService 开启LocalService和RemoteService");
    }

    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        LogUtils.i("JobHandlerService 开始Job");
        startService(this);
        // 7.0 以上手动重复执行
        if (KeepAliveConstants.MATCH_JOB_SCHEDULER_7) {
            startJobSchedulerSafety();
        }
        jobFinished(jobParameters, false);
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters jobParameters) {
        LogUtils.i("JobHandlerService 结束job");
        startService(this);
        return false;
    }

}

针对5.0以下的手机,则启动 AlarmHandlerService,采用 AlarmManager 定时器的效果

  1. 首先调用 startService 进行双进程守护。这个后面再说
  2. 只在第一次开启 AlarmManager 进行定时调用 startService,如果本地服务或远程服务被杀掉了,则立马启动起来
  3. 开启前台服务,这个后面再说
public class AlarmHandlerService extends Service {

    private static final int ALARM_MANAGER_REQUEST_CODE = 1000;
    private boolean hasStartAlarmManager;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        startService(this);
        if (!hasStartAlarmManager) {
            LogUtils.i("AlarmHandlerService 启动");
            startAlarmManagerSafety();
            hasStartAlarmManager = true;
        } else {
            LogUtils.i("AlarmHandlerService 开始调度");
        }
        return START_STICKY;
    }

    private void startAlarmManagerSafety() {
        try {
            AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
            if (am == null) {
                return;
            }
            Intent intent = new Intent(this, AlarmHandlerService.class);
            PendingIntent pendingIntent = PendingIntent.getService(this, ALARM_MANAGER_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
            if (pendingIntent == null) {
                return;
            }
            am.setRepeating(AlarmManager.RTC_WAKEUP,
                    System.currentTimeMillis() + KeepAliveConstants.INTERVAL_WAKEUP,
                    KeepAliveConstants.INTERVAL_WAKEUP, pendingIntent);
            LogUtils.i("AlarmHandlerService 开启AlarmManager定时器");
        } catch (Exception e) {
            e.printStackTrace();
            LogUtils.i("AlarmHandlerService 开启AlarmManager定时器失败");
        }
    }

    private void startService(Context context) {
        if (Utils.isServiceRunning(getApplicationContext(), LocalService.class.getName())
                && Utils.isRunningTaskExist(getApplicationContext(), getPackageName() + KeepAliveConstants.REMOTE_PROCESS_NAME)) {
            return;
        }
        if (KeepAliveConstants.OPEN_FOREGROUND_SERVICE) {
            LogUtils.i("AlarmHandlerService 开启前台服务");
            Utils.startForegroundSafety(this);
        }
        //启动本地服务
        Intent localIntent = new Intent(context, LocalService.class);
        Utils.startServiceSafety(context, localIntent);
        //启动守护进程
        Intent guardIntent = new Intent(context, RemoteService.class);
        Utils.startServiceSafety(context, guardIntent);
        LogUtils.i("AlarmHandlerService 开启LocalService和RemoteService");
    }

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

C. 采用双进程守护的方式

首先定义进程间通信的 aidl 文件

package com.jm.android.jmkeepalive.aidl;

interface GuardAidl {
    //相互唤醒服务
    void wakeUp();
}

然后就是本地服务 LocalService,这个服务做了以下的事情

  1. 刚开启时,立马绑定远程服务 RemoteService
  2. 绑定远程服务成功后,调用远程服务的接口
  3. 初始化音乐播放,并在合适的时候播放音乐。这个后面再说
  4. 注册屏幕广播,为了锁屏时开启一像素Activity。这个后面再说
  5. 自己开启前台服务。这个后面再说
  6. 当检测到与远程服务 RemoteService 断开时,重新启动并绑定远程服务
public final class LocalService extends Service {
    private ScreenReceiver mScreenReceiver;
    private MediaPlayerStatusReceiver mMediaPlayerStateReceiver;

    private MyBilder mBilder;
    private MediaPlayerUtil mediaPlayerUtil;

    @Override
    public void onCreate() {
        super.onCreate();
        if (mBilder == null) {
            mBilder = new MyBilder();
        }
    }

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        LogUtils.i("LocalService 开启,初始化音乐");
        //播放无声音乐
        if (mediaPlayerUtil == null) {
            mediaPlayerUtil = new MediaPlayerUtil(this);
        }
        // 如果是被拉活状态,那么判断是否在后台
        if (mediaPlayerUtil.isInitSuccess() && !Utils.isForeground(this)) {
            mediaPlayerUtil.play();
        }

        //像素保活
        if (mScreenReceiver == null) {
            mScreenReceiver = new ScreenReceiver();
        }
        IntentFilter screenFilter = new IntentFilter();
        screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
        screenFilter.addAction(Intent.ACTION_SCREEN_ON);
        Utils.registerBroadcastReceiverSafety(this, mScreenReceiver, screenFilter);

        //屏幕点亮状态监听,用于单独控制音乐播放
        if (mMediaPlayerStateReceiver == null) {
            mMediaPlayerStateReceiver = new MediaPlayerStatusReceiver();
        }
        IntentFilter mediaStatusFilter = new IntentFilter();
        mediaStatusFilter.addAction(KeepAliveConstants.ACTION_PLAY_MUSIC_ON);
        mediaStatusFilter.addAction(KeepAliveConstants.ACTION_PLAY_MUSIC_OFF);
        Utils.registerBroadcastReceiverSafety(this, mMediaPlayerStateReceiver, mediaStatusFilter);

        //启用前台服务,提升优先级
        if (KeepAliveConstants.OPEN_FOREGROUND_SERVICE) {
            LogUtils.i("LocalService 开启前台服务");
            Utils.startForegroundSafety(this);
        }

        //绑定守护进程
        Intent remoteServiceIntent = new Intent(this, RemoteService.class);
        Utils.bindServiceSafety(this, remoteServiceIntent, connection);
        LogUtils.i("LocalService 绑定到 RemoteService");

        return START_STICKY;
    }

    private class MediaPlayerStatusReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(final Context context, Intent intent) {
            if (TextUtils.equals(intent.getAction(), KeepAliveConstants.ACTION_PLAY_MUSIC_ON)) {
                LogUtils.i("LocalService 收到播放音乐广播");
                if (mediaPlayerUtil != null && mediaPlayerUtil.isInitSuccess()) {
                    mediaPlayerUtil.setPause(false);
                    mediaPlayerUtil.play();
                }
            } else if (TextUtils.equals(intent.getAction(), KeepAliveConstants.ACTION_PLAY_MUSIC_OFF)) {
                LogUtils.i("LocalService 收到暂停音乐广播");
                if (mediaPlayerUtil != null && mediaPlayerUtil.isInitSuccess()) {
                    mediaPlayerUtil.setPause(true);
                    mediaPlayerUtil.pause();
                }
            }
        }
    }

    private final class MyBilder extends GuardAidl.Stub {

        @Override
        public void wakeUp() throws RemoteException {

        }
    }

    private ServiceConnection connection = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
            LogUtils.i("LocalService 断开与 RemoteService 的连接");
            Intent remoteServiceIntent = new Intent(LocalService.this, RemoteService.class);
            Utils.startServiceSafety(LocalService.this, remoteServiceIntent);
            Utils.bindServiceSafety(LocalService.this, remoteServiceIntent, connection);

            boolean isForeground = Utils.isForeground(LocalService.this);
            Intent it = new Intent(isForeground ? KeepAliveConstants.ACTION_PLAY_MUSIC_OFF : KeepAliveConstants.ACTION_PLAY_MUSIC_ON);
            Utils.sendBroadcastSafety(LocalService.this, it);
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            LogUtils.i("LocalService 与 RemoteService 连接成功,通知开启前台服务");
            try {
                GuardAidl guardAidl = GuardAidl.Stub.asInterface(service);
                guardAidl.wakeUp();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    };

    @Override
    public void onDestroy() {
        super.onDestroy();
        LogUtils.i("LocalService 停止,解绑 RemoteService");
        unbindService(connection);
        unregisterReceiver(mScreenReceiver);
        unregisterReceiver(mMediaPlayerStateReceiver);
        if (mediaPlayerUtil != null) {
            mediaPlayerUtil.destory();
            mediaPlayerUtil = null;
        }
    }
}

然后就是远程服务 RemoteService,做了以下的事情

  1. 启动时立马绑定本地服务 LocalService
  2. 收到本地服务 LocalService 的方法调用时,开启前台服务。这个后面再说
  3. 当检测到与本地服务 LocalService 断开时,立马启动并绑定本地服务。
@SuppressWarnings(value = {"unchecked", "deprecation"})
public final class RemoteService extends Service {

    private MyBilder mBilder;


    @Override
    public void onCreate() {
        super.onCreate();
        if (mBilder == null) {
            mBilder = new MyBilder();
        }
    }

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        LogUtils.i("RemoteService 开启,绑定到 LocalService");
        Intent it = new Intent(this, LocalService.class);
        Utils.bindServiceSafety(this, it, connection);
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        LogUtils.i("RemoteService 停止,解绑 LocalService");
        Utils.unbindServiceSafety(this, connection);
    }

    private final class MyBilder extends GuardAidl.Stub {

        @Override
        public void wakeUp() throws RemoteException {
            if (KeepAliveConstants.OPEN_FOREGROUND_SERVICE) {
                // 开启前台服务
                LogUtils.i("RemoteService 开启前台服务");
                Utils.startForegroundSafety(RemoteService.this);
                //隐藏服务通知
                LogUtils.i("RemoteService 开启隐藏前台通知服务");
                Intent hideForegroundIntent = new Intent(RemoteService.this, HideForegroundService.class);
                Utils.startServiceSafety(RemoteService.this, hideForegroundIntent);
            }
        }

    }

    private final ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
            LogUtils.i("RemoteService 断开与 LocalService 的连接");
            Intent localServiceIntent = new Intent(RemoteService.this, LocalService.class);
            Utils.startServiceSafety(RemoteService.this, localServiceIntent);
            Utils.bindServiceSafety(RemoteService.this, localServiceIntent, connection);

            boolean isForeground = Utils.isForeground(RemoteService.this);
            Intent it = new Intent(isForeground ? KeepAliveConstants.ACTION_PLAY_MUSIC_OFF : KeepAliveConstants.ACTION_PLAY_MUSIC_ON);
            Utils.sendBroadcastSafety(RemoteService.this, it);
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            LogUtils.i("RemoteService 与 LocalService 连接成功");
        }
    };

}

C. 后台播放音乐

首先利用 Application 来判断当前程序是否进入了后台,并发送相应的广播

context.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {

    private int activityCount = 0;

    @Override
    public void onActivityCreated(Activity activity, Bundle bundle) {

    }

    @Override
    public void onActivityStarted(Activity activity) {
        activityCount++;
        if(activityCount == 1){
            LogUtils.i("从后台切入前台,发送停止播放音乐广播");
            Utils.sendBroadcastSafety(mContext, new Intent(KeepAliveConstants.ACTION_PLAY_MUSIC_OFF));
        }
    }

    @Override
    public void onActivityResumed(Activity activity) {

    }

    @Override
    public void onActivityPaused(Activity activity) {

    }

    @Override
    public void onActivityStopped(Activity activity) {
        activityCount--;
        if (activityCount == 0) {
            LogUtils.i("从前台切入后台,发送播放音乐广播");
            Utils.sendBroadcastSafety(mContext, new Intent(KeepAliveConstants.ACTION_PLAY_MUSIC_ON));
        }
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {

    }

    @Override
    public void onActivityDestroyed(Activity activity) {

    }
});

然后定义了音乐播放的封装类

public class MediaPlayerUtil {

    private MediaPlayer mediaPlayer;
    private Handler handler;
    private boolean isPause;//控制暂停
    private boolean isInitSuccess;

    public MediaPlayerUtil(Context context) {
        if (handler == null) {
            handler = new Handler();
        }
        try {
            if (mediaPlayer == null) {
                mediaPlayer = MediaPlayer.create(context, R.raw.novioce);
            }
            if (mediaPlayer == null) {
                isInitSuccess = false;
                return;
            }
            mediaPlayer.setVolume(0f, 0f);
            mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mediaPlayer) {
                    if (isPause) {
                        LogUtils.i("MediaPlayerUtil 播放结束,但是被暂停了,不继续播放");
                        return;
                    }
                    if (KeepAliveConstants.MEDIA_PLAYER_POWER) {
                        if (handler != null) {
                            LogUtils.i("MediaPlayerUtil 播放结束,省电模式,延迟10秒播放");
                            handler.postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    if (isPause) {
                                        LogUtils.i("MediaPlayerUtil 延迟结束准备播放,但是被暂停了,不继续播放");
                                        return;
                                    }
                                    play();
                                }
                            }, KeepAliveConstants.MEDIA_PLAYER_DELAY);
                        }
                    } else {
                        LogUtils.i("MediaPlayerUtil 播放结束,不省电,继续播放");
                        play();
                    }
                }
            });
            isInitSuccess = true;
        } catch (Exception e) {
            e.printStackTrace();
            isInitSuccess = false;
        }
    }

    public void play() {
        if (isInitSuccess && mediaPlayer != null && !mediaPlayer.isPlaying()) {
            try {
                mediaPlayer.start();
                LogUtils.i("MediaPlayerUtil 开始播放");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void pause() {
        if (isInitSuccess && mediaPlayer != null && mediaPlayer.isPlaying()) {
            try {
                mediaPlayer.pause();
                LogUtils.i("MediaPlayerUtil 暂停播放");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void setPause(boolean pause) {
        isPause = pause;
    }

    public boolean isInitSuccess() {
        return isInitSuccess;
    }

    public void destory() {
        try {
            if (mediaPlayer != null) {
                mediaPlayer.stop();
                mediaPlayer.reset();
                mediaPlayer.release();
                mediaPlayer = null;
                isInitSuccess = false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

然后在初始化本地服务 LocalService 时,初始化音乐播放器,并且判断当前如果已经进入了后台的话,则立刻播放音乐。这是因为如果程序已经在后台被杀死时,远程服务会重启本地服务,此时程序就处于后台,就应该播放音乐来继续保活。

//播放无声音乐
if (mediaPlayerUtil == null) {
    mediaPlayerUtil = new MediaPlayerUtil(this);
}
// 如果是被拉活状态,那么判断是否在后台
if (mediaPlayerUtil.isInitSuccess() && !Utils.isForeground(this)) {
    mediaPlayerUtil.play();
}

然后定义一个广播接收者,控制音乐的播放

private class MediaPlayerStatusReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(final Context context, Intent intent) {
        if (TextUtils.equals(intent.getAction(), KeepAliveConstants.ACTION_PLAY_MUSIC_ON)) {
            LogUtils.i("LocalService 收到播放音乐广播");
            if (mediaPlayerUtil != null && mediaPlayerUtil.isInitSuccess()) {
                mediaPlayerUtil.setPause(false);
                mediaPlayerUtil.play();
            }
        } else if (TextUtils.equals(intent.getAction(), KeepAliveConstants.ACTION_PLAY_MUSIC_OFF)) {
            LogUtils.i("LocalService 收到暂停音乐广播");
            if (mediaPlayerUtil != null && mediaPlayerUtil.isInitSuccess()) {
                mediaPlayerUtil.setPause(true);
                mediaPlayerUtil.pause();
            }
        }
    }
}

当本地服务 LocalService 或远程服务 RemoteService 与对方断开时,根据处于前台或后台的情况,决定发送播放或暂停的广播

boolean isForeground = Utils.isForeground(LocalService.this);
Intent it = new Intent(isForeground ? KeepAliveConstants.ACTION_PLAY_MUSIC_OFF : KeepAliveConstants.ACTION_PLAY_MUSIC_ON);
Utils.sendBroadcastSafety(LocalService.this, it);

在 onDestory 中,销毁音乐播放器和对应的广播接收者

unregisterReceiver(mMediaPlayerStateReceiver);
if (mediaPlayerUtil != null) {
    mediaPlayerUtil.destory();
    mediaPlayerUtil = null;
}

D. 锁屏开启一像素Activity

首先创建监听锁屏的广播。屏幕关闭则开启一像素Activity,屏幕亮起则发送关闭Activity的广播

@SuppressWarnings(value = {"unchecked", "deprecation"})
public final class ScreenReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(final Context context, Intent intent) {
        if (TextUtils.equals(intent.getAction(), Intent.ACTION_SCREEN_OFF)) {    //屏幕关闭的时候接受到广播
            // 开启一像素Activity
            LogUtils.i("ScreenReceiver 屏幕熄灭,开启一像素Activity");
            Intent it = new Intent(context, OnePixelActivity.class);
            it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            it.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
            Utils.startActivitySafety(context, it);
        } else if (TextUtils.equals(intent.getAction(), Intent.ACTION_SCREEN_ON)) {   //屏幕打开的时候发送广播  结束一像素
            // 结束一像素Activity
            LogUtils.i("ScreenReceiver 屏幕亮起,发送关闭一像素Activity广播");
            Utils.sendBroadcastSafety(context, new Intent(KeepAliveConstants.ACTION_FINISH_ONE_PIXEL_ACTIVITY));
        }
    }

}

然后申明一像素Activity,并在启动时判断当前屏幕是否已经亮起,如果已经亮起则立马结束该Activity。解决广播延迟带来的交互上的问题

public final class OnePixelActivity extends Activity {
    //注册广播接受者   当屏幕开启结果成功结束一像素的activity
    private BroadcastReceiver br;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LogUtils.i("OnePixelActivity 启动一像素Activity");
        //设定一像素的activity
        setOnePixel();
        //在一像素activity里注册广播接受者    接受到广播结束掉一像素
        br = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                LogUtils.i("OnePixelActivity 接受到广播,关闭一像素Activity");
                Utils.finishSafety(OnePixelActivity.this);
            }
        };
        Utils.registerBroadcastReceiverSafety(this, br, new IntentFilter(KeepAliveConstants.ACTION_FINISH_ONE_PIXEL_ACTIVITY));
        checkScreenOn();
    }

    private void setOnePixel(){
        Window window = getWindow();
        window.setGravity(Gravity.START | Gravity.TOP);
        WindowManager.LayoutParams params = window.getAttributes();
        params.x = 0;
        params.y = 0;
        params.height = 1;
        params.width = 1;
        window.setAttributes(params);
    }

    @Override
    protected void onDestroy() {
        Utils.unregisterBroadcastReceiverSafety(this, br);
        super.onDestroy();
    }

    @Override
    protected void onResume() {
        super.onResume();
        checkScreenOn();
    }

    private void checkScreenOn() {
        if (Utils.isScreenOn(this)) {
            LogUtils.i("OnePixelActivity 关闭一像素Activity");
            Utils.finishSafety(this);
        }
    }
}

E. 开启前台服务

开启的方法很简单,前面所有的服务都有对应的代码

if (KeepAliveConstants.OPEN_FOREGROUND_SERVICE) {
    LogUtils.i("LocalService 开启前台服务");
    Utils.startForegroundSafety(this);
}

然后利用7.0之前的BUG,新启一个服务并用同一个 id 开启前台服务,然后立刻关闭前台服务,就能将通知栏上的通知去掉。首先定义这个需要隐藏通知的服务

public class HideForegroundService extends Service {

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        LogUtils.i("隐藏前台服务通知启动");
        Utils.startForegroundSafety(this);
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                LogUtils.i("隐藏前台服务结束");
                Utils.stopForegroundSafety(HideForegroundService.this, true);
                Utils.stopServiceSafety(HideForegroundService.this);
            }
        }, KeepAliveConstants.HIDE_FOREGROUND_SERVICE_STOP_DELAY);
        return START_NOT_STICKY;
    }

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

然后在所有服务都开启过前台服务之后,开启这个隐藏通知的服务。这里选择本地服务 LocalService 调用远程服务 RemoteService 的方法时触发

private final class MyBilder extends GuardAidl.Stub {

    @Override
    public void wakeUp() throws RemoteException {
        if (KeepAliveConstants.OPEN_FOREGROUND_SERVICE) {
            // 开启前台服务
            LogUtils.i("RemoteService 开启前台服务");
            Utils.startForegroundSafety(RemoteService.this);
            //隐藏服务通知
            LogUtils.i("RemoteService 开启隐藏前台通知服务");
            Intent hideForegroundIntent = new Intent(RemoteService.this, HideForegroundService.class);
            Utils.startServiceSafety(RemoteService.this, hideForegroundIntent);
        }
    }

}

F. 代码中所有安全启动Activity、启动与绑定Service,安全注册与发送广播等工具类

相关工具类

public class Utils {

    public static boolean isMainProcess(Context context) {
        int pid = android.os.Process.myPid();
        String processName = "";
        ActivityManager mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        if (mActivityManager == null) {
            return false;
        }
        for (ActivityManager.RunningAppProcessInfo appProcess : mActivityManager.getRunningAppProcesses()) {
            if (appProcess.pid == pid) {
                processName = appProcess.processName;
                break;
            }
        }
        String packageName = context.getPackageName();
        return TextUtils.equals(processName, packageName);
    }

    public static void startForegroundSafety(Service service) {
        if (service == null) {
            return;
        }
        try {
            Intent intent = new Intent(service.getApplicationContext(), NotificationClickReceiver.class);
            intent.setAction(NotificationClickReceiver.CLICK_NOTIFICATION);
            Notification notification = NotificationUtils.createNotification(
                    service.getApplicationContext(),
                    KeepAliveConstants.FOREGROUND_NOTIFICATION_TITLE,
                    KeepAliveConstants.FOREGROUND_NOTIFICATION_DESCRIPTION,
                    KeepAliveConstants.FOREGROUND_NOTIFICATION_ICON_ID, intent);
            service.startForeground(KeepAliveConstants.FOREGROUND_NOTIFICATION_ID, notification);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void stopForegroundSafety(Service service, boolean removeNotification) {
        if (service == null) {
            return;
        }
        try {
            service.stopForeground(removeNotification);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void startServiceSafety(Context context, Intent intent) {
        if (context == null || intent == null) {
            return;
        }
        try {
            context.startService(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void stopServiceSafety(Service service) {
        if (service == null) {
            return;
        }
        try {
            service.stopSelf();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void startActivitySafety(Context context, Intent intent) {
        if (context == null || intent == null) {
            return;
        }
        try {
            context.startActivity(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void finishSafety(Activity activity) {
        if (activity == null) {
            return;
        }
        try {
            if (!activity.isFinishing()) {
                activity.finish();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void sendBroadcastSafety(Context context, Intent intent) {
        if (context == null || intent == null) {
            return;
        }
        try {
            context.sendBroadcast(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void registerBroadcastReceiverSafety(Context context, BroadcastReceiver receiver,
                                                       IntentFilter filter) {
        if (context == null || receiver == null || filter == null) {
            return;
        }
        try {
            context.registerReceiver(receiver, filter);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void unregisterBroadcastReceiverSafety(Context context, BroadcastReceiver receiver) {
        if (context == null || receiver == null) {
            return;
        }
        try {
            context.unregisterReceiver(receiver);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void bindServiceSafety(Context context, Intent intent, ServiceConnection connection) {
        if (context == null || intent == null || connection == null) {
            return;
        }
        try {
            context.bindService(intent, connection, Context.BIND_ABOVE_CLIENT);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void unbindServiceSafety(Context context, ServiceConnection connection) {
        if (context == null || connection == null) {
            return;
        }
        try {
            context.unbindService(connection);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static boolean isServiceRunning(Context ctx, String className) {
        boolean isRunning = false;
        try {
            if (ctx == null || TextUtils.isEmpty(className)) {
                return false;
            }
            ActivityManager activityManager = (ActivityManager) ctx
                    .getSystemService(Context.ACTIVITY_SERVICE);
            if (activityManager == null) {
                return false;
            }
            List servicesList = activityManager
                    .getRunningServices(Integer.MAX_VALUE);
            if (servicesList == null) {
                return false;
            }
            Iterator l = servicesList.iterator();
            while (l.hasNext()) {
                ActivityManager.RunningServiceInfo si = l.next();
                if (TextUtils.equals(className, si.service.getClassName())) {
                    isRunning = true;
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return isRunning;
    }

    public static boolean isRunningTaskExist(Context context, String processName) {
        try {
            if (context == null || TextUtils.isEmpty(processName)) {
                return false;
            }
            ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            if (am == null) {
                return false;
            }
            List processList = am.getRunningAppProcesses();
            if (processList == null) {
                return false;
            }
            for (ActivityManager.RunningAppProcessInfo info : processList) {
                if (TextUtils.equals(info.processName, processName)) {
                    return true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    public static boolean isForeground(Context context) {
        if (context == null) {
            return false;
        }
        try {
            ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            if (am == null) {
                return false;
            }
            List tasks = am.getRunningTasks(1);
            if (tasks != null && !tasks.isEmpty()) {
                ComponentName topActivity = tasks.get(0).topActivity;
                if (TextUtils.equals(topActivity.getPackageName(), context.getPackageName())) {
                    return true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    public static boolean isScreenOn(Context context) {
        if (context == null) {
            return false;
        }
        try {
            PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
            if (pm == null) {
                return false;
            }
            return pm.isScreenOn();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}

通知工具类

public class NotificationUtils {
    private NotificationManager manager;
    private String id;
    private String name;
    private Context context;
    private NotificationChannel channel;

    private NotificationUtils(Context context) {
        this.context = context;
        id = context.getPackageName();
        name = context.getPackageName();
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    private void createNotificationChannel() {
        if (channel == null) {
            channel = new NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH);
            channel.enableVibration(false);
            channel.enableLights(false);
            channel.enableVibration(false);
            channel.setVibrationPattern(new long[]{0});
            channel.setSound(null, null);
            getManager().createNotificationChannel(channel);
        }
    }

    private NotificationManager getManager() {
        if (manager == null) {
            manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        }
        return manager;
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    private Notification.Builder getChannelNotification(String title, String content, int icon, Intent intent) {
        //PendingIntent.FLAG_UPDATE_CURRENT 这个类型才能传值
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        return new Notification.Builder(context, id)
                .setContentTitle(title)
                .setContentText(content)
                .setSmallIcon(icon)
                .setAutoCancel(true)
                .setContentIntent(pendingIntent);
    }

    private NotificationCompat.Builder getNotification_25(String title, String content, int icon, Intent intent) {
        //PendingIntent.FLAG_UPDATE_CURRENT 这个类型才能传值
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        return new NotificationCompat.Builder(context, id)
                .setContentTitle(title)
                .setContentText(content)
                .setSmallIcon(icon)
                .setAutoCancel(true)
                .setVibrate(new long[]{0})
                .setContentIntent(pendingIntent);
    }

    public static Notification createNotification(@NonNull Context context, @NonNull String title, @NonNull String content, @NonNull int icon, @NonNull Intent intent) {
        NotificationUtils notificationUtils = new NotificationUtils(context);
        Notification notification = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            notificationUtils.createNotificationChannel();
            notification = notificationUtils.getChannelNotification(title, content, icon, intent).build();
        } else {
            notification = notificationUtils.getNotification_25(title, content, icon, intent).build();
        }
        return notification;
    }
}

G. 功能清单的配置

  1. JobHandlerService 必须要配置权限
  2. RemoteService 的进程必须要和 KeepAliveConstants.REMOTE_PROCESS_NAME 的值一致
  3. OnePixelActivity 最好配置 singleInstance 模式,否则屏幕点亮时会关闭一像素 Activity,此时如果当前程序正在后台,会将程序弹到前台。最好设置 excludeFromRecents 属性使新栈不出现在最近任务列表。最好设置主题透明,否则屏幕点亮关闭一像素 Activity 时会有一抹黑。


    
        

        

        

        

        

        
    



你可能感兴趣的:(Android:后台保活)