Android之Monkey源码分析(第七篇:Monkey程序如何获知App崩溃,然后自己停止运行的逻辑分析)

前言

    Monkey程序用来执行稳定性测试,当被测App出现Crash、ANR,Monkey程序会第一时刻获知崩溃情况,然后向标准错误中写入堆栈信息,同时自己可能会停止运行,取决于我们传递的命令行参数,你知道Monkey程序如何知道其他App的崩溃的吗?Monkey程序本身又是如何自己停止运行的呢?

    今天一起学习ActivityController,你将得到答案……

ActivityController类结构

    private class ActivityController extends IActivityController.Stub {
  
           …………省略很多代码…………

    }

    ActivityController位于Monkey.java类中,是以普通内部类的方式创建的,Monkey程序依赖ActivityController对象与AMS系统服务保持通信,ActivityManagerService通过ActivityController对象提供的API告知Monkey程序,Android系统发生了哪些事情? 这是我们的第一个问题的答案:Monkey程序是被AMS系统服务告知其他App发生的崩溃的。

    ActivityController作为跨进程的Binder对象(Android进程间通信标准),提供了哪些服务?这些在ActivityController中实现的方法预先已经在一个称为IActivityController的接口中进行了规范,这些方法表示ActivityController可以提供哪些跨进程的服务

    IActivityController是一个接口类,这个接口类的内部又定义了一个静态内部类,它的名字叫做Stub,所以才会有IActivityController.Stub这样的语法

    ActivityController的父类IActivityController.Stub位于IActivityController接口类中,作为Binder对象的ActivityController继承了IActivityController.Stub,IActivityController.Stub类又继承了Binder类,而Binder类又继承了IActivityController!

    那么IActivityController接口类中规定了哪些方法?(作为服务)即IActivityController规定的Binder服务可以提供哪些跨进程服务的功能呢?

IActivityController规定的方法(对外规定提供的服务)

说明:Android中的Binder标准,让跨进程之间的调用变得轻松,你只管调用,其他交给系统去实现,所以才会有这么方便的回调方法,AMS系统服务是运行在System Server进程中的一个独立线程,它可以方便的回调其他进程的方法,这正是Binder的功劳!牛逼!!

activityStarting() 当通过Intent启动Activity时,AMS系统服务可以跨进程的调用此方法
activityResuming() 当Activity需要恢复时,AMS会跨进程回调此方法
appCrashed() 当任意一个App发生Crash,AMS会跨进程的自动调用此方法
appEarlyNotResponding()当App发生ANR,AMS会跨进程调用此方法
systemNotResponding() 当Android系统没有响应的时候,AMS会跨进程调用此方法

    IActivityController接口规定了5个方法,说明该Binder可以提供5个服务。

    AMS由于持有IActivityController对应的子类远程Binder对象的引用,所以AMS系统服务在自己的业务逻辑中,加入了跨进程调用的这些方法,以此达成进程间通信。(各个进程之间不再孤立运行,而是间接改变对方进程内存中的数据完成通信,形成更为复杂的业务逻辑),在这里通信的两个进程,一个是Monkey进程、另一个是SystemServer进程,Monkey进程根据SystemServer中的AMS系统服务(线程形式)的告知(回调),用以完成自己的业务逻辑。(大写的牛逼)

两个通信的进程

1、Monkey进程

2、AMS系统服务所在进程(SystemServer进程)

    注意:实现了IActivityController接口的Binder对象中的所有方法全部在本地App进程中的Binder线程池中一个线程中运行…………,比如appCrashed()方法,它会运行在Monkey进程所在的Binder线程池中的1个线程中

    在Monkey程序中,ActivityController具体实现了一些根据系统情况的业务逻辑,比如系统告知出现App崩溃、出现ANR、出现Native崩溃,Monkey程序怎么做的业务逻辑

    在此之前,我们带着几个问题,继续学习……它们是:

                   1、Monkey程序什么时候向AMS注册的ActivityController对象(Binder对象)?

                   2、Monkey程序又是什么时候告知AMS终止注册的ActivityController对象(Binder对象)?

                   3、ActivityContoller中提供的方法又是如何实现自身业务逻辑的?

                   4、系统出现App崩溃,Monkey程序怎么做的?

                   5、系统出现Anr,Monkey程序又是怎么做的?

