Android Monkey原理探讨

简介: ## 0x0 概述 本文不涉及如何使用[monkey](https://developer.android.com/studio/test/monkey.html),官网说得够详细了,网上资料也一大堆。本文着重探讨monkey的实现原理,以及基于这些原理,我们可以做些什么?本文涉及的Monkey的源码位于AOSP的[development](https://android-review.goog

0x0 概述

本文不涉及如何使用monkey,官网说得够详细了,网上资料也一大堆。本文着重探讨monkey的实现原理,以及基于这些原理,我们可以做些什么?本文涉及的Monkey的源码位于AOSP的development项目的cmds目录下。

0x1 Monkey事件触发原理

Monkey的主要作用是,发送一些随机交互事件模拟人的随机操作。它是如何触发各种事件的呢?

1.1 触摸事件

触摸事件包括屏幕以及物理键的触摸,滑动,点击事件。我们可以通过发送一些adb命令模拟这些事件。不过Monkey实现时,直接调用hiden API实现。

InputManager.getInstance().injectInputEvent(keyEvent, int)

构造需要的事件,然后调用该接口就能触发了。

2. Activity事件

Activity事件是指我们调用Android系统组件的事件。一般测试中,我们也是通过adb shell am命令来实现的。Monkey在实现是,是直接通过IActivityManager实例:

//同样通过hiden API获得IActivityManager实例
IActivityManager am = ActivityManagerNative.getDefault();
//利用IActivityManager接口打开Activity
am.startActivity();

3. Window事件

Window事件是指操作Window的事件,例如转屏。这里直接用IWindowManager实现。

//获得IWindowManager实例
IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
//调用IWindowManager接口转屏
wm..thawRotation();

此外还会调用其他的一些hiden API获取崩溃信息,ANR信息等等。这些直接从底层hiden接口拿,比我们上层获取更加方便,准确。

0x2 Monkey的框架设计

上一节分析到Monkey的功能主要分成两块:

  1. 产生事件;
  2. 触发事件。

因此它的框架设计非常简洁,核心类是Monkey.java, MonkeyEventSource.java, MonkeyEvent.java。

核心类 说明
Monkey 程序的入口,同时也是调度中心,根据参数选择合适的MonkeyEventSource,并适时触发MonkeyEvent。
MonkeyEventSource MonkeyEvent的工厂,是一个接口。它有各种实现,例如随机生成MonkeyEvent,根据配置文件生成MonkeyEvent,根据网络数据生成MonkeyEvent等等。
MonkeyEvent 各种事件的具体实现,是一个抽象类,不同事件有不同实现。在Monkey中各种活动都是事件,除了基本的触摸事件,Activity事件外,事件之间的停顿也是通过一个MonkeyThrottleEvent来实现。这样概念的扩展,将各种活动一视同仁的对待,使设计变得简单。

image

image

如果我们需要扩展Monkey的功能,只需要增加自己实现的MonkeyEventSource和MonkeyEvent即可。

0x3 Monkey的运行原理

我们在terminal中执行adb shell monkey,实际上是执行手机中/system/bin/monkey这个脚本(在源码中也能看到),该脚本具体内容是

# Script to start "monkey" on the device, which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/framework/monkey.jar
trap "" HUP
exec app_process $base/bin com.android.commands.monkey.Monkey $*

所以他是通过/system/bin/app_process运行/system/framework/monkey.jar。
app_process的核心源码是app_main.cpp
它会新建一个native进程,初始化虚拟机,从CLASSPATH找到我们定义的Main Class,并把参数传给它。可以参考Android系统进程Zygote启动过程的源代码分析

0x4 自定义可执行jar包

根据上一节的分析,可以通过exec app_process直接执行一个jar包。所以理论上我们可以自己写一个jar包,丢给它执行。可以参考一下解析android framework下利用app_process来调用java写的命令及示例

这里需要注意,JVM中的jar文件是一对class文件的zip包。而在Android中,可执行文件是dex,所以Android里面的jar本质上里面也是dex,直接把eclipse导出的jar包放进去是无法执行的。

这里也有一个小例子(可以在附件中下载完整代码):

/**
 * Android可执行jar测试。
 * 测试程序会每隔三秒钟写一次文件,报告自己还活着,可以测试该程序的运行时长。
 *
 * 使用方式:
 * 1. 利用android_jar.sh一键生成一个dex文件test.dex;
 * 2. 将testJar放到/data/local/tmp/目录下,并授予执行权限;
 * 3. 运行testJar文件。
 * Created by shangjie on 2016/11/9.
 */
public class Monkey {
    public static void main(String[] args) {
        Process.setArgV0("io.light.monkey");
        System.out.println("Process start");
        Monkey main = new Monkey();
        main.run();
        System.exit(0);
    }

