默认
打赏 发表评论 38
想开发IM:买成品怕坑?租第3方怕贵?找开源自已撸?尽量别走弯路了... 找站长给点建议
应用保活终极总结(三):Android6.0及以上的保活实践(被杀复活篇)
阅读(259472) | 评论(38 收藏9 淘帖1
微信扫一扫关注!

原作者:“裂缝中的阳光dg”,本文由即时通讯网重新修订并整理发布,感谢原作者的无私分享。


1、前言


最新推荐:Android保活从入门到放弃:乖乖引导用户加白名单吧(附7大机型加白示例)》(此文发布于2020年06月13日)。

接上篇《应用保活终极总结(二):Android6.0及以上的保活实践(进程防杀篇)》,本文将重点讨论进程被杀后复活的可能性及实践。

随着AlarmManager唤醒、native进程拉起等方式的失效,APP常驻内存的时代将不复存在,尤其是当APP进程被杀死后,基本很难将其复活拉起。从用户的角度来讲,这是一种很好的发展,而这一切应该归功于谷歌和各大厂商不断追求良好的用户体验;从开发者的角度来说,尤其是即时通信类应用(如移动端IM、消息推送服务等),这将是毁灭性打击。如果用户使用你的聊天软件,但在使用过程中总是不能及时收到对方的消息,那将是一种什么样的体验,因此,厂商"白名单"便应运而生了。正是因为如此,本文探讨的相关方案不可能保证在任何时候,或者任何机型能够唤醒,一切都是相对存在的。

本文中的进程被杀复活方法主要针对Android 6.0及以上系统(低于Android 6.0系统的保活方案请见本系列的第1篇《应用保活终极总结(一):Android6.0以下的双进程守护保活实践》)

特别说明:本文中的Demo源码打包完整下载请至文末,直接从附件下载。

2、系列文章


本文是系列文章中的第2篇,本系列文章的大纲如下:


3、参考资料


Android进程保活详解:一篇文章解决你的所有疑问
微信团队原创分享:Android版微信后台保活实战分享(进程保活篇)
Android P正式版即将到来:后台应用保活、消息推送的真正噩梦
全面盘点当前Android后台保活方案的真实运行效果(截止2019年前)
融云技术分享:融云安卓端IM产品的网络链路保活技术实践
2020年了,Android后台保活还有戏吗?看我如何优雅的实现!
史上最强Android保活思路:深入剖析腾讯TIM的进程永生技术
Android进程永生技术终极揭密:进程被杀底层原理、APP对抗被杀技巧》(* 推荐
>> 更多同类文章 ……

4、回顾上篇:哪些情况下进程会被杀死


我们先了解下哪些情况下进程会被杀死:

  • 1)LowMemoryKiller:这种情况是触发了系统进程管理机制,通过系统会参照当前系统资源情况和oom_adj值来回收进程,oom_adj越大,越容易被杀死;
  • 2)第三方清理软件:杀死oom_adj值大于4的进程,如果拥有root权限,理论可杀死所有进程;
  • 3)厂商杀进程:可杀所有进程;
  • 4)Force-stop:可杀所有非系统进程。

详见本系列的第2篇《应用保活终极总结(二):Android6.0及以上的保活实践(进程防杀篇)》。

5、APP复活方案探讨和实践


5.1使用JobScheduler


JobScheduler是谷歌在Android 5.0引入的一个能够执行某项任务的API,它允许APP在将来达到一定条件时执行指定的任务。通常情况下,即使APP被强制停止,预定的任务仍然会被执行。

JobScheduler工作原理:

首先在一个实现了JobService的子类的onStartJob方法中执行这项任务,使用JobInfo的Builder方法来设定条件并和实现了JobService的子类的组件名绑定,然后调用系统服务JobScheduler的schedule方法。这样,即便在执行任务之前应用程序进程被杀,也不会导致任务不会执行,因为系统服务JobScheduler会使用bindServiceAsUser的方法把实现了JobService的子类服务启动起来,并执行它的onStartJob方法。


我们来看看Demo源码。

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;  
    }  
}

JobSchedulerManager.java:
/**JobScheduler管理类,单例模式 
 * 执行系统任务 
 * 
 * Created by jianddongguo on 2017/7/10. 
 */  
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;  
    }  
}

