STF [假如我来实现 STF 系统不用 node] 整体思路及实现,长文慎入

kyowang · 2018年03月05日 · 最后由 codeskyblue 回复于 2019年06月10日 · 4484 次阅读
本帖已被设为精华帖!

第一次写文章,希望为社区做点贡献。
STF 框架大家已经不陌生,论坛也有很多大牛们写的关于 STF 细节的优秀文章,这里我主要想给大家分享的是:

假如你的老板对你说:“小 A 啊,我们公司业务越来越好,测试人员越来越多,android 测试手机也多。几百台啦。
老是借来借去,一借借一天,测试也就几十分钟,效率好低啊。你有没有什么解决办法呢”,
这时候,你想起在论坛看过的 STF 框架,你终于抑制不住激动的情绪对老板说:“老板我就等你这句话呢,我都研究好久啦,STF”
老板:“STF 什么玩意儿?”
你:“STF 可以管理手机,远程使用,测试,不用借来借去的啦。基于 node js 的一个开源项目。我们可以拿来试试。”
老板:“我这人行走江湖几十年😄,最讨厌的就是 node js 了,你能不能换 java 呢。咱们得搞点儿自主知识产权的东西嘛。”
你:“...”
于是你陷入了无尽的沉思中。骚年,别怕,这里我就带你一起理一理,如何弄一个让你老板满意的系统。
首先,我们要思考一下整体系统应该如何搭建。看下图(用了一张老图,懒的改了)

图一(系统架构)
此图中我们能看出系统中有两个核心服务

一 HostAgent(暂时先这么叫,你完全可以取一个更加洋气的名字,比如 awsomeAgent)

Agent 主要部署在手机通过 usb 连接的电脑上,负责维护手机的状态,比如手机上线,下线,管理 apk 安装,卸载,以及终结 websocket,并转换成 tcp socket(面向手机上的服务)。
之所以把 agent 独立出来,主要还是考虑到手机众多,按照 stf 官方的兼容机 +PCI-E+USB hub 的配置,一台电脑最多带 28 台手机(4*7)。那么系统要维护上百台的话,就需要多台电脑来维护手机。因此独立出来方便扩展。
另外,为什么 websocket 是从浏览器到 agent,而不是浏览器到 webserver,后者看起来貌似要更合理一些,对的,看起来是要合理一些。我一开始也是采用的后者,之所以改为前者是因为我的 webserver 要部署在公司集中机房,而 agent 只用部署在办公室实验室里面,而大部分测试人员会通过办公电脑访问设备租用服务,这么一来,如果 websocket 是从浏览器到 webserver 终结,那么意味着所有 minicap 帧需要从实验室的手机,通过 tcp 到达 webserver,然后到达浏览器,比起第一种方案,数据到集中机房绕了一圈又回到了办公室网络,所以果断换成了第一种方案,事实上实测下来,多兜一圈的话的确会增加大概 500ms-1s 的延迟。为了高效选了前者,而你可以根据你的实际情况作出选择。

二 WebServer

WebServer 的主要职责是提供管理页面,设备租用页面。维护管理数据库,是 websock 的发起端(js),webserver 根据负荷可以扩展,但不同于 agent,server 的所有扩展机是对等的。agent 由于单个手机只能挂在一台电脑上,所以每台 agent 的资源是有区别的。

三 系统理解

