Android稳定性优化

要保证稳定性,需要保证减少crash和anr,对于减少crash,需要进行代码审核,但是人工审核难免也会漏掉,可以使用FindBugs(当前已经被SpotBugs替代),CheckStyle,PMD,Android Lint,可以使用这几种工具对代码进行检测

  • FindBugs,PMD 主要是侧重于检测代码缺陷
  • CheckStyle 主要规范代码风格
  • Andriod Lint 是全方位的检查,可以检查未使用的资源,代码风格,过时的api,不合理的布局等等。

做完这些前置的代码风格或者代码缺陷的检查后,只能是减少常见的crash发生,对于一些native crash,特殊的定制系统上的crash,这是没法检查出的,只能是在出现了crash后,将其收集,然后上报到服务端提供给开发人员进行分析。

Crash

对于crash,可以分为java层的crash和native crash,java 层crash的分析很方便,只要查看具体的堆栈信息就能分析,对于native crash,一般难以定位,这是 因为一般release版本的动态链接So库都是不带符号的,这就导致了无法进行分析和调试。在Android 系统的 Linux内核中,一种重要的进程间通信方式就是Linux的信号机制。Linux信号除了用于正常的进程间通信和同步外,还负责监控系统异常和中断,当应用程序发生异常时, Linux 内核会生成错误信号并通知当前进程。应用进程接收到错误信号后,可以捕获该信号并执行对应的信号处理函数。而在应用运行过程中发生严重错误时会发生 Crash 。 Linux 有一类专门用于描述Crash 的信号,所有的信号量都定义在文件中,
下面列出了几乎全部的信号量以及所代表的含义都标注出来了

#define SIGHUP 1  // 终端连接结束时发出(不管正常或非正常)
#define SIGINT 2  // 程序终止(例如Ctrl-C)
#define SIGQUIT 3 // 程序退出(Ctrl-\)
#define SIGILL 4 // 执行了非法指令,或者试图执行数据段,堆栈溢出
#define SIGTRAP 5 // 断点时产生,由debugger使用
#define SIGABRT 6 // 调用abort函数生成的信号,表示程序异常
#define SIGIOT 6 // 同上,更全,IO异常也会发出
#define SIGBUS 7 // 非法地址,包括内存地址对齐出错,比如访问一个4字节的整数, 但其地址不是4的倍数
#define SIGFPE 8 // 计算错误,比如除0、溢出
#define SIGKILL 9 // 强制结束程序,具有最高优先级,本信号不能被阻塞、处理和忽略
#define SIGUSR1 10 // 未使用,保留
#define SIGSEGV 11 // 非法内存操作,与SIGBUS不同,他是对合法地址的非法访问,比如访问没有读权限的内存,向没有写权限的地址写数据
#define SIGUSR2 12 // 未使用,保留
#define SIGPIPE 13 // 管道破裂,通常在进程间通信产生
#define SIGALRM 14 // 定时信号,
#define SIGTERM 15 // 结束程序,类似温和的SIGKILL,可被阻塞和处理。通常程序如果终止不了,才会尝试SIGKILL
#define SIGSTKFLT 16  // 协处理器堆栈错误
#define SIGCHLD 17 // 子进程结束时, 父进程会收到这个信号。
#define SIGCONT 18 // 让一个停止的进程继续执行
#define SIGSTOP 19 // 停止进程,本信号不能被阻塞,处理或忽略
#define SIGTSTP 20 // 停止进程,但该信号可以被处理和忽略
#define SIGTTIN 21 // 当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号
#define SIGTTOU 22 // 类似于SIGTTIN, 但在写终端时收到
#define SIGURG 23 // 有紧急数据或out-of-band数据到达socket时产生
#define SIGXCPU 24 // 超过CPU时间资源限制时发出
#define SIGXFSZ 25 // 当进程企图扩大文件以至于超过文件大小资源限制
#define SIGVTALRM 26 // 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.
#define SIGPROF 27 // 类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间
#define SIGWINCH 28 // 窗口大小改变时发出
#define SIGIO 29 // 文件描述符准备就绪, 可以开始进行输入/输出操作
#define SIGPOLL SIGIO // 同上,别称
#define SIGPWR 30 // 电源异常
#define SIGSYS 31 // 非法的系统调用

在 Android 系统上, Native 层最常见的导致 Crash的信号量如下:

#define SIGILL 4 // 执行了非法指令,或者试图执行数据段,堆栈溢出
#define SIGABRT 6 // 调用abort函数生成的信号,表示程序异常
#define SIGBUS 7 // 非法地址,包括内存地址对齐出错,比如访问一个4字节的整数, 但其地址不是4的倍数
#define SIGFPE 8 // 计算错误,比如除0、溢出
#define SIGSEGV 11 // 非法内存操作,与SIGBUS不同,他是对合法地址的非法访问,比如访问没有读权限的内存,向没有写权限的地址写数据
#define SIGSTKFLT 16  // 协处理器堆栈错误
#define SIGSYS 31 // 非法的系统调用

只要在应用程序中注册了这些信号的处理函数,当JNI Crash发生时,我们的处理函数就会被带调用到,然后获取到dump文件在上传。

具体的注册信号的处理函数,涉及到c++代码,大部分的andriod开发人员是不懂c或者c++代码的,所以,对于这种情况,可以使用框架,比如腾讯的bugly,爱奇艺的xCrash来对crash信息进行收集和上传,拿到crash信息后,在进行分析。

ANR

ANR (Application Not Responding)即应用无响应,在 Android系统中,应用发生的ANR有以下几种类型:

  1. KeyDispatchTimeout
    最常见的ANR 类型 是对输入事件 5s 内无响应,比如按键或触摸事件在此时间内无响应。
  2. BroadcastTimeout
    BroadcastReceiver在指定时间(前台广播 10s,后台广播60s)内无法处理完成,并且没有结束执行 onReceive 。
    从Android 6.0开始新增了TOP进程组,CPU调度优先分配给当前正在跟用户交互的APP,提升用户体验。
    adjType=”broadcast”的CPU调度组的选择取决于广播队列,当receiver接收到的广播来自于前台广播队列则采用前台进程组,当receiver接收到的广播来自于后台广播队列则采用后台进程组。前后台广播队列的CPU资源调度优先级不同,所以前台广播超时10秒就会ANR,而后台广播超时60秒才会ANR。更少的CPU资源分配就需要更长的时间来完成执行,这也就是为何两个广播队列定义了不同的超时阈值。
  3. ServiceTimeout
    这种类型在Android 应用中出现的概率很小,是指 Service 在特定的时间(前台服务,则超时时间是 20s;后台服务,则超时时间是200s)内无法处理完成。

ANR的常见原因:

  1. 在主线程耗时操作,如复杂的layout,庞大的for循环,IO操作等。
  2. 主线程被子线程同步锁block
  3. 主线程被binder对端block
  4. Binder被占满,导致主线程无法和system_server进程通信
  5. 得不到系统资源(cpu/memory/IO)

ANR分析
发生 ANR 后,会同时生成 traces.txt 文件,这个文件保存在 /data/anr/traces.txt 。首先要将app内部存储的traces.txt文件导出。
为了方便演示,下面在一个广播的onReceive方法中,进行耗时操作,造成ANR。


import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import test.cn.example.com.androidskill.R;
import test.cn.example.com.androidskill.ServiceWorkActivity;
import test.cn.example.com.util.LogUtil;

/**
 * 四大组件工作过程
 */
public class ChapterNineActivity extends AppCompatActivity implements View.OnClickListener{

    private MyBroadcastReceiverFive myBroadcastReceiverFive;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_charapter_nine);
        initView();
    }

    private void initView() {
        IntentFilter intentFilter = new IntentFilter("com.android.skill");
        myBroadcastReceiverFive = new MyBroadcastReceiverFive();
        registerReceiver(myBroadcastReceiverFive,intentFilter);
        findViewById(R.id.broadcast_work_normal).setOnClickListener(this);
    }

    private class MyBroadcastReceiverFive extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            LogUtil.e("动态注册  MyBroadcastReceiverFive");
            try {
                Thread.sleep(200*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.broadcast_work_normal:
                sendMyBroadcast();
                break;
            default:
                break;
        }
    }

    private void sendMyBroadcast() {
        Intent intent=new Intent();
        //定义广播的事件类型
        intent.setAction("com.android.skill");//构建意图,设置动作。把自定义的广播发出去。
        LogUtil.e(""+intent.getComponent());
        //发送广播
        sendBroadcast(intent,null);//null为广播接收者的权限

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //销毁是进行广播的注销,防止内存泄漏
        unregisterReceiver(myBroadcastReceiverFive);
    }
}

