全面盘点当前Android后台保活方案的真实运行效果(截止2019年前)

栏目: Android · 发布时间: 5年前

内容简介:本文原作者“minminaya”,作者网站:minminaya.cn,为了提升文章品质,即时通讯网对内容作了幅修订和改动,感谢原作者。对于IM应用和消息推送服务的开发者来说,在Android机型上的后台保活是个相当头疼的问题。

本文原作者“minminaya”,作者网站:minminaya.cn,为了提升文章品质,即时通讯网对内容作了幅修订和改动,感谢原作者。

1、引言

对于IM应用和消息推送服务的开发者来说,在Android机型上的后台保活是个相当头疼的问题。

老板一句: “为什么微信、QQ能收到消息,而你写的APP却不行?”,直接让人崩溃,话说老板你这APP要是整成微信、APP那么牛,直接进手机厂商白名单,还要 程序员 在这瞎忙活?

好了,抱怨归抱怨,活还得干,不然靠谁养活广大苦逼的程序员?

回到正题,Android程序员都知道,随着Android系统的不断完善和升级,Andriod应用的后台保活是一次比一次难(详见《 Android P正式版即将到来:后台应用保活、消息推送的真正噩梦 》),但日子还得过,只能一次次绞尽脑汁想出各种黑科技。但不幸的是,因为Andriod系统的不断升级,各种黑科技也只能适应某些版本的Android系统,无法一劳永逸解决问题。

全面盘点当前Android后台保活方案的真实运行效果(截止2019年前)

▲ Android各版本都是用“甜品”命名的

正因为Android系统版本的差异,也导致了各种保活黑科技的运行效果大相径庭,所以本文正好借此机会,盘点一下当前主流(截止2019年前)的保活黑科技在市面上各版本Android手机上的运行效果,希望能给大家提供一些客观的参考。

学习交流:

- 即时通讯/推送技术开发交流4群: 101279154 [推荐]

- 移动端IM开发入门文章:《 新手入门一篇就够:从零开发移动端IM

(本文同步发布于: http://www.52im.net/thread-2176-1-1.html

2、先总结一下,Android端APP为何要搞保活黑科技?

* 本节内容摘录自即时通讯网整理的《 Android P正式版即将到来:后台应用保活、消息推送的真正噩梦 》一文

其实Android端APP搞保活的目的倒不是为了干什么见不得人的坏事(但不排除动机不纯的开发者),主要是像IM即时通讯应用和资讯类应用等需要搞后台消息推送、运动类应用需要在后台实时监测用户的运动数据等,因为现在越来越多的手机厂商为了省电策略考虑,基本上如果你的应用没有被加入白名单,一旦处于后台就会被系统限制甚至干掉,但使用APP的用户才不听你这些解释——反正“我”就要你的APP能如期正常运行,开发者也是不得已而为之。

以消息推送为例,当APP处于后台或关闭时,消息推送对于某些应用来说非常有用,比如:

1)IM即时通讯聊天应用:聊天消息通知、音视频聊天呼叫等,典型代表有:微信、QQ、易信、米聊、钉钉、Whatsup、Line;

2)新闻资讯应用:最新资讯通知等,典型代表有:网易新闻客户端、腾讯新闻客户端;

3)SNS社交应用:转发/关注/赞等通知,典型代表有:微博、知乎;

4)邮箱客户端:新邮件通知等,典型代表有:QQ邮箱客户端、Foxmail客户端、网易邮箱大师;

5)金融支付应用:收款通知、转账通知等,典型代表有:支付宝、各大银行的手机银行等;

.... ....

在上述的各种应用中,尤其对于用户接触最多、最平常的IM聊天应用或新闻资讯来说,保活和消息推送简直事关APP的“生死”,消息推送这种能力已经被越来越多的APP作为基础能力之一,因为移动互联网时代下,用户的“全时在线”能力非常诱人和强大,能随时随地即时地将各种重要信息推送给用户,无疑是非常有意义的。

题外话: 实际上,对于后台消息推送能力,Android原版系统早就内置了系统级推送服务(跟iOS上的 APNs服务 是一个东西),它就是GCM服务(现在升级为FCM了),但众所周之的原因,谷哥的服务在国内都是用不了的(你懂的)—— 无奈啊!