好我们现在来理解一下,上图系统的设计思路。并且理清楚主要技术方案和可行性。

  • 手机动态管理

    首先对于 agent 来说,最重要的功能就是手机动态管理,因为,我不想我的系统每次插上一个新手机还需要手动敲一堆命令,或者在系统里面手动添加,这样太 low 对吧,我们想要的是手机通过 USB 插上 agent 电脑的一瞬间,我们的 agent 自动感知到手机接入,并且自动更新 server 的数据(online/offline 状态),如果是第一次插入的手机我们可能还想给手机自动截个屏上传,作为展示图标,因为这样显得更得体。而不是用一张通图,然后还要显示的信息是手机的品牌,型号,内存,屏幕等等信息这些我们只在新手机第一次接入时主动获取,并更新给 server 保存。
    好,那怎么感知呢?还好有开源库,那就是 google 的 ddmlib,android studio 就是用这个库来管理手机调试的。所以直接拿来用就好了。加入下面的依赖到你的项目里。

    <dependency>
        <groupId>com.android.tools.ddms</groupId>
        <artifactId>ddmlib</artifactId>
        <version>25.2.0</version>
    </dependency>
    

    然后你需要实现一个自己的 listener 来告知在相关事件里面你需要做的处理。例如:

    @Service
    public class DeviceChangeListener implements AndroidDebugBridge.IDeviceChangeListener {
    Logger  log = Logger.getLogger(DeviceChangeListener.class);
    
    @Override
    public void deviceConnected(IDevice iDevice) {
        log.info("Device: "+iDevice.getSerialNumber()+" connected");
        if(iDevice.getState() != null && iDevice.getState().equals(IDevice.DeviceState.ONLINE)){
            onOnline(iDevice);
        }
    }
    
    @Override
    public void deviceDisconnected(IDevice iDevice) {
        log.info("Device: "+iDevice.getSerialNumber()+" disconnected");
        onOffline(iDevice);
    }
    
    @Override
    public void deviceChanged(IDevice iDevice, int i) {
        log.info("Device: "+iDevice.getSerialNumber()+" changed");
        if(iDevice.getState().equals(IDevice.DeviceState.ONLINE)){
            onOnline(iDevice);
        }
    }
    
    public void onOnline(IDevice iDevice){
        log.info("Device: "+iDevice.getSerialNumber()+" online");
        onlineTask.onDeviceOnline(iDevice);
    }
    
    public void onOffline(IDevice iDevice){
        log.info("Device: "+iDevice.getSerialNumber()+" offline");
        offlineTask.onDeviceOffline(iDevice);
    }
    }
    

    这里放点核心代码去掉冗余,你需要实现 AndroidDebugBridge.IDeviceChangeListener 这个接口。注意在 deviceConnected 里面要判断是否是 ONLINE 状态,因为手机连接后有可能是 offline 或者未授权状态,然后在 online 的时候你需要一个异步任务去做你 online 要做的事情,不要阻塞事件感知的线程。
    现在你已经拥有了一个 Listener 处理函类了。你需要把它的实例注册给 ddmlib 的 AndroidDebugBridge。
    当然要先初始化一下,然后创建好 adb 对象后可能你要需要调用 isConnected 函数检查一下是否 adb 连接成功了。

    AndroidDebugBridge.init(false);
    AndroidDebugBridge.addDeviceChangeListener(listener);
    adb = AndroidDebugBridge.createBridge(adbpath,false);
    //adb.isConnected()
    

    嗯,恭喜你,到这里为止,你的 agent 已经可以动态感知手机的插入和拔出了。至于在 online 时你需要做什么具体的事情,在上面已经有罗列,你需要一张管理 device 的数据库表,里面有手机序列号,手机状态(online/offline),系统版本,品牌,型号等等的很多参数,你需要在 online 的时候动态更新这张表。

  • 手机服务管理及 forward 管理

好了,现在假设你感知到了手机的上线事件。为了完成设备远程控制(租用),是不是上线的时候还需要做点什么?
对的,你需要把 stf 的几个手机端的服务(minicap,minitouch,stf agent, stf service),从 stf 项目剥离出来,然后在手机上线的时候安装进手机。下面分享一些实用的命令给大家,都是代码片段,非完整代码,请自行修改使用。
然后我们需要把 stf 的服务编译好(具体参考 stf 官方文档),然后将其放入 agent 的项目的某个目录里面,这样才能用 adb install 命令或者 adb push 命令将其安装到手机上面。安装过程比较简单,可以参考下面的命令完成。

