编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 01.崩溃捕获设计实践
  • 02.崩溃治理优化总结
  • 03.Native崩溃治理实践
  • 04.ANR监控设计实践
  • 05.CPU消耗优化实践
  • 06.卡顿监控设计实践
  • 07.卡顿治理优化实践
  • 08.网络分析与优化实践
  • 09.线程优化实践操作
  • 10.高性能图片优化方案
  • 11.OOM异常优化实践
  • 12.内存监控优化方案
  • 13.内存治理优化实践
  • 14.FPS监测设计实践
  • 15.进程优化设计实践
  • 16.App启动优化实践
  • 17.App页面UI优化实践
  • 18.App稳定性专项实践
  • 19.App瘦身优化实践
  • 20.常见代码优化实践
  • 21.移动端防抓包实践
  • 22.App磁盘沙盒实践
  • 23.Ping工具开发实践
  • 24.Gradle构建优化实践
  • 25.CodeReview实践总结

15.进程优化设计实践

15.进程优化设计实践

目录介绍

  • 01.整体概述介绍
    • 1.1 项目背景
    • 1.2 遇到问题
    • 1.3 基础概念
    • 1.4 设计目标
  • 02.进程基础概念
    • 2.1 进程间如何通信
    • 2.2 内存紧张回收进程
    • 2.3 如何查看进程信息

01.整体概述介绍

1.1 项目背景

1.2 遇到问题

  • 进程被杀死的问题
    • 系统为什么会杀掉进程,app切换到后台后为什么容易被杀死?
    • 系统杀的为什么是我的进程,这是按照什么标准来选择的?
    • 系统是一次性干掉多个进程,还是一个接着一个杀,为什么?
  • 进程包活的问题
    • 保活套路一堆,方法有很多种,究竟如何进行进程保活才是比较恰当?

1.3 基础概念

1.4 设计目标

02.进程基础概念

2.1 进程间如何通信

2.2 内存紧张回收进程

  • 低杀,它的全名是 Low Memory Killer。
    • 低杀跟垃圾回收器 GC 很像,GC 的作用是保证应用有足够的内存可以使用,而低杀的作用是保证系统有足够的内存可以使用。
    • GC 会按照引用的强度来回收对象,而低杀会按照进程的优先级来回收资源。
  • 内存紧张时回收进程
    • 由于组件与进程是剥离的,因此进程回收不会影响组件的生命周期
  • 从低优先级进程开始回收
    • Empty Process
    • Hidden Process
    • Perceptible Process
    • Visible Process
    • Foreground Process
  • 备注:
    • 每一个APP进程都有一个oom_adj值,该值是根据进程所运行的组件计算出来的,值越小,优先级就越级。
    • Init和System Server进程的oom_adj等于-16,是最高的,保证不会被杀死。
    • PhoneApp具有persist属性,它的oom_adj被设置为-12,也能保证不会被杀死。
    • 可以通过/proc/<pid>oom_adj文件查看进程的oom_adj值。
    • 在Linux内核中,子进程的oom_adj值等于父进程的oom_adj,也就是说,Android里面的Native进程的oom_adj值与fork它的进程的oom_adj值一样。

2.3 如何查看进程信息

  • 如何查看所有的进程信息,直接在命令行输入指令即可,查看设备所有进程信息。
    • 第一步:adb shell;第二步:top
    • 下面这个就是当前所有进程信息,当把一鹿有车切换为前台进程时,所有进程信息如下所示:
    • image
      image
  • 上面显示系统当前的进程状况,那么这些都是代表什么意思呢?
    • 第一行表示的项目依次为当前时间、系统启动时间、当前系统登录用户数目、平均负载。
    • 第二行显示的是全部启动的、眼下执行的、挂起(Sleeping)的和僵尸(Zombie)进程。
    • 第三行显示的是眼下CPU的使用情况,包含系统占用的比例、用户使用比例、闲置(Idle)比例。
    • 第四行显示物理内存的使用情况,包含总的能够使用的内存、已用内存、空暇内存、缓冲区占用的内存。
    • 第五行显示交换分区的使用情况,包含总的交换分区、使用的、空暇的和用于快速缓存的交换分区。
      • PID(Process ID):进程标志号。是非零正整数
      • USER:进程全部者的username
      • PR:进程的优先级别
      • NI:进程的优先级别数值
      • VIRT:进程占用的虚拟内存值
      • RES:进程占用的物理内存值
      • SHR:进程使用的共享内存值
      • STAT:进程的状态。当中S表示休眠,R表示正在执行,Z表示僵死状态,N表示该进程优先值是负数
      • %CPU:该进程占用的CPU使用率
      • %MEM:该进程占用的物理内存和总内存的百分比
      • TIME:该进程启动后占用的总的CPU时间
  • 对于任何一个进程,我们都可以通过adb shell ps | grep 的方式来查看它的基本信息。下面来看一下某个具体app的进程信息……
    • 查询app的包名是:com.cheoo.app。
    • 在命令行中输入查询指令:adb shell "ps |grep com.cheoo"
    • image
      image
  • 那么进程信息查到后,这些信息都是什么意思呢?
    • u0_a288 USER 进程当前用户
    • 20331 进程ID
    • 541 进程的父进程ID
    • 5202131 进程的虚拟内存大小
    • 31156 实际驻留”在内存中”的内存大小
    • com.cheoo.app 进程名,也就是app的包名
  • 观看当前系统进程的变化。你可以反复切换某个app,然后观看该进程的优先级,状态,内存值一直变化。
    • 当app切换到前台时,可以发现该app进程优先级明显增高;

