最近在研究发自动给微信发消息,有研究过WebApi的方案实现出来了,但这种方式容易被封号,在想其他方案时想到用外挂这种偏门的方法看下行不行,于是就想到了android的UIAutomator测试框架,这个框架总体上来说是使用AccessibilityService来实现发送模似点击等消息,比如很多抢红包的方案就是用这个来做的.
UIAutomator测试框架使用起来其实很简单,首先新建一个android工程,在依赖里面引入:
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {exclude group: 'com.android.support', module: 'support-annotations'})
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
然后在androidtest目录下实现测试代码,默认情况下androidstudio已经新建了一个测试类ExampleInstrumentedTest,直接在里面实现我们的逻辑:
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
Instrumentation mInstrumentation;
UiDevice mDevice;
@Before
public void setUp() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mDevice = UiDevice.getInstance(mInstrumentation);
mInstrumentation.getTargetContext();
}
//加入这个注解表示是个测试用例入口
@Test
public void testWx() {
//按home键
mDevice.pressHome();
//启动微信
Context context = mInstrumentation.getContext();
Intent intent = context.getPackageManager().getLaunchIntentForPackage("com.tencent.mm");
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
context.startActivity(intent);
try {
//点搜索按钮
UiObject searchButton = mDevice.findObject(new UiSelector().description("搜索"));
searchButton.click();
//循环在搜索框里输入好友昵称
String[] users = new String[] {"辉", "ping.zhiping"};
for (String user : users) {
//从输入框输入昵称
mDevice.findObject(new UiSelector().resourceId("com.tencent.mm:id/hx")).setText(user);
UiObject list = mDevice.findObject(new UiSelector().className("android.widget.ListView"));
int count = list.getChildCount();
//从找到的好友里找到好友对应的item
UiObject findItem = list.getChild(new UiSelector().index(1));
//点击进入聊天窗口
findItem.click();
//找于是聊天窗口的消息输入框并输入消息
UiObject msgContent = mDevice.findObject(new UiSelector().resourceId("com.tencent.mm:id/aaa"));
msgContent.setText("xxx,我用外挂做测试");
//点发送按钮
UiObject sendButton = mDevice.findObject(new UiSelector().resourceId("com.tencent.mm:id/aag"));
sendButton.click();
//返回到搜索界面
mDevice.pressBack();
}
} catch (UiObjectNotFoundException e) {
e.printStackTrace();
}
}
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("cmdmac.org.myapplication", appContext.getPackageName());
}
}
写好后就可以直接在该文件点鼠标右键选Run textWx()就可以执行这个用例了,执行的时候可以看到他是跟用户操作是一样的,手机上的界面一直在动.
当在androidstudio启动这个测试用例的时候,实际上是使用adb发送了一条消息:
adb shell am instrument -w -r -e debug false -e class 'cmdmac.org.myapplication.ExampleInstrumentedTest#testWx' cmdmac.org.myapplication.test/android.support.test.runner.AndroidJUnitRunner
cmdmac.org.myapplication.ExampleInstrumentedTest#testWx就是用例的入口,cmdmac.org.myapplication.test/android.support.test.runner.AndroidJUnitRunner是我们测试框架的主类.
处理这条命令的实现如下:
private void runInstrument() throws Exception {
778 String profileFile = null;
779 boolean wait = false;
780 boolean rawMode = false;
781 boolean no_window_animation = false;
782 int userId = UserHandle.USER_CURRENT;
783 Bundle args = new Bundle();
784 String argKey = null, argValue = null;
785 IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
786 String abi = null;
787
788 String opt;
789 while ((opt=nextOption()) != null) {
790 //..解析各种参数
796 } else if (opt.equals("-e")) {
//class参数
797 argKey = nextArgRequired();
//cmdmac.org.myapplication.ExampleInstrumentedTest#testWx
798 argValue = nextArgRequired();
799 args.putString(argKey, argValue);
800 }
//...
807 } else {
808 System.err.println("Error: Unknown option: " + opt);
809 return;
810 }
811 }
812
813 //..... 最后一个参数cmdmac.org.myapplication.test/android.support.test.runner.AndroidJUnitRunner
818 String cnArg = nextArgRequired();
819
820 ComponentName cn;
821 //......
858 InstrumentationWatcher watcher = null;
859 UiAutomationConnection connection = null;
860 if (wait) {
861 watcher = new InstrumentationWatcher();
862 watcher.setRawOutput(rawMode);
863 connection = new UiAutomationConnection();
864 }
865 //......
//启动Instrucmemtation
889 if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, abi)) {
890 throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
891 }
//......
902 }
mAm赋值代码:mAm = ActivityManagerNative.getDefault();
好的,我们能否自己实现这段逻辑来调用呢,于是:
try {
Class cls = Class.forName("android.app.ActivityManagerNative");
Method method = cls.getMethod("getDefault");
Object am = method.invoke(null);
ComponentName cn = ComponentName.unflattenFromString("cmdmac.org.myapplication.test/android.support.test.runner.AndroidJUnitRunner");
Bundle args = new Bundle();
args.putString("class", "cmdmac.org.myapplication.ExampleInstrumentedTest#testWx");
Method startInstrumentation = am.getClass().getDeclaredMethod("startInstrumentation",
new Class[]{android.content.ComponentName.class, java.lang.String.class,
int.class, Bundle.class,
Class.forName("android.app.IInstrumentationWatcher")
, Class.forName("android.app.IUiAutomationConnection"), int.class, String.class});
startInstrumentation.setAccessible(true);
//InstrumentationWatcher在am.java是个内部类
startInstrumentation.invoke(am, cn, null, 0, args, new InstrumentationWatcher(), Class.forName("android.app.UiAutomationConnection").newInstance(), 0, null);
// am.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, abi)
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
InstrumentationWatcher在am.java是个内部类,我把它的实现copy了出来,但它还依赖IInstrumentationWatcher,但IInstrumentationWatcher是个隐藏的aidl接口,于是又把这个文件拿出来放到自己的工程里参与编译,编译可以搞定,以为可以搞定了,但运行的时候出错:
31 12:15:32.162 5452-5470/? I/TestRunner: failed: testWx(cmdmac.org.myapplication.ExampleInstrumentedTest)
----- begin exception -----
05-31 12:15:32.162 3204-3204/? I/MzInput_MzInputService: theme= white mNeedBlur=false
05-31 12:15:32.162 3204-3204/? D/MzInput_MzSettings: setTheme, theme: null
05-31 12:15:32.162 5452-5470/? I/TestRunner: java.lang.RuntimeException: Error while connecting UiAutomation
at android.app.UiAutomation.connect(UiAutomation.java:226)
at android.app.Instrumentation.getUiAutomation(Instrumentation.java:1929)
at android.app.Instrumentation.getUiAutomation(Instrumentation.java:1886)
at android.support.test.uiautomator.UiDevice.(UiDevice.java:105)
at android.support.test.uiautomator.UiDevice.getInstance(UiDevice.java:269)
at cmdmac.org.myapplication.ExampleInstrumentedTest.setUp(ExampleInstrumentedTest.java:34)
at java.lang.reflect.Method.invoke(Native Method)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at android.support.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:76)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at android.support.test.runner.AndroidJUnit4.run(AndroidJUnit4.java:101)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:384)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1949)
Caused by: android.os.DeadObjectException
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(Binder.java:619)
at android.app.IUiAutomationConnection$Stub$Proxy.connect(IUiAutomationConnection.java:238)
at android.app.UiAutomation.connect(UiAutomation.java:223)
at android.app.Instrumentation.getUiAutomation(Instrumentation.java:1929)
at android.app.Instrumentation.getUiAutomation(Instrumentation.java:1886)
at android.support.test.uiautomator.UiDevice.(UiDevice.java:105)
at android.support.test.uiautomator.UiDevice.getInstance(UiDevice.java:269)
at cmdmac.org.myapplication.ExampleInstrumentedTest.setUp(ExampleInstrumentedTest.java:34)
at java.lang.reflect.Method.invoke(Native Method)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at android.support.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:76)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at android.support.test.runner.AndroidJUnit4.run(AndroidJUnit4.java:101)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:384)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1949)
----- end exception -----
估计是真的要root权限了,让大家失望了...