利用UIAutomator发微信消息

最近在研究发自动给微信发消息,有研究过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()就可以执行这个用例了,执行的时候可以看到他是跟用户操作是一样的,手机上的界面一直在动.

方法还是挺简单的,但是这样只能在pc上执行,如果我想直接在手机端启动这个外挂呢,该怎么办呢?
那就得了解它的执行流程了,我们来分析分析:

当在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是我们测试框架的主类.

得知这个关键信息,那我们是否可以在手机端调用shell命令的方式来实现呢,马上试了下,发现调用了没反应,猜测是需要root权限.那这样门槛就高了,有没办法不用root权限呢,于是跟踪了下am instrument命令实现.执行上面这条命令会调用手机端的am.jar,它的实现源码在frameworks/base/cmds/am/src/com/android/commands/am/Am.java其实很多adb shell命令对应的源码都在frameworks/base/cmds目录,有兴趣的同学可以研究一下.

处理这条命令的实现如下:

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权限了,让大家失望了...

你可能感兴趣的:(android)