在《Appium基础学习之 | UiAutomator2.0使用》最后留下了三个问题
1.什么叫做运行器,比如AndroidJUnitRunner、instrumentationTestRunner?
2.UiAutomator2.0基于Instrumentation运行,好像非常复杂,这样做相对于UiAutomator1.0来说有什么优势呢?
3.adb shell am instrument命令的解析
从《Android基础知识学习-Instrumentation启动源码简析》中分析来看,在通过命令adb shell am instrument指定运行器时会取代默认Instrumentation来执行测试,从源码分析来看,默认系统的Instrumentation类中onCreate是一个空方法,而继承Instrumentation的AndroidJUnitRunner类重写了onCreate对将测试的数据做了一些处理,有兴趣的可以继续往下研究下源码看看AndroidJUnitRunner是如何运行并处理数据的。
一、AndroidJUnitRunner、instrumentationTestRunner
Instrumentation这个测试框架的运行器AndroidJUnitRunner与instrumentationTestRunner在执行测试是基于Junit的(注意是执行测试的类继承Junit的TestCase类,而不是说Instrumentation继承Junit)。
1.AndroidJUnitRunner与instrumentationTestRunner的区别
(1)instrumentationTestRunner运行器是一个旧的运行器,它只支持Junit3,所以如果使用这个运行器,测试方法必须以test开头。
(2)AndroidJUnitRunner运行器是Google推荐使用的,它的出现就是替换instrumentationTestRunner的,它支持所有instrumentationTestRunner的特性以及命令格式,并且有一些新的扩展。
二、Instrumentation
在《Android基础知识学习-Instrumentation启动源码简析》大致简述了Instrumentation的创建过程,Application对象是通过Instrumentation调用callApplicationOnCreate来完成Application的启动;而另外很重要的Activity的onCreate在文中并没有进行源码分析,但也有提到过,在整个Application初始化完成后,会回到ActivityManagerService的attachApplicationLocked方法中通过调用ActivityStackSupervisor的attachApplicationLocked()方法,最后是通过Instrumentation的newActivity、callActivityOnCreate初始化启动主Activity的(这个过程的源码分析就省略了)。然后会调用Activity的attach方法,这个调用是在ActivityThead的performLaunchActivity方法中执行的。
public Instrumentation getInstrumentation()
{
return mInstrumentation;
}private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.voiceInteractor);
}
调用这个attach入参的参数中关注getInstrumentation()这个方法,它返回的是mInstrumentation,这个值是在调用ActivityThread的handleBindApplication中初始化的。然后来到Activity的attach方法
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
private static final String TAG = "Activity";
private Instrumentation mInstrumentation;final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, IVoiceInteractor voiceInteractor) {......
mInstrumentation = instr;
}}
在Activity类中同样有一个成员变量mInstrumentation,在调用attach后把ActivityThread的mInstrumentation赋值给了Activity的mInstrumentation,后续每一个Activity在启动时经过attach()方法,都会把该mInstrumentation传入Activity赋给Activity.mInstrumentation, 也就是同一个应用进程内,所有Activity共用同一个Instrumentation。
三、UiAutomator2.0基于Instrumentation优缺点
在android应用UI层的自动化中使用Instrumentation调用Android接口还是非常少见的,使用Instrumentation比较常见的是在Android单元测试。但这并不是说基于Instrumentation就没有任何好处,只要能得到Instrumentation实例,它就可以游刃有余的拥抱应用本身,与Android应用实现无缝接触,就可以使用Android服务及接口,比如得到Context,控制Activity的生命周期、按键操作等等。在这么多好处的情况下,它的缺点很现实,就是不接受跨进程,前面说Instrumentation的时候也应该明白,在整个进程中从ActivityThread实例化Instrumentation后,无论是在Activity还是其他任何地方,都是唯一的Instrumentation。而如果是其他应用进程中,它又有另一个归属于该进程的Instrumentation实例。而UiAutomator2.0除了基于Instrumentation运行外,对于元素的查找与操作,依然支持Accessibility服务,这样就可以完美的弥补Instrumentation不支持跨进程操作的缺陷了。
另外要提的一点就是使用Instrumentation执行,会把测试代码APK与被测应用APK都推送到设备中运行,这时候测试代码的APK与被测应用APK处于同一进程中,这是基于Instrumentation运行测试的特点,所以它才能完成对被测应用的操作。如果被测应用并不是与UiAutomator2.0测试代码APK不在同一进程,也完全可以使用,因为它支持Accessibility服务对整个android系统事件的监控处理。
四、解决疑惑
在《Android基础知识学习-Instrumentation启动源码简析》的最后得出的结论
用adb shell am instrument命令执行测试,mInstrumentation这个实例指向的并不是默认的Instrumentation而是命令中指定的AndroidJUnitRunner,所以后面调用的是AndroidJUnitRunner的onCreate方法开始执行测试
为了证明这个是对的,上代码
1.被测应用
在Android Studio中新建项目,这个项目需要创建一个Activity,如MainActivity,代码如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Field mInstrumentation;
Object value = null;
try {
Class<?> activityThread = Class.forName("android.app.ActivityThread");
Method sCurrentActivityThread = activityThread.getMethod("currentActivityThread");
//获取ActivityThread 对象
Object activityThreadObject = sCurrentActivityThread.invoke(activityThread, new Object[0]);mInstrumentation = activityThread.getDeclaredField("mInstrumentation");
mInstrumentation.setAccessible(true);
value = mInstrumentation.get(activityThreadObject);
}catch (Exception e){
e.printStackTrace();
}
Log.d("debug", "Instrumentation: "+value.getClass().getName());
}
}
通过反射得到ActivityThread对象,然后得到它的成员变量mInstrumentation,最终log打印出mInstrumentation对象的类名。这个思路应该是很清晰的,因为进程中唯一的Instrumentation就是在ActivityThread中初始化的,所以反射得到ActivityThread对象。
2.测试应用
在项目androidTest目录下会默认创建一个测试类ExampleInstrumentedTest
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
private Context ct;
@Before
public void init(){
ct = InstrumentationRegistry.getContext();}
@Test
public void useAppContext() {
Intent testIntent = ct.getPackageManager().getLaunchIntentForPackage("ymxh.main");
ct.startActivity(testIntent);
}
通过InstrumentationRegistry得到测试应用的Context,然后启动App默认的Activity。
3.验证
(1)手动运行
把应用打包成APK安装在手机设备中,使用adb与手机设备连接上,然后手动点击运行,在Android Studio的Logcat日志输出窗口中会打印
debug: Instrumentation: android.app.Instrumentation
(2)通过adb shell am instrument命令运行
这里就是运行上面的ExampleInstrumentedTest类,日志打印
debug: Instrumentation: android.support.test.runner.AndroidJUnitRunner
最终验证可以看到,按正常流程使用app启动应用,系统初始化的是默认的Instrumentation;而通过命令执行启动应用,初始化的是指定的运行器。
很多文中说到UiAutomator1.0与UiAutomator2.0的区别都提到说UiAutomator2.0是android应用,需要打包成APK在设备运行;个人觉得这样的说法都是在说它基于Instrumentation的特点,而并不是说UiAutomator2.0本身。