向AMS注册ActivityController对象

   private boolean getSystemInterfaces() {
        mAm = ActivityManager.getService();
        
        ……省略代码……

        try {
            mAm.setActivityController(new ActivityController(), true);
            mNetworkMonitor.register(mAm);
        } catch (RemoteException e) {
            Logger.err.println("** Failed talking with activity manager!");
            return false;
        }

        ……省略代码……

    }

    Monkey程序在getSystemInterfaces()方法中获取所有需要使用的系统服务,使用Monkey对象持有的表示AMS系统服务的mAm,在ActivityManager对象中,有一个setActivityController()方法用于向AMS系统服务主动注册一个实现IActivityControler接口的Binder对象,即代码中的

            mAm.setActivityController(new ActivityController(), true);

    setActivityController()方法,传入的第一个参数新创建的ActivityController对象,第二个参数true,则告知AMS系统服务注册的是Monkey程序(程序中约定好的标准,如果是false则代表不是monkey程序)

    这里也是跨进程的Binder调用,最后实际调用的是位于ActivityManagerService中的setActivityControler()方法,下面看下这个方法的实现

ActivityManagerService中的setActivityController()方法

    @Override
    public void setActivityController(IActivityController controller, boolean imAMonkey) {
        enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
                "setActivityController()");
        synchronized (this) {
            mController = controller;
            mControllerIsAMonkey = imAMonkey;
            Watchdog.getInstance().setActivityController(controller);
        }
    }

    Monkey程序中创建的ActivityController对象(Binder对象)最终由ActivityManagerService对象持有的mController负责保存引用,一系列的远程回调都是通过mController完成的,这样完成了AMS系统服务与Monkey程序进程之间的通信……这里暂时点到这里,实在说不完了(注意看:最后还有个Watchdog)

    注意下面这一行代码:表示强制检查调用者App是否设置了android.Manifest.permission.SET_ACTIVITY_WATCHER权限,看来我们平时写App的时候,必须得设置这个权限,才能注册一个IActivityController(Binder对象)

        enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
                "setActivityController()");

解除注册ActivityController

        try {

            mAm.setActivityController(null, true);

            …………省略代码…………

        } catch (RemoteException e) {
            // just in case this was latent (after mCount cycles), make sure
            // we report it
            
            …………省略代码…………

        }

    Monkey程序会在结束前,再次调用setActivityController()方法,这次将传入的第一个参数赋值为null,此时的ActivityManagerService对象持有的mController会指向一个null,表示成功解除ActivityConroller对象的注册,第二个参数仍然为true,用于告知AMS,这是由Monkey程序所发起的调用(这个具体的实现在AMS的setActivityController()方法中可以看到)

Binder线程池

    无论是Monkey程序中ActivityController中实现的所有方法,还是AMS中的方法,都各自运行在属于自己进程内的Binder线程池中的一个工作线程中执行……这个概念点到为止!