05.内存阀值理解和查看

  • 思考以下进程是怎么被杀的呢?
    • 系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。
    • 打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app, 这套杀进程回收内存的机制就叫 Low Memory Killer。
  • 那这个不足怎么来规定呢,那就是内存阈值。
    • 可以使用cat /sys/module/lowmemorykiller/parameters/minfree来查看某个手机的内存阈值。
    • 得到的数值为:18432,23040,27648,32256,36864,46080。那么这几个阀值都是做什么用的呢?
    • 这6个数值分别代表android系统回收6种进程的阈值,这么看不方便查看,转换为M会更直观,这6个数值的单位为page 1page = 4K,所以通过 数值*4/1024就能转换为M:72M,90M,108M,126M,144M,180M
    • 也就是说1.前台进程(foreground),2.可见进程(visible),3.次要服务(secondary server),4.后台进程(hidden),5.内容供应节点(content provider),6.空进程(empty)这6类进程进行回收的内存阈值分别为72M,90M,108M,126M,144M,180M
  • 假设内存不足,手机后台进程很多,清除那些进程呢?
    • 或许有一个疑问,假设现在内存不足,空进程都被杀光了,现在要杀后台进程,但是手机中后台进程很多,难道要一次性全部都清理掉?当然不是的,进程是有它的优先级的,这个优先级通过进程的adj值来反映,它是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收。
  • 如何来查找一个进程的oom_adj?
    • 前面已经知道了如何查询某个app的进程,这里就不多说了。com.cheoo.app该进程的进程id是20331,现在开始查询它的adj值。
    • 输入指令:adb shell cat /proc/20331/oom_adj
    • image
      image
  • oom_adj的值是什么意思?
    • 看到adj值是0,0就代表这个进程是属于前台进程。按下home键,将应用至于后台,再次查看值为11,这adj级别值怎么解释呢。
    • UNKNOWN_ADJ 16 预留的最低级别,一般对于缓存的进程才有可能设置成这个级别
    • CACHED_APP_MAX_ADJ 15 缓存进程,空进程,在内存不足的情况下就会优先被kill
    • CACHED_APP_MIN_ADJ 9 缓存进程,也就是空进程
    • SERVICE_B_ADJ 8 不活跃的进程
    • PREVIOUS_APP_ADJ 7 切换进程
    • HOME_APP_ADJ 6 与Home交互的进程
    • SERVICE_ADJ 5 有Service的进程
    • HEAVY_WEIGHT_APP_ADJ 4 高权重进程
    • BACKUP_APP_ADJ 3 正在备份的进程
    • PERCEPTIBLE_APP_ADJ 2 可感知的进程,比如那种播放音乐
    • VISIBLE_APP_ADJ 1 可见进程
    • FOREGROUND_APP_ADJ 0 前台进程
    • PERSISTENT_SERVICE_ADJ -11 重要进程
    • PERSISTENT_PROC_ADJ -12 核心进程
    • SYSTEM_ADJ -16 系统进程
    • NATIVE_ADJ -17 系统起的Native进程
  • 根据oom_adj值对应用保活有啥意义?
    • 根据上面的adj值,其实系统在进程回收跟内存回收类似也是有一套严格的策略,大概是这个样子的,oom_adj越大,占用物理内存越多会被最先kill掉。
    • 那么现在对于进程如何保活这个问题就转化成,如何降低oom_adj的值,以及如何使得我们应用占的内存最少。

