本文主要是介绍App复活,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
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
-
-
-
-
- @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;
-
-
- 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
-
-
-
-
-
-
- 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(){
-
- if(AliveJobService.isJobServiceAlive() || isBelowLOLLIPOP()){
- return;
- }
-
- JobInfo.Builder builder = new JobInfo.Builder(JOB_ID,new ComponentName(mContext, AliveJobService.class));
-
- 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(){
-
- return Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP;
- }
- }
(3) AndroidManifest.xml
- <--! AliveJobService需要BIND_JOB_SERVICE权限-->
- <service
- android:name=".service.AliveJobService"
- android:permission="android.permission.BIND_JOB_SERVICE"/>
Doze模式讲解:
Doze,即休眠、打盹之意,是谷歌在Android M(6.0)提出为了延长电池使用寿命的一种节能方式,它的核心思想是在手机处于屏幕熄灭、不插电或静止不动一段时间后,手机会自动进入Doze模式,处于Doze模式的手机将停止所有非系统应用的WalkLocks、网络访问、闹钟、GPS/WIFI扫描、JobSheduler活动。当进入Doze模式的手机屏幕被点亮、移动或充电时,会立即从Doze模式恢复到正常,系统继续执行被Doze模式"冷冻"的各项活着。换而言之,Doze模式不会杀死进程,只是停止了进程相关的耗电活动,使其进入"休眠"状态。至Android N(7.0)后,谷歌进一步对Doze休眠机制进行了优化,休眠机制的应用场景和使用规则进行了扩展。Doze在Android 6.0中需要将手机平行放置一段时间才能开启,在7.0中则可随时开启。
因此,对于Android 5.0,JobSheduler的唤醒是非常有效的;对于Android 6.0,虽然谷歌引入了Doze模式,但通常很难真正进入Doze模式,所以JobSheduler的唤醒依然有效;对于Android 7.0,JobSheduler的唤醒会有一定的影响,我们可以在电池管理中给APP开绿色通道,防止手机Doze模式后阻止APP使用JobSheduler功能。注:如果遇到深度定制机型,这就要看运气了...
(4) 测试结果
三星C9(6.0):一键清理和强制停止(force stop)都能够唤醒APP;
三星Note4(5.0):一键清理和强制停止(force stop)都能够唤醒APP;
华为荣耀4X(6.0):一键清理和强制停止(force stop)都能够唤醒APP;
华为Mate8(7.0):失效(可能被华为屏蔽了);
2. 华为推送SDK
与个推、小米、极光推送类似,华为Push也是为开发者提供的一个消息推送平台,它建立了从云端到手机端的消息推送通道,支持上报标签、LBS信息、通知推送。换句话来说,就算我们的APP没有自己的服务器,也可以通过这些第三方推送平台,把最新消息及时地通知用户。
华为推送保活原理:华为推送服务以后台Service方式运行在一个独立进程里,主程序不需要常驻内存。当该后台Service接收到push消息后会以广播的方式通知主进程,触发相关回调接口。通常,被强制停止的APP无法接收到广播,但是华为push通道(即推送后台Service,它会常驻内存)能够强行将APP拉起来,这是因为其在发广播时利用了Intent.FLAG_INCLUDE_STOPPED_PACKAGES标记实现的。
(1) MyHwPushReceiver.java
-
-
-
-
-
-
-
-
- public class MyHwPushReceiver extends PushEventReceiver{
- private final static String TAG = "MyHwPushReceiver";
-
-
-
-
-
-
- @Override
- public void onToken(Context context, String token, Bundle bundle) {
- Log.i(TAG,"连接到华为推送服务器,token="+token);
- }
-
-
-
-
-
-
- @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"));
- }
-
-
-
-
-
-
-
-
-
-
- @Override
- public void onEvent(Context context, Event event, Bundle bundle) {
- super.onEvent(context, event, bundle);
- }
- }
讲解一下:
MyHwPushReceiver是华为接收器(com.huawei.android.pushagent.api.PushEventReceive)的子类,用于接收服务器传递过来的token,获取服务器连接状态,接收服务器推送过来的通知、透传等消息。需要注意的是,onToken方法、onPushMsg方法必须要实现,并且尽量不要在MyHwPushReceiver类中开启线程、处理Handler等。
(2) HwPushManager.java
-
-
-
-
-
-
-
- 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;
- }
-
-
-
-
-
- public void startRequestToken(){
- PushManager.requestToken(mContext);
- }
-
-
-
-
-
- public void isEnableReceiveNormalMsg(boolean isEnable){
- PushManager.enableReceiveNormalMsg(mContext,isEnable);
- }
-
-
-
-
-
- public void isEnableReceiverNotifyMsg(boolean isEnable){
- PushManager.enableReceiveNotifyMsg(mContext,isEnable);
- }
-
-
- }
讲解一下:
a. 透传消息,即对于信息传输通道来说不会去关心透传消息的消息体格式及内容,它只是负责消息的传递,不对消息做任何处理,当客户端接收到透传消息后,由客户端自己来决定如何处理消息,比如默默在后台处理消息、以通知的方式向用户展示消息等,因此它弥补了通知栏消息客户端无法捕获到相关动作的不足。
b. 通知栏消息,即被推送的通知发送后会在客户端系统通知栏收到展现,同时响铃或振动提醒用户,但客户端无法捕捉到通知栏消息的相关动作,来做进一步的处理。
c. 富媒体消息,即随着信息技术的升级发展,在互联网上传播的信息,不仅只有文字或图片,同时还可以包括动画、视频、互动、音乐或语音效果等。
(3) AndroidManifest.xml
-
- <uses-permission android:name="android.permission.INTERNET" />
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
- <uses-permission android:name="android.permission.READ_PHONE_STATE" />
- <uses-permission android:name="android.permission.WAKE_LOCK" />
-
- <receiver android:name=".receiver.MyHwPushReceiver" >
- <intent-filter>
-
- <action android:name="com.huawei.android.push.intent.REGISTRATION" />
-
- <action android:name="com.huawei.android.push.intent.RECEIVE" />
-
- <action android:name="com.huawei.android.push.intent.CLICK" />
-
- <action android:name="com.huawei.intent.action.PUSH_STATE" />
-
- <action android:name="com.huawei.android.push.plugin.RESPONSE" />
- </intent-filter>
- <meta-data android:name="CS_cloud_ablitity" android:value="@string/hwpush_ability_value"/>
- </receiver>
-
-
- <!-- 备注:Push相关的android组件需要添加到业务的AndroidManifest.xml,
- Push相关android组件运行在另外一个进程是为了防止Push服务异常而影响主业务 -->
-
- <receiver
- android:name="com.huawei.android.pushagent.PushEventReceiver"
- android:process=":pushservice" >
- <intent-filter>
- <action android:name="com.huawei.android.push.intent.REFRESH_PUSH_CHANNEL" />
- <action android:name="com.huawei.intent.action.PUSH" />
- <action android:name="com.huawei.intent.action.PUSH_ON" />
- <action android:name="com.huawei.android.push.PLUGIN" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.PACKAGE_ADDED" />
- <action android:name="android.intent.action.PACKAGE_REMOVED" />
-
-
- <data android:scheme="package" />
- </intent-filter>
- </receiver>
- <receiver
- android:name="com.huawei.android.pushagent.PushBootReceiver"
- android:process=":pushservice" >
- <intent-filter>
- <action android:name="com.huawei.android.push.intent.REGISTER" />
- <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
- </intent-filter>
- <meta-data
- android:name="CS_cloud_version"
- android:value="\u0032\u0037\u0030\u0035" />
- </receiver>
-
-
-
- <service
- android:name="com.huawei.android.pushagent.PushService"
- android:process=":pushservice" >
- </service>
-
-
-
-
- <activity
- android:name="com.huawei.android.pushselfshow.richpush.RichPushActivity"
- android:process=":pushservice"
- android:theme="@style/hwpush_NoActionBar"
- android:configChanges="orientation|screenSize|locale|layoutDirection"
- android:screenOrientation="portrait">
- <meta-data android:name="hwc-theme"
- android:value="androidhwext:style/Theme.Emui"/>
- <intent-filter>
- <action android:name="com.huawei.android.push.intent.RICHPUSH" />
-
-
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
- <activity
- android:name="com.huawei.android.pushselfshow.permission.RequestPermissionsActivity"
- android:theme="@android:style/Theme.DeviceDefault.Light.Dialog.NoActionBar"
- android:launchMode="singleTop"
- android:screenOrientation="portrait"
- android:configChanges="orientation|screenSize|locale|layoutDirection"
- android:exported="false">
- </activity>
讲解一下:
这里除了MyHwPushReceiver类,其他直接从华为推送官方Demo拷贝即可。在测试过程中,需要连接到网络,然后我这里是通过华为开发者联盟网页中给KeepAppAlive发送透传消息的,用来测试华为推送保活的可行性。一般来说,我们都是在自己的服务器开一个定时器,定时推送透传消息到客户端。注:部分华为手机可能还需要开启自启动权限;如何集成华为推送SDK,直接看官方文档即可;非华为手机需要安装"华为手机服务.apk"才能使用华为推送,这个有点坑。
(4) 接收日志
(5) 效果演示
3. 第三方应用互相唤醒
“
探讨一种新型的双进程守护应用保活方法
”就是利用第三方应用相互唤醒实现的,只是唤醒的方式比较 "坦率"。不会像腾讯、阿里全家桶那样 "委婉":当你启动其中的一个APP,该APP可能就会通过广播的方式偷偷唤醒它们这一个派系的其他APP,可谓是神不知鬼不觉。
Github项目地址:https://github.com/jiangdongguo/KeepingAppAlive
参考资料:
(1) Android之JobScheduler运行机制源码分析
(2) 安卓推送技术手册——使用透传消息的正确姿势
(3) 华为推送服务官网
(4) 流媒体、富媒体、多媒体到底有什么区别?
(5) 史上最全解析Android消息推送解决方案
(6) 华为推送接口文档
(7) Android集成华为推送的问题总结
(8) android接入华为推送注意点
这篇关于App复活的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!