(有关GCM的介绍详见:《 移动端IM实践:谷歌消息推送服务(GCM)研究(来自微信) 》、《 为何微信、QQ这样的IM工具不使用GCM服务推送消息? 》、《 求教android消息推送:GCM、XMPP、MQTT三种方案的优劣 》)。

全面盘点当前Android后台保活方案的真实运行效果(截止2019年前)

▲ 如果Android能有iOS的APNs这么强势的方案存在,那该是多美的事 ...

3、相关文章

应用保活终极总结(一):Android6.0以下的双进程守护保活实践

应用保活终极总结(二):Android6.0及以上的保活实践(进程防杀篇)

应用保活终极总结(三):Android6.0及以上的保活实践(被杀复活篇)

Android进程保活详解:一篇文章解决你的所有疑问

Android端消息推送总结:实现原理、心跳保活、遇到的问题等

深入的聊聊Android消息推送这件小事

微信团队原创分享:Android版微信后台保活实战分享(进程保活篇)

Android P正式版即将到来:后台应用保活、消息推送的真正噩梦

4、常见的Android端保活黑科技方案盘点

主要黑科技方案有:

1)监听广播:监听全局的静态广播,比如时间更新的广播、开机广播、解锁屏、网络状态、解锁加锁亮屏暗屏(3.1版本),高版本需要应用开机后运行一次才能监听这些系统广播,目前此方案失效。可以更换思路,做APP启动后的保活(监听广播启动保活的前台服务);
2)定时器、JobScheduler:假如应用被系统杀死,那么定时器则失效,此方案失效。JobService在5.0,5.1,6.0作用很大,7.0时候有一定影响(可以在电源管理中给APP授权);
3)双进程(NDK方式Fork子进程)、双Service守护:高版本已失效,5.0起系统回收策略改成进程组。双Service方案也改成了应用被杀,任何后台Service无法正常状态运行;
4)提高Service优先级:只能一定程度上缓解Service被立马回收。

针对上述方案,具体的实现思路,通常是这样的:

1)进程拉活:AIDL方式单进程、双进程方式保活Service(最极端的例子就是推送厂商的互相唤醒复活:极光、友盟、以及各大厂商的推送,同派系APP广播互相唤醒:比如今日头条系、阿里系);
2)降低oom_adj的值:常驻通知栏(可通过启动另外一个服务关闭Notification,不对oom_adj值有影响)、使用”1像素“的Activity覆盖在getWindow()的view上(据传某不可言说的IM大厂用过这个方案,虽然他们从未正面承认过)、循环播放无声音频(黑科技,7.0下杀不掉);
3)监听锁屏广播:使Activity始终保持前台;
4)使用自定义锁屏界面:覆盖了系统锁屏界面;
5)创建子进程:通过android:process属性来为Service创建一个进程;
6)白名单:跳转到系统白名单界面让用户自己添加app进入白名单。

5、汇总一下,主要的保活黑科技方案的具体代码实现

5.1 黑科技代码实现1:双进程拉活方案的代码实现

使用AIDL绑定方式新建2个Service优先级(防止服务同时被系统杀死)不一样的守护进程互相拉起对方,并在每一个守护进程的ServiceConnection的绑定回调里判断保活Service是否需要重新拉起和对守护线程进行重新绑定。

关于本方案的具体实现,即时通讯网的以下文章有更详细的介绍,您也可以仔细研读:

应用保活终极总结(一):Android6.0以下的双进程守护保活实践

应用保活终极总结(二):Android6.0及以上的保活实践(进程防杀篇)

应用保活终极总结(三):Android6.0及以上的保活实践(被杀复活篇)

本方案的具体代码实现,主要由以下4步构成。

1)新建一个AIDL文件:

KeepAliveConnection
interfaceKeepAliveConnection  {
}

2)新建一个服务类StepService,onBind()方法返回new KeepAliveConnection.Stub()对象,并在ServiceConnection的绑定回调中对守护进程服务类GuardService的启动和绑定:

/**
* 主进程 双进程通讯
*
* @author LiGuangMin
* @time Created by 2018/8/17 11:26
*/
public class StepService extends Service {
   privatefinalstaticString TAG = StepService.class.getSimpleName();
   privateServiceConnection mServiceConnection = newServiceConnection() {
       @Override
       publicvoidonServiceConnected(ComponentName componentName, IBinder iBinder) {
           Logger.d(TAG, "StepService:建立链接");
           booleanisServiceRunning = ServiceAliveUtils.isServiceAlice();
           if(!isServiceRunning) {
               Intent i = newIntent(StepService.this, DownloadService.class);
               startService(i);
           }
       }
 
       @Override
       publicvoidonServiceDisconnected(ComponentName componentName) {
           // 断开链接
           startService(newIntent(StepService.this, GuardService.class));
           // 重新绑定
           bindService(newIntent(StepService.this, GuardService.class), mServiceConnection, Context.BIND_IMPORTANT);
       }
   };
 
   @Nullable
   @Override
   publicIBinder onBind(Intent intent) {
       returnnewKeepAliveConnection.Stub() {
       };
   }
 
   @Override
   publicintonStartCommand(Intent intent, intflags, intstartId) {
       startForeground(1, newNotification());
       // 绑定建立链接
       bindService(newIntent(this, GuardService.class), mServiceConnection, Context.BIND_IMPORTANT);
       returnSTART_STICKY;
   }
}

3)对守护进程GuardService进行和2一样的处理:

/**
* 守护进程 双进程通讯
*
* @author LiGuangMin
* @time Created by 2018/8/17 11:27
*/
publicclassGuardService extendsService {
   privatefinalstaticString TAG = GuardService.class.getSimpleName();
   privateServiceConnection mServiceConnection = newServiceConnection() {
       @Override
       publicvoidonServiceConnected(ComponentName componentName, IBinder iBinder) {
           Logger.d(TAG, "GuardService:建立链接");
           booleanisServiceRunning = ServiceAliveUtils.isServiceAlice();
           if(!isServiceRunning) {
               Intent i = newIntent(GuardService.this, DownloadService.class);
               startService(i);
           }
       }
 
       @Override
       publicvoidonServiceDisconnected(ComponentName componentName) {
           // 断开链接
           startService(newIntent(GuardService.this, StepService.class));
           // 重新绑定
           bindService(newIntent(GuardService.this, StepService.class), mServiceConnection, Context.BIND_IMPORTANT);
       }
   };
 
   @Nullable
   @Override
   publicIBinder onBind(Intent intent) {
       returnnewKeepAliveConnection.Stub() {
       };
   }
 
   @Override
   publicintonStartCommand(Intent intent, intflags, intstartId) {
       startForeground(1, newNotification());
       // 绑定建立链接
       bindService(newIntent(this, StepService.class), mServiceConnection, Context.BIND_IMPORTANT);
       returnSTART_STICKY;
   }
}

4)在Activity中在启动需要保活的DownloadService服务后然后启动保活的双进程:

public class MainActivity extends AppCompatActivity {
   privateTextView mShowTimeTv;
   privateDownloadService.DownloadBinder mDownloadBinder;
   privateServiceConnection mServiceConnection = newServiceConnection() {
       @Override
       publicvoidonServiceConnected(ComponentName name, IBinder service) {
           mDownloadBinder = (DownloadService.DownloadBinder) service;
           mDownloadBinder.setOnTimeChangeListener(newDownloadService.OnTimeChangeListener() {
               @Override
               publicvoidshowTime(finalString time) {
                   runOnUiThread(newRunnable() {
                       @Override
                       publicvoidrun() {
                           mShowTimeTv.setText(time);
                       }
                   });
               }
           });
       }
 
       @Override
       publicvoidonServiceDisconnected(ComponentName name) {
       }
   };
 
   @Override
   protectedvoidonCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
 
       Intent intent = newIntent(this, DownloadService.class);
       startService(intent);
       bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
       //双守护线程,优先级不一样
       startAllServices();
   }
 
   @Override
   publicvoidonContentChanged() {
       super.onContentChanged();
       mShowTimeTv = findViewById(R.id.tv_show_time);
   }
 
   @Override
   protectedvoidonDestroy() {
       super.onDestroy();
       unbindService(mServiceConnection);
   }
 
   /**
    * 开启所有守护Service
    */
   privatevoidstartAllServices() {
       startService(newIntent(this, StepService.class));
       startService(newIntent(this, GuardService.class));
   }
}