07.应用被杀死原因

  • 在Android系统里,进程被杀的原因通常为以下几个方面:
    • a.应用Crash
    • b.系统回收内存(系统资源不足的时候)
    • c.用户触发(用户手动退出)
    • d.第三方root权限app(用户手动调用某些安全软件的清理功能干掉你的后台应用)
  • 目前app保活主要针对的是,对系统回收内存进行优化,以避免进程被轻易杀死。

08.白名单进程基础了解

参考文章

  • https://blog.csdn.net/blueangle17/article/details/86678533
  • https://www.jianshu.com/p/dd01580743e7
  • https://blog.csdn.net/tubby_ting/article/details/52830654
  • https://segmentfault.com/a/1190000020159573?utm_source=tag-newest
  • https://github.com/fanqieVip/keeplive
  • Android lowmemorykiller分析:
    • https://blog.csdn.net/u011733869/article/details/78820240

02.进程优先级的概念

  • 在 Android 中不同的进程有着不同的优先级,当两个进程的优先级相同时,低杀会优先考虑干掉消耗内存更多的进程。
  • 也就是如果我们应用占用的内存比其他应用少,并且处于后台时,我们的应用能在后台活下来,这也是内存优化为我们应用带来竞争力的一个直接体现。
  • 当用户通过多次点击达到一个页面,然后又打开了其他应用时,这时我们的应用处于后台,如果我们的应用在后台能活下来,意味着当用户再次启动我们的应用时,不需要再次进行这个繁琐的操作。

03.常见的进程有哪些

  • Android 系统会尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,有时候就需要移除旧进程以回收内存。 为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。

3.1 前台进程

  • 前台进程(Foreground Process)是优先级最高的进程,是正在于用户交互的进程,如果满足下面一种情况,则一个进程被认为是前台进程。
  • Activity 进程持有一个与用户交互的 Activity(该 Activity 的 onResume 方法被调用)
  • 进程持有一个 Service,并且这个 Service 处于下面几种状态之一
    • Service 与用户正在交互的 Activity 绑定
    • Service 调用了 startForeground 方法
    • Service 正在执行以下生命周期函数(onCreate、onStart、onDestroy )
  • BroadcastReceiver 进程持有一个 BroadcastReceiver,这个 BroadcastReceiver 正在执行它的 onReceive 方法

3.2 可见进程

  • 可见进程(Visible Process)不含有任何前台组件,但用户还能再屏幕上看见它,当满足一下任一条件时,进程被认定是可见进程。
  • Activity 进程持有一个 Activity,这个 Activity 处于 pause 状态,比如前台 Activity 打开了一个对话框,这样后面的 Activity 就处于 pause 状态
  • Service 进程持有一个 Service 这个 Service 和一个可见的 Activity 绑定。
  • 可见进程是非常重要的进程,除非前台进程已经把系统的可用内存耗光,否则系统不会终止可见进程。

3.3 服务进程

  • 服务进程(Service Process)可能在播放音乐或在后台下载文件,除非系统内存不足,否则系统会尽量维持服务进程的运行。
  • 当一个进程满足下面一个条件时,系统会认定它为服务进程。
  • Service 如果一个进程中运行着一个 Service,并且这个 service 是通过 startService 开启的,那这个进程就是一个服务进程。

3.4 后台进程

  • 当一个进程满足下面条件时,系统会认定它为后台进程。
  • Activity 当进程持有一个用户不可见的 Activity(Activity 的 onStop 方法被调用),但是 onDestroy 方法没有被调用,这个进程就会被系统认定为后台进程。
  • 系统会把后台进程保存在一个 LruCache 列表中,因为终止后台进程对用户体验影响不大,所以系统会酌情清理部分后台进程。
  • 你可以在 Activity 的 onSaveInstanceState 方法中保存一些数据,以免在应用被系统清理掉后,用户已输入的信息被清空,导致要重新输入。

