Instrumentation框架分析及其使用
本文旨在从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);
}
}
该方法主要做了这么几件事:
- 解析参数并处理异常,目前支持的参数为(-w,-p,-r,-e,–no_window_animation,–no-window-animation,–user,–abi)
- 获取测试包名和TestRunner,格式为测试包名/TestRunner
- 进行一些参数的逻辑处理(通常没有使用到,可以暂不关注)
- 启动TestRunner进行测试(mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, abi))
- 如果附带了-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()了:
1 | static public IActivityManager getDefault() { |
gDefault的定义是IActivityManager的一个单例对象
1 | private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() { |
获取到名为activity的服务后,调用asInterface方法:
1 | static public IActivityManager asInterface(IBinder obj) { |
返回的是一个ActivityManagerProxy对象,然后按照原来的流程应该执行的是startInstrumentation方法
1 | public boolean startInstrumentation(ComponentName className, String profileFile, |
将相关参数写入打包后调用mRemote.transact方法,这个mRemote即初始化ActivityManagerProxy时传入的IBinder对象,即ServiceManager.getService(“activity”)
1 | public static IBinder getService(String name) { |
可见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的实现:
1 | public final boolean transact(int code, Parcel data, Parcel reply, |
会调用onTransact方法:
1 | public boolean onTransact(int code, Parcel data, Parcel reply, int flags) |
由于statusCode不为SYSPROPS_TRANSACTION会调用父类ActivityManagerNative的onTransact方法,方法由于statusCode很多,我们只挑选了符合我们要求的部分的源码:
1 | case START_INSTRUMENTATION_TRANSACTION: { |
在读取出相应数据后调用startInstrumentation方法,开始执行Instrumentation
启动Instrumentation
所以回到之前am命令的处理,实际调用的是ActivityManagerService的startInstrumentation方法。所以Instrumentation的启动是由ActivityManagerService.startInstrumentation()方法完成的
public boolean startInstrumentation(ComponentName className,
String profileFile, int flags, Bundle arguments,
IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection,
int userId, String abiOverride) {
enforceNotIsolatedCaller("startInstrumentation");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, ALLOW_FULL_ONLY, "startInstrumentation", null);
// Refuse possible leaked file descriptors
if (arguments != null && arguments.hasFileDescriptors()) {
throw new IllegalArgumentException("File descriptors passed in Bundle");
}
synchronized(this) {
InstrumentationInfo ii = null;
ApplicationInfo ai = null;
try {
ii = mContext.getPackageManager().getInstrumentationInfo(
className, STOCK_PM_FLAGS);
ai = AppGlobals.getPackageManager().getApplicationInfo(
ii.targetPackage, STOCK_PM_FLAGS, userId);
} catch (PackageManager.NameNotFoundException e) {
} catch (RemoteException e) {
}
if (ii == null) {
reportStartInstrumentationFailure(watcher, className,
"Unable to find instrumentation info for: " + className);
return false;
}
if (ai == null) {
reportStartInstrumentationFailure(watcher, className,
"Unable to find instrumentation target package: " + ii.targetPackage);
return false;
}
int match = mContext.getPackageManager().checkSignatures(
ii.targetPackage, ii.packageName);
if (match < 0 && match != PackageManager.SIGNATURE_FIRST_NOT_SIGNED) {
String msg = "Permission Denial: starting instrumentation "
+ className + " from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingPid()
+ " not allowed because package " + ii.packageName
+ " does not have a signature matching the target "
+ ii.targetPackage;
reportStartInstrumentationFailure(watcher, className, msg);
throw new SecurityException(msg);
}
final long origId = Binder.clearCallingIdentity();
// Instrumentation can kill and relaunch even persistent processes
forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, false, userId,
"start instr");
ProcessRecord app = addAppLocked(ai, false, abiOverride);
app.instrumentationClass = className;
app.instrumentationInfo = ai;
app.instrumentationProfileFile = profileFile;
app.instrumentationArguments = arguments;
app.instrumentationWatcher = watcher;
app.instrumentationUiAutomationConnection = uiAutomationConnection;
app.instrumentationResultClass = className;
Binder.restoreCallingIdentity(origId);
}
return true;
}
该方法做了如下的事情:
- 检查TestRunner是否存在
- 检查TargetPackage是否存在
- 检测签名是否一致
- 强制关闭被测包
- 通过addAppLocked方法创建ProcessRecord
addAppLocked方法的源码如下:
final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated,
String abiOverride) {
ProcessRecord app;
if (!isolated) {
app = getProcessRecordLocked(info.processName, info.uid, true);
} else {
app = null;
}
if (app == null) {
app = newProcessRecordLocked(info, null, isolated, 0);
mProcessNames.put(info.processName, app.uid, app);
if (isolated) {
mIsolatedProcesses.put(app.uid, app);
}
updateLruProcessLocked(app, false, null);
updateOomAdjLocked();
}
// This package really, really can not be stopped.
try {
AppGlobals.getPackageManager().setPackageStoppedState(
info.packageName, false, UserHandle.getUserId(app.uid));
} catch (RemoteException e) {
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Failed trying to unstop package "
+ info.packageName + ": " + e);
}
if ((info.flags&(ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT))
== (ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT)) {
app.persistent = true;
app.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
}
if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) {
mPersistentStartingProcesses.add(app);
startProcessLocked(app, "added application", app.processName, abiOverride,
null /* entryPoint */, null /* entryPointArgs */);
}
return app;
}
之后会调用startProcessLocked方法启动进程,启动进程的过程就比较复杂了,暂时不去分析了,具体调用流程如下:startProcessLocked->Process.start->startViaZygote->zygoteSendArgsAndGetResult,zygoteSendArgsAndGetResult函数最终实现的,是向socket服务端写书据,把创建进程的请求通过socket通讯方式让framework的进程孵化类zygote创建新进程。而数据就是argsForZygote(一个以字符串List形式的把Process.start()所有调用参数都包含在里面的变量),具体的启动过程可以参考:android进程创建分析
socket服务端收到创建新进程的请求,ZygoteConnection.runOnce()接收到新进程的参数,然后调用Zygote.forkAndSpecialize()来fork一个子进程,在子进程中会接着关闭socket,调用ZygoteInit.invokeStaticMain(cloader, className, mainArgs),即调用ActivityThread.main()。 新的应用进程会从ActivityThread 的 main()函数处开始执行。
ActivityThread,新的进程
首先来看入口,main函数:
1 | public static void main(String[] args) { |
我们看到main方法初始化了主线程的消息队列,实例化了一个ActivityThread对象,然后调用了它的attach方法:
private void attach(boolean system) {
sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
ViewRootImpl.addFirstDrawHandler(new Runnable() {
@Override
public void run() {
ensureJitEnabled();
}
});
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManagerNative.getDefault();
try {
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
// Ignore
}
// Watch for getting close to heap limit.
BinderInternal.addGcWatcher(new Runnable() {
@Override public void run() {
if (!mSomeActivitiesChanged) {
return;
}
Runtime runtime = Runtime.getRuntime();
long dalvikMax = runtime.maxMemory();
long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
if (dalvikUsed > ((3*dalvikMax)/4)) {
if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
+ " total=" + (runtime.totalMemory()/1024)
+ " used=" + (dalvikUsed/1024));
mSomeActivitiesChanged = false;
try {
mgr.releaseSomeActivities(mAppThread);
} catch (RemoteException e) {
}
}
}
});
} else {
// Don't set application object here -- if the system crashes,
// we can't display an alert, we just want to die die die.
android.ddm.DdmHandleAppName.setAppName("system_process",
UserHandle.myUserId());
try {
mInstrumentation = new Instrumentation();
ContextImpl context = ContextImpl.createAppContext(
this, getSystemContext().mPackageInfo);
mInitialApplication = context.mPackageInfo.makeApplication(true, null);
mInitialApplication.onCreate();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate Application():" + e.toString(), e);
}
}
// add dropbox logging to libcore
DropBox.setReporter(new DropBoxReporter());
ViewRootImpl.addConfigCallback(new ComponentCallbacks2() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
synchronized (mResourcesManager) {
// We need to apply this change to the resources
// immediately, because upon returning the view
// hierarchy will be informed about it.
if (mResourcesManager.applyConfigurationToResourcesLocked(newConfig, null)) {
// This actually changed the resources! Tell
// everyone about it.
if (mPendingConfiguration == null ||
mPendingConfiguration.isOtherSeqNewer(newConfig)) {
mPendingConfiguration = newConfig;
sendMessage(H.CONFIGURATION_CHANGED, newConfig);
}
}
}
}
@Override
public void onLowMemory() {
}
@Override
public void onTrimMemory(int level) {
}
});
}
我们看到由于是非系统初始化(不是系统启动时启动的进程),传入的参数为false,我们重点关注前面一半的逻辑。这里又出现了
1 | final IActivityManager mgr = ActivityManagerNative.getDefault(); |
有了之前的经验我们已经知道这是指向的ActivityManagerService,然后调用了
1 | mgr.attachApplication(mAppThread); |
调用ActivityManagerService.attachApplication
1 | public final void attachApplication(IApplicationThread thread) { |
接着走到attachApplicationLocked,这个方法比较长,为了节约篇幅,不贴源码了,会调用ActivityThread的bindApplication方法
1 | thread.bindApplication(processName, appInfo, providers, app.instrumentationClass, |
而bindApplication做的事情是数据绑定并发送消息(源码部分节选)
1 | AppBindData data = new AppBindData(); |
在handleMessage方法中可以看到接到Message后的处理
1 | case BIND_APPLICATION: |
handleBindApplication方法实在是太长了,我们就截取和Instrumentation相关的部分吧
1 | if (data.instrumentationName != null) { |
方法中首先初始化了mInstrumentation,此处Load的Class即am instrument命令传入的TestRunner
1 | java.lang.ClassLoader cl = instrContext.getClassLoader(); |
然后对mInstrumentation进行了初始化
1 | mInstrumentation.init(this, instrContext, appContext, |
调用mInstrumentation的onCreate方法进行执行(参考下文的InstrumentationTestRunner介绍)
1 | mInstrumentation.onCreate(data.instrumentationArgs); |
最后调用mInstrumentation的callApplicationOnCreate方法启动测试包的Application
1 | mInstrumentation.callApplicationOnCreate(app); |
至此从命令到启动测试的流程就分析完了,下面我们就将从Instrumentation类出发,看看我们常见的Instrumentation,InstrumentationTestRunner,ActivityInstrumentationTestCase2类都分别做了哪些事
Instrumentation类源码分析
弄清楚了Instrumentation的启动之后,我们来分析下Instrumentation这个类及其功能吧
Instrumentation 流程控制函数分析
在Instrumentation类中有几个关键的流程控制函数,我们先来分析下这些方法:
onCreate方法
在Instrumentation中是个空方法,之前我们有提到ActivityThread的handleBindApplication方法会调用InstrumentationTestRunner的onCreate方法来启动测试代码,InstrumentationTestRunner重写了这个类(下文分析InstrumentationTestRunner时会分析)
start方法
start方法会创建一个新的线程来执行instrumentation,通常由继承Instrumentation的类来调用该方法,InstrumentationTestRunner在onCreate方法的最后面调用了这个方法执行instrumentation,下面是Instrumentation中start方法的源码
1 | public void start() { |
其中mRunner是一个InstrumentationThread对象,是负责运行Instrumentation的线程对象,运行该方法会触发Instrumentation的onStart方法
1 | private final class InstrumentationThread extends Thread { |
onStart方法
同onCreate方法一样,在Instrumentation中是个空方法,通过重写该方法能够在测试使执行时产生一些阻塞性动作,Google的原文是这样的
1 | /** |
onException方法
onException方法会在系统catch到Exception时由ActivityThread调用,在Instrumentation中它仅仅返回false,通过重写该方法可以在需要时返回true,来自定义异常的处理,此时发生异常的被测工程会继续执行下去而忽略该异常的发生,转交给自定义实现处理。
sendStatus方法
sendStatus方法是在测试执行过程中状态改变时会调用的方法,Instrumentation中已定义了如下四种状态,用户也可以定义自己需要的状态并在合适的地方调用sendStatus方法发送自定义的状态
1 | /** |
finish方法
finish方法的调用会终止Instrumentation的执行,使被测应用退出,其源码如下:
1 | public void finish(int resultCode, Bundle results) { |
Instrumentation中的几个内部类
Instrumentation定义了几个内部类,为了能够更好的阅读后文,我们先来学习以下这些内部类及其作用
ActivityResult
定义了Activity向源Activity传递的执行结果,有两个成员变量,一个是
1 | int mResultCode; |
ActivityMonitor
ActivityMonitor是用来监视应用中单个活动的,它可以用来监视一些指定的Intent。创建好ActivityMonitor的实例后,通过调用Instrumentation.addMonitor函数来添加这个实例。当Activity启动后,系统会匹配Instrumentation中的ActivityMonitory实例列表,如果匹配,就会累加计数器。
ActivityMonitor同样可以被用于获取新创建的Activity,通过waitForActivity方法,可以返回一个符合IntentFilter的Activity对象
ActivityMonitor有6个成员变量
1 | private final IntentFilter mWhich; //IntentFilter,被监视的Acitivity的条件 |
ActivityMonitor的两种构造函数
使用IntentFilter做筛选条件
1 | public ActivityMonitor( |
使用Activity类名做筛选条件
1 | public ActivityMonitor( |
其他关键方法
match方法在Instrumentation启动Activity时会被调用,根据初始化ActivityMonitor时使用的是IntentFilter还是Activity类名有不同的处理,但都是用于检查被添加的ActivityMonitor对象是否和新启动的Activity匹配,如果不一致则返回false,比较一致就把新的Activity写入ActivityMonitor对象的最后匹配成功Activity属性中
1 | final boolean match(Context who, Activity activity, Intent intent) { |
waitForActivity方法是一个阻塞方法,会一直等待直至有启动的Acitivity成功匹配,否则会一直阻塞
1 | /** |
waitForActivityWithTimeout方法作用同waitForActivity一致,但是有一个timeout,超时后就不会继续阻塞Instrumentation的执行了。
1 | /** |
InstrumentationThread
前文已介绍,负责运行Instrumentation的线程对象
EmptyRunnable
一个run方法为空的Runnable
SyncRunnable
同步任务类,提供一个方法waitForComplete,使任务完成前会一直阻塞,在Instrumentation.runOnMainSync方法中会被使用到,在主线程做操作时会阻塞Instrumentation的执行
1 | ``` |
/**
Start a new activity and wait for it to begin running before returning.
In addition to being synchronous, this method as some semantic
differences from the standard {@link Context#startActivity} call: the
activity component is resolved before talking with the activity manager
(its class name is specified in the Intent that this method ultimately
starts), and it does not allow you to start activities that run in a
different process. In addition, if the given Intent resolves to
multiple activities, instead of displaying a dialog for the user to
select an activity, an exception will be thrown.
The function returns as soon as the activity goes idle following the
call to its {@link Activity#onCreate}. Generally this means it has gone
through the full initialization including {@link Activity#onResume} and
drawn and displayed its initial window.
@param intent Description of the activity to start.
@see Context#startActivity
*/
public Activity startActivitySync(Intent intent) {
validateNotAppThread();synchronized (mSync) {
intent = new Intent(intent);
ActivityInfo ai = intent.resolveActivityInfo(
getTargetContext().getPackageManager(), 0);
if (ai == null) {
throw new RuntimeException(“Unable to resolve activity for: “ + intent);
}
String myProc = mThread.getProcessName();
if (!ai.processName.equals(myProc)) {
// todo: if this intent is ambiguous, look here to see if
// there is a single match that is in our package.
throw new RuntimeException(“Intent in process “
+ myProc + “ resolved to different process “
+ ai.processName + “: “ + intent);
}
intent.setComponent(new ComponentName(
ai.applicationInfo.packageName, ai.name));
final ActivityWaiter aw = new ActivityWaiter(intent);
if (mWaitingActivities == null) {
mWaitingActivities = new ArrayList();
}
mWaitingActivities.add(aw);
getTargetContext().startActivity(intent);
do {
try {
mSync.wait();
} catch (InterruptedException e) {
}
} while (mWaitingActivities.contains(aw));
return aw.activity;
}
}
1 |
|
prePerformCreate(activity);
activity.performCreate(icicle);
postPerformCreate(activity);
1 |
|
private void prePerformCreate(Activity activity) {
if (mWaitingActivities != null) {
synchronized (mSync) {
final int N = mWaitingActivities.size();
for (int i=0; i<N; i++) {
final ActivityWaiter aw = mWaitingActivities.get(i);
final Intent intent = aw.intent;
if (intent.filterEquals(activity.getIntent())) {
aw.activity = activity;
mMessageQueue.addIdleHandler(new ActivityGoing(aw));
}
}
}
}
}
1 |
|
public Activity waitForMonitor(ActivityMonitor monitor) {
Activity activity = monitor.waitForActivity();
synchronized (mSync) {
mActivityMonitors.remove(monitor);
}
return activity;
}
1 |
|
mTestRunner.setTest(testSuiteBuilder.build());
1 |
|
private static final String LOG_TAG = “TestGrouping”;
SortedSet<Class<? extends TestCase>> testCaseClasses;
protected String firstIncludedPackage = null;
private ClassLoader classLoader;
1 |
|
private final TestGrouping testGrouping = new TestGrouping(SORT_BY_c_QUALIFIED_NAME);
1 |
|
public TestGrouping(Comparator<Class<? extends TestCase>> comparator) {
testCaseClasses = new TreeSet<Class<? extends TestCase>>(comparator);
}
1 |
|
public List
List
for (Class<? extends TestCase> testCase : testCaseClasses) {
for (Method testMethod : getTestMethods(testCase)) {
testMethods.add(new TestMethod(testMethod, testCase));
}
}
return testMethods;
}
1 |
|
protected List
List
return select(methods, new TestMethodPredicate());
}
1 |
|
private
ArrayList
for (T item : items) {
if (predicate.apply(item)) {
selectedItems.add(item);
}
}
return selectedItems;
}
1 |
|
public boolean apply(Method method) {
return ((method.getParameterTypes().length == 0) &&
(method.getName().startsWith(“test”)) &&
(method.getReturnType().getSimpleName().equals(“void”)));
}
1 |
|
public TestGrouping addPackagesRecursive(String… packageNames) {
for (String packageName : packageNames) {
List<Class<? extends TestCase>> addedClasses = testCaseClassesInPackage(packageName);
if (addedClasses.isEmpty()) {
Log.w(LOG_TAG, “Invalid Package: ‘“ + packageName
+ “‘ could not be found or has no tests”);
}
testCaseClasses.addAll(addedClasses);
if (firstIncludedPackage == null) {
firstIncludedPackage = packageName;
}
}
return this;
}
1 |
|
private List<Class extends TestCase>> selectTestClasses(Set
List<Class extends TestCase>> testClasses = new ArrayList
new TestCasePredicate())) {
testClasses.add((Class<? extends TestCase>) testClass);
}
return testClasses;
}
1 |
|
private
ArrayList
for (T item : items) {
if (predicate.apply(item)) {
selectedItems.add(item);
}
}
return selectedItems;
}
1 |
|
public TestGrouping removePackagesRecursive(String… packageNames) {
for (String packageName : packageNames) {
testCaseClasses.removeAll(testCaseClassesInPackage(packageName));
}
return this;
}
1 |
|
private Context context; //
private final TestGrouping testGrouping = new TestGrouping(SORT_BY_FULLY_QUALIFIED_NAME); //
private final Set<Predicate
private List
private TestSuite rootSuite; //
private TestSuite suiteForCurrentClass; //
private String currentClassname; //
private String suiteName; //Suite名称
1 |
|
TestSuiteBuilder testSuiteBuilder = new TestSuiteBuilder(getClass().getName(),
getTargetContext().getClassLoader());
1 |
|
public TestSuiteBuilder(String name, ClassLoader classLoader) {
this.suiteName = name;
this.testGrouping.setClassLoader(classLoader);
this.testCases = Lists.newArrayList();
addRequirements(REJECT_SUPPRESSED);
}
1 |
|
public TestSuiteBuilder addTestSuite(TestSuite testSuite) {
for (TestCase testCase : (List
this.testCases.add(testCase);
}
return this;
}
1 |
|
private TestResult mTestResult; //存储测试结果
private String mTestClassName; //当前测试的名字
private List
private Context mContext; //测试目标APK的Context
private boolean mSkipExecution = false; //当出现异常时是否终止执行
private List
private Instrumentation mInstrumentation; //Instrumentation对象
private PerformanceResultsWriter mPerfWriter; //PerformanceResultsWriter对象,性能相关
1 |
|
public void setTest(Test test) {
setTest(test, test.getClass());
}
1 |
|
private void setTest(Test test, Class<? extends Test> testClass) {
mTestCases = (List
if (TestSuite.class.isAssignableFrom(testClass)) {
mTestClassName = TestCaseUtil.getTestName(test);
} else {
mTestClassName = testClass.getSimpleName();
}
}
1 |
|
public void runTest() {
runTest(createTestResult());
}
1 |
|
private void setContextIfAndroidTestCase(Test test, Context context, Context testContext) {
if (AndroidTestCase.class.isAssignableFrom(test.getClass())) {
((AndroidTestCase) test).setContext(context);
((AndroidTestCase) test).setTestContext(testContext);
}
}
1 |
|
private void setInstrumentationIfInstrumentationTestCase(
Test test, Instrumentation instrumentation) {
if (InstrumentationTestCase.class.isAssignableFrom(test.getClass())) {
((InstrumentationTestCase) test).injectInstrumentation(instrumentation);
}
}
1 |
|
private void setPerformanceWriterIfPerformanceCollectorTestCase(
Test test, PerformanceResultsWriter writer) {
if (PerformanceCollectorTestCase.class.isAssignableFrom(test.getClass())) {
((PerformanceCollectorTestCase) test).setPerformanceResultsWriter(writer);
}
}
1 |
|
public void run(TestResult result) {
result.run(this);
}
1 |
|
protected TestResult createTestResult() {
if (mSkipExecution) {
return new NoExecTestResult();
}
return new TestResult();
}
1 |
|
protected void run(final TestCase test) {
startTest(test);
Protectable p= new Protectable() {
public void protect() throws Throwable {
test.runBare();
}
};
runProtected(test, p);
endTest(test);
}
1 |
|
public void startTest(Test test) {
final int count= test.countTestCases();
synchronized(this) {
fRunTests+= count;
}
for (TestListener each : cloneListeners())
each.startTest(test);
}
1 |
|
public void runProtected(final Test test, Protectable p) {
try {
p.protect();
}
catch (AssertionFailedError e) {
addFailure(test, e);
}
catch (ThreadDeath e) { // don’t catch ThreadDeath by accident
throw e;
}
catch (Throwable e) {
addError(test, e);
}
}
1 |
|
public synchronized void addFailure(Test test, AssertionFailedError t) {
fFailures.add(new TestFailure(test, t));
for (TestListener each : cloneListeners())
each.addFailure(test, t);
}
1 |
|
public synchronized void addError(Test test, Throwable t) {
fErrors.add(new TestFailure(test, t));
for (TestListener each : cloneListeners())
each.addError(test, t);
}
1 |
|
public void runBare() throws Throwable {
Throwable exception= null;
setUp();
try {
runTest();
} catch (Throwable running) {
exception= running;
}
finally {
try {
tearDown();
} catch (Throwable tearingDown) {
if (exception == null) exception= tearingDown;
}
}
if (exception != null) throw exception;
}
1 |
|
1 |
|
public void endTest(Test test) {
for (TestListener each : cloneListeners())
each.endTest(test);
}
### NoExecTestResult类
NoExecTestResult继承自TestResult类,区别是重写了run方法,并不会执行Test,仅仅检查startTest和endTest能否正常通过
protected void run(final TestCase test) {
startTest(test);
endTest(test);
}