private static final String SHELL_OPEN_BROWSER = "am start -a android.intent.action.VIEW -d %s";
private static final String SHELL_ADB_INSTALL = "pm install -r %s";
private static final String SHELL_ADB_UNLOCK = "am start -anw io.appium.unlock/.Unlock";
private static final String SHELL_ADB_RECENT_TASK = "am start -anw com.android.systemui/.recent.RecentsActivity";
private static final String SHELL_ADB_LAUNCH = "am start -anw %s";
private static final String SHELL_ADB_HOME = "input keyevent 3";
private static final String SHELL_ADB_BACK = "input keyevent 4";
private static final String SHELL_ADB_POWER = "input keyevent 26";
private static final String SHELL_ADB_CHECK_SCREEN_ON = " dumpsys power | grep mScreenOn=";
private static final String SHELL_ADB_CHECK_SCREEN_ON_2 = " dumpsys power | grep \"Display Power\"";
private static final String SHELL_ADB_GET_IP = "ifconfig |grep \"inet addr\" |grep -v \"127.0.0.1\"";
private static final String SHELL_ADB_GET_IP2 = "ip addr show |grep inet |grep -v inet6 |grep -v \"127.0.0.1\"";
// Below is just part of some commands.
switch (info){
        case INFO_CPU:
            return "cat /proc/cpuinfo |grep Hardware";
        case INFO_RAM:
            return "cat /proc/meminfo |grep MemTotal";
        case INFO_BATTERY:
            return "dumpsys batterystats 2>/dev/null |grep Capacity";
        case INFO_RESOLUTION:
            return "wm size 2>/dev/null ";
        case INFO_RESOLUTION2:
            return "dumpsys window 2>/dev/null | grep  'init='";
        case INFO_PACKAGE_LIST:
            return "pm list packages 2>/dev/null |grep " + (para == null ? "" : para);
        case INFO_WEBVIEW_VERSION:
            return "pm dump com.google.android.webview 2>/dev/null | grep versionName";
        default:
            return "";
    }

stf 服务在安装好之后,我不建议你立即启动这些服务,一方面是启动之后会一直占用系统资源,另一方面是部分低版本手机,在 minicap/minitouch 启动之后,真机上的触摸屏就没有反应了。只能通过 minitouch 控制。还有就是如果是上线就启动,如果中途服务出了问题,你也无法预知。所以最好的办法是有远程控制 session 建立的时候动态启动服务,结束之后停止服务。
好,我认为你的服务也能正常启动了。不幸的是,很快你会发现新的问题又来了。
1 你的 server 要使用 websocket 来传输所有数据(minicap frame/minitouch command),而你发现 stf 服务都是提供的 tcp 服务,这个怎么办?中间肯定需要转换的。
2 另外一个问题是,stf 服务启动在手机上,agent 要连接这些 tcp 服务需要感知手机的 ip 地址,并且手机的 ip 和 agent 必须要 3 层路由可达。

对于第二个问题,adb 有提供一个 forward 功能可以把本机的某个端口和手机的某个端口映射起来,让你不用考虑手机的 ip,通过 agent 访问本机的 ip+ 端口就可以间接访问到手机上的对应服务。这个在 stf 文档里面也可以看到。具体命令如下:

//minicap forward
iDevice.createForward(item.getPortMinicap(),"minicap", IDevice.DeviceUnixSocketNamespace.ABSTRACT);
//minitouch forward
iDevice.createForward(item.getPortMinitouch(),"minitouch", IDevice.DeviceUnixSocketNamespace.ABSTRACT);
iDevice.createForward(item.getPortStfService(),"stfservice",IDevice.DeviceUnixSocketNamespace.ABSTRACT);
iDevice.createForward(item.getPortStfAgent(),"stfagent",IDevice.DeviceUnixSocketNamespace.ABSTRACT);

在你使用了 forward 功能之后,你可以方便的用 tcp 连接本机的端口就可以连接到对应的手机端 stf 服务了。嗯,你看起来很兴奋。
但是,你发现还有一个问题需要进入你的考虑范围,那就是,一个 agent 连接多台手机,对于本机端口也不能重复,所以,需要映射到本机的端口你需要根据手机的数量管理起来。当然这个小问题是难不倒你的。不过我仍然提供一个参考的解决方案给你。
本机为每个服务类型预留一个段,每个段有 100 个端口,基本够的,因为你每台机器带不了 100 台手机那么多。