3.5 空进程

  • 当一个进程不包含任何活跃的应用组件,则被系统认定为是空进程。系统保留空进程的目的是为了加快下次启动进程的速度。

  • 根据进程中当前活动组件的重要程度,Android 会将进程评定为它可以达到的最高级别。

    • 例如,如果某进程托管着服务和可见Activity,则会将此进程评定为可见进程。此外,一个进程的级别可能会因其他进程对它的依赖而有所提高,即服务于另一进程的进程其级别永远不会低于其所服务的进程。 例如,如果进程 A 中的内容提供程序为进程 B 中的客户端提供服务,或者如果进程 A 中的服务绑定到进程 B 中的组件,则进程 A 始终被视为至少与进程 B 同样重要。
    • 由于运行Service的进程其级别高于托管后台 Activity 的进程,因此启动长时间运行操作的 Activity 最好为该操作启动Service,而不是简单地创建工作线程,当操作有可能比 Activity 更加持久时尤要如此。例如,正在将图片上传到网站的 Activity 应该启动Service来执行上传操作,这样一来,即使用户退出 Activity,仍可在后台继续执行上传操作。使用Service可以保证,无论 Activity 发生什么情况,该操作至少具备“服务进程”优先级。
  • 01.经典的一像素保活

  • 02.双进程守护

  • 03.在后台播放一个无声的音频

  • 04.利用账号同步机制拉活

  • 05.使用JobService

  • 06.将service设置为前台进程

  • 07.第三方推送SDK唤醒

