问题
AndroidJUnitRunner 原理
编译产物 (自动化测试生成的2个apk对比)
启动方式 (主要研究AndroidJUnitRunner如何被调起)
运行方式 (研究 espresso和 uiautomator原理,如何模拟操作)
espresso和uiautomator是否可以打包到主apk
编译产物 执行自动化测试会编译出两个apk,我看到之后是很懵逼的,心里会涌现出了无数个🦙,这么多年竟然忽略了这个细节。
1 2 3 4 5 6 7 8 dependencies { implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' }
1.app-debug.apk
1.1 Manifest.xml
1 2 3 4 5 6 7 8 9 10 11 <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" android:compileSdkVersion="30" android:compileSdkVersionCodename="11" package="com.demo.sample" platformBuildVersionCode="30" platformBuildVersionName="11"> ... </manifest>
package为com.demo.sample
1.2 依赖
正常的依赖了gradle中配置的依赖
2.app-debug-androidTest.apk
2.1Manifest.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:compileSdkVersion="30" android:compileSdkVersionCodename="11" package="com.demo.sample.test" platformBuildVersionCode="30" platformBuildVersionName="11"> <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="30" /> <instrumentation android:label="Tests for com.demo.sample" android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.demo.sample" android:handleProfiling="false" android:functionalTest="false" /> ... </manifest>
这里有个细节,manifest中package是com.demo.sample.test,和实际apk相比多了.test
2.2 依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 debugAndroidTestRuntimeClasspath - Resolved configuration for runtime for variant: debugAndroidTest +--- androidx.test.ext:junit:1.1.3 | +--- junit:junit:4.12 | | \--- org.hamcrest:hamcrest-core:1.3 | +--- androidx.test:core:1.4.0 | | +--- androidx.annotation:annotation:1.0.0 -> 1.2.0 | | +--- androidx.test:monitor:1.4.0 | | | \--- androidx.annotation:annotation:1.0.0 -> 1.2.0 | | \--- androidx.lifecycle:lifecycle-common:2.0.0 -> 2.3.1 | | \--- androidx.annotation:annotation:1.1.0 -> 1.2.0 | +--- androidx.test:monitor:1.4.0 (*) | \--- androidx.annotation:annotation:1.0.0 -> 1.2.0 +--- androidx.test.espresso:espresso-core:3.4.0 | +--- androidx.test:runner:1.4.0 | | +--- androidx.annotation:annotation:1.0.0 -> 1.2.0 | | +--- androidx.test:monitor:1.4.0 (*) | | +--- androidx.test.services:storage:1.4.0 | | | +--- androidx.test:monitor:1.4.0 (*) | | | \--- com.google.code.findbugs:jsr305:2.0.1 | | \--- junit:junit:4.12 (*) | +--- androidx.test.espresso:espresso-idling-resource:3.4.0 | +--- com.squareup:javawriter:2.1.1 | +--- javax.inject:javax.inject:1 | +--- org.hamcrest:hamcrest-library:1.3 | | \--- org.hamcrest:hamcrest-core:1.3 | +--- org.hamcrest:hamcrest-integration:1.3 | | \--- org.hamcrest:hamcrest-library:1.3 (*) | \--- com.google.code.findbugs:jsr305:2.0.1 +--- androidx.annotation:annotation:{strictly 1.2.0} -> 1.2.0 (c) //这可真是一万个🦙呀,根本不知道为啥被引入的, \--- androidx.lifecycle:lifecycle-common:{strictly 2.3.1} -> 2.3.1 (c) // 只能看源码,这个任务留给以后吧
综上,不同的地方基本只有Manifest中的不同,其他资源正常。
启动方式 正确流程:
1 2 3 4 5 6 7 8 am可执行文件 -> app_process可执行文件 -> AM.java -> Instrument.java -> AMS -> Process -> ActivityThread.java -> Instrumentation.java
一般am 直接调用AMS服务,但是am instrument 时为特殊场景
1 adb shell am instrument -w -m -e debug false -e class 'com.demo.sample.ExampleInstrumentedTest' com.demo.sample.test/androidx.test.runner.AndroidJUnitRunner
根据aosp经验,adb shell am 会调用AMS中的onShellCommand方法
ActivityManagerService.java
1 2 3 4 5 6 7 8 9 10 public class ActivityManagerService extends IActivityManager.Stub implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback { @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { (new ActivityManagerShellCommand(this, false)).exec( this, in, out, err, args, callback, resultReceiver); } }
ActivityManagerShellCommand.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 final class ActivityManagerShellCommand extends ShellCommand { @Override public int onCommand(String cmd) { if (cmd == null) { return handleDefaultCommands(cmd); } final PrintWriter pw = getOutPrintWriter(); try { switch (cmd) { ... case "instrument": getOutPrintWriter().println("Error: must be invoked through 'am instrument'."); return -1; ... default: return handleDefaultCommands(cmd); } } catch (RemoteException e) { pw.println("Remote exception: " + e); } return -1; } }
又一个🦙出现在我的眼前,这儿直接是打印一个日志,那肯定不是走这里了,这样的话只能从cmd 去跟代码了。
上面的命令可以列出手机支持的服务,可惜列出来的服务没有am,现在我们应该去手机/system/bin目录下去找am
am就是一个shell脚本
1 2 3 4 5 6 7 8 9 # !/system/bin/sh if [ "$1" != "instrument" ] ; then cmd activity "$@" else //我们关注点是else这个分支 base=/system export CLASSPATH=$base/framework/am.jar exec app_process $base/bin com.android.commands.am.Am "$@" fi
这段代码不难理解, args = [am instrument ],如果$1是instrument,则走else,这里直接执行了/system/bin/app_process,这里我们直接看com.android.commands.am.Am代码吧,这里给出com.android.commands.am.Am文件吧
com.android.commands.am.Am.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public class Am extends BaseCommand { public static void main (String[] args) { (new Am()).run(args); } @Override public void onRun () throws Exception { mAm = ActivityManager.getService(); if (mAm == null ) { System.err.println(NO_SYSTEM_ERROR_CODE); throw new AndroidException("Can't connect to activity manager; is the system running?" ); } mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package" )); if (mPm == null ) { System.err.println(NO_SYSTEM_ERROR_CODE); throw new AndroidException("Can't connect to package manager; is the system running?" ); } String op = nextArgRequired(); if (op.equals("instrument" )) { runInstrument(); } else { runAmCmd(getRawArgs()); } } public void runInstrument () throws Exception { Instrument instrument = new Instrument(mAm, mPm); String opt; while ((opt=nextOption()) != null ) { if (opt.equals("-w" )) { instrument.wait = true ; } else if (opt.equals("-m" )) { instrument.protoStd = true ; } else if (opt.equals("-e" )) { final String argKey = nextArgRequired(); final String argValue = nextArgRequired(); instrument.args.putString(argKey, argValue); } } if (instrument.userId == UserHandle.USER_ALL) { System.err.println("Error: Can't start instrumentation with user 'all'" ); return ; } instrument.componentNameArg = nextArgRequired(); instrument.run(); } }
Instrument.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class Instrument { public void run () throws Exception { StatusReporter reporter = null ; float [] oldAnims = null ; try { InstrumentationWatcher watcher = null ; UiAutomationConnection connection = null ; if (reporter != null ) { watcher = new InstrumentationWatcher(reporter); connection = new UiAutomationConnection(); } final ComponentName cn = parseComponentName(componentNameArg); if (!mAm.startInstrumentation(cn, profileFile, flags, args, watcher, connection, userId, abi)) { throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString()); } } catch (Exception ex) { } finally { } } }
下一步应该在AMS中找 startInstrumentation方法,有个自己编译的aosp手机是如此的重要,下面截图是AMS调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 ublic class ActivityManagerService extends IActivityManager .Stub implements Watchdog .Monitor , BatteryStatsImpl .BatteryCallback { public boolean startInstrumentation (ComponentName className, String profileFile, int flags, Bundle arguments, IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection, int userId, String abiOverride) { synchronized (this ) { ActiveInstrumentation activeInstr = new ActiveInstrumentation(this ); activeInstr.mClass = className; String defProcess = ai.processName;; if (ii.targetProcesses == null ) { activeInstr.mTargetProcesses = new String[]{ai.processName}; } else if (ii.targetProcesses.equals("*" )) { activeInstr.mTargetProcesses = new String[0 ]; } else { activeInstr.mTargetProcesses = ii.targetProcesses.split("," ); defProcess = activeInstr.mTargetProcesses[0 ]; } activeInstr.mTargetInfo = ai; activeInstr.mProfileFile = profileFile; activeInstr.mArguments = arguments; activeInstr.mWatcher = watcher; activeInstr.mUiAutomationConnection = uiAutomationConnection; activeInstr.mResultClass = className; activeInstr.mHasBackgroundActivityStartsPermission = checkPermission( START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; boolean disableHiddenApiChecks = ai.usesNonSdkApi() || (flags & INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS) != 0 ; if (disableHiddenApiChecks) { enforceCallingPermission(android.Manifest.permission.DISABLE_HIDDEN_API_CHECKS, "disable hidden API checks" ); } final boolean mountExtStorageFull = isCallerShell() && (flags & INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL) != 0 ; final long origId = Binder.clearCallingIdentity(); forceStopPackageLocked(ii.targetPackage, -1 , true , false , true , true , false , userId, "start instr" ); if (mUsageStatsService != null ) { mUsageStatsService.reportEvent(ii.targetPackage, userId, UsageEvents.Event.SYSTEM_INTERACTION); } ProcessRecord app = addAppLocked(ai, defProcess, false , disableHiddenApiChecks, mountExtStorageFull, abiOverride); app.setActiveInstrumentation(activeInstr); activeInstr.mFinished = false ; activeInstr.mRunningProcesses.add(app); if (!mActiveInstrumentation.contains(activeInstr)) { mActiveInstrumentation.add(activeInstr); } Binder.restoreCallingIdentity(origId); } return true ; } }
这里的链路比较长,我这里直接放出代码,有兴趣可以自己跟一下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public final class ActivityThread extends ClientTransactionHandler { private void handleBindApplication (AppBindData data) { try { final ClassLoader cl = instrContext.getClassLoader(); mInstrumentation = (Instrumentation) cl.loadClass(data.instrumentationName.getClassName()).newInstance(); } catch (Exception e) { throw new RuntimeException( "Unable to instantiate instrumentation " + data.instrumentationName + ": " + e.toString(), e); } final ComponentName component = new ComponentName(ii.packageName, ii.name); mInstrumentation.init(this , instrContext, appContext, component, data.instrumentationWatcher, data.instrumentationUiAutomationConnection); } }
启动的是app-debug.apk,为什么可以加载app-debug-androidTest.apk中的AndroidJUnitRunner ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 public final class ActivityThread extends ClientTransactionHandler { private void handleBindApplication (AppBindData data) { final InstrumentationInfo ii; if (data.instrumentationName != null ) { try { ii = new ApplicationPackageManager(null , getPackageManager()) .getInstrumentationInfo(data.instrumentationName, 0 ); } catch (PackageManager.NameNotFoundException e) { throw new RuntimeException( "Unable to find instrumentation info for: " + data.instrumentationName); } if (!Objects.equals(data.appInfo.primaryCpuAbi, ii.primaryCpuAbi) || !Objects.equals(data.appInfo.secondaryCpuAbi, ii.secondaryCpuAbi)) { Slog.w(TAG, "Package uses different ABI(s) than its instrumentation: " + "package[" + data.appInfo.packageName + "]: " + data.appInfo.primaryCpuAbi + ", " + data.appInfo.secondaryCpuAbi + " instrumentation[" + ii.packageName + "]: " + ii.primaryCpuAbi + ", " + ii.secondaryCpuAbi); } mInstrumentationPackageName = ii.packageName; mInstrumentationAppDir = ii.sourceDir; mInstrumentationSplitAppDirs = ii.splitSourceDirs; mInstrumentationLibDir = getInstrumentationLibrary(data.appInfo, ii); mInstrumentedAppDir = data.info.getAppDir(); mInstrumentedSplitAppDirs = data.info.getSplitAppDirs(); mInstrumentedLibDir = data.info.getLibDir(); } else { ii = null ; } if (ii != null ) { ApplicationInfo instrApp; try { instrApp = getPackageManager().getApplicationInfo(ii.packageName, 0 , UserHandle.myUserId()); } catch (RemoteException e) { instrApp = null ; } if (instrApp == null ) { instrApp = new ApplicationInfo(); } ii.copyTo(instrApp); instrApp.initForUser(UserHandle.myUserId()); final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo, appContext.getClassLoader(), false , true , false ); final ContextImpl instrContext = ContextImpl.createAppContext(this , pi, appContext.getOpPackageName()); try { final ClassLoader cl = instrContext.getClassLoader(); mInstrumentation = (Instrumentation) cl.loadClass(data.instrumentationName.getClassName()).newInstance(); } catch (Exception e) { throw new RuntimeException( "Unable to instantiate instrumentation " + data.instrumentationName + ": " + e.toString(), e); } final ComponentName component = new ComponentName(ii.packageName, ii.name); mInstrumentation.init(this , instrContext, appContext, component, data.instrumentationWatcher, data.instrumentationUiAutomationConnection); } }
总结为一句话,LoadedApk会帮我们把app-debug-androidTest.apk进行装载。很多插件化的原理也是如此,我知道的ACDD就是这样。
紧接着AndroidJUnitRunner就被启动了
运行方式 AndroidJUnitRunner的运行这里不讲,主要看uiautomator的运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class UiDevice implements Searchable { public UiObject2 findObject (BySelector selector) { AccessibilityNodeInfo node = ByMatcher.findMatch(this , selector, getWindowRoots()); return node != null ? new UiObject2(this , selector, node) : null ; } AccessibilityNodeInfo[] getWindowRoots() { waitForIdle(); Set<AccessibilityNodeInfo> roots = new HashSet(); AccessibilityNodeInfo activeRoot = getUiAutomation().getRootInActiveWindow(); if (activeRoot != null ) { roots.add(activeRoot); } if (UiDevice.API_LEVEL_ACTUAL >= Build.VERSION_CODES.LOLLIPOP) { for (AccessibilityWindowInfo window : getUiAutomation().getWindows()) { AccessibilityNodeInfo root = window.getRoot(); if (root == null ) { Log.w(LOG_TAG, String.format("Skipping null root node for window: %s" , window.toString())); continue ; } roots.add(root); } } return roots.toArray(new AccessibilityNodeInfo[roots.size()]); } }
很重要的方法:getWindowRoots 调用了UiAutomation的getRootInActiveWindow方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public final class UiAutomation { public AccessibilityNodeInfo getRootInActiveWindow () { final int connectionId; synchronized (mLock) { throwIfNotConnectedLocked(); connectionId = mConnectionId; } return AccessibilityInteractionClient.getInstance() .getRootInActiveWindow(connectionId); } public void connect (int flags) { synchronized (mLock) { mClient = new IAccessibilityServiceClientImpl(mRemoteCallbackThread.getLooper()); } try { mUiAutomationConnection.connect(mClient, flags); mFlags = flags; } catch (RemoteException re) { throw new RuntimeException("Error while connecting " + this , re); } } }
UiAutomationConnection binder Proxy 帮助链接ACC
IAccessibilityServiceClientImpl binder Stub 接受acc的回调
Instrumentation 你没有看错 ActivityThread里面的也是它,自动化测试中AndroidJUnitRunner继承它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class Instrumentation { final void init (ActivityThread thread, Context instrContext, Context appContext, ComponentName component, IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection) { mThread = thread; mMessageQueue = mThread.getLooper().myQueue(); mInstrContext = instrContext; mAppContext = appContext; mComponent = component; mWatcher = watcher; mUiAutomationConnection = uiAutomationConnection; } final void basicInit (ActivityThread thread) { mThread = thread; } public UiAutomation getUiAutomation (@UiAutomationFlags int flags) { boolean mustCreateNewAutomation = (mUiAutomation == null ) || (mUiAutomation.isDestroyed()); if (mUiAutomationConnection != null ) { if (!mustCreateNewAutomation && (mUiAutomation.getFlags() == flags)) { return mUiAutomation; } if (mustCreateNewAutomation) { mUiAutomation = new UiAutomation(getTargetContext().getMainLooper(), mUiAutomationConnection); } else { mUiAutomation.disconnect(); } mUiAutomation.connect(flags); return mUiAutomation; } return null ; } }
espresso和uiautomator是否可以打包到主apk 根据上面启动方式,最终是启动了1个apk,通过LoadedApk装载了另一个apk,所以无论espresso和uiautomator在哪个apk中,都不会影响自动化测试运行。
利用Intrumentation是否可以搞黑科技 是否可以通过一个apk去运行另一个apk的instrument ,然后运行自动化测试的模拟操作?
第一个apk中手动注册一个 MyInstrumentation
1 2 3 4 5 6 <instrumentation android:label="Tests for com.demo.sample" android:name=".MyInstrumentation" android:targetPackage="com.demo.sample" android:handleProfiling="false" android:functionalTest="false" />
第二个apk中使用Runtime.getRuntime().exec()
1 am instrument -w -m -e debug false com.demo.sample/com.demo.sample.MyInstrumentation
运行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Permission Denial: startInstrumentation asks to run as user -2 but is calling from uid u0a164; this requires android.permission.INTERACT_ACROSS_USERS_FULL at android.os.Parcel.createException(Parcel.java:2071) at android.os.Parcel.readException(Parcel.java:2039) at android.os.Parcel.readException(Parcel.java:1987) at android.app.IActivityManager$Stub$Proxy.startInstrumentation(IActivityManager.java:5443) at com.android.commands.am.Instrument.run(Instrument.java:512) at com.android.commands.am.Am.runInstrument(Am.java:196) at com.android.commands.am.Am.onRun(Am.java:80) at com.android.internal.os.BaseCommand.run(BaseCommand.java:56) at com.android.commands.am.Am.main(Am.java:50) at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method) at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:338) Caused by: android.os.RemoteException: Remote stack trace: at com.android.server.am.UserController.handleIncomingUser(UserController.java:1683) at com.android.server.am.ActivityManagerService.startInstrumentation(ActivityManagerService.java:15696) at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:2350) at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2738) at android.os.Binder.execTransactInternal(Binder.java:1021)
通过查看代码 userid可以外部传入
1 2 3 if (opt.equals("--user")) { instrument.userId = parseUserArg(nextArgRequired()); }
1 2 3 am instrument -w -m --user 0 -e debug false com.demo.sample/com.demo.sample.MyInstrumentation //或者 app_process -Djava.class.path=/system/framework/am.jar /data/local/tmp com.android.commands.am.Am instrument -w -m --user 0 -e debug false com.demo.sample/com.demo.sample.MyInstrumentation
新的问题又来了
1 2 3 4 5 6 7 8 9 10 java.lang.RuntimeException: Exception thrown in onCreate() of ComponentInfo{com.demo.sample/com.demo.sample.MyInstrumentation}: java.lang.SecurityException: You do not have android.permission.RETRIEVE_WINDOW_CONTENT required to call registerUiTestAutomationService from pid=14037, uid=10164 at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6457) at android.app.ActivityThread.access$1300(ActivityThread.java:219) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1859) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7356) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
缺少 android.permission.RETRIEVE_WINDOW_CONTENT权限,但是这个权限是系统应用的,暂时没有想到如果规避这个权限,我这边尝试了很多种方式都绕不过去。
这里解释下为什么必须要用Uiautomator,因为正常的Acc辅助功能需要授权,而Uiautomator中的通过adb shell启动的自动化测试无需授权。
假如一个apk中可以调起另一个apk中的Uiautomator,我们可以想象一个场景,支付宝中有一个instrumentation,里面可以使用uiautomator,高德app中可以调起这个instrumentation,就可以实现模拟用户的行为了。
异常分析:registerUiTestAutomationService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public final class UiAutomationConnection extends IUiAutomationConnection .Stub { @Override public void connect (IAccessibilityServiceClient client, int flags) { if (client == null ) { throw new IllegalArgumentException("Client cannot be null!" ); } synchronized (mLock) { throwIfShutdownLocked(); if (isConnectedLocked()) { throw new IllegalStateException("Already connected." ); } mOwningUid = Binder.getCallingUid(); registerUiTestAutomationServiceLocked(client, flags); storeRotationStateLocked(); } } }
UiAutomationConnection是在app_process 进程中创建的,而app_process 是另一个apk创建的,这样的情况就是 UiAutomationConnection的group process 是 另一个apk;可以尝试启动app_process设置一些参数。adb shell 之所以可以是因为是通过adb shell 创建的app_process
instrumentation 这个可以使用,但是没有应用场景 uiautomator 只能通过adb shell am instrument …调用,否则将有权限问题;因为uiautomator使用了acc辅助权限,当链接AccessibilityService时,无法获取权限,所以脱离adb,无法实现自动模拟操作的行为。