5.2 黑科技代码实现2:监听到锁屏广播后使用“1”像素Activity提升优先级

“1”像素保活这么流氓的手段,传说是某IM大厂用过的方案 ...

本方法的具体代码实现主要由以下6步组成。

1)该Activity的View只要设置为1像素然后设置在Window对象上即可。在Activity的onDestroy周期中进行保活服务的存活判断从而唤醒服务。”1像素”Activity如下:

public class SinglePixelActivity extends AppCompatActivity {
   private static final String TAG = SinglePixelActivity.class.getSimpleName();
 
   @Override
   protected void onCreate(@NullableBundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       Window mWindow = getWindow();
       mWindow.setGravity(Gravity.LEFT | Gravity.TOP);
       WindowManager.LayoutParams attrParams = mWindow.getAttributes();
       attrParams.x = 0;
       attrParams.y = 0;
       attrParams.height = 1;
       attrParams.width = 1;
       mWindow.setAttributes(attrParams);
       ScreenManager.getInstance(this).setSingleActivity(this);
   }
 
   @Override
   protectedvoidonDestroy() {
       if(!SystemUtils.isAppAlive(this, Constant.PACKAGE_NAME)) {
           Intent intentAlive = newIntent(this, DownloadService.class);
           startService(intentAlive);
       }
       super.onDestroy();
   }
}

2)对广播进行监听,封装为一个ScreenReceiverUtil类,进行锁屏解锁的广播动态注册监听:

public class ScreenReceiverUtil {
   privateContext mContext;
   privateSreenBroadcastReceiver mScreenReceiver;
   privateSreenStateListener mStateReceiverListener;
 
   publicScreenReceiverUtil(Context mContext) {
       this.mContext = mContext;
   }
 
   publicvoidsetScreenReceiverListener(SreenStateListener mStateReceiverListener) {
       this.mStateReceiverListener = mStateReceiverListener;
       // 动态启动广播接收器
       this.mScreenReceiver = newSreenBroadcastReceiver();
       IntentFilter filter = newIntentFilter();
       filter.addAction(Intent.ACTION_SCREEN_ON);
       filter.addAction(Intent.ACTION_SCREEN_OFF);
       filter.addAction(Intent.ACTION_USER_PRESENT);
       mContext.registerReceiver(mScreenReceiver, filter);
   }
 
   publicvoidstopScreenReceiverListener() {
       mContext.unregisterReceiver(mScreenReceiver);
   }
 
   /**
    * 监听sreen状态对外回调接口
    */
   publicinterfaceSreenStateListener {
       voidonSreenOn();
 
       voidonSreenOff();
 
       voidonUserPresent();
   }
 
   publicclassSreenBroadcastReceiver extendsBroadcastReceiver {
       @Override
       publicvoidonReceive(Context context, Intent intent) {
           String action = intent.getAction();
           if(mStateReceiverListener == null) {
               return;
           }
           if(Intent.ACTION_SCREEN_ON.equals(action)) { // 开屏
               mStateReceiverListener.onSreenOn();
           } elseif(Intent.ACTION_SCREEN_OFF.equals(action)) { // 锁屏
               mStateReceiverListener.onSreenOff();
           } elseif(Intent.ACTION_USER_PRESENT.equals(action)) { // 解锁
               mStateReceiverListener.onUserPresent();
           }
       }
   }
}

3)对1像素Activity进行防止内存泄露的处理,新建一个ScreenManager类:

public class ScreenManager {
   privatestaticfinalString TAG = ScreenManager.class.getSimpleName();
   privatestaticScreenManager sInstance;
   privateContext mContext;
   privateWeakReference<Activity> mActivity;
 
   privateScreenManager(Context mContext) {
       this.mContext = mContext;
   }
 
   publicstaticScreenManager getInstance(Context context) {
       if(sInstance == null) {
           sInstance = newScreenManager(context);
       }
       returnsInstance;
   }
 
   /** 获得SinglePixelActivity的引用
    * @param activity
    */
   publicvoidsetSingleActivity(Activity activity) {
       mActivity = newWeakReference<>(activity);
   }
 
