美文网首页
性能优化<第九篇>:进程保活方案

性能优化<第九篇>:进程保活方案

作者: NoBugException | 来源:发表于2021-06-28 00:23 被阅读0次

为什么保证应用某些业务的正常运行,有时候必须要保证进程是存活的,但是目前所有的进程保活方案都不是绝对的,不管如何保活,进程总是存在一定的概率会被系统杀死。
本文将围绕进程保活方案展开详细讲解。

1、Android中的进程

一般情况下,一个应用只有一个进程,整个应用功能将由唯一的一个Application管理。
在清单文件AndroidManifest.xml中,声明了四大组件,分别是:Activity、Service、Receiver、Provider,它们都可以使用

android:process=":processName"

将组件分配到其它进程中执行。

所以,一个应用可以有多个进程,每个进程都分别管理着自己的Application实例。

在一个应用或者多个应用之间的进程可以相互通信,它们之间的通信由Android自带的IPC机制来完成,但是,想要通信,必须要保证进程活着,如果进程一旦被杀死,那么进程间通信的桥梁就会断开,从而无法通信。

为了业务的需要,进程保活方案迫在眉睫。

2、明确保活的切入点

Android系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,需要清除旧进程来回收内存。 为了确定保留或终止哪些进程,系统会对进程进行分类。 需要时,系统会首先消除重要性最低的进程,然后是清除重要性稍低一级的进程,依此类推,以回收系统资源。

进程分为:前台进程可见进程服务进程后台进程空进程

前台进程:即当前正在前台运行的进程,说明用户当前正在与通过该进程与系统进行交互,所以该进程为最重要的进程,
除非系统的内容已经到不堪重负的情况,否则系统是不会将改进程终止的。

可见进程:一般还是显示在屏幕中,但是用户并没有直接与之进行交互,该进程对用户来说同样是非常重要的进程,
除非为了保证前台进程的正常运行,否则Android系统一般是不会将该进程终止的。

服务进程:便是拥有Service进程,该进程一般是在后台为用户服务的。一般情况下,Android系统是不会将其中断的,
除非系统的内容以及达到崩溃的边缘,必须通过释放该进程才能保证前台进程的正常运行时,才可能将其终止。

后台进程:一般对用户的作用不大,缺少该进程并不会影响用户对系统的体验。
所以如果系统需要终止某个进程才能保证系统正常运行,那么会有非常大的几率将该进程终止。

空进程:对用户没有任何作用的进程,该进程一般是为缓存机制服务的,当系统需要终止某个进程保证系统的正常服务时,会首先将该进程终止。

通过进程的概念,我们知道了进程是被系统杀死的,系统将通过某种机制将进程设置优先级,优先级低的进程最先会被杀死。

所以,进程保护方案的切入点是: 提高进程优先级,降低进程被杀死的概率

3、低内存管理机制

低内存管理机制是系统为了提高性能,选择性的杀死优先级较低的进程,英文是LowMemoryKiller,可以在系统日志中搜索到,简称LMK

为什么要引入LMK?

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

LMK基本原理?

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

低内存阀值?

如果您的手机已经Root,并且支持LMK,那么可以使用如下命令查看您的低内存阀值:

adb shell
su
cat /sys/module/lowmemorykiller/parameters/minfree

如果minfree文件没有读取权限就给它一个权限:

    adb shell chmod 777 /sys/module/lowmemorykiller/parameters/minfree

可能会输出下列阈值,如下:

18432,23040,27648,32256,36864,46080

这些阀值可以配置,所以不同的手机,它们的值不一定一样。
Android将进程分成6个等级,和上面介绍的进程的分类较为类似,这6个等级分别是:
    前台进程(foreground)
    可见进程(visible)
    次要服务(secondary server)
    后台进程(hidden)
    内容供应节点(content provider)
    空进程(empty)

上面6个等级分别和6个阀值一一对应:
    前台进程               -->        18432 page = 73728KB    -->   72M
    可见进程               -->        23040 page = 92160KB    -->   90M
    次要服务               -->        27648 page = 110592KB    -->   108M
    后台进程               -->        32256 page = 129024KB    -->   126M
    内容供应节点           -->        36864 page = 147456KB    -->   144M
    空进程                 -->        46080 page = 184320KB    -->   180M

minfree的单位是page, 1 page = 4KB。

* 当可用内存小于180M时,系统会杀死空进程;
* 当可用内存小于144M时,系统会杀死内容供应节点进程;
* 当可用内存小于126M时,系统会杀死后台进程,后台进程较多,优先杀死adj值较高的进程;
* 当可用内存小于108M时,系统会杀死次要服务进程;
* 当可用内存小于90M时,系统会杀死可见进程;
* 当可用内存小于72M时,系统会直接杀死前台进程,前台进程比较敏感,给用户的感觉就是app直接闪退;

