STF STF 框架之 minicap 工具

易寒 · August 14, 2015 · Last by DJL箫氏 replied at March 04, 2019 · 14425 hits
本帖已被设为精华帖!

stf
minicap

minicap介绍

WEB 端批量移动设备管理控制工具 STF 的环境搭建和运行文章了解到STF这个工具,然后试用了一下。最近在做一个测试工具,发现Android原生的截图工具截图非常缓慢,然后想起了stf工具中截图非常快,甚至连执行monkey的动作都能在web端查看,这就很爽了,所以在github上提了一个Issue,询问这个是如何实现的,很快得到答复,stf自己写了一个工具叫minicap用来替代原生的screencap,这个工具是stf框架的依赖工具。

minicap使用

minicap工具是用NDK开发的,属于Android的底层开发,该工具分为两个部分,一个是动态连接库.so文件,一个是minicap可执行文件。但不是通用的,因为CPU架构的不同分为不同的版本文件,STF提供的minicap文件根据CPU 的ABI分为如下4种:

.
├── bin
├── arm64-v8a
├── minicap
└── minicap-nopie
├── armeabi-v7a
├── minicap
└── minicap-nopie
├── x86
├── minicap
└── minicap-nopie
└── x86_64
├── minicap
└── minicap-nopie
└── shared
├── android-10
└── armeabi-v7a
└── minicap.so
├── android-14
├── armeabi-v7a
└── minicap.so
└── x86
└── minicap.so
├── android-15
├── armeabi-v7a
└── minicap.so
└── x86
└── minicap.so
├── android-16
├── armeabi-v7a
└── minicap.so
└── x86
└── minicap.so
├── android-17
├── armeabi-v7a
└── minicap.so
└── x86
└── minicap.so
├── android-18
├── armeabi-v7a
└── minicap.so
└── x86
└── minicap.so
├── android-19
├── armeabi-v7a
└── minicap.so
└── x86
└── minicap.so
├── android-21
├── arm64-v8a
└── minicap.so
├── armeabi-v7a
└── minicap.so
├── x86
└── minicap.so
└── x86_64
└── minicap.so
├── android-22
├── arm64-v8a
└── minicap.so
├── armeabi-v7a
└── minicap.so
├── x86
└── minicap.so
└── x86_64
└── minicap.so
├── android-9
└── armeabi-v7a
└── minicap.so
└── android-M
├── arm64-v8a
└── minicap.so
├── armeabi-v7a
└── minicap.so
├── x86
└── minicap.so
└── x86_64
└── minicap.so

从上面可以看出,minicap可执行文件分为4种,分别针对arm64-v8aarmeabi-v7a,x86,x86_64 架构。而minicap.so文件在这个基础上还要分为不同的sdk版本。

获取设备的CPU版本和系统版本

CPU版本

adb shell getprop ro.product.cpu.abi | tr -d '\r'

58deMacBook-Pro:minicap wuxian$ adb shell getprop ro.product.cpu.abi | tr -d '\r'
armeabi-v7a

系统版本

adb shell getprop ro.build.version.sdk | tr -d '\r'

58deMacBook-Pro:minicap wuxian$ adb shell getprop ro.build.version.sdk | tr -d '\r'
22

将文件push到手机

根据上面获取的信息,将适合设备的可执行文件和.so文件push到手机的/data/local/tmp目录下,如果你不想自己build这些文件可以去STF框架的源码下找到vendor/minicap文件夹下找到这些文件,我上面的tree信息就是我在stf根目录vendor/minicap下打印的,所以我们将这两个文件导入到我手机的/data/local/tmp目录下:

shell@shamu:/data/local/tmp $ ls -l
-rw-rw-r-- shell shell 1053609 2015-08-07 19:19 1.png
-rwxr-xr-x shell shell 1062992 2015-08-03 12:02 busybox
-rwxr-xr-x shell shell 358336 2015-08-03 12:02 busybox1
drwxrwxrwx shell shell 2015-07-21 15:16 dalvik-cache
-rw-r--r-- shell shell 193 2015-08-13 19:44 krperm.txt
-rwxrwxrwx shell shell 370424 2015-08-07 18:16 minicap
-rw-rw-rw- shell shell 13492 2015-08-07 18:26 minicap.so
-rw------- shell shell 11192 2015-08-06 10:46 ui.xml
-rw------- shell shell 2501 2015-08-07 10:36 uidump.xml