AndroidManifest.xml:
<--! AliveJobService需要BIND_JOB_SERVICE权限-->  
<service  
         android:name=".service.AliveJobService"  
          android:permission="android.permission.BIND_JOB_SERVICE"/>  

5.2Doze模式讲解


Doze:即休眠、打盹之意。是谷歌在Android M(6.0)提出为了延长电池使用寿命的一种节能方式,它的核心思想是在手机处于屏幕熄灭、不插电或静止不动一段时间后,手机会自动进入Doze模式。

处于Doze模式的手机将停止所有非系统应用的WalkLocks、网络访问、闹钟、GPS/WIFI扫描、JobSheduler活动。当进入Doze模式的手机屏幕被点亮、移动或充电时,会立即从Doze模式恢复到正常,系统继续执行被Doze模式"冷冻"的各项活动。

换而言之,Doze模式不会杀死进程,只是停止了进程相关的耗电活动,使其进入"休眠"状态。至Android N(即Android 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功能。

注:如果遇到深度定制机型,这就要看运气了...


5.3Demo测试结果


  • 三星C9(Android 6.0):一键清理和强制停止(force stop)都能够唤醒APP;
  • 三星Note4(Android 5.0):一键清理和强制停止(force stop)都能够唤醒APP;
  • 华为荣耀4X(Android 6.0):一键清理和强制停止(force stop)都能够唤醒APP;
  • 华为Mate8(Android 7.0):失效(可能被华为屏蔽了)。

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

6、华为推送SDK的APP复活方案研究学习


与个推、小米、极光推送类似,华为Push也是为开发者提供的一个消息推送平台,它建立了从云端到手机端的消息推送通道,支持上报标签、LBS信息、通知推送。换句话来说,就算我们的APP没有自己的服务器,也可以通过这些第三方推送平台,把最新消息及时地通知用户。

华为推送复活原理:华为推送服务以后台Service方式运行在一个独立进程里,主程序不需要常驻内存。当该后台Service接收到push消息后会以广播的方式通知主进程,触发相关回调接口。通常,被强制停止的APP无法接收到广播,但是华为push通道(即推送后台Service,它会常驻内存)能够强行将APP拉起来,这是因为其在发广播时利用了Intent.FLAG_INCLUDE_STOPPED_PACKAGES标记实现的。

来看看Demo代码。

MyHwPushReceiver.java:
/** 华为接收器子类,用于接收推送消息 
 * 
 * Created by jianddongguo on 2017/7/10. 
 */  
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);  
    }  
}

讲解一下:
MyHwPushReceiver是华为接收器(com.huawei.android.pushagent.api.PushEventReceive)的子类,用于接收服务器传递过来的token,获取服务器连接状态,接收服务器推送过来的通知、透传等消息。需要注意的是,onToken方法、onPushMsg方法必须要实现,并且尽量不要在MyHwPushReceiver类中开启线程、处理Handler等。

HwPushManager.java:
/**
 * 华为推送控制类 
 * 
 * Created by jianddongguo on 2017/7/15. 
 */    
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);  
    }  
}

讲解一下:
  • a. 透传消息:即对于信息传输通道来说不会去关心透传消息的消息体格式及内容,它只是负责消息的传递,不对消息做任何处理,当客户端接收到透传消息后,由客户端自己来决定如何处理消息,比如默默在后台处理消息、以通知的方式向用户展示消息等,因此它弥补了通知栏消息客户端无法捕获到相关动作的不足;
  • b. 通知栏消息:即被推送的通知发送后会在客户端系统通知栏收到展现,同时响铃或振动提醒用户,但客户端无法捕捉到通知栏消息的相关动作,来做进一步的处理;
  • c. 富媒体消息:即随着信息技术的升级发展,在互联网上传播的信息,不仅只有文字或图片,同时还可以包括动画、视频、互动、音乐或语音效果等。

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" />  
 <!-- 第三方相关 :接收Push消息(注册、Push消息、Push连接状态、标签,LBS上报结果)广播 -->  
      <receiver android:name=".receiver.MyHwPushReceiver" >  
          <intent-filter>  
              <!-- 必须,用于接收token-->  
              <action android:name="com.huawei.android.push.intent.REGISTRATION" />  
              <!-- 必须,用于接收消息-->  
              <action android:name="com.huawei.android.push.intent.RECEIVE" />  
              <!-- 可选,用于点击通知栏或通知栏上的按钮后触发onEvent回调-->  
              <action android:name="com.huawei.android.push.intent.CLICK" />  
              <!-- 可选,查看push通道是否连接,不查看则不需要-->  
              <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服务异常而影响主业务 -->  
      <!-- PushSDK:PushSDK接收外部请求事件入口 -->  
      <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>  
  
      <!-- PushSDK:Push服务 -->  
      <service  
          android:name="com.huawei.android.pushagent.PushService"  
          android:process=":pushservice" >  
      </service>  
  
      <!-- PushSDK:富媒体呈现页面,用于呈现服务器下发的富媒体消息 -->  
      <!-- locale|layoutDirection 切换语言后不重新创建activity -->  
      <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"才能使用华为推送,这个有点坑。