   /**
    * 启动SinglePixelActivity
    */
   publicvoidstartActivity() {
       Intent intent = newIntent(mContext, SinglePixelActivity.class);
       intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
       mContext.startActivity(intent);
   }
 
   /**
    * 结束SinglePixelActivity
    */
   publicvoidfinishActivity() {
       if(mActivity != null) {
           Activity activity = mActivity.get();
           if(activity != null) {
               activity.finish();
           }
       }
   }
}

4)对1像素的Style进行特殊处理,在style文件中新建一个SingleActivityStyle:

<stylename="SingleActivityStyle"parent="android:Theme.Holo.Light.NoActionBar">
       <itemname="android:windowBackground">@android:color/transparent</item>
       <itemname="android:windowFrame">@null</item>
       <itemname="android:windowNoTitle">true</item>
       <itemname="android:windowIsFloating">true</item>
       <itemname="android:windowContentOverlay">@null</item>
       <itemname="android:backgroundDimEnabled">false</item>
       <itemname="android:windowAnimationStyle">@null</item>
       <itemname="android:windowDisablePreview">true</item>
       <itemname="android:windowNoDisplay">false</item>

5)让SinglePixelActivity使用singleInstance启动模式,在manifest文件中:

<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"/>

6)在保活服务类DownloadService中对监听的广播进行注册和对SinglePixelActivity进行控制:

public class DownloadService extends Service {
   publicstaticfinalintNOTICE_ID = 100;
   privatestaticfinalString TAG = DownloadService.class.getSimpleName();
   privateDownloadBinder mDownloadBinder;
   privateNotificationCompat.Builder mBuilderProgress;
   privateNotificationManager mNotificationManager;
 
   privateScreenReceiverUtil mScreenListener;
   privateScreenManager mScreenManager;
   privateTimer mRunTimer;
 
   privateintmTimeSec;
   privateintmTimeMin;
   privateintmTimeHour;
 
   privateScreenReceiverUtil.SreenStateListener mScreenListenerer = newScreenReceiverUtil.SreenStateListener() {
       @Override
       publicvoidonSreenOn() {
           mScreenManager.finishActivity();
           Logger.d(TAG, "关闭了1像素Activity");
       }
 
       @Override
       publicvoidonSreenOff() {
           mScreenManager.startActivity();
           Logger.d(TAG, "打开了1像素Activity");
       }
 
       @Override
       publicvoidonUserPresent() {
       }
   };
   privateOnTimeChangeListener mOnTimeChangeListener;
 
   @Override
   publicvoidonCreate() {
       super.onCreate();
 
//        注册锁屏广播监听器
       mScreenListener = newScreenReceiverUtil(this);
       mScreenManager = ScreenManager.getInstance(this);
       mScreenListener.setScreenReceiverListener(mScreenListenerer);
 
       mDownloadBinder = newDownloadBinder();
       mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
   }
 
   @Override
   publicintonStartCommand(Intent intent, intflags, intstartId) {
       Logger.d(TAG, "onStartCommand");
       startRunTimer();
       returnSTART_STICKY;
   }
 
   @Nullable
   @Override
   publicIBinder onBind(Intent intent) {
 
       returnmDownloadBinder;
   }
 
   @Override
   publicbooleanonUnbind(Intent intent) {
       Logger.d(TAG, "onUnbind");
       returnsuper.onUnbind(intent);
   }
 
   @Override
   publicvoidonDestroy() {
       super.onDestroy();
       NotificationManager mManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
       if(mManager == null) {
           return;
       }
       mManager.cancel(NOTICE_ID);
       stopRunTimer();
//        mScreenListener.stopScreenReceiverListener();
   }
 
   privatevoidstartRunTimer() {
       TimerTask mTask = newTimerTask() {
           @Override
           publicvoidrun() {
               mTimeSec++;
               if(mTimeSec == 60) {
                   mTimeSec = 0;
                   mTimeMin++;
               }
               if(mTimeMin == 60) {
                   mTimeMin = 0;
                   mTimeHour++;
               }
               if(mTimeHour == 24) {
                   mTimeSec = 0;
                   mTimeMin = 0;
                   mTimeHour = 0;
               }
               String time = "时间为:"+ mTimeHour + " : "+ mTimeMin + " : "+ mTimeSec;
               if(mOnTimeChangeListener != null) {
                   mOnTimeChangeListener.showTime(time);
               }
               Logger.d(TAG, time);
           }
       };
       mRunTimer = newTimer();
       // 每隔1s更新一下时间
       mRunTimer.schedule(mTask, 1000, 1000);
   }
 
