源代码
之前其实我已经写过了Espresso2.0使用AS和Gradle执行用例的Sample,详细可以见Testerhome之前的帖子。其实接着我还是想看看Espresso的实现。所以就有了这篇文章,但是代码量太大,也来不及看,所以其实想一边写这篇文章一边看的。嗯。
下载之后导入即可看到下面的图示,证明你已经拿到了游戏中的第一个item。
慢慢看
接着我们先来看testrunner,单纯从名字来看其实感觉是不是Espresso自己的runner呢,现在还不知道,我们打开所有的目录大概浏览下。如下
这个时候我们眼前一亮,看到了一个熟悉的名字GoogleInstrumentation,点击进去一看
public class
GoogleInstrumentation
extends ExposedInstrumentationApi
那么好吧,我表示不明白extends这个东西有啥用,那么继续去看了ExposedInstrumentationApi。整个代码还是很简单的。
package com.google.android.apps.common.testing.testrunner;
import android.app.Activity;
import android.app.Fragment;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
/**
* Exposes select hidden android apis to the compiler.
* These methods are stripped from the android.jar sdk compile time jar, however
* are called at runtime (and exist in the android.jar on the device).
*
* This class is built with neverlink=1 to ensure it is never actually included in
* our apk.
*/
public abstract class ExposedInstrumentationApi extends Instrumentation {
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
throw new RuntimeException();
}
public void execStartActivities(Context who, IBinder contextThread,
IBinder token, Activity target, Intent[] intents, Bundle options) {
throw new RuntimeException();
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Fragment target,
Intent intent, int requestCode, Bundle options) {
throw new RuntimeException();
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode) {
throw new RuntimeException();
}
}
那么这里我们看到了其实应该使用了Instrumentation下面的方法,所以继续调查了execStartActivity这个方法,发现该方法其实在MonitoringInstrumentation下面,这个类我们可以认为是Instrumentation的一个扩展,那么在下面我们找到了这里使用的几个方法。
但是在Android文档中并没有对该方法有什么描述,我表示就很焦虑了。接着我继续Google了这个方法到底要干嘛。突然发现了如下的知识,嗯
以下是StartActivity方法的将Activity调起来的部分过程
从Activity类的startActivity()方法开始,这个方法会调用Activity类中的public void startActivityForResult()方法 startActivityForResult()方法会调用Instrumentation类中的public ActivityResult execStartActivity()方法,这个方法加上了{@hide}对外是不可见的
这里眼前又一亮,因为我们看到了在启动Activity的过程中第二步就会去调用这个execStartActivity方法,而且这个方法本身是hide的。那么到这里,我们终于明白了,为什么ExposedInstrumentationApi文件夹目录叫做hidden了。然后我们也理解了源码中的第一句话。
- Exposes select hidden android apis to the compiler. 由于该方法本身是隐藏的,所以我们需要暴露出来以之后被使用。
到这里,我们差不多知道ExposedInstrumentationApi是干什么的了,那么接着我们继续来看GoogleInstrumentation。
继续一步一步
Instrumentation方法下原本就有onCreate方法,而在这里Espresso重写了该方法。如下
@Override
public void onCreate(Bundle arguments) {
Log.i(LOG_TAG, "Instrumentation Started!");
tryLoadingIntentSpy();
InstrumentationRegistry.registerInstance(this);
ActivityLifecycleMonitorRegistry.registerInstance(lifecycleMonitor);
handlerForMainLooper = new Handler(Looper.getMainLooper());
mainThread = Thread.currentThread();
executorService = Executors.newCachedThreadPool();
Looper.myQueue().addIdleHandler(idleHandler);
super.onCreate(arguments);
}
这个方法开始我看着还是觉得很奥妙的。比如这里的tryLoadingIntentSpy();,当然这个方法还是很重的,我们继续来看实现
private void tryLoadingIntentSpy() {
// Wouldn't it be easier to call IntentSpyImpl.getInstance() and be done with it? We don't do
// this to avoid bringing in common lib dependencies (which cause painful conflicts with some
// android projects) into G3ITR. Instead, we try to load IntentSpyImpl via reflection. Projects
// that don't have intento in deps will not have IntentSpyImpl included at runtime (i.e. we
// proceed as normally) and leave intentSpy as null (to be checked later). However, if it is
// loaded, we can call any method of IntentSpy.
try {
Class> c = Class.forName("com.google.android.apps.common.testing.intento.IntentSpyImpl");
intentSpy = (IntentSpy) c.getMethod(
"load", Context.class, Context.class).invoke(null, getTargetContext(), getContext());
Log.i(LOG_TAG, "IntentSpyImpl loaded");
} catch (ClassNotFoundException cnfe) {
Log.i(LOG_TAG, "IntentSpyImpl not loaded: " + cnfe.getMessage());
} catch (NoSuchMethodException nsme) {
throw new RuntimeException(
"IntentSpyImpl is available at runtime, but getInstance method was not found", nsme);
} catch (SecurityException se) {
throw new RuntimeException(
"IntentSpyImpl is available at runtime, but calling it failed.", se);
} catch (IllegalAccessException iae) {
throw new RuntimeException(
"IntentSpyImpl is available at runtime, but calling it failed.", iae);
} catch (IllegalArgumentException iare) {
throw new RuntimeException(
"IntentSpyImpl is available at runtime, but calling it failed.", iare);
} catch (InvocationTargetException ite) {
throw new RuntimeException(
"IntentSpyImpl is available at runtime, but calling it failed.", ite);
}
}
其实注释已经写的很清楚了,我不知道我理解的对不对。从实现上其实就是为了避免各种错误所以使用了反射的机制来获取这个运行的Intent,拿到这个intentSpy之后,我们就可以使用下面的方法了,不过稍等,这个intentSpy又是个什么鬼。我们看上面的目录结构,其实可以看到有两个interface。
其实简单看了下这个interface,就是实现了若干方法,也是为了拿到Activity的一些东西,先不管了。继续往下看。
InstrumentationRegistry.registerInstance(this);
ActivityLifecycleMonitorRegistry.registerInstance(lifecycleMonitor);
这两个其实查了下Android文档,其实就是两个监听的方法。描述类似
Records/exposes the instrumentation currently running and stores a copy of the instrumentation arguments Bundle in the registry.
那么在onCreate中调用也是可以理解,肯定后续会使用到。