一、screencap命令介绍
二、screencap源码分析
screencap源码路径位于:
//源码路径
frameworks/base/cmds/screencap/screencap.cpp
//编译配置文件,Android.bp代替了Android.mk
frameworks/base/cmds/screencap/Android.bp
//cc_binary表示需要编译成二进制可执行文件
cc_binary {
//name表示编译之后的名称
"screencap", :
//srcs表示编译源文件
["screencap.cpp"], :
//shared_libs表示依赖了哪些动态库
[ :
"libcutils",
"libutils",
"libbinder",
"libhwui",
"libui",
"libgui",
],
//cflags表示指定编译器的编译选项
[ :
"-Wall",
"-Werror",
"-Wunused",
"-Wunreachable-code",
],
}
3. screencap.cpp分析
screencap中的main方法如下:
int main(int argc, char** argv)
{
//获取系统的显示列表
std::optional<PhysicalDisplayId> displayId = SurfaceComposerClient::getInternalDisplayId();
if (!displayId) {
fprintf(stderr, "Failed to get token for internal displayn");
return 1;
}
const char* pname = argv[0];
bool png = false;
int c;
//参数解析
while ((c = getopt(argc, argv, "phd:")) != -1) {
switch (c) {
case 'p':
//指定-p表示生成png图片
png = true;
break;
case 'd':
displayId = atoll(optarg);
break;
case '?':
case 'h':
usage(pname, *displayId);
return 1;
}
}
argc -= optind;
argv += optind;
int fd = -1;
const char* fn = NULL;
if (argc == 0) {
fd = dup(STDOUT_FILENO);
} else if (argc == 1) {
fn = argv[0];
fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664);
if (fd == -1) {
fprintf(stderr, "Error opening file: %s (%s)n", fn, strerror(errno));
return 1;
}
const int len = strlen(fn);
if (len >= 4 && 0 == strcmp(fn+len-4, ".png")) {
png = true;
}
}
if (fd == -1) {
usage(pname, *displayId);
return 1;
}
void const* mapbase = MAP_FAILED;
ssize_t mapsize = -1;
void* base = NULL;
uint32_t w, s, h, f;
size_t size = 0;
// setThreadPoolMaxThreadCount(0) actually tells the kernel it's
// not allowed to spawn any additional threads, but we still spawn
// a binder thread from userspace when we call startThreadPool().
// See b/36066697 for rationale
ProcessState::self()->setThreadPoolMaxThreadCount(0);
ProcessState::self()->startThreadPool();
ui::Dataspace outDataspace;
sp<GraphicBuffer> outBuffer;
//使用ScreenshotClient屏幕截图操作
status_t result = ScreenshotClient::capture(*displayId, &outDataspace, &outBuffer);
if (result != NO_ERROR) {
close(fd);
return 1;
}
result = outBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base);
if (base == nullptr || result != NO_ERROR) {
String8 reason;
if (result != NO_ERROR) {
reason.appendFormat(" Error Code: %d", result);
} else {
reason = "Failed to write to buffer";
}
fprintf(stderr, "Failed to take screenshot (%s)n", reason.c_str());
close(fd);
return 1;
}
w = outBuffer->getWidth();
h = outBuffer->getHeight();
s = outBuffer->getStride();
f = outBuffer->getPixelFormat();
size = s * h * bytesPerPixel(f);
if (png) {
//创建图片信息对象
const SkImageInfo info =
SkImageInfo::Make(w, h, flinger2skia(f), kPremul_SkAlphaType,
dataSpaceToColorSpace(outDataspace));
SkPixmap pixmap(info, base, s * bytesPerPixel(f));
struct FDWStream final : public SkWStream {
size_t fBytesWritten = 0;
int fFd;
FDWStream(int f) : fFd(f) {}
size_t bytesWritten() const override { return fBytesWritten; }
bool write(const void* buffer, size_t size) override {
fBytesWritten += size;
return size == 0 || ::write(fFd, buffer, size) > 0;
}
} fdStream(fd);
//屏幕数据进行编码,这个地方传入的kPNG表示png图片格式编码
(void)SkEncodeImage(&fdStream, pixmap, SkEncodedImageFormat::kPNG, 100);
if (fn != NULL) {
//通知图片视频库更新
notifyMediaScanner(fn);
}
} else {
//如果没指定生成png,后面直接输出到终端了,一大堆乱码
uint32_t c = dataSpaceToInt(outDataspace);
write(fd, &w, 4);
write(fd, &h, 4);
write(fd, &f, 4);
write(fd, &c, 4);
size_t Bpp = bytesPerPixel(f);
for (size_t y=0 ; y<h ; y++) {
write(fd, base, w*Bpp);
base = (void *)((char *)base + s*Bpp);
}
}
close(fd);
if (mapbase != MAP_FAILED) {
munmap((void *)mapbase, mapsize);
}
return 0;
}
在以上方法中有一个通知媒体库的调用notifyMediaScanner,实现代码如下:
static status_t notifyMediaScanner(const char* fileName) {
String8 cmd("am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://");
cmd.append(fileName);
cmd.append(" > /dev/null");
int result = system(cmd.string());
if (result < 0) {
fprintf(stderr, "Unable to broadcast intent for media scanner.n");
return UNKNOWN_ERROR;
}
return NO_ERROR;
}
该方法通过am命令发送广播通知媒体扫描实现图片或者视频加入媒体库。 受该命令启发,如果我们通过adb push图片或者视频到sdcard之后,如果需要更新图片或者视频到视频库,可以使用am命令发送广播通知。对于通过adb控制手机来说相当有用。
adb shell am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://xxxx
screencap官方实现的时候只支持了png格式的图片格式。通过查找SkEncodedImageFormat类中的定义,原来是可以通过修改支持很多中图片格式的。比如jpeg、webp等图片格式。SkEncodedImageFormat中定义的所有支持的格式如下:
enum class SkEncodedImageFormat {
#ifdef SK_BUILD_FOR_GOOGLE3
kUnknown,
#endif
kBMP,
kGIF,
kICO,
kJPEG,
kPNG,
kWBMP,
kWEBP,
kPKM,
kKTX,
kASTC,
kDNG,
kHEIF,
};
4、总结
-
通过am发送广播命令通知媒体库更新
通过adb push图片或者视频之后,如果想让媒体库立即更新。可以通过发送如下命令通知媒体库扫描。如下:
adb shell am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://xxxx
2. 可以通过修改screencap源码中的编码类型生成更小的图片
比如如果需要通过screencap屏幕截图实现电脑同屏,那就需要更小的图片加快传输。
原文始发于微信公众号(哆啦安全):screencap命令源码分析
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论