01.经典的一像素保活

  • 据说这个是手Q的进程保活方案,基本思想,系统一般是不会杀死前台进程的。所以要使得进程常驻,我们只需要在锁屏的时候在本进程开启一个Activity,为了欺骗用户,让这个Activity的大小是1像素,并且透明无切换动画,在开屏幕的时候,把这个Activity关闭掉,所以这个就需要监听系统锁屏广播,如下。
    public class MainActivity extends AppCompatActivity {
     
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
       }
    }
  • 如果直接启动一个Activity,当我们按下back键返回桌面的时候,oom_adj的值是8,上面已经提到过,这个进程在资源不够的情况下是容易被回收的。现在造一个一个像素的Activity。
    public class LiveActivity extends Activity {
     
        public static final String TAG = LiveActivity.class.getSimpleName();
     
        public static void actionToLiveActivity(Context pContext) {
            Intent intent = new Intent(pContext, LiveActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            pContext.startActivity(intent);
        }
     
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Log.d(TAG, "onCreate");
            setContentView(R.layout.activity_live);
     
            Window window = getWindow();
            //放在左上角
            window.setGravity(Gravity.START | Gravity.TOP);
            WindowManager.LayoutParams attributes = window.getAttributes();
            //宽高设计为1个像素
            attributes.width = 1;
            attributes.height = 1;
            //起始坐标
            attributes.x = 0;
            attributes.y = 0;
            window.setAttributes(attributes);
     
            ScreenManager.getInstance(this).setActivity(this);
        }
     
        @Override
        protected void onDestroy() {
            super.onDestroy();
            Log.d(TAG, "onDestroy");
        }
    }
  • 为了做的更隐藏,最好设置一下这个Activity的主题,当然也无所谓了
    <style name="LiveStyle">
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowAnimationStyle">@null</item>
        <item name="android:windowNoTitle">true</item>
    </style>
  • 在屏幕关闭的时候把LiveActivity启动起来,在开屏的时候把LiveActivity 关闭掉,所以要监听系统锁屏广播,以接口的形式通知MainActivity启动或者关闭LiveActivity。
    public class ScreenBroadcastListener {
     
        private Context mContext;
     
        private ScreenBroadcastReceiver mScreenReceiver;
     
        private ScreenStateListener mListener;
     
        public ScreenBroadcastListener(Context context) {
            mContext = context.getApplicationContext();
            mScreenReceiver = new ScreenBroadcastReceiver();
        }
     
        interface ScreenStateListener {
     
            void onScreenOn();
     
            void onScreenOff();
        }
     
        /**
         * screen状态广播接收者
         */
        private class ScreenBroadcastReceiver extends BroadcastReceiver {
            private String action = null;
     
            @Override
            public void onReceive(Context context, Intent intent) {
                action = intent.getAction();
                if (Intent.ACTION_SCREEN_ON.equals(action)) { // 开屏
                    mListener.onScreenOn();
                } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // 锁屏
                    mListener.onScreenOff();
                }
            }
        }
     
        public void registerListener(ScreenStateListener listener) {
            mListener = listener;
            registerListener();
        }
     
        private void registerListener() {
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_SCREEN_ON);
            filter.addAction(Intent.ACTION_SCREEN_OFF);
            mContext.registerReceiver(mScreenReceiver, filter);
        }
    }
    
    public class ScreenManager {
     
        private Context mContext;
     
        private WeakReference<Activity> mActivityWref;
     
        public static ScreenManager gDefualt;
     
        public static ScreenManager getInstance(Context pContext) {
            if (gDefualt == null) {
                gDefualt = new ScreenManager(pContext.getApplicationContext());
            }
            return gDefualt;
        }
        private ScreenManager(Context pContext) {
            this.mContext = pContext;
        }
     
        public void setActivity(Activity pActivity) {
            mActivityWref = new WeakReference<Activity>(pActivity);
        }
     
        public void startActivity() {
                LiveActivity.actionToLiveActivity(mContext);
        }
     
        public void finishActivity() {
            //结束掉LiveActivity
            if (mActivityWref != null) {
                Activity activity = mActivityWref.get();
                if (activity != null) {
                    activity.finish();
                }
            }
        }
    }
  • 现在MainActivity改成如下
    public class MainActivity extends AppCompatActivity {
     
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            final ScreenManager screenManager = ScreenManager.getInstance(MainActivity.this);
            ScreenBroadcastListener listener = new ScreenBroadcastListener(this);
             listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() {
                @Override
                public void onScreenOn() {
                    screenManager.finishActivity();
                }
     
                @Override
                public void onScreenOff() {
                    screenManager.startActivity();
                }
            });
        }
    }
  • 按下back之后,进行锁屏,现在测试一下oom_adj的值,果然将进程的优先级提高了。但是还有一个问题,内存也是一个考虑的因素,内存越多会被最先kill掉,所以把上面的业务逻辑放到Service中,而Service是在另外一个 进程中,在MainActivity开启这个服务就行了,这样这个进程就更加的轻量,
    public class LiveService extends Service {
     
        public  static void toLiveService(Context pContext){
            Intent intent=new Intent(pContext,LiveService.class);
            pContext.startService(intent);
        }
     
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
     
     
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            //屏幕关闭的时候启动一个1像素的Activity,开屏的时候关闭Activity
            final ScreenManager screenManager = ScreenManager.getInstance(LiveService.this);
            ScreenBroadcastListener listener = new ScreenBroadcastListener(this);
            listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() {
                @Override
                public void onScreenOn() {
                    screenManager.finishActivity();
                }
                @Override
                public void onScreenOff() {
                    screenManager.startActivity();
                }
            });
            return START_REDELIVER_INTENT;
        }
    }
    
    <service android:name=".LiveService"
        android:process=":live_service"/>
  • 通过上面的操作,我们的应用就始终和前台进程是一样的优先级了,为了省电,系统检测到锁屏事件后一段时间内会杀死后台进程,如果采取这种方案,就可以避免了这个问题。但是还是有被杀掉的可能,所以我们还需要做双进程守护,关于双进程守护,比较适合的就是aidl的那种方式,但是这个不是完全的靠谱,原理是A进程死的时候,B还在活着,B可以将A进程拉起来,反之,B进程死的时候,A还活着,A可以将B拉起来。所以双进程守护的前提是,系统杀进程只能一个个的去杀,如果一次性杀两个,这种方法也是不OK的。
  • 事实上,那么我们先来看看Android5.0以下的源码,ActivityManagerService是如何关闭在应用退出后清理内存的
    Process.killProcessQuiet(pid);
  • 应用退出后,ActivityManagerService就把主进程给杀死了,但是,在Android5.0以后,ActivityManagerService却是这样处理的:
    Process.killProcessQuiet(app.pid);  
    Process.killProcessGroup(app.info.uid, app.pid);
  • 在应用退出后,ActivityManagerService不仅把主进程给杀死,另外把主进程所属的进程组一并杀死,这样一来,由于子进程和主进程在同一进程组,子进程在做的事情,也就停止了。所以在Android5.0以后的手机应用在进程被杀死后,要采用其他方案。