另外,进程的adj也有阀值,输入以下命令可以获取adj的阀值:

adb shell
su
cat /sys/module/lowmemorykiller/parameters/adj

如果adj文件没有读取权限就给它一个权限:

    adb shell chmod 777 /sys/module/lowmemorykiller/parameters/adj

可能会输出下列阈值,如下:

0,58,117,176,529,1000

adj的阀值和低内存阀值一一对应:

前台进程               -->        18432 page = 73728KB    -->   72M    -->   adj(0)
可见进程               -->        23040 page = 92160KB    -->   90M    -->   adj(58)
次要服务               -->        27648 page = 110592KB    -->   108M    -->   adj(117)
后台进程               -->        32256 page = 129024KB    -->   126M    -->   adj(176)
内容供应节点           -->        36864 page = 147456KB    -->   144M    -->   adj(529)
空进程                 -->        46080 page = 184320KB    -->   180M    -->   adj(1000)

* 当可用内存小于180M时,系统会杀死adj大于等于1000的进程;
* 当可用内存小于144M时,系统会杀死adj大于等于529的进程;
* 当可用内存小于126M时,系统会杀死adj大于等于176的进程;
* 当可用内存小于108M时,系统会杀死adj大于等于117的进程;
* 当可用内存小于90M时,系统会杀死adj大于等于58的进程;
* 当可用内存小于72M时,系统会杀死adj大于等于0的进程;

其中,adj值越大,越优先被系统杀死;

进程的adj值?

系统为每个进程都分配了一个adj。

输入以下命令可以查看设备中所有的进程:

    adb shell
    ps

输入以下命令可以查看指定进程的adj值以及进程打分情况:
    adb shell
    su
    cat /proc/<pid>/oom_adj
    cat /proc/<pid>/oom_score
    cat /proc/<pid>/oom_score_adj

oom_adj是adj值,比如取值为0;(最低取值为-17)
oom_score是系统打分+用户打分,比如取值为23;(最低取值为0)
oom_score_adj是用户打分,比如取值为0;(最低取值为-1000)

Native进程,三者的取值分别为:-17、0、-1000,也就是说,Native进程无法被杀死。


oom_adj的取值范围是-17~16,进程的优先级通过进程的adj值来反映,它是linux内核分配给每个系统进程的一个值,进程回收机制根据这个值来决定是否进行回收。
oom_adj的值越小,进程的优先级越高。

oom_adj的取值对应表如下:
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进程

现在打开Android模拟器,进程测试。

【第一步】 用AS检查当前进程ID

图片.png

在AS的Terminal中输入命令:

adb shell
su
cat /proc/6325/oom_adj

当前进程在前台时,ADJ值为0,如图:

图片.png

按Home键(或Recent键)将进程切入后台,ADJ值从0变成了11,如图:

图片.png

将进程从后台切换到前台,ADJ值又从11变成0;

4、保活方案一:一像素保活

方案?

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

目的?

启动一个1像素的界面是为了Activity提权,将ADJ值变小。

代码实现?

1、创建1个像素的Activity

public class KeepActivity extends Activity {
    private static final String TAG = "KeepActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e(TAG,"启动Keep");
        Window window = getWindow();
        //设置这个activity在左上角
        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);

        KeepManager.getInstance().setKeepActivity(this);
    }
}

Activity的主题:

<style name="KeepTheme">
    <item name="android:windowBackground">@null</item>
    <item name="android:windowIsTranslucent">true</item>
</style>

2、广播接收者

public class KeepReceiver extends BroadcastReceiver {
    private static final String TAG = "KeepReceiver";
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.e(TAG, "receive:" + action);
        if (TextUtils.equals(action, Intent.ACTION_SCREEN_OFF)) {
            //灭屏 开启1px activity
            KeepManager.getInstance().startKeep(context);
        } else if (TextUtils.equals(action, Intent.ACTION_SCREEN_ON)) {
            //亮屏 关闭
            KeepManager.getInstance().finishKeep();
        }
    }
}

3、创建广播注册管理单例类

public class KeepManager {
    private static final KeepManager ourInstance = new KeepManager();

    public static KeepManager getInstance() {
        return ourInstance;
    }