ActivityController的具体实现

    Monkey程序借助创建的ActivityController对象与AMS系统服务实现进程间通信,这当然依赖于Android的Binder机制!

    系统服务AMS会在发生崩溃、ANR、以及系统不可用时,通过ActivityController提供的服务,通过跨进程调用ActivityController的方法(),此时的Monkey程序在这些远程调用的方法中根据Android系统的情况,从而加入自己的业务逻辑,接下来我们看下Monkey程序中是如何实现的这些业务逻辑的!

    private class ActivityController extends IActivityController.Stub {
        public boolean activityStarting(Intent intent, String pkg) {
             
            ……省略很多代码……                

            return allow;
        }

        private boolean isActivityStartingAllowed(Intent intent, String pkg) {
           
            ……省略很多代码……

            return false;
        }

        public boolean activityResuming(String pkg) {

            ……省略很多代码……

            return allow;
        }

        public boolean appCrashed(String processName, int pid,
                String shortMsg, String longMsg,
                long timeMillis, String stackTrace) {
            
            ……省略很多代码……            

            return false;
        }

        public int appEarlyNotResponding(String processName, int pid, String annotation) {
            return 0;
        }

        public int appNotResponding(String processName, int pid, String processStats) {
            
            ……省略很多代码……

            return (mKillProcessAfterError) ? -1 : 1;
        }

        public int systemNotResponding(String message) {

            ……省略很多代码……            

            return (mKillProcessAfterError) ? -1 : 1;
        }
    }

    上面代码是ActivityController的全貌,每个方法都会在属于Monkey程序的Binder线程池中的1个工作线程中执行,执行结束后的结果会再回传到ActivityManagerService中,AMS系统服务再根据返回结果去做一些具体的业务逻辑,接下来我们学习每个方法是如何实现一些业务逻辑?

    1、获悉某个App崩溃

    2、获悉某个App发送Anr

    3、获悉系统出现问题

    4、AMS启动Activity

    5、AMS恢复Activity

    Monkey程序会做什么呢?

activityStarting(Intent,String)方法分析

       public boolean activityStarting(Intent intent, String pkg) {
            final boolean allow = isActivityStartingAllowed(intent, pkg);
            if (mVerbose > 0) {
                // StrictMode's disk checks end up catching this on
                // userdebug/eng builds due to PrintStream going to a
                // FileOutputStream in the end (perhaps only when
                // redirected to a file?)  So we allow disk writes
                // around this region for the monkey to minimize
                // harmless dropbox uploads from monkeys.
                StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
                Logger.out.println("    // " + (allow ? "Allowing" : "Rejecting") + " start of "
                        + intent + " in package " + pkg);
                StrictMode.setThreadPolicy(savedPolicy);
            }
            currentPackage = pkg;
            currentIntent = intent;
            return allow;
        }

    当一个Activity通过Intent方式启动时,AMS系统服务会回调此方法,它会将启动Activity的Intent对象、包名,作为参数跨进程传递过来(这俩对象都支持序列化),Monkey程序在activityStarting()方法中写下了属于自己的业务逻辑:Monkey程序可以告知AMS,哪些App可以启动,哪些App不能启动的业务逻辑!

    activityStarting()方法运行在Monkey程序中的Binder线程池中的一个工作线程中,它的返回结果会被AMS系统服务所使用(AMS的业务逻辑也在一个Binder线程池中的一个线程中执行)返回值的两种情况,会决定AMS是否会启动Activity

1、返回值为true

表示AMS可以启动Activity

2、返回值为false

表示AMS不可以启动Activity

    一起分析下activityStrating()的方法体

注意:activityStrating()方法只有在通过Intent对象启动Activity的时候,才会被AMS回调此方法

传入的参数intent,表示启动Activity时的Intent对象,传入参数pkg表示要启动的Activity所在的包名

1、获取启动结果

通过执行isActivityStartingAllowed()方法,获取Activity能否启动的结果,结果会保存在局部变量allow中

2、日志输出

根据Monkey对象持有的mVerbose值,决定是否输出日志

3、保存当前启动的包名

Monkey类持有的currentPackage保存最近要启动的Activity所在的包名

4、保存当前启动时的Intent对象

Monkey类持有的currentIntent负责保存最近启动Activity的Intent对象

5、向AMS系统服务返回结果

Binder线程池中的线程会返回一个boolean,AMS系统服务收到此值,决定是否启动Activity