   privatevoidstopRunTimer() {
       if(mRunTimer != null) {
           mRunTimer.cancel();
           mRunTimer = null;
       }
       mTimeSec = 0;
       mTimeMin = 0;
       mTimeHour = 0;
       Logger.d(TAG, "时间为:"+ mTimeHour + " : "+ mTimeMin + " : "+ mTimeSec);
   }
 
   publicinterfaceOnTimeChangeListener {
       voidshowTime(String time);
   }
 
   publicclassDownloadBinder extendsBinder {
       publicvoidsetOnTimeChangeListener(OnTimeChangeListener onTimeChangeListener) {
           mOnTimeChangeListener = onTimeChangeListener;
       }
   }
}

6.3 黑科技代码实现3:在后台播放音乐

后台播放音乐这种保活方法,亲身经历过:

记得当时用的是某运动记步APP,它为了保活就是这么干的。之所以被我发现,是因为在我的Android手机上,每次打开这个APP居然总能莫名其妙听到若有若无的环境噪音样的声音,尤其安静的场所下更明显。我个人估计这个APP里用的保活音频文件,很可能就是程序员在简陋的条件下随手自已录制的,虽然也是不得以为之,但做法确实是有点粗糙。

好了,回到正题,本方案的具体代码实现主要是以下3步。

1)准备一段无声的音频,新建一个播放音乐的Service类,将播放模式改为无限循环播放。在其onDestroy方法中对自己重新启动:

public class PlayerMusicService extends Service {
   privatefinalstaticString TAG = PlayerMusicService.class.getSimpleName();
   privateMediaPlayer mMediaPlayer;
 
   @Nullable
   @Override
   publicIBinder onBind(Intent intent) {
       returnnull;
   }
 
   @Override
   publicvoidonCreate() {
       super.onCreate();
       Logger.d(TAG, TAG + "---->onCreate,启动服务");
       mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.silent);
       mMediaPlayer.setLooping(true);
   }
 
   @Override
   publicintonStartCommand(Intent intent, intflags, intstartId) {
       newThread(newRunnable() {
           @Override
           publicvoidrun() {
               startPlayMusic();
           }
       }).start();
       returnSTART_STICKY;
   }
 
   privatevoidstartPlayMusic() {
       if(mMediaPlayer != null) {
           Logger.d(TAG, "启动后台播放音乐");
           mMediaPlayer.start();
       }
   }
 
   privatevoidstopPlayMusic() {
       if(mMediaPlayer != null) {
           Logger.d(TAG, "关闭后台播放音乐");
           mMediaPlayer.stop();
       }
   }
 
   @Override
   publicvoidonDestroy() {
       super.onDestroy();
       stopPlayMusic();
       Logger.d(TAG, TAG + "---->onCreate,停止服务");
       // 重启自己
       Intent intent = newIntent(getApplicationContext(), PlayerMusicService.class);
       startService(intent);
   }
}

2)在保活的DownloadServie服务类的onCreate方法中对PlayerMusicService进行启动:

Intent intent = newIntent(this, PlayerMusicService.class);
startService(intent);

3)在Manifest文件中进行注册:

<service
           android:name=".service.PlayerMusicService"
           android:enabled="true"
           android:exported="true"
           android:process=":music_service"/>

6.4 黑科技代码实现4:使用JobScheduler唤醒Service

本方案代码实现由以下3步组成。

1)新建一个继承自JobService的ScheduleService类,在其onStartJob回调中对DownloadService进行存活的判断来重启:

public class ScheduleService extends JobService {
   privatestaticfinalString TAG = ScheduleService.class.getSimpleName();
 
   @Override
   publicbooleanonStartJob(JobParameters params) {
 
       booleanisServiceRunning = ServiceAliveUtils.isServiceAlice();
       if(!isServiceRunning) {
           Intent i = newIntent(this, DownloadService.class);
           startService(i);
           Logger.d(TAG, "ScheduleService启动了DownloadService");
       }
       jobFinished(params, false);
       returnfalse;
   }
 