adb forward tcp:1313 localabstract:minicap #(host预留1300)
adb forward tcp:1111 localabstract:minitouch #(host预留1400)
adb forward tcp:1100 tcp:1100 #(host预留1500)
adb forward tcp:1090 tcp:1090 #(host预留1600)

对于第一个问题,tcp 的连接你得自己来写代码了。你通过刚才的端口,建立 tcp 连接到对应的手机上(服务得先起来才行:)),然后正确读写即可。
对于连上 minicap/minitouch 之后,如果读写并解码,后面我尽量单独开一个帖子讲一下。如果有时间的话。

  • 设备租用会话管理

现在有了上面的铺垫,可能你已经在 agent 里面实现了几个线程,这几个线程分别负责 agent 到手机 stf 服务的 tcp 连接的编解码。
对于一个设备租用的会话来说,会话资源会包括上面 4 个线程,还会包括一个 websocket session,可能还有端口资源(forward 相关)等等。而 agent 会在这两者之间不断交换数据。
这时候你发现把这些资源管理起来很有必要,要保证一个设备租用会话里面的所有资源,在启动的时候,合理申请,并且在正常结束,或者异常结束时能够全部被回收。否则手机一多,你的整个状态就是混乱的。并且可能带来各种意想不到的问题。

你可以尝试实现一个 manager 类,它负责管理所有租用 session 的创建,然后实现一个租用 session 类,它负责维护并管理所有 session 相关资源的聚合,启动,和释放。
按照之前的建议,stf 服务的启动和结束,也可以完全和租用 session 的生命周期匹配起来,即租用 session 启动的时候,同时启动手机里面的 stf 服务,租用 session 结束的时候也同时结束 stf 服务在手机中的运行。

  • websocket

    嗯。能看到这里,说明你对自己开发一个设备管理租用系统真的很有兴趣,你即将在执着的道路上越走越远,越走越远:)
    好了,现在 agent 的部分大概已经讲的差不多了。我们是时候考虑一下 websocket 方案了。把最后一块拼图拼出来,你就可以拉着你的老板 show 一下你的新系统了。
    对于 websocket,你可以用原始的 websocket 对象,也可以使用各种库,这里我选的是 SockJS 库。考虑到和 java 服务端 spring 所支持的 websocket 方案配合。spring 支持的 STOMP 不太适合我们这个使用场景,所以我没有使用 STOPM 方式。
    更详细的请参考 spring 官方文档:https://docs.spring.io/spring/docs/5.0.0.BUILD-SNAPSHOT/spring-framework-reference/html/websocket.html
    这里我先展示点前端的 js 部分逻辑。
    js 在 load 的时候构建 SockJS 对象,建立 socket 连接,然后注册几个回调函数,onmessage 会在服务端调用 sendmessage 函数发送信息后调用,onopen 会在 socket 建立好之后调用,做一些初始化工作,onclose 则在 socket 断开时调用。下面的代码只是框架我去掉了冗余的内容,加了点注释,
    重点是在 onmessage 里面,里面 要区分并处理 minicap 帧信息,minitouch 初始化信息,stf agent 和 stf service 的反馈等信息。

function connect(){
    socket = new SockJS(hostUrlPrefix + '/socket');
    socket.onopen = function () {
        console.log("on open");
        sendStart(serial,reso);
    }
    socket.onmessage = function (msg) {
        var prefix = msg.data.substr(0,6);
        var len = msg.data.length;
        // console.log("Received msg length:"+ len);
        // Handle message from websocket server。
        // it could be minicap frame or stf agent or stf service command feedback, or some other information
    }
    socket.onclose = function () {
        console.log("onClose received! closing");
        // sendClose(safeCode);
        disconnect();
        showEndFrame();
    }
}
  • minicap 帧处理

