STF Minicap 项目貌似不更新了,开启 Android 画面传输替代方案

JamesChung · 2019年12月16日 · 最后由 yyy 回复于 2021年05月18日 · 6114 次阅读

背景

自从 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,只是建议,还没有尝试,有需要的同学不嫌弃的话,可以按照这个思路进一步优化。

共收到 4 条回复 时间 点赞

大佬,小米的基于 Android9 和 Android10 的 miui11 系统上,出现安全键盘是 Airtest 好像也不支持截图,有什么办法可以解决吗。试过录制视频截图,截出来的图,有安全键盘的区域是黑的。

哲豪 回复

除了小米的 miui11,其他手机的安全键盘都 OK 了?

JamesChung 回复

其他的安全键盘 走的是 minicap。目前没有发现问题

10:32:16][DEBUG]<airtest.utils.nbsp> [javacap_sever]b''
Unhandled exception in thread started by <function start_get_screen at 0x136aca290>
Traceback (most recent call last):
  File "main.py", line 78, in start_get_screen
    frame = javacap.get_frame_from_stream()
  File "/usr/local/lib/python3.7/site-packages/airtest/core/android/javacap.py", line 103, in get_frame_from_stream
    return self.frame_gen.send(None)
  File "/usr/local/lib/python3.7/site-packages/airtest/utils/threadsafe.py", line 27, in send
    return self.it.send(*args)
  File "/usr/local/lib/python3.7/site-packages/airtest/core/android/javacap.py", line 77, in get_frames
    header = s.recv(4)
  File "/usr/local/lib/python3.7/site-packages/airtest/utils/safesocket.py", line 32, in recv
    raise socket.error("socket connection broken")
OSError: socket connection broken

@zhonglinlins 楼主,请问我单手机通过 wifi 连接 adb 可以正常运行,但是此时再开一个终端窗口输入adb devices的话,原来运行正常的窗口就会报这个错误,是什么原因呢?

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