本Demo是根据该大神的博客写的https://blog.csdn.net/andrexpert/article/details/75045678
该文写的主要目的在于巩固下对app保活的使用以及理解,主要用于自己回头查看,不喜勿喷,可以直接看上面链接的内容,以上链接同样附有完整demo,亲测有效。
一、核心思想归纳
App保活主要通过两个方面:1.降低omm_adj值,尽量办证进程不被系统杀死;2.倘若进程被杀死,通过其他方式让进程复活。
二、APP保活方案探讨
1.开启前台service
将service置为前台,降低omm_adj的值,通过使用 startForeground()方法将当前Service置于前台来提高Service的优先级。需要注意的是,对API大于18而言 startForeground()方法需要弹出一个可见通知,可以开启另一个Service将通知栏移除,其oom_adj值还是没变的。实现代码如下:
a) ForegroundService.java
public class ForegroundService extends Service {
private static final String TAG = ForegroundService.class.getSimpleName();
public static final int NOTICE_ID = 200;
public ForegroundService() {
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onCreate() {
super.onCreate();
if(Constant.DEBUG)
Log.d(TAG,"ForegroundService---->onCreate被调用,启动前台service");
//如果API大于18,需要弹出一个可见通知
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
final String CHANNEL_ID = "channel_id_1";
final String CHANNEL_NAME = "channel_name_1";
//获取状态通知栏管理
NotificationManager mNotificationManager = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
//只在Android O之上需要渠道
NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID,
CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
//如果这里用IMPORTANCE_NOENE就需要在系统的设置里面开启渠道,
//通知才能正常弹出
mNotificationManager.createNotificationChannel(notificationChannel);
}
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setContentTitle("ReadMsg");
if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O){
builder.setChannelId(CHANNEL_ID);
}
builder.setContentText("ForegroundService is runing...");
startForeground(NOTICE_ID,builder.build());
// 如果觉得常驻通知栏体验不好
// 可以通过启动CancelNoticeService,将通知移除,oom_adj值不变
Intent intent = new Intent(this,CancelNoticeService.class);
startService(intent);
}else{
startForeground(NOTICE_ID,new Notification());
}
}
/**
* START_STICKY
* 作用是当Service进程被kill后,系统会尝试重新创建这个Service,且会保留Service的状态为开始状态,但不保留传递的Intent对象,onStartCommand方法一定会被重新调用
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 如果Service被终止
// // 当资源允许情况下,重启service
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
// 如果Service被杀死,干掉通知
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
NotificationManager mManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
mManager.cancel(NOTICE_ID);
}
if(Constant.DEBUG)
Log.d(TAG,"ForegroundService---->onDestroy,前台service被杀死");
// 重启自己
Intent intent = new Intent(getApplicationContext(),ForegroundService.class);
startService(intent);
}
}
讲解一下:
1.onStartCommand方法中返回START_STICKY,其作用是当Service进程被kill后,系统会尝试重新创建这个Service,且会保留Service的状态为开始状态,但不保留传递的Intent对象,onStartCommand方法一定会被重新调用。
2.在onDestory方法中重新启动自己,也就是说,只要Service在被销毁时走到了onDestory这里我们就重新启动它。
3.另外需要注意的是在android 8.0以上,通知需要添加channel。
b) CancelNoticeService.java
public class CancelNoticeService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2){
final String CHANNEL_ID = "channel_id_1";
final String CHANNEL_NAME = "channel_name_1";
//获取状态通知栏管理
NotificationManager mNotificationManager = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
//只在Android O之上需要渠道
NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID,
CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
//如果这里用IMPORTANCE_NOENE就需要在系统的设置里面开启渠道,
//通知才能正常弹出
mNotificationManager.createNotificationChannel(notificationChannel);
}
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O){
builder.setChannelId(CHANNEL_ID);
}
startForeground(ForegroundService.NOTICE_ID,builder.build());
// 开启一条线程,去移除DaemonService弹出的通知
new Thread(new Runnable() {
@Override
public void run() {
// 延迟1s
SystemClock.sleep(1000);
// 取消CancelNoticeService的前台
stopForeground(true);
// 移除DaemonService弹出的通知
NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
manager.cancel(ForegroundService.NOTICE_ID);
// 任务完成,终止自己
stopSelf();
}
}).start();
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
c) AndroidManifest.xml
<service
android:name=".service.ForegroundService"
android:enabled="true"
android:exported="true"
android:process=":foreground_service" />
<service
android:name=".service.CancelNoticeService"
android:enabled="true"
android:exported="true"
android:process=":service" />
讲解一下:
一个Service没有自己独立的进程,它一般是作为一个线程运行于它所在的应用进程中,且应用进程名称与包名一致。如果希望指定的组件和应用运行在指定的进程中,就需要通过android:process属性来为其创建一个进程,因此android:process=”:foreground_service”;android:enabled属性的作用是Android系统是否实例化应用程序中的组件;android:exported属性的作用是当前组件(Service)是否可以被包含本身以外的应用中的组件启动。
2.监听锁屏广播,“制造‘1像素’惨案”
a) ScreenReceiverUtil.java
/** 静态监听锁屏、解锁、开屏广播
* a) 当用户锁屏时,将SportsActivity置于前台,同时开启1像素悬浮窗;
* b) 当用户解锁时,关闭1像素悬浮窗;
*
* Created by jianddongguo on 2017/7/8.
* http://blog.csdn.net/andrexpert
*/
public class ScreenReceiverUtil {
private Context mContext;
// 锁屏广播接收器
private SreenBroadcastReceiver mScreenReceiver;
// 屏幕状态改变回调接口
private SreenStateListener mStateReceiverListener;
public ScreenReceiverUtil(Context mContext){
this.mContext = mContext;
}
public void setScreenReceiverListener(SreenStateListener mStateReceiverListener){
this.mStateReceiverListener = mStateReceiverListener;
// 动态注册系统锁屏广播接收器
this.mScreenReceiver = new SreenBroadcastReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_PRESENT);
mContext.registerReceiver(mScreenReceiver,filter);
}
public void stopScreenReceiverListener(){
mContext.unregisterReceiver(mScreenReceiver);
}
public class SreenBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.d("KeepAppAlive","SreenLockReceiver-->监听到系统广播:"+action);
if(mStateReceiverListener == null){
return;
}
if(Intent.ACTION_SCREEN_ON.equals(action)){ // 开屏
mStateReceiverListener.onSreenOn();
}else if(Intent.ACTION_SCREEN_OFF.equals(action)){ // 锁屏
mStateReceiverListener.onSreenOff();
}else if(Intent.ACTION_USER_PRESENT.equals(action)){ // 解锁
mStateReceiverListener.onUserPresent();
}
}
}
// 监听sreen状态对外回调接口
public interface SreenStateListener {
void onSreenOn();
void onSreenOff();
void onUserPresent();
}
}
讲解一下:
由于静态注册广播接收器,无法接收到系统的锁屏(Intent.ACTION_SCREEN_OFF)和开屏(Intent.ACTION_SCREEN_ON)广播,因此必须通过动态注册来监听。
b)ScreenManager.java
**1像素管理类
*
* Created by jianddongguo on 2017/7/8.
* http://blog.csdn.net/andrexpert
*/
public class ScreenManager {
private static final String TAG = "ScreenManager";
private Context mContext;
private static ScreenManager mSreenManager;
// 使用弱引用,防止内存泄漏
private WeakReference<Activity> mActivityRef;
private ScreenManager(Context mContext){
this.mContext = mContext;
}
// 单例模式
public static ScreenManager getScreenManagerInstance(Context context){
if(mSreenManager == null){
mSreenManager = new ScreenManager(context);
}
return mSreenManager;
}
// 获得SinglePixelActivity的引用
public void setSingleActivity(Activity mActivity){
mActivityRef = new WeakReference<>(mActivity);
}
// 启动SinglePixelActivity
public void startActivity(){
if(Constant.DEBUG)
Log.d(TAG,"准备启动SinglePixelActivity...");
Intent intent = new Intent(mContext,SinglePixelActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
// 结束SinglePixelActivity
public void finishActivity(){
if(Constant.DEBUG)
Log.d(TAG,"准备结束SinglePixelActivity...");
if(mActivityRef != null){
Activity mActivity = mActivityRef.get();
if(mActivity != null){
mActivity.finish();
}
}
}
}
讲解一下:
Java中为对象的引用分了四个级别:强引用、软引用、弱引用、虚引用。这里,我们使用了弱引用WeakReference来防止内存泄漏,为了解释这个问题,我们举这么一个例子:有两个类class A和class B,分别实例化这两个类得到a,b,其中a又作为实例化B时传入的构造参数,代码如下:
A a = new A();
B b = new B(a);
从这两行代码来看,a是对象A的引用,b是对象B的引用,对象B同时依赖于对象A,对象A和对象B之间形成了强引用。当a=null时,a不在指向对象A,通常情况下,对象A在不被其他对象引用时会被GC回收,但是由于B还依赖于对象A,对象A不会被GC回收,从而造成内存泄漏(除非b=null,对象A和对象B才会被GC同时回收)。如果使用弱引用的话,对象A只会被WeakReference所依赖,当a=null时,GC会回收它,从而避免了内存泄漏。
c) SinglePixelActivity.java
/**1像素Activity
*
* Created by jianddongguo on 2017/7/8.
*/
public class SinglePixelActivity extends AppCompatActivity {
private static final String TAG = "SinglePixelActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(Constant.DEBUG)
Log.d(TAG,"onCreate--->启动1像素保活");
Window mWindow = getWindow();
mWindow.setGravity(Gravity.LEFT | Gravity.TOP);
WindowManager.LayoutParams attrParams = mWindow.getAttributes();
attrParams.x = 0;
attrParams.y = 0;
attrParams.height = 300;
attrParams.width = 300;
mWindow.setAttributes(attrParams);
// 绑定SinglePixelActivity到ScreenManager
ScreenManager.getScreenManagerInstance(this).setSingleActivity(this);
}
@Override
protected void onDestroy() {
if(Constant.DEBUG)
Log.d(TAG,"onDestroy--->1像素保活被终止");
if(! SystemUtils.isAPPALive(this,Constant.PACKAGE_NAME)){
Intent intentAlive = new Intent(this, MainActivity.class);
intentAlive.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intentAlive);
Log.i(TAG,"SinglePixelActivity---->APP被干掉了,我要重启它");
}
super.onDestroy();
}
}
讲解一下:
既然我们已经知道Window对象在一个Activity中的位置,这里我们通过getWindow方法来获得SinglePixelActivity 的Window对象,然后为其设置相关属性,比如窗体的大小、位置、坐标等,来达到所需的”1像素”界面效果
d)MainActivity.java
public class MainActivity extends BaseActivity implements SendMsgView{
private static final String TAG = "MainActivity";
// 读取新短信的广播action
private static final String SMS_RECEIVED_ACTION = "android.provider.Telephony.SMS_RECEIVED";
// 申请权限code码
private static final int CODE_READ_SMS = 102;
// 广播
// 动态注册锁屏等广播
private ScreenReceiverUtil mScreenListener;
// 1像素Activity管理类
private ScreenManager mScreenManager;
private ScreenReceiverUtil.SreenStateListener mScreenListenerer = new ScreenReceiverUtil.SreenStateListener() {
@Override
public void onSreenOn() {
// 亮屏,移除"1像素"
mScreenManager.finishActivity();
}
@Override
public void onSreenOff() {
// 接到锁屏广播,将SportsActivity切换到可见模式
// "咕咚"、"乐动力"、"悦动圈"就是这么做滴
// Intent intent = new Intent(SportsActivity.this,SportsActivity.class);
// startActivity(intent);
// 如果你觉得,直接跳出SportActivity很不爽
// 那么,我们就制造个"1像素"惨案
mScreenManager.startActivity();
}
@Override
public void onUserPresent() {
// 解锁,暂不用,保留
}
};
private JobSchedulerManager mJobManager;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//注册EventBus
EventBus.getDefault().register(this);
// 2. 启动系统任务
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mJobManager = JobSchedulerManager.getJobSchedulerInstance(this);
mJobManager.startJobScheduler();
}
// 开启前台服务
startForegroundService();
// 开启音乐服务
startPlayMusicService();
// 1. 注册锁屏广播监听器
mScreenListener = new ScreenReceiverUtil(this);
mScreenManager = ScreenManager.getScreenManagerInstance(this);
mScreenListener.setScreenReceiverListener(mScreenListenerer);
//具体操作代码
}
private void startForegroundService() {
Intent intent = new Intent(this, ForegroundService.class);
startService(intent);
}
private void stopForegroundService() {
Intent intent = new Intent(this, ForegroundService.class);
stopService(intent);
}
private void startPlayMusicService() {
Intent intent = new Intent(this,PlayerMusicService.class);
startService(intent);
}
private void stopPlayMusicService() {
Intent intent = new Intent(this, PlayerMusicService.class);
stopService(intent);
}
/**
* 销毁EventBus
*/
@Override
protected void onDestroy() {
super.onDestroy();
if(Constant.DEBUG)
Log.d(TAG,"--->onDestroy");
EventBus.getDefault().unregister(this);
mScreenListener.stopScreenReceiverListener();
}
}
<activity android:name=".activity.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".activity.Main2Activity" />
<activity
android:name=".activity.SinglePixelActivity"
android:configChanges="keyboardHidden|orientation|screenSize|navigation|keyboard"
android:excludeFromRecents="true"
android:finishOnTaskLaunch="false"
android:launchMode="singleInstance"
android:theme="@style/SingleActivityStyle" />
讲解一下:
android:launchMode属性用于指定activity的启动模式,总共分为四种,即standar模式,每次启动activity都会创建其实例,并加入到任务栈的栈顶;singleTop模式,每次启动activity如果栈顶时该activity则无需创建,其余情况都要创建该activity的实例;singleTask模式,如果被启动的activity的实例存在栈中,则不需要创建,只需要把此activity加入到栈顶,并把该activity以上的activity实例全部pop;singleInstance模式,将创建的activity实例放入单独的栈中,该栈只能存储这个实例,且是作为共享实例存在;
android:configChanges属性用于捕获手机状态的改变,即当手机状态(如切换横竖屏、屏幕大小)改变时会保存当前活动状态重启Activity,由于SinglePixelActivity肩负着保活的特殊使命,这里使用android:configChanges属性防止Activity重启,它只是调用了onConfigurationChanged(Configuration newConfig)来通知手机状态的改变;
android:excludeFromRecents属性用于控制SinglePixelActivity不在最近任务列表中显示;
android:finishOnTaskLaunch属性用于标记当用户再起启动应用(TASK)时是否关闭已经存在的Activity的实例,false表示不关闭;
android:theme属性用于指定Activity显示主题,这里我们自定义主题SingleActivityStyle
<!--1像素Activity风格 @android:color/transparent-->
<style name="SingleActivityStyle" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@null</item>
<item name="android:windowFrame">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:backgroundDimEnabled">false</item>
<item name="android:windowAnimationStyle">@null</item>
<item name="android:windowDisablePreview">true</item>
<item name="android:windowNoDisplay">false</item>
</style>
3 .循环播放一段无声音频
a) PlayerMusicService.java
/**循环播放一段无声音频,以提升进程优先级
*
* Created by jianddongguo on 2017/7/11.
* http://blog.csdn.net/andrexpert
*/
public class PlayerMusicService extends Service {
private final static String TAG = "PlayerMusicService";
private MediaPlayer mMediaPlayer;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
if(Constant.DEBUG)
Log.d(TAG,TAG+"---->onCreate,启动服务");
mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.silent);
mMediaPlayer.setLooping(true);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
startPlayMusic();
}
}).start();
return START_STICKY;
}
private void startPlayMusic(){
if(mMediaPlayer != null){
if(Constant.DEBUG)
Log.d(TAG,"启动后台播放音乐");
mMediaPlayer.start();
}
}
private void stopPlayMusic(){
if(mMediaPlayer != null){
if(Constant.DEBUG)
Log.d(TAG,"关闭后台播放音乐");
mMediaPlayer.stop();
}
}
@Override
public void onDestroy() {
super.onDestroy();
stopPlayMusic();
if(Constant.DEBUG)
Log.d(TAG,TAG+"---->onCreate,停止服务");
// 重启自己
Intent intent = new Intent(getApplicationContext(),PlayerMusicService.class);
startService(intent);
}
}
b) AndroidManifest.xml
<service
android:name=".service.PlayerMusicService"
android:enabled="true"
android:exported="true"
android:process=":music_service" />
复活方案:请继续看大神blog: https://blog.csdn.net/andrexpert/article/details/75174586
源码地址:https://download.csdn.net/download/silence_sep/10562709