这个时候你应该会想到,在 agent 端从 minicap socket 里面读到的每一帧都是 2 进制的内容,我怎么把它传输到浏览器,并在浏览器显示出手机的画面来呢?
嗯,这的确是一个问题。首先你参考了 stf 框架,发现它是用 js 的 blob 的结构把每一帧发送到浏览器,并在浏览器解开,画到 canvas 上,完成一帧的显示。
于是你也在网上搜了一圈,如何在 java 后端构造 blob 格式并且通过 websocket 发送给浏览器,很遗憾,没有太多的信息可以给你帮助。
而且你发现 spring 提供的 websocket 框架发送二进制貌似也不好用。于是在千钧一发之际,你不得不寻找其他方法。
嗯。你最终会发现可以将 2 进制图片编码成 base64 的格式,
传输到浏览器,而 img 元素刚好可以直接展示 base64 编码过的图片文件,当然,唯一的缺点是每一帧数据会大 1/3。

function showFrame(message) {
    var imgx = imgpool.next();
    imgx.onload=function(){
        if(canvas.width * imgx.height != canvas.height * imgx.width){
            canvas.width = imgx.width;
            canvas.height = imgx.height;
        }
        ctx.drawImage(imgx,0,0,canvas.width,canvas.height);
        imgx.onload = imgx.onerror = null;
        imgx.src = BLANK_IMG;
        imgx = null;
    }
    imgx.onerror = function() {
        // Happily ignore. I suppose this shouldn't happen, but
        // sometimes it does, presumably when we're loading images
        // too quickly.

        // Do the same cleanup here as in onload.
        imgx.onload = imgx.onerror = null;
        imgx.src = BLANK_IMG;
        imgx = null;
    }
    imgx.src="data:image/jpg;base64,"+message;
    frameCount++ ;
}

java 编码部分,我在发送前有可能对图片做压缩,所以多调用了一个压缩函数。

Base64.Encoder encoder = Base64.getEncoder();
String result = encoder.encodeToString(compressImg(head.getJpg(),qSize));

这样你就得到了需要最终发送的 string,和收到帧数据后的处理逻辑。需要提醒的是在浏览器端,因为 websocket 只有一个链接,后端对应了 4 个 tcp 链接的数据,这里面你需要做一个区分。具体如何区分,我相信这个问题是难不倒你的。

  • minitouch 命令与按键转换

好的,现在画面已经可以正确显示在浏览器上了。你是不是已经有了很大的成就感,一个牛逼的系统就要搞定啦。

对于如何将 cavas 上的鼠标操作以及键盘事件转化为 minitouch 支持的格式。你还需要一些代码来打磨。
对于键盘,浏览器得到的键盘码和安卓相同字符的键盘码有比较大的出入,你最好要维护一个映射表做对应的转换。
下面的代码是对功能按键的映射,对于键盘按键,功能键 只有 keydown/keyup 貌似没有 keypress 事件。而下面的代码
先判断是否为 特殊按键特殊按键需要转换,正常字符直接把字符发送给 stf agent 服务即可。

更详细的测试和文档可以参见:
https://dvcs.w3.org/hg/d4e/raw-file/tip/key-event-test.html
http://unixpapa.com/js/testkey.html
https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode

function keydown(event) {
    e = event || window.event;
    var char = getChar(event || window.event)
    var msg;
    var keyc;
    if(!char) {
        switch (event.keyCode){
            case 8://BACKSPACE
                e.preventDefault ? e.preventDefault() : (e.returnValue = false);
                keyc=67;
                break;
            case 13://ENTER
                keyc=66;
                break;
            case 27: //ESC
                keyc=111;
                break;
            case 38://UP Arrow
                keyc=19;
                break;
            case 37://LEFT Arrow
                keyc=21;
                break;
            case 40://DOWN Arrow
                keyc=20;
                break;
            case 39://RIGHT Arrow
                keyc=22;
                break;
            case 46://DELETE
                keyc=112;
                break;
            default:
                return;
        }
        msg = {
            event : 2, //key press
            keyCode : keyc,
            shiftKey : e.shiftKey,
            ctrlKey : e.ctrlKey,
            altKey : e.altKey,
            metaKey : e.metaKey,
            symKey : false,
            functionKey : false,
            capsLockKey : false,
            scrollLockKey : false,
            numLockKey :false
        };
        var message = JSON.stringify(msg);
        //0 means keyevnet
        message = "0"+message;
        console.log(message);
        sendStfAgentCommand(message);
    }
}