05.使用JobService

  • JobScheduler是作为进程死后复活的一种手段,native进程方式最大缺点是费电,Native进程费电的原因是感知主进程是否存活有两种实现方式,在 Native 进程中通过死循环或定时器,轮训判断主进程是否存活,当主进程不存活时进行拉活。
  • 其次5.0以上系统不支持。 但是JobScheduler可以替代在Android5.0以上native进程方式,这种方式即使用户强制关闭,也能被拉起来,亲测可行。
    JobSheduler@TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public class MyJobService extends JobService {
        @Override
        public void onCreate() {
            super.onCreate();
            startJobSheduler();
        }
     
        public void startJobSheduler() {
            try {
                JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(getPackageName(), MyJobService.class.getName()));
                builder.setPeriodic(5);
                builder.setPersisted(true);
                JobScheduler jobScheduler = (JobScheduler) this.getSystemService(Context.JOB_SCHEDULER_SERVICE);
                jobScheduler.schedule(builder.build());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
     
        @Override
        public boolean onStartJob(JobParameters jobParameters) {
            return false;
        }
     
        @Override
        public boolean onStopJob(JobParameters jobParameters) {
            return false;
        }
    }

06.将service设置为前台进程

  • 这种大部分人都了解,据说这个微信也用过的进程保活方案,移步微信Android客户端后台保活经验分享,这方案实际利用了Android前台service的漏洞。原理如下
    • 对于 API level < 18 :调用startForeground(ID, new Notification()),发送空的Notification ,图标则不会显示。
    • 对于 API level >= 18:在需要提优先级的service A启动一个InnerService,两个服务同时startForeground,且绑定同样的 ID。Stop 掉InnerService ,这样通知栏图标即被移除。
    public class KeepLiveService extends Service {
     
        public static final int NOTIFICATION_ID=0x11;
     
        public KeepLiveService() {
        
        }
     
        @Override
        public IBinder onBind(Intent intent) {
            throw new UnsupportedOperationException("Not yet implemented");
        }
     
        @Override
        public void onCreate() {
            super.onCreate();
             //API 18以下,直接发送Notification并将其置为前台
            if (Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN_MR2) {
                startForeground(NOTIFICATION_ID, new Notification());
            } else {
                //API 18以上,发送Notification并将其置为前台后,启动InnerService
                Notification.Builder builder = new Notification.Builder(this);
                builder.setSmallIcon(R.mipmap.ic_launcher);
                startForeground(NOTIFICATION_ID, builder.build());
                startService(new Intent(this, InnerService.class));
            }
        }
     
        public  class  InnerService extends Service{
            @Override
            public IBinder onBind(Intent intent) {
                return null;
            }
            @Override
            public void onCreate() {
                super.onCreate();
                //发送与KeepLiveService中ID相同的Notification,然后将其取消并取消自己的前台显示
                Notification.Builder builder = new Notification.Builder(this);
                builder.setSmallIcon(R.mipmap.ic_launcher);
                startForeground(NOTIFICATION_ID, builder.build());
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        stopForeground(true);
                        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                        manager.cancel(NOTIFICATION_ID);
                        stopSelf();
                    }
                },100);
     
            }
        }
    }
  • 测试结果
    • 在没有采取前台服务之前,启动应用,oom_adj值是0,按下返回键之后,变成9(不同ROM可能不一样)
    • 在采取前台服务之后,启动应用,oom_adj值是0,按下返回键之后,变成2(不同ROM可能不一样),确实进程的优先级有所提高。

07.第三方推送SDK唤醒

  • 相互唤醒的意思就是,假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其他阿里系的app给唤醒了。这个完全有可能的。
  • 此外,开机,网络切换、拍照、拍视频时候,利用系统产生的广播也能唤醒app,不过Android N已经将这三种广播取消了。

02.Android中如何控件进程

  • 如果需要控制某个组件所属的进程,则可在清单文件中执行以下操作
    • 各类组件标签:< activity >、< service >、< receiver > 和 < provider >等均支持 android:process属性,此属性可以指定该组件应在哪个进程运行,通过此属性可以使每个组件均在各自的进程中运行,或者使一些组件共享一个进程,而其他组件则不共享。 此外,还可以通过该属性使不同应用的组件在相同的进程中运行,但前提是这些应用共享相同的 Linux 用户 ID 并使用相同的证书进行签署
    • 此外,< application > 元素也支持 android:process 属性,以设置适用于所有组件的默认值
  • 如果内存不足而系统又需要内存时,系统可能会在某一时刻关闭某一进程
    • Android 系统将权衡所有进程对用户的相对重要程度来决定终止哪个进程,被终止的进程中运行的应用组件也会随之销毁, 当这些组件需要再次运行时,系统将为它们重启进程

参考

  • Android 黑科技保活实现原理揭秘
    • https://weishu.me/2020/01/16/a-keep-alive-method-on-android/
贡献者: yangchong211
上一篇
14.FPS监测设计实践
下一篇
16.App启动优化实践