Instrumentation框架分析及其使用

article/2025/9/21 0:38:43

本文旨在从Android系统源码出发,简单梳理Instrumentation框架的行为及逻辑结构,供有兴趣的同学一起学习

从am instrument谈起

am instrument命令的执行

我们知道,命令行运行Android测试的命令是adb shell am instrument,这个命令是如何调起我们的测试代码来进行测试的呢,让我们从am命令的处理源码来开始一步步的查看吧。

am.java是android系统处理am命令的类,其位于/frameworks/base/cmds/am/src/com/android/commands/am/下,有Android源码的同学可以到相关目录下自行查看

onRun方法是am处理各个不同命令的分发处,我们可以看到am命令有多种用法,其中am instrumentation命令会调用runInstrument()方法

public void onRun() throws Exception {mAm = ActivityManagerNative.getDefault();if (mAm == null) {System.err.println(NO_SYSTEM_ERROR_CODE);throw new AndroidException("Can't connect to activity manager; is the system running?");}String op = nextArgRequired();if (op.equals("start")) {runStart();} else if (op.equals("startservice")) {runStartService();} else if (op.equals("stopservice")) {runStopService();} else if (op.equals("force-stop")) {runForceStop();} else if (op.equals("kill")) {runKill();} else if (op.equals("kill-all")) {runKillAll();} else if (op.equals("instrument")) {runInstrument();} else if (op.equals("broadcast")) {sendBroadcast();} else if (op.equals("profile")) {runProfile();} else if (op.equals("dumpheap")) {runDumpHeap();} else if (op.equals("set-debug-app")) {runSetDebugApp();} else if (op.equals("clear-debug-app")) {runClearDebugApp();} else if (op.equals("bug-report")) {runBugReport();} else if (op.equals("monitor")) {runMonitor();} else if (op.equals("hang")) {runHang();} else if (op.equals("restart")) {runRestart();} else if (op.equals("idle-maintenance")) {runIdleMaintenance();} else if (op.equals("screen-compat")) {runScreenCompat();} else if (op.equals("to-uri")) {runToUri(0);} else if (op.equals("to-intent-uri")) {runToUri(Intent.URI_INTENT_SCHEME);} else if (op.equals("to-app-uri")) {runToUri(Intent.URI_ANDROID_APP_SCHEME);} else if (op.equals("switch-user")) {runSwitchUser();} else if (op.equals("start-user")) {runStartUserInBackground();} else if (op.equals("stop-user")) {runStopUser();} else if (op.equals("stack")) {runStack();} else if (op.equals("lock-task")) {runLockTask();} else if (op.equals("get-config")) {runGetConfig();} else {showError("Error: unknown command '" + op + "'");}
}

以下是runInsturmentation方法的源码

private void runInstrument() throws Exception {String profileFile = null;boolean wait = false;boolean rawMode = false;boolean no_window_animation = false;int userId = UserHandle.USER_CURRENT;Bundle args = new Bundle();String argKey = null, argValue = null;IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));String abi = null;String opt;while ((opt=nextOption()) != null) {if (opt.equals("-p")) {profileFile = nextArgRequired();} else if (opt.equals("-w")) {wait = true;} else if (opt.equals("-r")) {rawMode = true;} else if (opt.equals("-e")) {argKey = nextArgRequired();argValue = nextArgRequired();args.putString(argKey, argValue);} else if (opt.equals("--no_window_animation")|| opt.equals("--no-window-animation")) {no_window_animation = true;} else if (opt.equals("--user")) {userId = parseUserArg(nextArgRequired());} else if (opt.equals("--abi")) {abi = nextArgRequired();} else {System.err.println("Error: Unknown option: " + opt);return;}}if (userId == UserHandle.USER_ALL) {System.err.println("Error: Can't start instrumentation with user 'all'");return;}String cnArg = nextArgRequired();ComponentName cn = ComponentName.unflattenFromString(cnArg);if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);InstrumentationWatcher watcher = null;UiAutomationConnection connection = null;if (wait) {watcher = new InstrumentationWatcher();watcher.setRawOutput(rawMode);connection = new UiAutomationConnection();}float[] oldAnims = null;if (no_window_animation) {oldAnims = wm.getAnimationScales();wm.setAnimationScale(0, 0.0f);wm.setAnimationScale(1, 0.0f);}if (abi != null) {final String[] supportedAbis = Build.SUPPORTED_ABIS;boolean matched = false;for (String supportedAbi : supportedAbis) {if (supportedAbi.equals(abi)) {matched = true;break;}}if (!matched) {throw new AndroidException("INSTRUMENTATION_FAILED: Unsupported instruction set " + abi);}}if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, abi)) {throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());}if (watcher != null) {if (!watcher.waitForFinish()) {System.out.println("INSTRUMENTATION_ABORTED: System has crashed.");}}if (oldAnims != null) {wm.setAnimationScales(oldAnims);}
}