下面是activity_charapter_nine.xml布局文件




    

点击按钮,过10秒钟,变会发生ANR。

下面获取产生的ANR文件。
首先给adb配置一下path环境变量,在Android studio中的deminal窗口中,
输入 adb shell
在接着输入 cd /data/anr
接着输入 ls
按 Ctrl + D 快捷键 退出
在输入 adb pull /data/anr/traces.txt
Android稳定性优化_第1张图片

经过上面的几个步骤,就会将traces.txt文件导出到当前目录下。
输入 cd /data/anr 是进入/data/anr目录
ls 命令是,查看/data/anr目录,
下面打印的traces.txt表示在/data/anr目录下,有一个traces.txt文件。

将traces.txt文件导出后,可以通过AS进行分析,首先将traces.txt文件中的内容复制后,然后点击AS的顶部菜单栏, Analyze菜单,在弹出的下拉菜单菜单中,选择"Analyze Stack Trace",在弹出的对话框中,提前复制的traces.txt文件中的内容粘贴到弹出的对话框中
Android稳定性优化_第2张图片
,点击"Normalize"按钮,在点击"OK"按钮,会看到如下所示的界面。
Android稳定性优化_第3张图片
左侧是所有的线程列表,点击左侧的线程列表中的每个线程,右侧就会显示当前被选中的线程详细信息。通过上下滚动左侧的线程列表,可以看到灰色的线程,这些灰色的线程,就是发生了ANR的线程,点击这些灰色是线程,就可以查看具体的信息。图中右侧红框中显示的就是导致ANR的原因,很明显,在ChapterNineActivity类的MyBroadcastReceiverFive这个广播的onReceive方法中,导致了ANR。

以上便是一个简单的广播中由于进行了耗时操作超过了10秒钟,导致了ANR原因的分析。
主要分析要点:判断时间是不是对的上,然后关注主线程是否存在耗时,死锁,等锁等问题,可以基本判断是app自身的问题,还是系统问题,如果是系统问题,结合binder_sample,dvm_lock_sample来分别定位下binder call耗时和系统持有锁的耗时的问题。

ANR监控
对于线上产生的ANR,需要进行监控,将产生的ANR的信息记录下来,并上传到服务端,供开发人员分析,下面介绍常见的四中监控ANR的方式:

1.BlockCanary

原理巧妙的利用了Android原生Looper.loop中的一个log打印逻辑。

public final class Looper {

    ...

    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        ...

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
	    
            if (logging != null) {
	        //关键代码1 在消息分发前,logging对象调用println方法,执行打印操作
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            ...

            try {
	        //关键代码2,处理消息
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } 
            ...
	    
            if (logging != null) {
	    //关键代码3  在消息分发后,logging对象调用println方法,执行打印操作
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            ...

            msg.recycleUnchecked();
        }
    }

    ...
}

在Looper类的loop()方法中,有个无限循环中省略了很多的逻辑,
在关键代码1处,在消息分发前,调用logging对象的println方法打印,
在关键代码2处, 发送消息
在关键代码3处,在消息分发后,logging对象调用println方法,执行打印操作
android中的各种操作都是通过主线程的Handler来发送消息,然后在handler中进行消息的处理的。如果触发了某个操作,主线程的Handler关联的Looper的消息队列中,就会收到这个消息,并在Looper的loop()方法中处理这个消息,所以可以在loop()方法中的消息处理前的第一个logging.println()方法中,记录一个时间T1,在消息处理完的第二个logging.println()方法中在再次记录一个时间T2,计算这两次记录是时间差值,如果时间差值大于阀值,则可能表示在msg.target.dispatchMessage(msg)这个消息处理过程中,出现了耗时操作。
下面是BlockCanary的原理图(BlockCanary原理):
Android稳定性优化_第4张图片
这个方案优点:

  • 可以灵活配置,可监控常见的app应用性能,当然也可以作为部分场景的ANR监测,并且这个库还提供了准确的ANR和耗时调用栈等信息。具体dump的信息包括:
    • 基本信息:安装包标示、机型、api等级、uid、CPU内核数、进程名、内存、版本号等
    • 耗时信息:实际耗时、主线程时钟耗时、卡顿开始时间和结束时间
    • CPU信息:时间段内CPU是否忙,时间段内的系统CPU/应用CPU占比,I/O占CPU使用率
    • 堆栈信息:发生卡慢前的最近堆栈,可以用来帮助定位卡慢发生的地方和重现路径