activityResuming(String)方法分析

        public boolean activityResuming(String pkg) {
            StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites(); //与严格模式有关……
            Logger.out.println("    // activityResuming(" + pkg + ")");  //输出日志
            boolean allow = MonkeyUtils.getPackageFilter().checkEnteringPackage(pkg)
                    || (DEBUG_ALLOW_ANY_RESTARTS != 0); //获取是否为允许启动的应用
            if (!allow) { //如果是不同意启动的应用
                if (mVerbose > 0) { //只输出一行日志……
                    Logger.out.println("    // " + (allow ? "Allowing" : "Rejecting")
                            + " resume of package " + pkg);
                }
            }
            currentPackage = pkg;//记录当前屏幕中启动应用的包名
            StrictMode.setThreadPolicy(savedPolicy); //还是严格模式
            return allow; //返回Activity的是否允许启动……
        }

    AMS恢复一个Activity前(onResume()方法调用前),会跨进程调用该方法,该方法的返回值表示能否恢复Activity,传入参数pkg表示包名

1、获取StrictMode.ThreadPolicy对象

这个应该是严格模式用的吧?

2、向标准输出写入日志

表示包名的日志会写入到标准输出

3、获取包名是否启动

结果会保存在局部变量allow中

4、不允许启动Activity的情况

日志规格大于0,向标准输出写入一行日志

                    Logger.out.println("    // " + (allow ? "Allowing" : "Rejecting")
                            + " resume of package " + pkg);

5、更新Monkey类持有的当前包名

currentPackage = pkg

6、创建好的严格模式对象传入到StrictMode的静态方法setThreadPolicy中

7、向调用者返回结果(AMS会收到结果)

appCrashed()方法分析

        /**
         * 出现App崩溃时,AMS系统服务会回调此方法,此方法运行在当前Monkey进程的binder线程池中的一个线程中,AMS牛逼,监控着哪个进程崩溃了(看袁辉辉大佬的解读)
         * @param processName AMS告知的进程名 ,字符串天生支持序列化
         * @param pid AMS告知进程的pid
         * @param shortMsg 短的堆栈信息
         * @param longMsg 长的堆栈信息
         * @param timeMillis 时间戳
         * @param stackTrace 堆栈信息
         * @return 当返回true时,表示可以重启进程,当返回false时,表示立即杀死它(进程),AMS会使用此返回值
         */
        public boolean appCrashed(String processName, int pid,
                String shortMsg, String longMsg,
                long timeMillis, String stackTrace) {
            StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites(); //
            Logger.err.println("// CRASH: " + processName + " (pid " + pid + ")"); //向标准错误流中输出日志(该方法在当前进程的binder线程中执行)
            Logger.err.println("// Short Msg: " + shortMsg); //向标准错误流中输出日志(默认到屏幕中)
            Logger.err.println("// Long Msg: " + longMsg);
            Logger.err.println("// Build Label: " + Build.FINGERPRINT);
            Logger.err.println("// Build Changelist: " + Build.VERSION.INCREMENTAL);
            Logger.err.println("// Build Time: " + Build.TIME);
            Logger.err.println("// " + stackTrace.replace("\n", "\n// ")); //这就是为啥线程堆栈前都有两个//的原因
            StrictMode.setThreadPolicy(savedPolicy); //严格模式

            if (mMatchDescription == null
                    || shortMsg.contains(mMatchDescription)
                    || longMsg.contains(mMatchDescription)
                    || stackTrace.contains(mMatchDescription)) { //当没有设置匹配的堆栈信息时、或者短信息包含指定的内容、或者长消息包括指定的内容、或者堆栈信息包含指定的内容,会走这里
                if (!mIgnoreCrashes || mRequestBugreport) { //如果没有设置忽略崩溃,或者必须得需要bugreport,会走这里
                    synchronized (Monkey.this) { //线程间同步,appCrashed()方法在Monkey进程自己的Binder线程池中的某个线程中运行,这样Binder线程池里的线程会与Monkey的主线程竞争同一个对象锁(线程间同步)
                                                 //Monkey主线程,每循环一次才释放一次Monkey对象锁,如果Monkey主线程一直持有Monkey对象不放,则Binder线程池里的线程会一直被阻塞,等待这个Monkey对象锁被释放,这种情况下,AMS线程就会被这个Binder线程池中的线程影响
                        if (!mIgnoreCrashes) { //如果没有设置忽略崩溃的选项
                            mAbort = true; //修改共享变量mAbort,Monkey进程(主线程)读取中断的标志位发现true,会因此结束程序。此共享变量需要保护,所以使用对象锁,防止同时写入
                        }
                        if (mRequestBugreport){ //如果用户设置了需要崩溃报告
                            mRequestAppCrashBugreport = true; //binder线程修改此共享变量,它表示需要AppCrashBugreport的标志位,monkey主进程(主线程)会一直在事件循环中读取这个共享变量
                            mReportProcessName = processName; //设置需要上报的进程名字(程序名) 此共享变量需要保护(只保护需要写的共享变量)
                        }
                    } //这里,Binder线程池中的工作线程,会释放对象锁,Monkey主进程(主线程)获取到对象锁后才能继续执行(用对象锁做的线程间同步)
                    return !mKillProcessAfterError; //这个返回值,是给AMS用的……默认值一定返回的是true啊,出现Crash,要求系统重启app进程……怪不得设置了忽略App崩溃之后自动重启了呢
                }
            }
            return false; //当一个App进程出现崩溃后触发,返回true时,表示AMS重启进程(所以你很快可以看到重启的应用),当返回false时,表示立即杀死它(进程),返回值表示AMS如何处理该进程……
        }

    当任何一个App出现崩溃时,AMS会跨进程调用此方法,Monkey程序获悉到有App进程出现崩溃,则会进行自己的业务逻辑

    在Monkey程序启动的命令行中如果没有指定忽略崩溃,此时Monkey对象持有的mIgnoreCrashes为false,此时会改变共享变量mAbort的值为true,表示Monkey主线程会结束,而Monkey主线程在运行循环中,如果循环结束,后果是Monkey程序的结束

 

    具体细节先不写了,文章太长了,想分开一篇来写