启动工具

首先我们测试一下我们的minicap工具是否可用,命令如下(其中-P后面跟的参数为你屏幕的尺寸,你可以修改成你自己设备的尺寸):

adb shell LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap -P 1440x2560@1440x2560/0 -t

最后输出OK就表明minicap可用:

58deMacBook-Pro:minicap wuxian$ adb shell LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap -P 1440x2560@1440x2560/0 -t
PID: 7105
INFO: Using projection 1440x2560@1440x2560/0
INFO: (external/MY_minicap/src/minicap_22.cpp:240) Creating SurfaceComposerClient
INFO: (external/MY_minicap/src/minicap_22.cpp:243) Performing SurfaceComposerClient init check
INFO: (external/MY_minicap/src/minicap_22.cpp:250) Creating virtual display
INFO: (external/MY_minicap/src/minicap_22.cpp:256) Creating buffer queue
INFO: (external/MY_minicap/src/minicap_22.cpp:261) Creating CPU consumer
INFO: (external/MY_minicap/src/minicap_22.cpp:265) Creating frame waiter
INFO: (external/MY_minicap/src/minicap_22.cpp:269) Publishing virtual display
INFO: (jni/minicap/JpgEncoder.cpp:64) Allocating 11061252 bytes for JPG encoder
INFO: (external/MY_minicap/src/minicap_22.cpp:284) Destroying virtual display
OK

然后我们启动minicap工具,命令如下(就比上面的检测工具少了个-t):
adb shell LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap -P 1440x2560@1440x2560/0

本地端口转发

上面其实是启动了一个socket服务器,我们需要跟该socket服务通信,首先我们要将本地的端口映射到minicap工具上,端口自己随意:

adb forward tcp:1717 localabstract:minicap

获取信息

然后使用命令nc localhost 1717来与minicap通信,然后你会发现好多乱码。

这里写图片描述

效果

上面的乱码我们也看不懂,官方提供了一个demo来看效果,在minicap项目下的example目录,我们来启动该例子:

58deMacBook-Pro:example wuxian$ PORT=9002 node app.js
Listening on port 9002

然后我们在浏览器下输入localhost:9002就可以看到如下效果了:
这里写图片描述

minicap传输的信息解析

我们在上面的nc localhost 1717 那一步可以看出来,minicap工具会不断的向命令行下输出乱码信息,但是这些信息是有规则的,只是我们无法实际查看。但是我们做的工具需要用java来获得该信息,所以弄懂这些格式是很有必要的,结果分析后得出这些信息分3部分

Banner模块(第一部分)

这一部分的信息只在连接后,只发送一次,是一些汇总信息,一般为24个16进制字符,每一个字符都表示不同的信息:

位置 信息
0 版本
1 该Banner信息的长度,方便循环使用
2,3,4,5 相加得到进程id号
6,7,8,9 累加得到设备真实宽度
10,11,12,13 累加得到设备真实高度
14,15,16,17 累加得到设备的虚拟宽度
18,19,20,21 累加得到设备的虚拟高度
22 设备的方向
23 设备信息获取策略

携带图片大小信息和图片二进制信息模块(第二部分)

得到上面的Banner部分处理完成后,以后不会再发送Banner信息,后续只会发送图片相关的信息。那么接下来就接受图片信息了,第一个过来的图片信息的前4个字符不是图片的二进制信息,而是携带着图片大小的信息,我们需要累加得到图片大小。这一部分的信息除去前四个字符,其他信息也是图片的实际二进制信息,比如我们接受到的信息长度为n,那么4~(n-4)部分是图片的信息,需要保存下来。

只携带图片二进制信息模块(第三部分)

每一个变化的界面都会有上面的[携带图片大小信息和图片二进制信息模块],当得到大小后,或许发送过来的数据都是要组装成图片的二进制信息,知道当前屏幕的数据发送完成。
有2种方式可以看出来图片组装完成了:

  • 又遇到第二部分
  • 设定大小的数据已经装满了

Java的实现

/**
*
*/

package com.wuba.utils.screenshot;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import javax.imageio.ImageIO;

import org.apache.log4j.Logger;

