如何实现进程保活的最新方案

作者: 不正经的创作者 | 来源:发表于2020-05-20 16:33 被阅读0次

    简介

    现在只要是社交 APP 没有哪个开发者不想让自己的 APP 永久常驻的,想要永久常驻除非你们家的实力非常雄厚,APP 用户量非常大,那么厂商都会主动来找你,把你们家的 APP 加入白名单。否则永久常驻是不可能甚至都不给你权限后台运行。既然不能永久常驻,那么我们有没有一个办法可以使我们的 APP 不那么容易被系统杀死勒?或者说是杀死后能主动唤醒,显然是可以的,下面我们进入主题吧。

    怎么使用

    代码传送阵

    1. down 代码 https://github.com/yangkun19921001/KeepAlive.git ,将 live_library 放入自己工程

    2. 在 KeepAliveRuning onRuning 中实现需要保活的代码

        public class KeepAliveRuning implements IKeepAliveRuning {
            /**这里实现 Socket / 推送 等一些保活组件*/
            @Override
            public void onRuning() {
                //TODO--------------------------------------------
                Log.e("runing?KeepAliveRuning", "true");
            }
      
            @Override
            public void onStop() {
                Log.e("runing?KeepAliveRuning", "false");
            }
        }
      
    3. 开启保活

          public void start() {
              //启动保活服务
              KeepAliveManager.toKeepAlive(
                      getApplication()
                      , HIGH_POWER_CONSUMPTION,
                      "进程保活",
                      "Process: System(哥们儿) 我不想被杀死",
                      R.mipmap.ic_launcher,
                      new ForegroundNotification(
                              //定义前台服务的通知点击事件
                              new ForegroundNotificationClickListener() {
                                  @Override
                                  public void foregroundNotificationClick(Context context, Intent intent) {
                                      Log.d("JOB-->", " foregroundNotificationClick");
                                  }
                              })
              );
          }
      
    4. 停止保活

         KeepAliveManager.stopWork(getApplication());
      

    最终效果

    开启保活

    我们应该知道正常的话点击手机回收垃圾桶后台的应用都会被 kill 掉,还有主动点击 AS Logcat 的进程停止运行的按钮,我们也会发现进程会自动起来并且 pid 跟上一次不一样了。要的就是这种效果,下面我们来了解下进程保活的知识吧.

    未开启保活

    进程优先级

    官网详细介绍

    进程

    如果内存不足,需要为其他用户提供更紧急服务的进程又需要内存时,Android 可能会决定在某一时刻关闭某一进程。在被终止进程中运行的应用组件也会随之销毁。 当这些组件需要再次运行时,系统将为它们重启进程。

    决定终止哪个进程时,Android 系统将权衡它们对用户的相对重要程度。例如,相对于托管可见 Activity 的进程而言,它更有可能关闭托管屏幕上不再可见的 Activity 的进程。 因此,是否终止某个进程的决定取决于该进程中所运行组件的状态。 下面,我们介绍决定终止进程所用的规则。

    进程生命周期

    Android 系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终需要移除旧进程来回收内存。 为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 必要时,系统会首先消除重要性最低的进程,然后是重要性略逊的进程,依此类推,以回收系统资源。

    重要性层次结构一共有 5 级。以下列表按照重要程度列出了各类进程(第一个进程最重要,将是最后一个被终止的进程):

    名称 概括 回收状态
    前台进程 正在交互 只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们
    可见进程 没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程 可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。
    服务进程 正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。 除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。
    后台进程 对用户不可见的 Activity 的进程 系统可能随时终止它们
    空进程 不含任何活动应用组件的进程 最容易为杀死

    LMK(LowMemoryKiller)

    • 为什么引入 LMK ?

      进程的启动分冷启动和热启动,当用户退出某一个进程的时候,并不会真正的将进程退出,而是将这个进程放到后台,以便下次启动的时候可以马上启动起来,这个过程名为热启动,这也是Android 的设计理念之一。这个机制会带来一个问题,每个进程都有自己独立的内存地址空间,随着应用打开数量的增多, 系统已使用的内存越来越大,就很有可能导致系统内存不足。为了解决这个问题,系统引入 LowmemoryKiller (简称 lmk ) 管理所有进程,根据一定策略来 kill 某个进程并释放占用的内存,保证系统的正常运行。

    • LMK 基本原理

      所有应用进程都是从 zygote 孵化出来的,记录在 AMS 中mLruProcesses 列表中,由 AMS 进行统一管理,AMS 中会根据进程的状态更新进程对应的 oom_adj 值,这个值会通过文件传递到 kernel 中去,kernel 有个低内存回收机制,在内存达到一定阀值时会触发清理 oom_adj 值高的进程腾出更多的内存空间

    • LMK 杀进程标准

      minfree : 存放6个数值,单位内存页面数 ( 一个页面 4kb )

    得到的数值为:18432 , 23040 , 27648 , 32256 , 36864 , 46080

    这 6 个数值分别代表 android 系统回收 6 种进程的阈值,这么看不方便查看,转换为 M 会更直观,这 6 个数值的单位为 page 1 page = 4 KB ,所以通过数值 * 4 / 1024 就能转换为M : 72 M , 90 M , 108 M , 126 M , 144 M , 180M

    也就是说:

    1.前台进程(foreground),2.可见进程(visible),3.次要服务(secondary server),4.后台进程(hidden),5.内容供应节点(content provider),6.空进程(empty)这6类进程进行回收的内存阈值分别为72M,90M,108M,126M,144M,180 M

    当内存到 180 M的时候会将空进程进行回收,当内存到 144 M 的时候把空进程回收完以后开始对内容供应节点进行回收,并不是所有的内容供应节点都回收,而是通过判断它的优先级进行回收,优先级是用 oom_adj 的值来表示,值越大回收的几率越高

    adj 查看:

    ```
      cat /sys/module/lowmemorykiller/parameters/adj
    
    ```
    

    查看进程 adj 值:

    ```
      adb shell ps
    ```
    

    值越低越不易被回收,0 代表就不会被回收。

    内存阈值在不同的手机上不一样,一旦低于该值, Android 便开始按顺序关闭进程. 因此 Android 开始结束优先级最低的空进程,即当可用内存小于 180MB (46080)

    进程保活方案

    Activity 提权

    这里可见 oom_adj 为 0 是不会被回收的

    后台 oom_adj 为 6 内存不足会被回收

    锁屏 oom_adj 开启一像素 Activity 为 0 相当于可见进程,不易被回收

    实现原理:

    监控手机锁屏解锁事件,在屏幕锁屏时启动 1 个像素透明的 Activity ,在用户解锁时将 Activity 销毁掉,从而达到提高进程优先级的作用。

    代码实现

    1. 创建 onePxActivity

          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              //设定一像素的activity
              Window window = getWindow();
              window.setGravity(Gravity.START | Gravity.TOP);
              WindowManager.LayoutParams params = window.getAttributes();
              params.x = 0;
              params.y = 0;
              params.height = 1;
              params.width = 1;
              window.setAttributes(params);
              //在一像素activity里注册广播接受者    接受到广播结束掉一像素
              br = new BroadcastReceiver() {
                  @Override
                  public void onReceive(Context context, Intent intent) {
                      finish();
                  }
              };
              registerReceiver(br, new IntentFilter("finish activity"));
              checkScreenOn("onCreate");
          }
      
    2. 创建锁屏开屏广播接收

          @Override
          public void onReceive(final Context context, Intent intent) {
              if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {    //屏幕关闭的时候接受到广播
                  appIsForeground = IsForeground(context);
                  try {
                      Intent it = new Intent(context, OnePixelActivity.class);
                      it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                      it.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
                      context.startActivity(it);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
                  //通知屏幕已关闭,开始播放无声音乐
                  context.sendBroadcast(new Intent("_ACTION_SCREEN_OFF"));
              } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {   //屏幕打开的时候发送广播  结束一像素
                  context.sendBroadcast(new Intent("finish activity"));
                  if (!appIsForeground) {
                      appIsForeground = false;
                      try {
                          Intent home = new Intent(Intent.ACTION_MAIN);
                          home.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                          home.addCategory(Intent.CATEGORY_HOME);
                          context.getApplicationContext().startActivity(home);
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                  }
                  //通知屏幕已点亮,停止播放无声音乐
                  context.sendBroadcast(new Intent("_ACTION_SCREEN_ON"));
              }
          }
      

    Service 提权

    创建一个前台服务用于提高 app 在按下 home 键之后的进程优先级

    private void startService(Context context) {
            try {
                Log.i(TAG, "---》启动双进程保活服务");
                //启动本地服务
                Intent localIntent = new Intent(context, LocalService.class);
                //启动守护进程
                Intent guardIntent = new Intent(context, RemoteService.class);
                if (Build.VERSION.SDK_INT >= 26) {
                    startForegroundService(localIntent);
                    startForegroundService(guardIntent);
                } else {
                    startService(localIntent);
                    startService(guardIntent);
                }
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
            }
        }
    

    注意如果开启 startForegroundService 前台服务,那么必须在 5 s内开启一个前台进程的服务通知栏,不会报 ANR

      startForeground(KeepAliveConfig.FOREGROUD_NOTIFICATION_ID, notification);
    

    广播拉活(在 8.0 以下很受用)

    在发生特定系统事件时,系统会发出广播,通过在 AndroidManifest 中静态注册对应的广播监听器,即可在发生响应事件时拉活。但是从android 7.0 开始,对广播进行了限制,而且在 8.0 更加严格。

    以静态广播的形式注册

      <receiver android:name=".receive.NotificationClickReceiver">
      <intent-filter>
      <action android:name="CLICK_NOTIFICATION"></action>
      </intent-filter>
      </receiver>
    

    全家桶 拉活

    有多个 app 在用户设备上安装,只要开启其中一个就可以将其他的app 也拉活。比如手机里装了手 Q、QQ 空间、兴趣部落等等,那么打开任意一个 app 后,其他的 app 也都会被唤醒。

    Service 机制拉活

    将 Service 设置为 START_STICKY,利用系统机制在 Service 挂掉后自动拉活

    只要 targetSdkVersion 不小于5,就默认是 START_STICKY。 但是某些 ROM 系统不会拉活。并且经过测试,Service 第一次被异常杀死后很快被重启,第二次会比第一次慢,第三次又会比前一次慢,一旦在短时间内 Service 被杀死 4-5 次,则系统不再拉起。

    账号同步拉活(只做了解,不靠谱)

    手机系统设置里会有 “帐户” 一项功能,任何第三方 APP 都可以通过此功能将数据在一定时间内同步到服务器中去。系统在将 APP 帐户同步时,会将未启动的 APP 进程拉活

    JobScheduler 拉活(靠谱,8.0 官方推荐)

    JobScheduler 允许在特定状态与特定时间间隔周期执行任务。可以利用它的这个特点完成保活的功能,效果即开启一个定时器,与普通定时器不同的是其调度由系统完成。

    注意 setPeriodic 方法 在 7.0 以上如果设置小于 15 min 不起作用,可以使用setMinimumLatency 设置延时启动,并且轮询

        public static void startJob(Context context) {
            try {
                mJobScheduler = (JobScheduler) context.getSystemService(
                        Context.JOB_SCHEDULER_SERVICE);
                JobInfo.Builder builder = new JobInfo.Builder(10,
                        new ComponentName(context.getPackageName(),
                                JobHandlerService.class.getName())).setPersisted(true);
                /**
                 * I was having this problem and after review some blogs and the official documentation,
                 * I realised that JobScheduler is having difference behavior on Android N(24 and 25).
                 * JobScheduler works with a minimum periodic of 15 mins.
                 *
                 */
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    //7.0以上延迟1s执行
                    builder.setMinimumLatency(KeepAliveConfig.JOB_TIME);
                } else {
                    //每隔1s执行一次job
                    builder.setPeriodic(KeepAliveConfig.JOB_TIME);
                }
                mJobScheduler.schedule(builder.build());
    
            } catch (Exception e) {
                Log.e("startJob->", e.getMessage());
            }
        }
    

    推送拉活

    根据终端不同,在小米手机(包括 MIUI)接入小米推送、华为手机接入华为推送。

    Native 拉活

    Native fork 子进程用于观察当前 app 主进程的存亡状态。对于 5.0以上成功率极低。

    后台循环播放一条无声文件

      //如果选择流氓模式,就默认接收了耗电的缺点,但是保活效果很好。
      if (mediaPlayer == null && KeepAliveConfig.runMode == 
      RunMode.HIGH_POWER_CONSUMPTION) {
                mediaPlayer = MediaPlayer.create(this, R.raw.novioce);
                mediaPlayer.setVolume(0f, 0f);
                mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                    @Override
                    public void onCompletion(MediaPlayer mediaPlayer) {
                        Log.i(TAG, "循环播放音乐");
                        play();
                    }
                });
                play();
            }
    

    双进程守护 (靠谱)

    两个进程相互绑定 (bindService),如果有其中一个进程被杀,那么另外一个进程就会将被杀的进程重新拉起

    总结

    进程保活就讲到这里了,最后我自己是结合里面最靠谱的(Activity + Service 提权 + Service 机制拉活 + JobScheduler 定时检测进程是否运行 + 后台播放无声文件 + 双进程守护),然后组成了一个进程保活终极方案。 文章中只是部分代码,感兴趣的可以下载 demo 试下保活效果。

    相关文章

      网友评论

        本文标题:如何实现进程保活的最新方案

        本文链接:https://www.haomeiwen.com/subject/hzdnohtx.html