该方法主要做了这么几件事:

  1. 解析参数并处理异常,目前支持的参数为(-w,-p,-r,-e,–no_window_animation,–no-window-animation,–user,–abi)
  2. 获取测试包名和TestRunner,格式为测试包名/TestRunner
  3. 进行一些参数的逻辑处理(通常没有使用到,可以暂不关注)
  4. 启动TestRunner进行测试(mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, abi))
  5. 如果附带了-w参数,会等待至执行完成,否则直接结束处理

各个指令含义解析:

  • -w, 等待执行完成后才返回,否则直接返回(Instrumentation的执行在不同线程,不管是否带该参数都会正确执行)
  • -p, 带1个参数,将一些配置写入指定文件(具体用处还未研究,后续有需要再补充)
  • -r, 输出原始的数据(具体用处还未研究,后续有需要再补充)
  • -e, 带两个参数,将这两个参数作为键值对传递给TestRunner,由TestRunner处理(后面会提到)
  • –no_window_animation或–no-window-animation,执行Instrumentation过程中禁用动画效果,执行完后会恢复
  • –user, 带1个参数,使用指定的uid运行(具体用处还未研究,后续有需要再补充)
  • –abi, 带1个参数,使用指定的abi运行(具体用处还未研究,后续有需要再补充)

mAm是一个IActivityManager的对象,调用其startInstrumentation方法开始处理Instrumentation,下面我们来看看ActivityManager相关的知识

ActivityManager相关知识

ActivityManager是android框架的一个重要部分,它负责一新ActivityThread进程创建,Activity生命周期的维护,下图为这几个类之间的层次关系:

在这张图中,绿色的部分是在SDK中开放给应用程序开发人员的接口,蓝色的部分是一个典型的Proxy模式,红色的部分是底层的服务实现,是真正的动作执行者。这里的一个核心思想是Proxy模式,关于代理模式相关知识,请参考(暂却,后续补上)。以上仅是简单的介绍了下者几个类的关系,随着我们上文的步伐,我们会一点点分析出am命令是如何让Android系统跑起来测试用例的。

获取ActivityManager

还记得之前在am命令中启动Instrumentation的命令么?对的就是这个mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, abi)
其中的mAm为mAm = ActivityManagerNative.getDefault();
接下来便是要研究ActivityManagerNative.getDefault()了:

static public IActivityManager getDefault() {return gDefault.get();
}

gDefault的定义是IActivityManager的一个单例对象

private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {protected IActivityManager create() {IBinder b = ServiceManager.getService("activity");if (false) {Log.v("ActivityManager", "default service binder = " + b);}IActivityManager am = asInterface(b);if (false) {Log.v("ActivityManager", "default service = " + am);}return am;}
};

获取到名为activity的服务后,调用asInterface方法:

static public IActivityManager asInterface(IBinder obj) {if (obj == null) {return null;}IActivityManager in =(IActivityManager)obj.queryLocalInterface(descriptor);if (in != null) {return in;}return new ActivityManagerProxy(obj);
}

返回的是一个ActivityManagerProxy对象,然后按照原来的流程应该执行的是startInstrumentation方法

public boolean startInstrumentation(ComponentName className, String profileFile,int flags, Bundle arguments, IInstrumentationWatcher watcher,IUiAutomationConnection connection, int userId, String instructionSet)throws RemoteException {Parcel data = Parcel.obtain();Parcel reply = Parcel.obtain();data.writeInterfaceToken(IActivityManager.descriptor);ComponentName.writeToParcel(className, data);data.writeString(profileFile);data.writeInt(flags);data.writeBundle(arguments);data.writeStrongBinder(watcher != null ? watcher.asBinder() : null);data.writeStrongBinder(connection != null ? connection.asBinder() : null);data.writeInt(userId);data.writeString(instructionSet);mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0);reply.readException();boolean res = reply.readInt() != 0;reply.recycle();data.recycle();return res;
}

将相关参数写入打包后调用mRemote.transact方法,这个mRemote即初始化ActivityManagerProxy时传入的IBinder对象,即ServiceManager.getService(“activity”)

public static IBinder getService(String name) {try {IBinder service = sCache.get(name);if (service != null) {return service;} else {return getIServiceManager().getService(name);}} catch (RemoteException e) {Log.e(TAG, "error in getService", e);}return null;
}

可见ServiceManager会先从sCache缓存中查看是否有对应的Binder对象,有则返回,没有则调用getIServiceManager().getService(name),那么要获取这个以activity命名的Service,它是在哪里创建的呢?通过全局搜索,我们找到这个调用关系,由于中间的方法实在是太太太太太长了,大家有兴趣的自己去看源码吧,其调用过程如下:zygote->main->new SystemServer().run()->[SystemServer]startBootstrapServices()->[SystemServer]mActivityManagerService.setSystemProcess()->[ActivityManagerService]ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true)

由此可见,这个名为mRemote的Binder对应的是ActivityManagerService,ActivityManagerService的transact方法继承了Binder的实现:

public final boolean transact(int code, Parcel data, Parcel reply,int flags) throws RemoteException {if (false) Log.v("Binder", "Transact: " + code + " to " + this);if (data != null) {data.setDataPosition(0);}boolean r = onTransact(code, data, reply, flags);if (reply != null) {reply.setDataPosition(0);}return r;
}

会调用onTransact方法:

public boolean onTransact(int code, Parcel data, Parcel reply, int flags)throws RemoteException {if (code == SYSPROPS_TRANSACTION) {// We need to tell all apps about the system property change.ArrayList<IBinder> procs = new ArrayList<IBinder>();synchronized(this) {final int NP = mProcessNames.getMap().size();for (int ip=0; ip<NP; ip++) {SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);final int NA = apps.size();for (int ia=0; ia<NA; ia++) {ProcessRecord app = apps.valueAt(ia);if (app.thread != null) {procs.add(app.thread.asBinder());}}}}int N = procs.size();for (int i=0; i<N; i++) {Parcel data2 = Parcel.obtain();try {procs.get(i).transact(IBinder.SYSPROPS_TRANSACTION, data2, null, 0);} catch (RemoteException e) {}data2.recycle();}}try {return super.onTransact(code, data, reply, flags);} catch (RuntimeException e) {// The activity manager only throws security exceptions, so let's// log all others.if (!(e instanceof SecurityException)) {Slog.wtf(TAG, "Activity Manager Crash", e);}throw e;}
}

由于statusCode不为SYSPROPS_TRANSACTION会调用父类ActivityManagerNative的onTransact方法,方法由于statusCode很多,我们只挑选了符合我们要求的部分的源码:

case START_INSTRUMENTATION_TRANSACTION: {data.enforceInterface(IActivityManager.descriptor);ComponentName className = ComponentName.readFromParcel(data);String profileFile = data.readString();int fl = data.readInt

http://chatgpt.dhexx.cn/article/uaFbmgae.shtml

相关文章

android.app.instrumentation解析

已经在Android SDK中学习了很多关于JUnit的内容&#xff0c;但是感觉一直有几个问题没有解决&#xff08;不知道大家是否有同样的感受&#xff09;JUnit的测试都自动化的&#xff0c;完全是不需要任何操作的&#xff0c;有2个问题我一直都还没有找到答案&#xff0c;这2个问题如…

Java探针 Instrumentation

背景&#xff1a;假如我们想打印出某些系统->某些类->某些方法的执行耗时&#xff0c;方式有很多&#xff0c;但是想要无侵入的做到这一点&#xff0c;只有Java探针一种方式。这也是很多调用链系统依赖的技术基础。 什么是Java探针 通俗来讲&#xff0c;就是Java提供的一…

opentelemetry-java-instrumentation翻译

原文地址&#xff1a;原文 OpenTelemetry Instrumentation for Java 这个项目提供了一个Java agent JAR&#xff0c;这个jar可以attached to任何Java8的应用上&#xff0c;动态地注入&#xff08;inject&#xff09;字节码从大量的流行库和框架&#xff08;popular libraries …

Instrument

使用Instruments 里面的Automation&#xff0c;可以对iOS进行自动化测试。 参考这篇文章&#xff1a;http://www.codeproject.com/KB/iPhone/UI_Automation_Testing.aspx 我用的是xcode4.2。 在这里下载修改好的项目&#xff0c;xcode4.2下用的&#xff1a;http://download.csd…

android Instrumentation

Android提供了一系列强大的测试工具&#xff0c;它针对Android的环境&#xff0c;扩展了业内标准的JUnit测试框架。尽管你可以使用JUnit测试Android工程&#xff0c;但Android工具允许你为应用程序的各个方面进行更为复杂的测试&#xff0c;包括单元层面及框架层面。 Android测…

Instrumentation 应用简介

引用&#xff1a; java-instrumentation 引用&#xff1a;Instrumentation 新功能 简介 java Instrumentation指的是可以用独立于应用程序之外的代理&#xff08;agent&#xff09;程序来监测和协助运行在JVM上的应用程序。这种监测和协助包括但不限于获取JVM运行时状态&#…

AndroidStudio单元测试——instrumentation

前言&#xff1a;这几天老大要我搞代码自动测试&#xff0c;eclispe的已经解决了&#xff0c;可他们都是用android studio&#xff0c;所以要在android studio 上重新试验&#xff0c;这个有难度啊&#xff0c;android studio国内资料极少&#xff0c;更不要说单元测试了。goog…

Android Instrumentation 简介

Instrumentation 简介 APIs && Source code 官方APIs地址(需要翻墙)Source code Instrumentation 特点 该框架基于JUnit&#xff0c;因此既可以直接使用Junit 进行测试&#xff0c;也可以使用Instrumentation 来测试Android 组件其为Android 应用的每种组件提供了测…

冲突域和广播域?

如何理解冲突域和广播域&#xff1f; 冲突域&#xff1a; 【定义】在同一个冲突域中的每一个节点都能收到所有被发送的帧。简单的说就是同一时间内只能有一台设备发送信息的范围。 【分层】基于OSI的第一层物理层 【设备】第二层设备能隔离冲突域&#xff0c;比如Switch。交…

广播域与冲突域

广播域与冲突域 一个集线器&#xff08;中继器&#xff09;连接的网络成为冲突域&#xff0c;因为每台主机都连接在了同一条线路上&#xff0c;所以传送信息时会冲突。 冲突域是基于第一层(物理层) 而交换机的本质是一个多借口网桥&#xff0c;就是说由交换机组成的网络中&…

冲突域广播域

网络互连设备可以将网络划分为不同的冲突域、广播域。但是&#xff0c;由于不同的网络互连设备可能工作在OSI模型的不同层次上。因此&#xff0c;它们划分冲突域、广播域的效果也就各不相同。如中继器工作在物理层&#xff0c;网桥和交换机工作在数据链路层&#xff0c;路由器工…

广播域和冲突域问题

该图中有几个冲突域几个广播域&#xff1f; 解答&#xff1a; 1、两个广播域&#xff0c;七个冲突域。 这样的&#xff1a;集线器属于物理层&#xff0c;所有接口同属于一个冲突域、一个广播域&#xff1b;交换机属于数据链路层&#xff0c;每个接口是一个单独的冲突域…

冲突域和碰撞域的理解

如何理解冲突域和广播域&#xff1f; 转载 冲突域&#xff1a; 【定义】在同一个冲突域中的每一个节点都能收到所有被发送的帧。简单的说就是同一时间内只能有一台设备发送信息的范围。 【分层】基于OSI的第一层物理层 【设备】第二层设备能隔离冲突域&#xff0c;比如Swi…

如何计算冲突域和广播域-图解分析

如何理解冲突域和广播域?冲突域:【定义】在同一个冲突域中的每一个节点都能收到所有被发送的帧。简单的说就是同一时间内只能有一台设备发送信息的范围。【分层】基于OSI的第一层(数据链路层)物理层【设备】第二层设备能隔离冲突域,比如Switch。交换机能缩小冲突域的范围,交…

有关冲突域的定义

一开始学习网络的时候&#xff0c;对于冲突域和广播域的理解仅仅是从设备上进行理解的&#xff0c;即集线器是一个冲突域&#xff0c;交换机能够分离冲突域&#xff0c;不能够分离广播域&#xff0c;路由器可以分离组播域。至于冲突域到底是什么&#xff0c;怎么样定义的&#…

如何辨别数清冲突域和广播域

1、首先&#xff0c;须知第一层不能隔离冲突域和广播域。例如集线器或者直接连PC 2、其次&#xff0c;第二层可以隔离冲突域&#xff0c;但不能隔离广播域。例如&#xff0c;二层交换机 3、接着&#xff0c;第三层可以隔离广播域&#xff0c;默认隔离冲突域&#xff0c;例如&…

详解广播域和冲突域的区别

总览 1、广播域可以跨网段&#xff0c;而冲突域只是发生的同一个网段的。以太网中&#xff0c;冲突域是由hub组织的。一个hub就是一个冲突域。交换机的每个端口都是一个冲突域。网段&#xff0c;又叫潜在冲突域。 2、冲突域在同一个冲突域中的每一个节点都能收到所有被发送的…

冲突域

一、冲突域 一个站点向另一个站点发出信号&#xff0c;除目的站点外&#xff0c;有多少站点能收到这个信息&#xff0c;这些站点就构成一个冲突域。在同一个冲突域中的每个节点都能收到所有被发送的帧&#xff0c;冲突域是基于第一层&#xff08;物理层&#xff09;。 传统共享…

collision domain - 冲突域

英文&#xff1a; Collision Domain 中文&#xff1a; 冲突域 介绍&#xff1a; 不同主机或设备同时发出的帧可能会互相冲突的网络区域。一条导线上所有工作站的集合&#xff0c;或一个物理网段上所有节点的集合&#xff0c;或以太网上竞争同一带宽的节点的集合都是一个冲突域…

Java 程序员开发常用的工具

1、常用开发工具 2、常用接口测试工具 3、常用远程连接工具 4、一些其他常用工具 5、总结 1、常用开发工具 作为一名Java程序开发人员&#xff0c;可以的选择集成开发环境IDE&#xff08;Integrated Development Environment&#xff09;非常多&#xff0c;得益于Java是一门开…