appEarlyNotResponding()方法分析

        /**
         * 当鉴定为ANR时触发(AMS用来鉴定ANR,袁辉辉那里讲的)
         * 鉴定出现ANR时会触发?Early是什么意思?
         * @param processName 进程名
         * @param pid 进程id
         * @param annotation 描述?
         * @return 返回一个0
         */
        public int appEarlyNotResponding(String processName, int pid, String annotation) {
            return 0;
        }

appNotResponding()方法分析

        /**
         * app发生ANR时,AMS回调此方法,此方法在Monkey进程的某个binder线程池中的某个线程中执行
         * @param processName 进程名
         * @param pid 进程id
         * @param processStats 进程状态
         * @return 当返回0时,表示弹出应用无响应的dialog,如果返回1时,表示继续等待,如果返回-1时,表示立即杀死进程
         * 当一个应用进程出现ANR时会触发,卧槽,这个API我发现是给Setting App准备的吧,我算明白了……
         */
        public int appNotResponding(String processName, int pid, String processStats) {
            StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites(); //创建一个StrictMode.ThreadPolicy对象,貌似是严格模式?
            Logger.err.println("// NOT RESPONDING: " + processName + " (pid " + pid + ")"); //向标准错误流中打印发生ANR的进程名和pid
            Logger.err.println(processStats); //打印进程的状态
            StrictMode.setThreadPolicy(savedPolicy); //这个静态方法setThreadPolicy()干啥的?

            if (mMatchDescription == null || processStats.contains(mMatchDescription)) {
                synchronized (Monkey.this) { //Binder线程池中的线程,需要获取到对象锁后,才能继续运行,一旦Monkey主线程释放掉对象锁,此时开始继续Binder线程的执行,而Monkey主线程因为没有对象锁,所以造成程序的停留,完美的线程间同步(进程间同步)
                    mRequestAnrTraces = true;  //修改共享变量(共享内存),表示需要请求ANR的Trace
                    mRequestDumpsysMemInfo = true; //修改共享变量,表示需要输出内存信息
                    mRequestProcRank = true; //修改共享变量,表示请求
                    if (mRequestBugreport) { //如果命令行参数中,执行了需要bugreport时
                        mRequestAnrBugreport = true; //修改共享变量,表示需要请求anr的bugreport
                        mReportProcessName = processName; //修改共享变量,保存上报的进程名
                    }
                }
                if (!mIgnoreTimeouts) { //如果没有在命令行中设置忽略超时,出现ANR后,monkey程序即会停止
                    synchronized (Monkey.this) { //与monkey的主线程(主进程)竞争Monkey对象锁,binder线程池中的线程可能会阻塞在这里(那样AMS所在进程SystemServer中的Binder线程池中的一个binder线程也会被阻塞,没错,看来Monkey主线程不能太累,会影响System_Server的执行(见袁辉辉篇)
                        mAbort = true; //Monkey程序是否中断的标志位,修改此共享变量,不过此处重复获取同一个对象锁,有意思
                    }
                }
            }

            return (mKillProcessAfterError) ? -1 : 1;  //返回值-1为要求AMS立即杀死进程,1表示不杀死进程吗?而且这个返回值可以根据命令行设置mKillProcessAfterError的值
        }

    当任何App出现ANR时,AMS通过此方法告知Monkey程序,Monkey程序获悉后加入了自己的处理逻辑