   @Override
   publicbooleanonStopJob(JobParameters params) {
       returnfalse;
   }
}

2)在DownloadService服务类中进行JobScheduler的注册和使用:

/**
    * 使用JobScheduler进行保活
    */
   private void useJobServiceForKeepAlive() {
       JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
       if(jobScheduler == null) {
           return;
       }
       jobScheduler.cancelAll();
       JobInfo.Builder builder =
           newJobInfo.Builder(1024, newComponentName(getPackageName(), ScheduleService.class.getName()));
       //周期设置为了2s
       builder.setPeriodic(1000* 2);
       builder.setPersisted(true);
       builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
       intschedule = jobScheduler.schedule(builder.build());
       if(schedule <= 0) {
           Logger.w(TAG, "schedule error!");
       }
   }

3)在manifest文件中进行权限设置:

<service
           android:name=".service.ScheduleService"
           android:enabled="true"
           android:exported="true"
    android:permission="android.permission.BIND_JOB_SERVICE"/>

7、总结一下,以上方案在当前主流手机上的运行效果

【1】双进程守护方案(基于onStartCommand() return START_STICKY):

1)原生5.0、5.1:原生任务栏滑动清理app,Service会被杀掉,然后被拉起,接着一直存活;
2)金立F100(5.1):一键清理直接杀掉整个app,包括双守护进程。不手动清理情况下,经测试能锁屏存活至少40分钟;
3)华为畅享5x(6.0):一键清理直接杀掉整个app,包括双守护进程。不手动清理下,锁屏只存活10s。结论:双进程守护方案失效;
4)美图m8s(7.1.1):一键清理直接杀掉整个app,包括双守护进程。不清理情况下,锁屏会有被杀过程(9分钟左右被杀),之后重新复活,之后不断被干掉然后又重新复活。结论:双守护进程可在后台不断拉起Service;
5)原生7.0:任务栏清除APP后,Service存活。使用此方案后Service照样存活;
6)LG V30+(7.1.2):不加双进程守护的时候,一键清理无法杀掉服务。加了此方案之后也不能杀掉服务,锁屏存活(测试观察大于50分钟);
7)小米8(8.1):一键清理直接干掉app并且包括双守护进程。不清理情况下,不加守护进程方案与加守护进程方案Service会一直存活,12分钟左右closed。结论:此方案没有起作用。

▲ 结论: 除了华为此方案无效以及未更改底层的厂商不起作用外(START_STICKY字段就可以保持Service不被杀)。此方案可以与其他方案混合使用。

【2】监听锁屏广播打开1像素Activity(基于onStartCommand() return START_STICKY):

1)原生5.0、5.1:锁屏后3s服务被干掉然后重启(START_STICKY字段起作用);
2)华为畅享5x(6.0):锁屏只存活4s。结论:方案失效;
3)美图m8s(7.1.1):同原生5.0;
4)原生7.0:同美图m8s;
5)LG V30+(7.1.2):锁屏后情况跟不加情况一致,服务一致保持运行,结论:此方案不起作用;
6)小米8(8.1):关屏过2s之后app全部被干掉。结论:此方案没有起作用。

▲ 结论: 此方案无效果。

【3】故意在后台播放无声的音乐(基于onStartCommand() return START_STICKY):

1)原生5.0、5.1:锁屏后3s服务被干掉然后重启(START_STICKY字段起作用);
2)华为畅享5x(6.0):一键清理后服务依然存活,需要单独清理才可杀掉服务,锁屏8分钟后依然存活。结论:此方案适用;
3)美图m8s(7.1.1):同5.0;
4)原生7.0:任务管理器中关闭APP后服务被干掉,大概过3s会重新复活(同仅START_STICKY字段模式)。结论:看不出此方案有没有其作用;
5)LG V30+(7.1.2):使用此方案前后效果一致。结论:此方案不起作用;
6)小米8(8.1):一键清理可以杀掉服务。锁屏后保活超过20分钟。

▲ 结论: 成功对华为手机保活。小米8下也成功突破20分钟。