    private KeepManager() {
    }
    private KeepReceiver keepReceiver;
    private WeakReference<Activity> mKeepActivity;
    /**
     * 注册
     * @param context
     */
    public void registerKeepReceiver(Context context){
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_SCREEN_ON);
        keepReceiver = new KeepReceiver();
        context.registerReceiver(keepReceiver, filter);
    }

    /**
     * 反注册
     * @param context
     */
    public void unRegisterKeepReceiver(Context context){
        if (null != keepReceiver) {
            context.unregisterReceiver(keepReceiver);
        }
    }

    /**
     * 启动1个像素的KeepActivity
     * @param context
     */
    public void startKeep(Context context) {
        Intent intent = new Intent(context, KeepActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }

    /**
     * finish1个像素的KeepActivity
     */
    public void finishKeep() {
        if (null != mKeepActivity) {
            Activity activity = mKeepActivity.get();
            if (null != activity) {
                activity.finish();
            }
            mKeepActivity = null;
        }
    }

    public void setKeepActivity(KeepActivity mKeepActivity) {
        this.mKeepActivity = new WeakReference<Activity>(mKeepActivity);
    }
}
5、保活方案二:Service提权
1、创建一个前台服务用于提高app在按下home键之后的进程优先级;
2、Service限制

https://developer.android.google.cn/about/versions/oreo/background#services

startForeground(ID,Notification):使Service成为前台Service。 前台服务需要在通知栏显示一条通知。


代码实现:

public class ForegroundService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel("service", "service",
                    NotificationManager.IMPORTANCE_LOW);
            NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            if (manager == null)
                return;
            manager.createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, "service").setAutoCancel(true).setCategory(
                    Notification.CATEGORY_SERVICE).setOngoing(true).setPriority(
                    NotificationManager.IMPORTANCE_LOW).build();
            startForeground(10, notification);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            //如果 18 以上的设备 启动一个Service startForeground给相同的id
            //然后结束那个Service
            startForeground(10, new Notification());
            startService(new Intent(this, InnnerService.class));
        } else {
            startForeground(10, new Notification());
        }
    }

    public static class InnnerService extends Service {

        @Override
        public void onCreate() {
            super.onCreate();
            startForeground(10, new Notification());
            stopSelf();
        }

        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    }
}
6、保活方案三:广播拉活
在发生特定系统事件时,系统会发出广播,通过在 AndroidManifest 中静态注册对应的广播监听器,
即可在发生响应事件时拉活。但是从android 7.0开始,对广播进行了限制,而且在8.0更加严格。

https://developer.android.google.cn/about/versions/oreo/background.html#broadcasts

可静态注册广播列表

https://developer.android.google.cn/guide/components/broadcast-exceptions.html
7、保活方案四:“全家桶”拉活
有多个app在用户设备上安装,只要开启其中一个就可以将其他的app也拉活。
比如手机里装了手Q、QQ空间、兴趣部落等等,那么打开任意一个app后,其他的app也都会被唤醒。
8、保活方案五:Service机制(Sticky)拉活
将 Service 设置为 START_STICKY,利用系统机制在 Service 挂掉后自动拉活。

START_STICKY:“粘性”。如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。
随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。
如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。

START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。

START_REDELIVER_INTENT:重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,
并将Intent的值传入。

START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。

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

代码实现如下:

public class StickyService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }
}


public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
    onStart(intent, startId);
    return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
}
8、保活方案五:账户同步拉活

相关文章

  • 性能优化<第九篇>:进程保活方案

    为什么保证应用某些业务的正常运行,有时候必须要保证进程是存活的,但是目前所有的进程保活方案都不是绝对的,不管如何保...

  • 性能优化07-进程保活

    性能优化07-进程保活 一、进程回收 1.进程的优先级 Android系统将尽量长时间地保持应用进程,但为了新建进...

  • 第一期:截至 2016年3月,Android 开发有哪些新技术出

    Android:关于 Android 进程保活,你所需要知道的一切Android性能优化典范Depth-LIB-A...

  • 第十六周 进程保活

    话题:进程保活 这个问题时常在面试中被问到关键字:Android 进程保活招式大全 参考答案 1.进程保活方案 -...

  • 进程保活方案学习

    进程保活方案 进程保活主要有两个方案 提高进程优先级,降低死亡几率 在进程被杀死后进行拉活 进程为什么会死亡 从L...

  • 进程保活方案

    一.保活的两个方案 A.提供进程优先级,降低进程被杀死的概率。 B. 在进程被杀死后,进行拉活。 必须先了解一下进...

  • 进程保活方案

    方案:1、开启一个像素的Activity2、前台服务3、相互唤醒(广播或系统应用等)4、JobSheduler5、...

  • 003 APP保活

    1 进程常识 2 如何保活 1像素Activity保活据说这个是手Q的进程保活方案,基本思想,系统一般是不会杀死前...

  • 开篇

    目前关注的领域 Android 网络优化长连接短连接 Android Push 优化进程保活消息展示 Androi...

  • Android进程保活方案

    目的 进程保活是各个公司想要实现的一个功能,可以“无赖”地一直霸占你的手机,不被系统和第三方杀掉,以下是几种方案,...

网友评论

      本文标题:性能优化<第九篇>:进程保活方案

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