而对于鼠标在 canvas 上滑动,对应到真实的手机屏幕上,我们可以参考如下代码,做对应转换:

function getXFromEvent(event){

    var offx = event.offsetX;
    var canvasWidth = canvas.offsetWidth;
    var actX = offx*deviceWidth/canvasWidth;
    return parseInt(actX);
}
function getYFromEvent(event){
    var offy = event.offsetY;
    var canvasHeight = canvas.offsetHeight;
    var actY = offy*deviceHeight/canvasHeight;
    return parseInt(actY);
}

到此为止,基本就可以把功能串通了。你很高兴的找来老板演示了一把,老板也很开心,马上准备全公司推广试用。

但是好景不长,你在实验室搭建的过程中,发现,你们公司主要都是台式机或者部分 mac 机,默认一个台式机后面带 4-6 个 USB 接口。
你要挂 40 台机器,就需要 8-10 台物理机。这样机器利用率太低。
如果你足够细心,在研究 stf 的问题列表时你能找到 stf 两个作者的一些回答。对你很有用。
他们也用台式机,但是他们不用主板自带的 USB 口,因为太不可靠,而且供电不足。他们采用 PCI-E USB 卡,的方式提供 USB 口。
一块 PCI-E USB 卡 4 个 USB 口。然后再加 4 个 USB hub,而 USB hub 的挑选也是一件头疼的事。因为大部分 usb hub 要么供电不足,
要么都是 3.0,不够稳定。相比较而言 2.0 要比 3.0 的 hub 更稳定一些,3.0 只是速度快对我们价值不大。

于是你照着这个方案,你还得去买 PCI-E 卡,自己插到电脑上,然后 USB hub 国内买不到 stf 推荐的。只能买到 orico 的一款 10 口带供电的。
你发现你好像快变成一个实验室管理员了。

这时候,手机口有了。实验室手机摆的乱七八糟,你觉得找一个手机架,把手机搁起来可能更好,于是你又到网上搜到了一个满意的架子,
到货后,嗯,你傻眼了。因为得自己装起来,因为没有别人会帮你做这件事。好的,此事占用你 1 个下午时间。
很明显你是不会被这些琐事所打倒的对吧,因为你心里那牛逼的工程还没完工,同事们还没大面积用起来不是?

很快,实验室基本成型了。同事们也开始用了起来,并且发现挺好用的,速度也不错。正如下面的截图所示。

嗯。老板找到你,说大家用起来反应不错,效率确实提升,我们要继续优化系统,保证稳定好用。
然后你试了各种方法让界面和操作延迟更小,比如,按读写拆分 minicap 线程,比如按帧率动态调节图片画质,比如,提供了远程调试功能,让开发也能随便使用你平台的手机随时调试应用。还有比如加入脚本录制等等。
在长期的运营过程中,你发现手机 不能长期挂着,这样电池容易出问题,比如挂久了,有的手机的电池容易鼓包(有点吓人对吧,还好没有爆炸,这是万幸)。
于是你不得不在每周结束的时候把所有机器都下掉。周一来再挂。
并且发现很多手机现在直接挂上去默认 usb 调试关闭了,需要手动切换一下 usb 状态。为了保障我们的系统更稳定的运行,这点事情对你来说不算什么。
然后随着使用变多,有的同事会向你反应,有的手机用不了。这时你抱着怀疑的态度来到他说的手机旁,发现部分手机挂着挂着就重启,并且进入系统失败了,需要手动重启。
部分手机是进入了 recover 模式,也需要手动重启,还有部分手机会因为莫名其妙的 usb 连接原因,直接掉线.....

嗯。当你走到这一步时,你应该能意识到,你已经成功变成了一名资深运维人员😄