缺点:

  • 谷歌已经明确标注This must be in a local variable, in case a UI event sets the logger
    这个looger对象是可以被更改的,已经有开发者遇到在使用WebView时logger被set为Null导致BlockCanary失效,只能让BlockCanary在WebView初始化之后调用start。

  • 如果dispatchMessage执行的非常久是无法触发BlockCanary的逻辑。

  • 谷歌在Looper中还有一个标注这里的queue.next是可能block的,场景就是文章开始提到的InputEvent。此处block同样会触发ANR,但BlockCanary同样无法适用的。在Activity的dispatchTouchEvent方法和dispatchKeyEvent方法中,做耗时操作,代码如下:

    import android.os.Bundle;
    import android.support.annotation.Nullable;
    import android.support.v7.app.AppCompatActivity;
    import android.view.KeyEvent;
    import android.view.MotionEvent;
    import android.widget.TextView;
    
    import test.cn.example.com.androidskill.util.DensityUtil;
    import test.cn.example.com.util.LogUtil;
    
    public class InputEventAnrActivity extends AppCompatActivity {
     	@Override
    	protected void onCreate(@Nullable Bundle savedInstanceState) {
        	super.onCreate(savedInstanceState);
        	TextView textView = new TextView(this);
        	textView.setTextSize(DensityUtil.sp2px(this,22));
        	textView.setText("触摸当前界面");
       		setContentView(textView);
    }
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        try {
            Thread.sleep(8*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Exception exception = new Exception("test anr");
        exception.printStackTrace();
        return super.dispatchTouchEvent(ev);
    }
    
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
        	try {
            	Thread.sleep(8*1000);
       		 } catch (InterruptedException e) {
           	 	e.printStackTrace();
        	}
        	
        	Exception exception = new Exception("test anr");
            exception.printStackTrace();
        	return super.dispatchKeyEvent(event);
    	 }
    }
    

    下面是触发ANR后的log日志:
    Android稳定性优化_第5张图片
    会看到InputEvent在queue.next中block,不会继续执行dispatchMessage,而是从native回调给InputEventReceiver.dispatchInputEvent处理分发,所以BlockCanary也就无法监控到这类ANR。

  • 无法监控CPU资源紧张造成系统卡顿,无法响应的ANR。

2.ANR-WatchDog

ANR-WatchDog的原理和WatchDog的原理是类似的。
WatchDog的监控原理就是向被监控的线程的Handler的消息队列中post一个任务,也就是HandlerChecker本身,然后HandlerChecker这个任务就会在被监控的线程对应Handler维护的消息队列中被执行,如果消息队列因为某一个任务卡住,那么HandlerChecker这个任务就无法及时的执行到,超过了指定的时间后就会被认为当前被监控的这个线程发生了卡死(死锁造成的卡死或者执行耗时任务造成的卡死)

下图是WatchDog原理:
Android稳定性优化_第6张图片
ANR-WatchDog是参考Android WatchDog机制(com.android.server.WatchDog.java)起个单独线程向主线程发送一个变量+1操作,自我休眠自定义ANR的阈值,休眠过后判断变量是否+1完成,如果未完成则告警。
下图是ANR-WatchDog原理:
Android稳定性优化_第7张图片

该方案的优点:

  1. 兼容性好,各个机型版本通用
  2. 无需修改APP逻辑代码,非侵入式
  3. 逻辑简单,性能影响不大

缺点:
无法保证能捕捉所有ANR,对阈值的设置直接影响捕获概率。使用Watchdog机制来实现在线的anr监控可能并不能百分百准确,比如5秒发生anr,在快到5秒的临界值的时候耗时任务正好执行完成了,这时候执行anr检测任务,在检测任务执行过程中,有可能Watchdog线程wait的时间也到了,这时候发现检测任务还没执行完于是就报了一个anr,这是不准确的;另一种情况可能是5秒anr已经发生了,但是Watchdog线程检测还是wait,也就是anr发生的时间和Watchdog线程wait的时间错开了,等到下一次Watchdog线程开始wait的时候,anr已经发生完了,主线程可能已经恢复正常,这时候就会漏掉这次发生的anr信息搜集。

