背景

自从 Android10 版本出来之后,Minicap 项目一直没有相应的 release,感觉sorccu小哥哥不更新项目了,后来才有了我上次发的帖子 minicap 支持 Android 10 版本
再后来发现在小米的基于 Android9 和 Android10 的 miui11 系统上,执行 minicap 失败,后来又找到手机中快速截图的方案,并且实施在系统中。但受限于任职期间公司的主营业务的资产,没有办法详细给大家介绍。今天突然发现Airtest项目早就实现,并且时间是 2 年前。哎,真是牛啊,不得不佩服技术储备的前置性。下面就分析一下这里面相关的代码。

介绍

主要相关的文件:Yosemite.apk and javacap.py

Yosemite.apk

建立一个手机中运行的 socket server,等待连接,有 socket client 连接后,启动线程执行一个循环,并且在循环中调用隐藏方法截图发送给 socket client。
这边先简单介绍一个功能。(这个 App 项目并未开源,如果只是应用,就没必要研究里面具体的实现方式;如果想具体了解,请自行想办法😄

javacap.py
  1. 启动 Yosemite.apk 中的截图 server:

    # setup agent proc
    apkpath = self.adb.path_app(self.APP_PKG)
    cmds = ["CLASSPATH=" + apkpath, 'exec', 'app_process', '/system/bin', self.SCREENCAP_SERVICE,
            "--scale", "100", "--socket", "%s" % deviceport, "-lazy", "2>&1"]
    proc = self.adb.start_shell(cmds)
    
  2. 建立 socketclient 连接 server:

    proc, nbsp, localport = self._setup_stream_server()
    s = SafeSocket()
    s.connect((self.adb.host, localport))
    t = s.recv(24)
    
  3. 读取 server 发送的内容:

    while not stopping:
        s.send(b"1")
        # recv frame header, count frame_size
        if self.RECVTIMEOUT is not None:
            header = s.recv_with_timeout(4, self.RECVTIMEOUT)
        else:
            header = s.recv(4)
        if header is None:
            LOGGING.error("javacap header is None")
            # recv timeout, if not frame updated, maybe screen locked
            stopping = yield None
        else:
            frame_size = struct.unpack("<I", header)[0]
            frame_data = s.recv(frame_size)
            stopping = yield frame_data
    

    看到yield,就知道 javacap.py 类的最终目的返回的是一个生成器;这样在循环截图的时候可以增加效率。

  4. 弄清楚逻辑之后,尝试写一个简单逻辑,循环不断从 Yosemite 的手机服务中获取图片。

    from airtest.core.android.android import ADB, Javacap
    
    adb = ADB()
    devices = adb.devices()
    if not devices:
        raise RuntimeError("At lease one adb device required")
    adb.serialno = devices[0][0]
    javacap = Javacap(adb)
    while True:
        frame = javacap.get_frame_from_stream()
    

    frame就是我们要获取的手机图片,用上面代码亲测小米 6,帧率大概 13 左右,只能说是能用。期间Yosemite服务不需要重启;归功于yield,socket client 也不需要重新连接。

建议

云真机用途的同学,增加一个队列,并且新建一个线程,这个线程直接从 Yosemite 服务获取图片并且增加在队列中。需要用图片的话,直接从队列里pop,只是建议,还没有尝试,有需要的同学不嫌弃的话,可以按照这个思路进一步优化。


↙↙↙阅读原文可查看相关链接,并与作者交流