import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.IDevice.DeviceUnixSocketNamespace;
import com.android.ddmlib.TimeoutException;
import com.sun.org.apache.bcel.internal.generic.NEW;
import com.wuba.utils.TimeUtil;

/**
* @date 2015年8月12日 上午11:02:53
*/

public class MiniCapUtil implements ScreenSubject{

private Stack<Byte[]> stack = new Stack<Byte[]>();

private Logger LOG = Logger.getLogger(MiniCapUtil.class);
private Banner banner;
private static final int PORT = 1717;
private Socket socket;
private int readBannerBytes = 0;
private int bannerLength = 2;
private int readFrameBytes = 0;
private int frameBodyLength = 0;
private byte[] frameBody = new byte[0];
private IDevice device;
private int total;
private boolean debug = false;

private List<AndroidScreenObserver> observers = new ArrayList<AndroidScreenObserver>();

private byte[] finalBytes = null;

private BufferedImage bufferedImage;

public MiniCapUtil(IDevice device) {
this.device = device;
init();

}

private void init() {
banner = new Banner();
try {
this.device.createForward(PORT, "minicap",
DeviceUnixSocketNamespace.ABSTRACT);
} catch (TimeoutException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (AdbCommandRejectedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

public void takeBufferedImageByMinicap() {
InputStream stream = null;
DataInputStream input = null;
try {
socket = new Socket("localhost", PORT);
while (true) {
stream = socket.getInputStream();
input = new DataInputStream(stream);
byte[] buffer;
int len = 0;
while (len == 0) {
len = input.available();
}
buffer = new byte[len];
input.read(buffer);
LOG.info("length=" + buffer.length);
if (debug) {
continue;
}
byte[] currentBuffer = subByteArray(buffer, 0, buffer.length);
for (int cursor = 0; cursor < len;) {
int byte10 = buffer[cursor] & 0xff;
if (readBannerBytes < bannerLength) {
switch (readBannerBytes) {
case 0:
// version
banner.setVersion(byte10);
break;
case 1:
// length
bannerLength = byte10;
banner.setLength(byte10);
break;
case 2:
case 3:
case 4:
case 5:
// pid
int pid = banner.getPid();
pid += (byte10 << ((readBannerBytes - 2) * 8)) >>> 0;
banner.setPid(pid);
break;
case 6:
case 7:
case 8:
case 9:
// real width
int realWidth = banner.getReadWidth();
realWidth += (byte10 << ((readBannerBytes - 6) * 8)) >>> 0;
banner.setReadWidth(realWidth);
break;
case 10:
case 11:
case 12:
case 13:
// real height
int realHeight = banner.getReadHeight();
realHeight += (byte10 << ((readBannerBytes - 10) * 8)) >>> 0;
banner.setReadHeight(realHeight);
break;
case 14:
case 15:
case 16:
case 17:
// virtual width
int virtualWidth = banner.getVirtualWidth();
virtualWidth += (byte10 << ((readBannerBytes - 14) * 8)) >>> 0;
banner.setVirtualWidth(virtualWidth);

break;
case 18:
case 19:
case 20:
case 21:
// virtual height
int virtualHeight = banner.getVirtualHeight();
virtualHeight += (byte10 << ((readBannerBytes - 18) * 8)) >>> 0;
banner.setVirtualHeight(virtualHeight);
break;
case 22:
// orientation
banner.setOrientation(byte10 * 90);
break;
case 23:
// quirks
banner.setQuirks(byte10);
break;
}

cursor += 1;
readBannerBytes += 1;

if (readBannerBytes == bannerLength) {
LOG.info(banner.toString());
}
} else if (readFrameBytes < 4) {
// 第二次的缓冲区中前4位数字和为frame的缓冲区大小
frameBodyLength += (byte10 << (readFrameBytes * 8)) >>> 0;
cursor += 1;
readFrameBytes += 1;
total = frameBodyLength;

} else {
LOG.info("图片大小 : " + total);
// LOG.info("frame body部分");
// LOG.info(String.format("设想图片的大小 : %d", total));
if (len - cursor >= frameBodyLength) {
byte[] subByte = subByteArray(currentBuffer,
cursor, cursor + frameBodyLength);
frameBody = byteMerger(frameBody, subByte);
if ((frameBody[0] != -1) || frameBody[1] != -40) {
LOG.error(String
.format("Frame body does not start with JPG header"));
return;
}
LOG.info(String.format("实际图片的大小 : %d",
frameBody.length));
if (finalBytes == null) {
finalBytes = subByteArray(frameBody, 0,
frameBody.length);
new Thread(new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
try {
createImageFromByte();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
cursor += frameBodyLength;
frameBodyLength = 0;
readFrameBytes = 0;
frameBody = new byte[0];
} else {
// LOG.debug(String.format("body(len=%d)", len
// - cursor));
byte[] subByte = subByteArray(currentBuffer,
cursor, len);
frameBody = byteMerger(frameBody, subByte);
frameBodyLength -= (len - cursor);
readFrameBytes += (len - cursor);
cursor = len;
}
}
}
}
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

finally {
if (socket != null && socket.isConnected()) {
try {
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

}

private synchronized void createImageFromByte() throws IOException {
if (finalBytes.length == 0) {
LOG.info("frameBody大小为0");
}
InputStream in = new ByteArrayInputStream(finalBytes);
BufferedImage bufferedImage = ImageIO.read(in);
notifyObservers(bufferedImage);
// String filePath = String.format("0.jpg");
// LOG.info(filePath);
// ImageIO.write(bufferedImage, "jpg", new File(filePath));
finalBytes = null;
}

// java合并两个byte数组
private static byte[] byteMerger(byte[] byte_1, byte[] byte_2) {
byte[] byte_3 = new byte[byte_1.length + byte_2.length];
System.arraycopy(byte_1, 0, byte_3, 0, byte_1.length);
System.arraycopy(byte_2, 0, byte_3, byte_1.length, byte_2.length);
return byte_3;
}

private static byte[] subByteArray(byte[] byte1, int start, int end) {
byte[] byte2 = new byte[end - start];
System.arraycopy(byte1, start, byte2, 0, end - start);
return byte2;
}

private String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder();
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv + " ");
}
return stringBuilder.toString();

}

/*
* (non-Javadoc)
*
* @see
* com.wuba.utils.screenshot.AndroidScreenSubject#registerObserver(com.wuba
* .utils.screenshot.AndroidScreenObserver)
*/

@Override
public void registerObserver(AndroidScreenObserver o) {
// TODO Auto-generated method stub
observers.add(o);

}

/*
* (non-Javadoc)
*
* @see
* com.wuba.utils.screenshot.AndroidScreenSubject#removeObserver(com.wuba
* .utils.screenshot.AndroidScreenObserver)
*/

@Override
public void removeObserver(AndroidScreenObserver o) {
// TODO Auto-generated method stub
int index = observers.indexOf(o);
if (index != -1) {
observers.remove(o);
}

}

/*
* (non-Javadoc)
*
* @see com.wuba.utils.screenshot.AndroidScreenSubject#notifyObservers()
*/

@Override
public void notifyObservers(BufferedImage image) {
// TODO Auto-generated method stub
for (AndroidScreenObserver observer : observers) {
observer.frameImageChange(image);
}
}

}

总结

1.在实际过程由于minicap发送信息的速度很快,如果不及时处理,会造成某一次获取的数据是将minicap多次发送的数据一起处理了,这就会造成错误。所以上面的代码是将生成BufferImage的操作放到了线程中,但是最好是将获取socket数据部分和解析数据部分独立开来,获取socket数据将获取到的数据立即放到队列中,然后立马得到下一次数据的获取,数据解析部分在独立线程中来获取队列中的信息来解析。这样就能避免上面提到的问题。
2.目前不支持下面三款机器和模拟器

  • Xiaomi "HM NOTE 1W" (Redmi Note 1W),
  • Huawei "G750-U10" (Honor 3X)
  • Lenovo "B6000-F" (Yoga Tablet 8).

3.我们实测的速度(针对N6)原生为5秒左右,minicap在1秒内。

源码

用java写了一个小demo,有兴趣可以下载体验下
minicap_java

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 29 条回复 时间 点赞

碉堡了!

好强大!

简直了

按照LZ的文章步骤不能成功。

很多手机直接push对应的minicap 和minicap.so 文件后,
执行adb shell LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap -P 480x854@480x854/0 -t 命令会出现错误。

错误信息“/system/bin/sh: /data/local/tmp/minicap: can't execute: Permission denied”

建议按照https://github.com/openstf/minicap 中的描述进行。

总结一下直接的步骤:
git clone https://github.com/openstf/minicap.git
cd minicap
git submodule init
git submodule update
ndk-build

注意ndk的版本,android-ndk-r10e-linux-x86_64.bin

由于直git的minicap 项目依赖libjpeg-turbo模块 ,所以需要进行
git submodule init
git submodule update

注意:直接下载项目和git clone 项目是不一样的,所以要用git clone 。否则submodule的操作不会成功。

还需要注意一点:

#!/usr/bin/env bash
# Fail on error, verbose output
set -exo pipefail
# Build project
ndk-build 1>&2
# Figure out which ABI and SDK the device has
abi=$(adb shell getprop ro.product.cpu.abi | tr -d '\r')
sdk=$(adb shell getprop ro.build.version.sdk | tr -d '\r')
rel=$(adb shell getprop ro.build.version.release | tr -d '\r')
# PIE is only supported since SDK 16
if (($sdk >= 16)); then
bin=minicap
else
bin=minicap-nopie
fi
args=
if [ "$1" = "autosize" ]; then
set +o pipefail
size=$(adb shell dumpsys window | grep -Eo 'init=\d+x\d+' | head -1 | cut -d= -f 2)
if [ "$size" = "" ]; then
w=$(adb shell dumpsys window | grep -Eo 'DisplayWidth=\d+' | head -1 | cut -d= -f 2)
h=$(adb shell dumpsys window | grep -Eo 'DisplayHeight=\d+' | head -1 | cut -d= -f 2)
size="${w}x${h}"
fi
args="-P $size@$size/0"
set -o pipefail
shift
fi
# Create a directory for our resources
dir=/data/local/tmp/minicap-devel
adb shell "mkdir $dir 2>/dev/null"
# Upload the binary
adb push libs/$abi/$bin $dir
# Upload the shared library
if [ -e jni/minicap-shared/aosp/libs/android-$rel/$abi/minicap.so ]; then
adb push jni/minicap-shared/aosp/libs/android-$rel/$abi/minicap.so $dir
else
adb push jni/minicap-shared/aosp/libs/android-$sdk/$abi/minicap.so $dir
fi
# Run!
adb shell LD_LIBRARY_PATH=$dir $dir/$bin $args "$@"
# Clean up
adb shell rm -r $dir

在一些电脑上面

size=$(adb shell dumpsys window | grep -Eo 'init=\d+x\d+' | head -1 | cut -d= -f 2)

需要修改成

size=$(adb shell dumpsys window | grep -Eo 'init=[0-9]+x[0-9]+' | head -1 | cut -d= -f 2)

不然无法获取大height和width

真碉堡!

在github看得一头雾水,搜到了楼主这篇好文,太赞了!!
补充一下:
运行 adb shell LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap -P 1080x1800@1080x1800/0 -t
提示Permission denied的,试下 adb shell chmod 0755 /data/local/tmp/minicap
另外,minicap和minicap.so,最好还是去官方github上直接下载自己手机对应的 https://github.com/openstf/stf/tree/master/vendor/minicap

58deMacBook-Pro:example wuxian$ PORT=9002 node app.js
Listening on port 9002
这步骤不懂····PORT 放前面么。 还有我node app.js 就会报错的说
Error: Cannot find module 'ws'
at Function.Module._resolveFilename (module.js:336:15)
at Function.Module._load (module.js:278:25)
at Module.require (module.js:365:17)
at require (module.js:384:17)
at Object. (/Users/lemon/work/git/minicap/example/app.js:1:85)
at Module._compile (module.js:460:26)
at Object.Module._extensions..js (module.js:478:10)
at Module.load (module.js:355:32)
at Function.Module._load (module.js:310:12)
at Function.Module.runMain (module.js:501:10)

汪汪 [Topic was deleted] 中提及了此贴 29 Jun 20:34

为什么我最后页面上只显示一个红框

#8楼 @ws328686288 你需要先npm install

@doctorq
手机cpu :armeabi-v7a
SDK: 23
Android手机设备已root
同时adb remount
执行adb shell LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/ minicap.so -P 1280x720@1280x720/0 -t
报错: /system/bin/sh: /data/local/tmp/minicap.so: can't execute: Permission denied
请问minicap.so为什么没有访问权限呢?

@jianjianjianbing 我是从github上直接下载的,权限问题没有解决,请指教

minicap-nopie的作用是:

if (($sdk >= 16)); then
bin=minicap
else
bin=minicap-nopie
fi

所以sdk level>= 16 push minicamp, 否则pushminicap-nopie

上面代码来自于:github/minicap/run.sh

#12楼 @victors 基础问题 chmod 777 minicap; chmod 777 minicap.so

亲 有没有 C++ 或MFC 的 图片解析 代码。。我转不过来。。。解不出图片。。。帮帮忙
@DoctorQ 博主大大 或 网友 谁 帮个忙

#15楼 @lfjking @victors @kilmer 谁有 时间 帮个忙 昨天 图片 解了一天 没成。。 崩溃了

#5楼 @kilmer 我用MFC 解 图片 出来 图片 颜色 不对会是 什么原因呢?

这个没有stf配置出来的顺滑,是什么原因呢

汪汪 STF 框架之 minitouch 工具 中提及了此贴 06 Dec 15:53
易寒 关于自动化中截图操作讨论 中提及了此贴 20 Dec 12:27
wangst 关于自动化中截图操作讨论 中提及了此贴 20 Dec 16:34

你好请问下,读取到的16进制乱码,用python如何处理。

携带图片大小信息和图片二进制信息模块(第二部分),只携带图片二进制信息模块(第三部分) // 我觉得这个标题会让人觉得minicap会发送三部分数据,而实际只有两种形式,第一种 Banner,第二种:包含图片描述信息和图片内容的二进制块。发送完Banner后,就是不断的 图片描述+内容 的二进制块

codefarmer 回复

你好大哥 你解析出来了吗 我现在也在做这方面 转不过来呀

shuta STF 实时显示设备截图功能源码分析 中提及了此贴 27 May 11:01

多谢分享,特意注册个号来回复下。
我在windows上用Android studio直接打开,能正常运行。我的手机是4.4的。
遇到的问题有push 库到手机上时,代码需要改下:

问题一:
要把原来代码的File.separator改成"/",否则push成了tmp\minicap
修改前:
// 将minicap的可执行文件和.so文件一起push到设备中
device.pushFile(minicapBinFile.getAbsolutePath(), REMOTE_PATH
+ File.separator + MINICAP_BIN);
device.pushFile(minicapSoFile.getAbsolutePath(), REMOTE_PATH
+ File.separator + MINICAP_SO);
修改后:
// 将minicap的可执行文件和.so文件一起push到设备中
device.pushFile(minicapBinFile.getAbsolutePath(), REMOTE_PATH
+ "/" + MINICAP_BIN);
device.pushFile(minicapSoFile.getAbsolutePath(), REMOTE_PATH
+ "/" + MINICAP_SO);

问题二:
getADBPath里面的adb路径需要写死。
修改前:
if(adbPath != null){
adbPath += File.separator + adbPlatformTools;
}else {
return null;
}
adbPath += File.separator + "adb";
修改后:
if(adbPath != null) {
adbPath += File.separator + adbPlatformTools;
} else {
adbPath = "D:\Software\android_tools\android-sdk\platform-tools";
}
}
adbPath += File.separator + "adb.exe";

git submodule init 失败的可以试试先 git submodule sync

陈恒捷 Appium IOS 测试速度优化策略 中提及了此贴 07 Aug 13:11
water iOS-minicap + WDA 实现 ios 远程真机测试 中提及了此贴 26 Sep 09:22

楼主你好,我也再看这个工具,这个工具是截图后传到 pc 上,如果我不需要传入到 pc 上,只保留到 sdcard上,有什么办法么?

楼主,你好;
从github上下载的minicap没有你所说的这个文件夹vendor/minicap,要怎么办?
你那有编译好的吗?

真厉害 对这个文章相见恨晚,学习了

PID: 7105
INFO: Using projection 1080x1920@1080x1920/0

请教,执行完adb shell LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap -P 1080x1920@1080x1920/0 -t后,只显示这两行,并没有出现OK字样,是出了什么问题,需要清理什么杀掉进程吗 windows和shell下面都没有这个pid

adb forward出来的端口只能本地访问,大家一般是怎么解决的,修改重编译adb还是本地写代码转发,还是怎样?

rayncc 回复

为啥我运行是一个红框:

lb 回复

选在stf github上面比较老的版本 比如 v1.2.0就行了

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up