3.SafeLooper

SafeLooper是个比较新奇的思路,本身就是一个堵塞的消息,在自己内部进行消息的处理,通过反射接管主线程Looper的功能。此方案使用反射进行message管理会有很大的性能损耗,但可以自由定制,这种AOP的思想是可以借鉴的。

4.FileObserver

从ANR的流程就可以知道/data/anr文件夹的变化代表着ANR的发生,AMS在dumpStackTrace方法中给了我们一些提示。按照这个思路,当ANR发生的时候,我们是可以通过监听该文件的写入情况来判断是否发生了ANR,看起来这是一个不错的时机。需要注意的是,所有应用发生ANR的时候都会进行回调,因此需要做一些过滤与判断,如包名、进程号等。
优点:

  1. 基于原生接口调用,时机和内容准确。
  2. 无性能问题实现简单

缺点:
最大的困难是兼容性问题,这个方案受限于Android系统的SELinux机制,5.0以后基本已经使低权限应用无法监听到trace文件了。

进程保活

为了提高应用的稳定性,防止应用被系统在内存不足的情况下杀掉,需要提供进程的优先级,保证进程不被系统很容易的杀掉。
进程保活从两个方面入手:

1. 提高进程的优先级

  • 在提高进程优先级前,先了解下系统是如何回收资源的。
    Android系统为每个应用都分配了一个固定大小的内存,可以通过以下两种方式获取
    方式一:

      ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
      //最大分配内存,单位是MB
      int memory = activityManager.getMemoryClass();
    

    方式二:

    Runtime rt=Runtime.getRuntime();
    long maxMemory=rt.maxMemory();
    //這個可以直接得到app可使用的最大memory size算出來是MB
    log.i("maxMemory:",Long.toString(maxMemory/(1024*1024)));
    

当手机上运行的应用很多时,就会导致手机的内存吃紧,为了保证正在使用的进程的稳定运行或新建进程,最终需要清除旧进程来回收内存。 为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 必要时,系统会首先消除重要性最低的进程,然后是清除重要性稍低一级的进程,依此类推,以回收系统资源。

应用进程的优先级

进程的重要性,划分5级:

前台进程 (Foreground process)

可见进程 (Visible process)

服务进程 (Service process)

后台进程 (Background process)

空进程 (Empty process)

