ATX android-uiautomator-server 源码解读

CC · 2018年04月25日 · 最后由 bauul 回复于 2018年04月26日 · 3326 次阅读

纯粹作为个人学习记录
时序图:

源码解读:
https://github.com/openatx/android-uiautomator-server

这块框架源码的作者,代码写的真的很漂亮😁

主服务入口:
package com.github.uiautomator.stub;

AutomatorHttpServer 轻量级 HTTP 服务

int PORT = 9008;
AutomatorHttpServer server = new AutomatorHttpServer(PORT);

public class AutomatorHttpServer extends NanoHTTPD {

    public AutomatorHttpServer(int port) {
        super(port);
    }
    ### 把路由与JsonRpc结合
    private Map<String, JsonRpcServer> router = new HashMap<String, JsonRpcServer>();

    public void route(String uri, JsonRpcServer rpc) {
        router.put(uri, rpc);
    }

    @Override
    public Response serve(String uri, Method method,
                          Map<String, String> headers, Map<String, String> params,
                          Map<String, String> files) {
        Log.d(String.format("URI: %s, Method: %s, params, %s, files: %s", uri, method, params, files));

        if ("/stop".equals(uri)) {
            stop();
            return newFixedLengthResponse("Server stopped!!!");
        } else if ("/ping".equals(uri)) {
            return newFixedLengthResponse("pong");
        } else if ("/screenshot/0".equals(uri)) {
            float scale = 1.0f;
            if (params.containsKey("scale")) {
                try {
                    scale = Float.parseFloat(params.get("scale"));
                } catch (NumberFormatException e) {
                }
            }
            int quality = 100;
            if (params.containsKey("quality")) {
                try {
                    quality = Integer.parseInt(params.get("quality"));
                } catch (NumberFormatException e) {
                }
            }
            File f = new File(InstrumentationRegistry.getTargetContext().getFilesDir(), "screenshot.png");
            UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).takeScreenshot(f, scale, quality);

            try {
                return newChunkedResponse(Response.Status.OK, "image/png", new FileInputStream(f));
            } catch (FileNotFoundException e) {
                Log.e(e.getMessage());
                return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "Internal Server Error!!!");
            }
        } else if (router.containsKey(uri)) {
            JsonRpcServer jsonRpcServer = router.get(uri);
            ByteArrayInputStream is = null;
            if (params.get("NanoHttpd.QUERY_STRING") != null)
                is = new ByteArrayInputStream(params.get("NanoHttpd.QUERY_STRING").getBytes());
            else if (files.get("postData") != null)
                is = new ByteArrayInputStream(files.get("postData").getBytes());
            else
                return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "Invalid http post data!");
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            try {
               #把http请求转换成rpc请求进行处理
                jsonRpcServer.handleRequest(is, os);
                return newFixedLengthResponse(Response.Status.OK, "application/json", new ByteArrayInputStream(os.toByteArray()), os.size());
            } catch (IOException e) {
                return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "Internal Server Error!!!");
            }
        } else
            return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found!!!");
    }

}

初始化,把 AutomatorService 实现和接口分别填入 JsonRpcServer 中,处理由 JsonRpcServer 进行处理

@Before
public void setUp() throws Exception {
    launchService();
    server.route("/jsonrpc/0", new JsonRpcServer(new ObjectMapper(), new AutomatorServiceImpl(), AutomatorService.class));
    server.start();
}

JsonRpc 消息处理格式

{ "method": "方法名", "params": [“参数数组”], "id":  方法ID}

看下 AutomatorServiceImpl 的实现

@Override
    public boolean click(int x, int y) {
        return device.click(x, y);
    }

再看下 device.click 是哪里的方法,是原生 Uiautomator 的方法了

package android.support.test.uiautomator;
    /**
     * Perform a click at arbitrary coordinates specified by the user
     *
     * @param x coordinate
     * @param y coordinate
     * @return true if the click succeeded else false
     * @since API Level 16
     */
    public boolean click(int x, int y) {
        Tracer.trace(x, y);
        if (x >= getDisplayWidth() || y >= getDisplayHeight()) {
            return (false);
        }
        return getAutomatorBridge().getInteractionController().clickNoSync(x, y);
    }

再看下 click 的数据入参为 (int x, int y)

所以对应的 JSONRPC 报文格式应该是类似:

预期:{ "method": "click", "params": [11,22], "id": "asdabdabsdada"}
实际:{"jsonrpc": "2.0", "id": "b1e82a522ee935dcff77740bbe3e4921", "method": "click", "params": [134.5, 1003.5]}
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 3 条回复 时间 点赞

看 2.0 的源码啊,netty 的,感觉比 1.0 还漂亮,虽然我也不懂,不过我会装😊

CC #2 · 2018年04月26日 Author
bauul 回复

我没在看 2.0 的源码,我是看他抽离的把 2.0 的动作方法封装成了一个实现,再做了层 control 类似的调用,主要感觉代码结构用到不少设计模式,代码写的比较漂亮

CC 回复

那么问题来了,用到了哪些设计模式呢😈

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册