题外话,当你成为资深运维人员之后,公司会采购新的手机,进入实验室。你在挂新手机的时候,你会发现,
vivo 的手机需要强制登录 vivo 账号,并且用 adb install 安装 apk 时,会弹出对话框让输入密码。
小米部分手机,minitouch 的权限(模拟触屏操作)需要登录小米账号,并且,插入 sim 卡,才能在开发者选项里面打开。
我能感受到你的愤怒,开发者选项开个权限,你让我登录账号我忍了。插 sim 卡是什么鬼,手机都是我的,
嗯嗯,你尽管骂吧,因为反正他们也听不到的。而且就算听到也不会改的,因为我看到过论坛无数人在骂了。

好的,本文就到此结束了。

共收到 34 条回复 时间 点赞
恒温 将本帖设为了精华贴 03月06日 17:26

作者是个 stf 资深用户呀

厉害,搞一下

貌似没有 adbkit,这块比较有用 光屏幕操作共享 意义还不是很大,希望谁能重写 adbkit

这些坑我都遇到过,但就是没有勇气把 stf 改成其它语言。赶紧把我招进去做吧。😁

心向东 回复

adbkit 本身就能单独使用,我以前写过关于这个的帖子

楼主这个是每次更新整张图吗?印象中 minicap 好像是可以局部更新的

心向东 回复

adbkit 是 js 的,其实就是 adb 操作的包装,系统实现肯定夸不过这一步。没有单独说。基于 ddmlib 已经够简单了实际上。

bauul 回复

minicap 没有局部更新,不过 minicap 在界面没有任何变化的时候就不会产生帧。

codeskyblue 回复

单独使用是可以但是需要 和其它系统一起控制权限的话就麻烦了

不错 深度好文。最近还在跟别人聊要不要搞个社区的设备租用平台

期待楼主后续~

点个赞,相当棒。可以加入 stf QQ 群里一起交流:168170256。

憋搞了

在做 python 版的默默膜拜下~💯

楼主,你们在公司是什么岗位?

你已经成功变成了一名资深运维人员😂
@Lihuazhang ,当年我们搭建 DE 的 lab 好多了

mark 一下,准备 4 月份开始做一个 python 版本的出来,借鉴借鉴~

能否告知一下,所需要的 PCI-E usb 扩展卡 和 usb hub 的具体牌子和型号?

PCI-E 卡现在很少,在网上搜了,选了西霸的,用下来感觉一般。
usb hub 选的 Orico 的 10 口带供电的 2.0 那款。160 左右吧。

只能说你们老板资源多,能投人重复造轮子👍

#16 楼 @yoegg 请问 python 版本的情况怎样了?

—— 来自 TesterHome 官方 安卓客户端

可以把完整代码发出来学习下吗?

太佩服楼主了,对 STF 这块研究的很深入啊。一直都想花时间研究,哎

usb hub,orico 的是坑货,用了就知道经验谈。。。
实践下来,还是推荐使用西普莱的工业级 USB HUB,或者金田的 usb hub。。。

😂给楼主点赞...我们组之前撸的就是 react.js + python 版本的...

楼主太强了😱

我们管理 200 多台手机,用工业级 USB HUB 暂时没发现问题,而且连接 wifi 插座,每天晚上断电,避免电池鼓包。
那些重新挂上去默认 usb 调试关闭的手机我们是集中到放在一台 agent 上的,每星期手动断电一次。

看到最后说要登陆账号&插入 SIM 卡才能使用开发者选项/打开 USB 调试,我会心的笑了。。。说多了都是泪

simple 专栏文章:[精华帖] 社区历年精华帖分类归总 中提及了此贴 12月13日 14:44

第一次看到用其他语言写的 为楼主点个赞

simple [精彩盘点] TesterHome 社区 2018 年 度精华帖 中提及了此贴 01月07日 12:08

已经用 Python 又撸了一遍

楼主就没有想过开发板?如果手机能通过网络连接到 stf,而不是通过 USB 线就好

@codeskyblue @ 王小夫 因为很多地方需要定制,需要重新开发,打算用 python 或 java 写,你们 python 写的开源不。借鉴借鉴!膜拜下。

需要 登录 後方可回應,如果你還沒有帳號按這裡 注册