探讨Android6.0及以上系统APP常驻内存(保活)实现-复活篇
(转载请声明出处:http://blog.csdn.net/andrexpert/article/details/75174586)
APP保活系列(最高支持到Android 7.0)
(1) 探讨一种新型的双进程守护应用保活方法
(2) 探讨Android6.0及以上系统APP常驻内存(保活)实现-争宠篇
(3) 探讨Android6.0及以上系统APP常驻内存(保活)实现-复活篇
APP常驻内存(保活),又旧事重提,距离上一次的研究亦有半年有余。最近,用户反馈说多进程守护方案在华为Mate8(7.0)保活效果不是很好,有时候还是不能及时收到消息,于是,又带着怀疑的眼光,重新找回原来的代码进行测试,顺便分析了市场上主流运动类APP保活方法(微信、手Q就算了,富人家的孩子,不具代表性),同时也对系统对内存中APP的管理规则进行了进一步探索。本文便是对最近一周的探索、学习、测试的总结之一,以备将来不时之需。
一、APP复活核心思想归纳
随着AlarmManager唤醒、native进程拉起等方式的失效,APP常驻内存的时代将不复存在,尤其是当APP进程被杀死后,基本很难将其拉起。从用户的角度来讲,这是一种很好的发展,而这一切应该归功于谷歌和各大厂商不断追求良好的用户体验;从开发者的角度来说,尤其是即时通信类应用,这将是毁灭性打击。如果用户使用你的聊天软件,但在使用过程中总是不能及时收到对方的消息,那将是一种什么样的体验,因此,厂商"白名单"便应运而生了。正是因为如此,本文探讨的相关方案不可能保证在任何时候,或者任何机型能够唤醒,一切都是相对存在的。
接下来,我们先了解下哪些情况下进程会被杀死:
1. LowMemoryKiller:这种情况是触发了系统进程管理机制,通过系统会参照当前系统资源情况和oom_adj值来回收进程,oom_adj越大,越容易被杀死;
2. 第三方清理软件:杀死oom_adj值大于4的进程,如果拥有root权限,理论可杀死所有进程;
3. 厂商杀进程:可杀所有进程
4. Force-stop:可杀所有非系统进程
二、APP复活方案探讨
1. 使用JobScheduler
JobScheduler是谷歌在Android 5.0引入的一个能够执行某项任务的API,它允许APP在将来达到一定条件时执行指定的任务。通常情况下,即使APP被强制停止,预定的任务仍然会被执行。JobScheduler工作原理:
首先在一个实现了JobService的子类的onStartJob方法中执行这项任务,使用JobInfo的Builder方法来设定条件并和实现了JobService的子类的组件名绑定,然后调用系统服务JobScheduler的schedule方法。这样,即便在执行任务之前应用程序进程被杀,也不会导致任务不会执行,因为系统服务JobScheduler会使用bindServiceAsUser的方法把实现了JobService的子类服务启动起来,并执行它的onStartJob方法
(1) AliveJobService.java
/**JobService,支持5.0以上forcestop依然有效
*
* Created by jianddongguo on 2017/7/10.
*/
@TargetApi(21)
public class AliveJobService extends JobService {
private final static String TAG = "KeepAliveService";
// 告知编译器,这个变量不能被优化
private volatile static Service mKeepAliveService = null;
public static boolean isJobServiceAlive(){
return mKeepAliveService != null;
}
private static final int MESSAGE_ID_TASK = 0x01;
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
// 具体任务逻辑
if(SystemUtils.isAPPALive(getApplicationContext(), Contants.PACKAGE_NAME)){
Toast.makeText(getApplicationContext(), "APP活着的", Toast.LENGTH_SHORT)
.show();
}else{
Intent intent = new Intent(getApplicationContext(), SportsActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
Toast.makeText(getApplicationContext(), "APP被杀死,重启...", Toast.LENGTH_SHORT)
.show();
}
// 通知系统任务执行结束
jobFinished( (JobParameters) msg.obj, false );
return true;
}
});
@Override
public boolean onStartJob(JobParameters params) {
if(Contants.DEBUG)
Log.d(TAG,"KeepAliveService----->JobService服务被启动...");
mKeepAliveService = this;
// 返回false,系统假设这个方法返回时任务已经执行完毕;
// 返回true,系统假定这个任务正要被执行
Message msg = Message.obtain(mHandler, MESSAGE_ID_TASK, params);
mHandler.sendMessage(msg);
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
mHandler.removeMessages(MESSAGE_ID_TASK);
if(Contants.DEBUG)
Log.d(TAG,"KeepAliveService----->JobService服务被关闭");
return false;
}
}
(2) JobSchedulerManager.java
/**JobScheduler管理类,单例模式
* 执行系统任务
*
* Created by jianddongguo on 2017/7/10.
* http://blog.csdn.net/andrexpert
*/
public class JobSchedulerManager {
private static final int JOB_ID = 1;
private static JobSchedulerManager mJobManager;
private JobScheduler mJobScheduler;
private static Context mContext;
private JobSchedulerManager(Context ctxt){
this.mContext = ctxt;
mJobScheduler = (JobScheduler)ctxt.getSystemService(Context.JOB_SCHEDULER_SERVICE);
}
public final static JobSchedulerManager getJobSchedulerInstance(Context ctxt){
if(mJobManager == null){
mJobManager = new JobSchedulerManager(ctxt);
}
return mJobManager;
}
@TargetApi(21)
public void startJobScheduler(){
// 如果JobService已经启动或API<21,返回
if(AliveJobService.isJobServiceAlive() || isBelowLOLLIPOP()){
return;
}
// 构建JobInfo对象,传递给JobSchedulerService
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID,new ComponentName(mContext, AliveJobService.class));
// 设置每3秒执行一下任务
builder.setPeriodic(3000);
// 设置设备重启时,执行该任务
builder.setPersisted(true);
// 当插入充电器,执行该任务
builder.setRequiresCharging(true);
JobInfo info = builder.build();
//开始定时执行该系统任务
mJobScheduler.schedule(info);
}
@TargetApi(21)
public void stopJobScheduler(){
if(isBelowLOLLIPOP())
return;
mJobScheduler.cancelAll();
}
private boolean isBelowLOLLIPOP(){
// API< 21
return Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP;
}
}
(3) AndroidManifest.xml
<--! AliveJobService需要BIND_JOB_SERVICE权限-->
Doze模式讲解:
2. 华为推送SDK
与个推、小米、极光推送类似,华为Push也是为开发者提供的一个消息推送平台,它建立了从云端到手机端的消息推送通道,支持上报标签、LBS信息、通知推送。换句话来说,就算我们的APP没有自己的服务器,也可以通过这些第三方推送平台,把最新消息及时地通知用户。
华为推送保活原理:华为推送服务以后台Service方式运行在一个独立进程里,主程序不需要常驻内存。当该后台Service接收到push消息后会以广播的方式通知主进程,触发相关回调接口。通常,被强制停止的APP无法接收到广播,但是华为push通道(即推送后台Service,它会常驻内存)能够强行将APP拉起来,这是因为其在发广播时利用了Intent.FLAG_INCLUDE_STOPPED_PACKAGES标记实现的。
(1) MyHwPushReceiver.java
/** 华为接收器子类,用于接收推送消息
*
* Created by jianddongguo on 2017/7/10.
* http://blog.csdn.net/andrexpert
*
*/
public class MyHwPushReceiver extends PushEventReceiver{
private final static String TAG = "MyHwPushReceiver";
/**
* pushToken申请成功后,会自动回调该方法
* 应用通过该方法获取token(必须实现)
* */
@Override
public void onToken(Context context, String token, Bundle bundle) {
Log.i(TAG,"连接到华为推送服务器,token="+token);
}
/**
* 推送消息下来时会自动回调onPushMsg方法
* 用于接收透传消息(必须实现)
* */
@Override
public boolean onPushMsg(Context context, byte[] msgBytes, Bundle bundle) {
if(Contants.DEBUG){
try {
Log.i(TAG,"接收透传消息:"+new String(msgBytes,"UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
// 启动应用
return false;
}
/**
* 连接状态的回调方法
* */
@Override
public void onPushState(Context context, boolean connectState) {
Log.i(TAG,"是否连接到华为推送服务器:"+(connectState?"connected":"disconnected"));
}
/**
* 该方法会在设置标签、LBS信息之后,点击打开通知栏消息
* 点击通知栏上的按钮之后被调用,由业务决定是否调用该函数。
* Event类型:
* NOTIFICATION_OPEMED,通知栏中的通知被点击打开
* NOTIFICATION_CLICK_BTN,通知栏中通知上的按钮被点击
* PLUGINRSP,标签上报回应
* */
@Override
public void onEvent(Context context, Event event, Bundle bundle) {
super.onEvent(context, event, bundle);
}
}
讲解一下:
/**华为推送控制类
*
* Created by jianddongguo on 2017/7/15.
* http://blog.csdn.net/andrexpert
*/
public class HwPushManager {
private static HwPushManager mPushManager;
private Context mContext;
private HwPushManager(Context mContext){
this.mContext = mContext;
}
public static HwPushManager getInstance(Context mContext){
if(mPushManager == null){
mPushManager = new HwPushManager(mContext);
}
return mPushManager;
}
/**向服务器请求Token
* 前提是已经在华为开发者联盟注册本应用,并申请、审核通过Push权益
* */
public void startRequestToken(){
PushManager.requestToken(mContext);
}
/**
* 是否接收服务器传递过来的透传消息
* */
public void isEnableReceiveNormalMsg(boolean isEnable){
PushManager.enableReceiveNormalMsg(mContext,isEnable);
}
/**
* 是否接收自呈现消息
* */
public void isEnableReceiverNotifyMsg(boolean isEnable){
PushManager.enableReceiveNotifyMsg(mContext,isEnable);
}
}
讲解一下:
讲解一下:
Github项目地址:https://github.com/jiangdongguo/KeepingAppAlive
参考资料:
(1) Android之JobScheduler运行机制源码分析
(2) 安卓推送技术手册——使用透传消息的正确姿势
(3) 华为推送服务官网
(4) 流媒体、富媒体、多媒体到底有什么区别?
(5) 史上最全解析Android消息推送解决方案
(6) 华为推送接口文档
(7) Android集成华为推送的问题总结
(8) android接入华为推送注意点