一、screencap命令介绍
安卓系统中screencap命令是一个屏幕截图命令。以下是该命令的帮助说明:
在终端通过adb shell命令执行屏幕截图,可以将当前的屏幕保存为png图片。如下所示:
如果命令不带-p参数,会将屏幕的原始数据直接输出到终端。
二、screencap源码分析
1.screencap源码路径
screencap源码路径位于:
//源码路径frameworks/base/cmds/screencap/screencap.cpp//编译配置文件,Android.bp代替了Android.mkframeworks/base/cmds/screencap/Android.bp
2.screencap中的Android.bp分析
该源码使用Android.bp文件配置来编译的。Android.bp文件信息如下(顺便学习Android.bp的语法了):
//cc_binary表示需要编译成二进制可执行文件cc_binary { //name表示编译之后的名称 name: "screencap", //srcs表示编译源文件 srcs: ["screencap.cpp"], //shared_libs表示依赖了哪些动态库 shared_libs: [ "libcutils", "libutils", "libbinder", "libhwui", "libui", "libgui", ], //cflags表示指定编译器的编译选项 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 .可以通过修改screencap源码中的编码类型生成更小的图片比如如果需要通过screencap屏幕截图实现电脑同屏,那就需要更小的图片加快传输。
如果你对安卓系统相关的开发学习感兴趣:
可加作者的QQ群(1017017661),本群专注安卓系统方面的技术,欢迎加群技术交流。