【FJU项目】1像素进程保活(二)

1 前言

前面一篇【Android实例】1像素进程保活(一)是最基础的写法,但是这种写法还有些不足,还可以进行优化。因为内存也是一个考虑的因素,内存越多的进程会最先被kill掉,所以我们可以开启一个服务,然后将该服务放在另一个进程中,这样这个进程就更加的轻量,更不容易被杀死。

系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app, 这套杀进程回收内存的机制就叫 Low Memory Killer。

2 查看手机内存阈值

  1. 首先你的手机需要得到root权限
  2. 在获得root权限的前提下进行以下步骤:
  • 在手机开启USB调试并连接AS的情况下,在Terminal里输入:adb shell
  • 然后输入(获取超级权限):su
  • 接着输入(授权):chmod 777 /sys/module/lowmemorykiller/parameters/minfree
  • 最后输入(查看):cat /sys/module/lowmemorykiller/parameters/minfree

一般,你可以得到的数值为:18432,23040,27648,32256,36864,46080。这6个数值分别代表android系统回收6种进程的阈值,这么看不方便查看,转换为M会更直观,这6个数值的单位为page, 1 p a g e = 4 K \color{red}{1page = 4K} ,所以通过 4 / 1024 \color{red}{数值*4/1024} 就能转换为M:72M,90M,108M,126M,144M,180M,即 f o r e g r o u n d \color{red}{前台进程(foreground)} v i s i b l e \color{red}{可见进程(visible)} s e c o n d a r y s e r v e r \color{red}{次要服务(secondary server)} h i d d e n \color{red}{后台进程(hidden)} c o n t e n t p r o v i d e r \color{red}{内容供应节点(content provider)} e m p t y \color{red}{空进程(empty)} 这6类进程进行回收的内存阈值分别为72M,90M,108M,126M,144M,180M。这些数值指的是可用内存。

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

ps|grep 包名

来得到进程的id。通过进程id可以查看oom_adj的值:

cat /proc/进程id/oom_adj

adj值为0,代表前台进程;我们按下Back键,将应用至于后台,再次查看adj值为9;如果按home键adj值为8,不同的手机可能值不太一样。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进程

备注:(上表的数字可能在不同系统会有一定的出入)

3 Service的onStartCommand的4种返回值

  • **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后一定能重启。

4 源码讲解

代码在【Android实例】1像素进程保活(一)基础上改动,改动的地方有:

  • 新建一个OnePixelService服务
package com.yds.jianshu.onepixel;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public class OnePixelService extends Service {
    OnePixelManager manager;
    public OnePixelService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        manager = new OnePixelManager();
        manager.registerOnePixelReceiver(this);//注册广播接收者
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        manager.unregisterOnePixelReceiver(this);
    }
}

这里onStartCommand返回为START_STICKY,保证服务被杀死后可以被重新创建。初始化OnePixelManager管理类,然后注册OnePixelReceiver服务,该服务主要是用来监听屏幕变化,registerOnePixelReceiver源码如下:

    /**
     * 一像素广播接收者注册方法。该方法中初始化OnePixelReceiver,并添加了过滤条件
     * 屏幕息屏和亮屏。然后注册该广播接收者
     * @param context
     */
    public void registerOnePixelReceiver(Context context){
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_SCREEN_ON);
        onePixelReceiver = new OnePixelReceiver();
        context.registerReceiver(onePixelReceiver,filter);
    }
  • AndroidManifest.xml中注册服务
        <service
            android:name="com.yds.jianshu.onepixel.OnePixelService"
            android:enabled="true"
            android:exported="false"
            android:process=":one_pixel_service"
            ></service>
  • android:exported=“false”:表示该服务不能被其他应用程序组件调用或跟它交互,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。
  • android:process=":one_pixel_service":开启一个进程,并且服务处于该进程中。
  • MainActivity中调用
package com.yds.jianshu.mobile;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.example.myapplication.R;
import com.yds.jianshu.onepixel.OnePixelManager;
import com.yds.jianshu.onepixel.OnePixelService;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity{
    private static final String TAG = "[MainActivity]";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initData();
    }
    private void initData(){
        startOnePixelService();

    }

    private void startOnePixelService(){
        Intent intent = new Intent();
        intent.setClass(MainActivity.this, OnePixelService.class);
        startService(intent);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

Android5.0以下,ActivityManagerService杀死进程代码如下:

Process.killProcessQuiet(pid);  

如上代码所示,应用退出后,ActivityManagerService就把主进程给杀死了。在Android5.0以后,ActivityManagerService杀死进程代码如下:

Process.killProcessQuiet(app.pid);  
Process.killProcessGroup(app.info.uid, app.pid);  

如上所示,在应用退出后,ActivityManagerService不仅把主进程给杀死,另外把主进程所属的进程组一并杀死,这样一来,由于子进程和主进程在同一进程组,子进程在做的事情,也就停止了。所以在Android5.0以后的手机应用在进程被杀死后,要采用其他方案。

5 类图

涉及到的几个类的类图如下所示(仅供参考):
实线箭头:关联
虚线箭头:依赖
在这里插入图片描述

6 总结

  • 为什么要用服务来进行1像素保活?
    因为当进程在同一adj级别下,内存越多,就会最先被杀死,所以可以重开一个进程,将一像素的业务逻辑放在Service里,这样使得相比之前,Service所在进程更加轻量,更不容易被杀死。而且,在onStartCommand中使用START_STICKY,可以让服务在被杀死后重新创建,提高保活率。

  • 1像素保活是为了降低应用被杀死的概率,在Android5.0以上的手机上无法保证应用被杀死后能再次“复活”应用。

源码:https://github.com/Yedongsheng/Jianshu/tree/develop

发布了107 篇原创文章 · 获赞 142 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/u013293125/article/details/97620829