    public void run() {
                File file = Environment.getExternalStorageDirectory();
        file = new File(file, "ali_jar_process.txt");
        if (file.exists()) {
            boolean isDelete = file.delete();
            println("Delete file: " + isDelete);
        }

        try {
            boolean result = file.createNewFile();
            println("Create file: " + result);
        } catch (IOException e) {
            e.printStackTrace();
        }

        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HH:mm:ss");
        int i  = 0;
        while (i < 900) {
            String time = sdf.format(new Date());
            String content = time + " I am still live.";
            println(content);
            writeFile(file, content);

            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            i ++;
        }
    }


    void writeFile(File file, String mValue) {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(file, true))) {
            bw.newLine();
            bw.write(mValue);
            bw.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void println(String msg) {
        System.out.println(msg);
    }
}

我可以看到它成功运行了:
image

进一步分析该进程,通过ps命令,我们看到该进程是属于shell用户组的,因此它没有root,system那样高的权限,但是比一般的用户进程权限也要高很多,可以多做很多事情,例如静默地安装,卸载。
image

通过查看该进程的oom_adj文件,我们知道他的ADJ值是-17,根据源码ProcessList-17是NATIVE_ADJ是不受系统控制的,理论上不会被系统杀掉,可以一直运行,不过我自己的测试中,发现还是会die,还在分析原因。从我们直观经验中,monkey,UiAutomator的确可以运行很久。
image

0x5 总结

上述Monkey的分析同样适用于UiAutomator等Android内部的测试工具,更重要的是,我们可以基于这些原理实现我们自己的功能强大,且稳定可靠的测试工具。

0x6 引用

  1. 解析android framework下利用app_process来调用java写的命令及示例
  2. Android系统进程Zygote启动过程的源代码分析
  3. 有关Monkey命令的两个隐藏选项
目录
相关文章
|
7月前
|
安全 Shell Android开发
Android系统 init.rc sys/class系统节点写不进解决方案和原理分析
Android系统 init.rc sys/class系统节点写不进解决方案和原理分析
386 0
|
7月前
|
安全 Android开发
Android13 Root实现和原理分析
Android13 Root实现和原理分析
622 0
|
7月前
|
Java Android开发
Android系统 获取用户最后操作时间回调实现和原理分析
Android系统 获取用户最后操作时间回调实现和原理分析
195 0
|
3月前
|
安全 Android开发 Kotlin
Android经典实战之SurfaceView原理和实践
本文介绍了 `SurfaceView` 这一强大的 UI 组件,尤其适合高性能绘制任务,如视频播放和游戏。文章详细讲解了 `SurfaceView` 的原理、与 `Surface` 类的关系及其实现示例,并强调了使用时需注意的线程安全、生命周期管理和性能优化等问题。
169 8
|
1月前
|
缓存 Java 数据库
Android的ANR原理
【10月更文挑战第18天】了解 ANR 的原理对于开发高质量的 Android 应用至关重要。通过合理的设计和优化,可以有效避免 ANR 的发生,提升应用的性能和用户体验。
58 8
|
7月前
|
Android开发 移动开发 小程序
binder机制原理面试,安卓app开发教程
binder机制原理面试,安卓app开发教程
binder机制原理面试,安卓app开发教程
|
2月前
|
XML 前端开发 Android开发
Android View的绘制流程和原理详细解说
Android View的绘制流程和原理详细解说
39 3
|
3月前
|
ARouter 测试技术 API
Android经典面试题之组件化原理、优缺点、实现方法?
本文介绍了组件化在Android开发中的应用,详细阐述了其原理、优缺点及实现方式,包括模块化、接口编程、依赖注入、路由机制等内容,并提供了具体代码示例。
48 2
|
3月前
|
编解码 前端开发 Android开发
Android经典实战之TextureView原理和高级用法
本文介绍了 `TextureView` 的原理和特点,包括其硬件加速渲染的优势及与其他视图叠加使用的灵活性,并提供了视频播放和自定义绘制的示例代码。通过合理管理生命周期和资源,`TextureView` 可实现高效流畅的图形和视频渲染。
250 12
|
2月前
|
Java 调度 Android开发
Android面试题之Kotlin中async 和 await实现并发的原理和面试总结
本文首发于公众号“AntDream”,详细解析了Kotlin协程中`async`与`await`的原理及其非阻塞特性,并提供了相关面试题及答案。协程作为轻量级线程,由Kotlin运行时库管理,`async`用于启动协程并返回`Deferred`对象,`await`则用于等待该对象完成并获取结果。文章还探讨了协程与传统线程的区别,并展示了如何取消协程任务及正确释放资源。
32 0