【4】使用JobScheduler唤醒Service(基于onStartCommand() return START_STICKY):

1)原生5.0、5.1:任务管理器中干掉APP,服务会在周期时间后重新启动。结论:此方案起作用;
2)华为畅享5x(6.0):一键清理直接杀掉APP,过12s左右会自动重启服务,JobScheduler起作用;
3)美图m8s(7.1.1):一键清理直接杀掉APP,无法自动重启;
4)原生7.0:同美图m8s(7.1.1);
5)小米8(8.1):同美图m8s(7.1.1)。

▲ 结论: 只对5.0,5.1、6.0起作用。

【5】混合使用的效果,并且在通知栏弹出通知:

1)原生5.0、5.1:任务管理器中干掉APP,服务会在周期时间后重新启动。锁屏超过11分钟存活;
2)华为畅享5x(6.0):一键清理后服务依然存活,需要单独清理才可杀掉服务。结论:方案适用;
3)美图m8s(7.1.1):一键清理APP会被杀掉。正常情况下锁屏后服务依然存活;
4)原生7.0:任务管理器中关闭APP后服务被干掉,过2s会重新复活;
5)小米8(8.1):一键清理可以杀掉服务,锁屏下后台保活时间超过38分钟;
6)荣耀10(8.0):一键清理杀掉服务,锁屏下后台保活时间超过23分钟。

▲ 结论: 高版本情况下可以使用弹出通知栏、双进程、无声音乐提高后台服务的保活概率。

8、补充:ServiceAliveUtils 类代码如下

public class ServiceAliveUtils {
   publicstaticbooleanisServiceAlice() {
       booleanisServiceRunning = false;
       ActivityManager manager =
           (ActivityManager) MyApplication.getMyApplication().getSystemService(Context.ACTIVITY_SERVICE);
       if(manager == null) {
           returntrue;
       }
       for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
           if("demo.lgm.com.keepalivedemo.service.DownloadService".equals(service.service.getClassName())) {
               isServiceRunning = true;
           }
       }
       returnisServiceRunning;
   }
}

9、写在最后

Android P(即Android 9)已于2018年8月7日的正式发布,此版本的Android省电策略等限制,对于APP的后台保活来说将更为困难。预计2019年Android P将会成为Android设备的主流系统,到那时才是真正噩梦的开始。

关于Android P在保活方面的问题,请详细阅读《 Android P正式版即将到来:后台应用保活、消息推送的真正噩梦 》。

附录:更多有关IM/推送的心跳保活处理的文章

应用保活终极总结(一):Android6.0以下的双进程守护保活实践

应用保活终极总结(二):Android6.0及以上的保活实践(进程防杀篇)

应用保活终极总结(三):Android6.0及以上的保活实践(被杀复活篇)

Android进程保活详解:一篇文章解决你的所有疑问

Android端消息推送总结:实现原理、心跳保活、遇到的问题等

深入的聊聊Android消息推送这件小事

为何基于TCP协议的移动端IM仍然需要心跳保活机制?

微信团队原创分享:Android版微信后台保活实战分享(进程保活篇)

微信团队原创分享:Android版微信后台保活实战分享(网络保活篇)

移动端IM实践:实现Android版微信的智能心跳机制

移动端IM实践:WhatsApp、Line、微信的心跳策略分析

Android P正式版即将到来:后台应用保活、消息推送的真正噩梦

全面盘点当前Android后台保活方案的真实运行效果(截止2019年前)

>> 更多同类文章 ……

(本文同步发布于: http://www.52im.net/thread-2176-1-1.html


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据结构与算法JavaScript描述

数据结构与算法JavaScript描述

[美] Michael McMillan / 王群锋、杜 欢 / 人民邮电出版社 / 2014-8 / 49.00元

通过本书的学习,读者将能自如地选择最合适的数据结构与算法,并在JavaScript开发中懂得权衡使用。此外,本书也概述了与数据结构与算法相关的JavaScript特性。 本书主要内容如下。 数组和列表:最常用的数据结构。 栈和队列:与列表类似但更复杂的数据结构。 链表:如何通过它们克服数组的不足。 字典:将数据以键-值对的形式存储。 散列:适用于快速查找和检索。......一起来看看 《数据结构与算法JavaScript描述》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具