systemNotResponding()方法分析

        /**
         * 当系统看门狗监测到系统挂了也会触发该方法
         * 系统没响应时,AMS会回调此方法
         * @param message 没响应的原因
         * @return 返回的数字,表示退出状态码
         * 如果返回1,表示继续等待,返回-1,表示系统正常自杀(这里的正常自杀,系统自己主动自杀,该保存的数据先保存好,然后自杀,并不是因为其他原因导致的自杀)
         */
        public int systemNotResponding(String message) {
            StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
            Logger.err.println("// WATCHDOG: " + message); //向标准错误打印WATCHDOG
            StrictMode.setThreadPolicy(savedPolicy);

            synchronized (Monkey.this) { //竞争对象锁,Binder线程池中的某个工作线程,先竞争对象锁,获取到对象锁才能执行代码块中的方法,否则会被阻塞(对象锁作为线程间同步的方式)
                if (mMatchDescription == null || message.contains(mMatchDescription)) { //如果没有设置匹配信息或者设置了匹配信息并包含的情况下
                    if (!mIgnoreCrashes) { //如果没有设置忽略崩溃,系统无响应时,Monkey程序也会结束
                        mAbort = true; //Binder线程池中的工作线程修改共享变量(写入),Monkey的主线程会轮询该共享变量,当发现为true,Monkey程序会结束循环,主线程结束,整个monkey程序也就结束了
                    }
                    if (mRequestBugreport) { //如果在命令行中执行的需要bugreport
                        mRequestWatchdogBugreport = true; //修改共享变量,需要bugreport
                    }
                }
                mWatchdogWaiting = true; //修改共享变量(共享内存),watchDog的标志位为true
            } //释放Monkey对象锁,这里为何要释放对象锁,莫非想要monkey程序继续运行?
            synchronized (Monkey.this) { //再次获取对象锁,如果没有获取到,Binder线程池中的某个工作线程将被阻塞在这里
                while (mWatchdogWaiting) { //如果需要watchDog等待
                    try {
                        Monkey.this.wait(); //Binder线程池的线程将一直停留在这里,等待Monkey对象锁
                    } catch (InterruptedException e) {
                    }
                }
            }
            return (mKillProcessAfterError) ? -1 : 1; //返回值-1表示,需要干死进程,1表示不需要干死进程
        }
    }

    当系统无响应时,AMS会通过此方法告知Monkey程序,具体逻辑看下注释吧

总结

    这篇文章写了太久,横跨一年了都,收获必须满满的,原来Monkey程序没有什么神奇之处,依然使用Android Framework层的API,同样依赖系统服务才能完成自己的业务逻辑,Android博大精深,值得学习

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值