前台进程的重要性最高,依次递减,空进程的重要性最低,下面分别来介绍每种级别的进程

  1. 前台进程
    用户当前操作所必需的进程。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。拥有以下特征的进程都是前台进程:

    • 拥有用户正在交互的 Activity(已调用 onResume())
    • 在另外一个单独进程中的Service绑定到用户正在交互的Activity,这个Service所在的进程也是前台进程
    • 正在“前台”运行的 Service(服务已调用startForeground())
    • 正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
    • 正执行其 onReceive() 方法的 BroadcastReceiver
  2. 可见进程
    没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。
    拥有以下特征的进程都是可见进程:

    • 不在前台、但仍对用户可见的 Activity(已调用 onPause())
    • 在另外一个单独进程中的Service绑定到可见Activity,这个Service所在的进程也是可见进程
  3. 服务进程
    尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作
    (例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。
    拥有以下特征的进程都是服务进程:

    • 正在运行 startService() 方法启动的服务,且不属于上述两个更高类别进程的进程
  4. 后台进程
    后台进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU 列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。
    拥有以下特征的进程都是后台进程:

    • 对用户不可见的 Activity 的进程(已调用 Activity的onStop() 方法)
  5. 空进程
    保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。
    为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。
    拥有以下特征的进程都是空进程:

    • 不含任何活动应用组件的进程

Android 进程回收策略

Android 中对于内存的回收,是一种根据 OOM_ADJ 阈值级别触发相应力度的内存回收的机制

ADJ级别 (7.0系统及以后)取值 (7.0系统前)取值 含义
NATIVE_ADJ -1000 -17 系统创建的Native进程,不被系统管理
SYSTEM_ADJ -900 -16 系统进程,在运行的过程中永远不会杀掉,如果杀掉可能会导致严重问题
PERSISTENT_PROC_ADJ -800 -12 核心进程,系统不会杀掉这类进程,但即使杀掉,影响面也没有SYSTEM_ADJ 进程那么严重
PERSISTENT_SERVICE_ADJ -700 -11 正在运行的服务进程,一般不会被杀掉
FOREGROUND_APP_ADJ 0 0 前台进程,是指正在前台运行的应用,被杀概率不大
VISIBLE_APP_ADJ 100 1 可见进程,用户正在使用,或者有界面在显示,除非出现异常,否则系统不会杀掉这类进程
PERCEPTIBLE_APP_ADJ 200 2 可感知的进程,虽然不在前台,但应用进程还在状态,系统除非到内存非常紧张才会杀掉这类进程,比如播放音乐的应用
BACKUP_APP_ADJ 300 3 正在备份的进程
HEAVY_WEIGHT_APP_ADJ 400 4 高权重进程,system/rootdir/init.rc文件中设置
SERVICE_ADJ 500 5 有Service 的进程
HOME_APP_ADJ 600 6 与Home 有交互的进程,比如有桌面部件和应用正在通信, widget 小挂件之类,一般尽量避免杀掉此类进程
PREVIOUS_APP_ADJ 700 7 切换进程,可以理解为从可见进程切换过来的进程的状态
SERVICE_B_ADJ 800 8 不活跃的进程
CACHED_APP_MIN_ADJ 900 9 不可见进程的adj最小值
CACHED_APP_MAX_ADJ 906 15 不可见进程的adj最大值
UNKNOWN_ADJ 1001 16 一般是指缓存进程

关于具体的ADJ的取值,可以查看framework层的ProcessList.java的源码,7.0系统及以后的ADJ的值和7.0系统以前的ADJ值是不同的。

查看手机中某个应用的进程oom_adj值
1.首先保证要参看的应用正在运行
2.在AS的Terminal窗口中,输入如下命令
Android稳定性优化_第8张图片
u0_a1250 28922 3744 1069152 SyS_epoll_ 000000 S test.cn.example.com.androidskill
从左到右依次是
USER 进程当前用户;
PID Process ID,进程ID;
PPID Process Parent ID,进程的父进程ID;
VSIZE Virtual Size,进程的虚拟内存大小;
RSS Resident Set Size,实际驻留"在内存中"的内存大小;
WCHAN 休眠进程在内核中的地址;
PC Program Counter;
NAME 进程名;

图中红框1处,androidskill是要查看的包名中的关键字符,通过这个关键字符,可以过滤出需要查看的进程
图中红框2处,是要查看的进程的进程ID
图中红框3处,就是当前查看的进程的包名
图中红框4处,proc/后面的数字就是要查看的进程的id
图中红框5处,就是当前查看的进程的oom_adj的值,这个值是0,表示是前台进程,
图中红框6处,就是当前查看的进程的oom_adj的值,这个值是6,(点击了Home键,让应用切换到了后台了,应该是后台进程,但是adj=6代表的含义是Home进程, app退入后台理论上应该进adj_previous 很明显得到的结果应该是在厂商客制化过的设备上得出来的)

如果是要查看所有的进程,则只需要输入adb shell后,接着输入ps命令即可查看所有的进程信息。

进程刚启动时ADJ等于INVALID_ADJ,当执行完attachApplication(),该该进程的curAdj和setAdj不相等,则会触发执行setOomAdj()将该进程的节点/proc/pid/oom_score_adj写入oomadj值。具体关于ADJ的分析,可以参考解读Android进程优先级ADJ算法,这里由于篇幅的原因就不展开了。

Android手机中进程被杀,可能有如下几种情况:

进程杀死场景 调用接口 可能影响范围
触发系统进程管理机制 Lowmemorykiller 从进程的adj值由大到小依次杀死,OOM_ADJ值相同,则占用内存大的优先杀死
被第三方的应用杀死(无Root) killBackgroundProcess 只杀死OOM_ADJ为4(7.0系统是400)以上的进程
被第三方应用杀死(有Root) force—stop或者kill 理论上是可以杀死所有的进程,一般只杀非系统关键进程和非前台,非可见的进程
厂商杀进程 force-stop或者kill 理论山可杀所有进程,包括native进程
用户主动"强行停止"进程 force-stop 只能停用第三方和非system/phone进程应用(停用system进程应用会造成Android系统重启)

当内存不足时,进程优先级低的(oom_adj 越大的)、占内存大的 App 进程将会被优先杀掉,下图参数为Android原生阈值,当系统剩余空闲内存低于某阈值(比如147MB),则从ADJ大于或等于相应阈值(比如900)的进程中,选择ADJ值最大的进程,如果存在多个ADJ相同的进程,则选择内存最大的进程。 如下是64位机器,LMK默认阈值图:
Android稳定性优化_第9张图片
综上可以得出结论:想要减少进程被杀的概率,就需要提高进程的优先级,并且减少应用的内存占用。当前很多的应用都会使用推送的功能,这个功能就需要app所在的进程一直存活,如果进程被杀掉,则无法及时收推送的消息。基于这个需求,很多的应用都会拼命的提高自己的app的进程优先级,防止被系统,第三方应用,厂商给杀掉。下面介绍几种提高进程优先级的方案:

  1. 利用Activity提升
    监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素的 Activity,在用户解锁时将 Activity 销毁掉。注意该 Activity 需设计成用户无感知。通过该方案,可以使进程的优先级在屏幕锁屏时间由4提升为最高优先级1。这个方案主要解决第本方案主要解决第三方应用及系统管理工具在检测到锁屏事件后一段时间(一般为5分钟以内)内会杀死后台进程,已达到省电的目的问题的场景。

  2. 利用前台服务
    Android 中 Service 的优先级为4,通过 setForeground 接口可以将后台 Service 设置为前台 Service,使进程的优先级由4提升为2,从而使进程的优先级仅仅低于用户当前正在交互的进程,与可见进程优先级一致,使进程被杀死的概率大大降低。从 Android2.3 开始调用 setForeground 将后台 Service 设置为前台 Service 时,必须在系统的通知栏发送一条通知,
    也就是前台 Service 与一条可见的通知时绑定在一起的,对于不需要常驻通知栏的应用来说,该方案虽好,但却是用户感知的,无法直接使用。方案挑战应对措施:通过实现一个内部 Service,在 LiveService 和其内部 Service 中同时发送具有相同 ID 的 Notification,然后将内部 Service 结束掉。随着内部 Service 的结束,Notification 将会消失,但系统优先级依然保持为2。这个应对措施在7.1版本后,Google修复了这个bug。7.1以后的版本,需要在重新想办法。

2. 进程被杀后拉活

  1. 利用系统广播拉活
  2. 利用第三方应用广播拉活
  3. 利用系统Service机制拉活(将 Service 设置为 START_STICKY)
  4. 利用Native进程拉活
  5. 利用账号同步机制拉活
  6. 利用系统通知管理权限进行拉活
  7. 利用辅助功能拉活,将应用加入厂商或管理软件白名单
  8. 其他还有一些技术之外的措施,比如说应用内 Push 通道的选择:国外版应用:接入 Google 的 GCM。国内版应用:根据终端不同,在小米手机(包括 MIUI)接入小米推送、华为手机接入华为推送;其他手机可以考虑接入腾讯信鸽或极光推送与小米推送做 A/B Test。

由于很多的方案都已经不能起到作用,这里就不展开了,可以参考下面这篇文章的解决方案
【腾讯Bugly干货分享】Android 进程保活招式大全

参考:
浅谈Android ANR在线监控原理
https://www.jb51.net/article/132609.htm

ANR-WatchDog
https://github.com/SalomonBrys/ANR-WatchDog

Android ANR监测方案解析
https://www.sohu.com/a/220647552_741445

BlockCanary — 轻松找出Android App界面卡顿元凶
http://blog.zhaiyifan.cn/2016/01/16/BlockCanaryTransparentPerformanceMonitor/

AndroidPerformanceMonitor
https://github.com/markzhai/AndroidPerformanceMonitor

Android进程保活终极方案总结
https://blog.csdn.net/hxl517116279/article/details/90815307

【腾讯Bugly干货分享】Android 进程保活招式大全
https://segmentfault.com/a/1190000006251859

android进程保活实践
https://www.jianshu.com/p/53c4d8303e19

关于 Android 进程保活,你所需要知道的一切
https://www.jianshu.com/p/63aafe3c12af

解读Android进程优先级ADJ算法
http://gityuan.com/2018/05/19/android-process-adj/

你可能感兴趣的:(android)