推送接收日志:
应用保活终极总结(三):Android6.0及以上的保活实践(被杀复活篇)_2.png

Demo效果演示:
应用保活终极总结(三):Android6.0及以上的保活实践(被杀复活篇)_3.gif

7、APP复活的另一种可能性:第三方应用互相唤醒


应用保活终极总结(一):Android6.0以下的双进程守护保活实践》就是利用第三方应用相互唤醒实现的,只是唤醒的方式比较 "坦率"。不会像腾讯、阿里全家桶那样 "委婉":当你启动其中的一个APP,该APP可能就会通过广播的方式偷偷唤醒它们这一个派系的其他APP,可谓是神不知鬼不觉。

8、源码下载


KeepingAppAlive-master(52im.net).zip (1.29 MB , 下载次数: 294 , 售价: 3 金币)

(-- 全文完 --) (原文链接:点此进入

即时通讯网 - 即时通讯开发者社区! 来源: - 即时通讯开发者社区!

标签:进程保活
上一篇:应用保活终极总结(二):Android6.0及以上的保活实践(进程防杀篇)下一篇:socket连接是和用户账号一一关联的吗?

本帖已收录至以下技术专辑

推荐方案
评论 38
学习了。
签名:
神秘人  发表于 7 年前
其实在国内最佳实践应该是接入各个平台的推送服务吧,比如检测如果是华为手机就注册华为的推送服务,小米手机就使用 mipush,开发上会有一些工作量。但是至少通知栏消息都是可保证到达的,而且这种情况下也不会耗电。国外就直接 FCM 就好了。
这些保活措施说实话其实还是挺流氓的。
签名: 该会员没有填写今日想说内容.
引用:大尾巴鱼 发表于 2017-11-01 16:25
其实在国内最佳实践应该是接入各个平台的推送服务吧,比如检测如果是华为手机就注册华为的推送服务,小米手 ...

是的,推送的话最佳实践肯定还是用厂商的推送通道。
关键问题嘛,即使让你钻空子找到了个法子,定制ROM轻易就能把你禁了,系统是不允许有这么流氓的软件存在的。微信QQ除外
引用:SingleInstance 发表于 2017-11-06 16:53
关键问题嘛,即使让你钻空子找到了个法子,定制ROM轻易就能把你禁了,系统是不允许有这么流氓的软件存在的 ...

正解!
准备用上试试,ov手机太牛,app时间不长就会被干掉
引用:laorencel 发表于 2017-11-16 11:02
准备用上试试,ov手机太牛,app时间不长就会被干掉

现在保活难度越来越大
学习了 现在正在搞IM的开发
被推送服务搞得不要不要的  试试看
引用:北方竹子 发表于 2017-12-28 10:05
被推送服务搞得不要不要的  试试看

国家的推送联盟不知道什么时候都真干点事出来
学习了
保活难度越来越大了
高版本越来越难了
引用:liam 发表于 2018-06-04 09:04
高版本越来越难了

是的,那个统一推送联盟再不干活,大家都得玩死
保活没有终极大招,经常被客户追着问,“人家微信、QQ都可以,为啥你们的APP非得加到手机白名单”,哎
引用:小马哥 发表于 2018-07-11 16:14
保活没有终极大招,经常被客户追着问,“人家微信、QQ都可以,为啥你们的APP非得加到手机白名单”,哎

那你把这篇文章给他们看《腾讯开发微信花了多少钱?技术难度真这么大?难在哪?
强制可以杀死;小米5,Android7.0,怎么都是死;
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

返回顶部