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

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

第一次写文章,希望为社区做点贡献。
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 条回复 时间 点赞

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

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

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

心向东 回复

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

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

心向东 回复

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

bauul 回复

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

codeskyblue 回复

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

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

期待楼主后续~

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

憋搞了

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

你已经成功变成了一名资深运维人员😂
@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 版本的...

楼主太强了😱

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

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

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

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

已经用 Python 又撸了一遍

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

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

恒温 将本帖设为了精华贴 03月06日 17:26

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

作者是个 stf 资深用户呀

厉害,搞一下

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