如何捕获android设备屏幕内容? [复制]

Posted

技术标签:

【中文标题】如何捕获android设备屏幕内容? [复制]【英文标题】:How to capture the android device screen content? [duplicate] 【发布时间】:2011-03-05 07:11:36 【问题描述】:

可能重复:How to programatically take a screenshot on android?

如何捕获android设备屏幕内容并使用快照数据制作图像文件?我应该使用哪个 API,或者在哪里可以找到相关资源?

顺便说一句: 不是相机快照,而是设备屏幕

【问题讨论】:

【参考方案1】:

使用以下代码:

Bitmap bitmap;
View v1 = MyView.getRootView();
v1.setDrawingCacheEnabled(true);
bitmap = Bitmap.createBitmap(v1.getDrawingCache());
v1.setDrawingCacheEnabled(false);

这里的MyViewView,我们需要通过它包含在屏幕中。您也可以通过这种方式从任何View 中获取DrawingCache(没有getRootView())。

还有另一种方法.. 如果我们将ScrollView 作为根视图,那么最好使用以下代码,

LayoutInflater inflater = (LayoutInflater) this.getSystemService(LAYOUT_INFLATER_SERVICE);
FrameLayout root = (FrameLayout) inflater.inflate(R.layout.activity_main, null); // activity_main is UI(xml) file we used in our Activity class. FrameLayout is root view of my UI(xml) file.
root.setDrawingCacheEnabled(true);
Bitmap bitmap = getBitmapFromView(this.getWindow().findViewById(R.id.frameLayout)); // here give id of our root layout (here its my FrameLayout's id)
root.setDrawingCacheEnabled(false);

这里是getBitmapFromView() 方法

public static Bitmap getBitmapFromView(View view) 
        //Define a bitmap with the same size as the view
        Bitmap returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),Bitmap.Config.ARGB_8888);
        //Bind a canvas to it
        Canvas canvas = new Canvas(returnedBitmap);
        //Get the view's background
        Drawable bgDrawable =view.getBackground();
        if (bgDrawable!=null) 
            //has background drawable, then draw it on the canvas
            bgDrawable.draw(canvas);
        else 
            //does not have background drawable, then draw white background on the canvas
            canvas.drawColor(Color.WHITE);
        // draw the view on the canvas
        view.draw(canvas);
        //return the bitmap
        return returnedBitmap;
    

它将显示整个屏幕,包括隐藏在您的ScrollView 中的内容 于 2016 年 4 月 20 日更新 还有另一种更好的截图方式。这里我截取了WebView的截图。

WebView w = new WebView(this);
    w.setWebViewClient(new WebViewClient()
    
        public void onPageFinished(final WebView webView, String url) 

            new Handler().postDelayed(new Runnable()
                @Override
                public void run() 
                    webView.measure(View.MeasureSpec.makeMeasureSpec(
                                    View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED),
                            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
                    webView.layout(0, 0, webView.getMeasuredWidth(),
                            webView.getMeasuredHeight());
                    webView.setDrawingCacheEnabled(true);
                    webView.buildDrawingCache();
                    Bitmap bitmap = Bitmap.createBitmap(webView.getMeasuredWidth(),
                            webView.getMeasuredHeight(), Bitmap.Config.ARGB_8888);

                    Canvas canvas = new Canvas(bitmap);
                    Paint paint = new Paint();
                    int height = bitmap.getHeight();
                    canvas.drawBitmap(bitmap, 0, height, paint);
                    webView.draw(canvas);

                    if (bitmap != null) 
                        try 
                            String filePath = Environment.getExternalStorageDirectory()
                                    .toString();
                            OutputStream out = null;
                            File file = new File(filePath, "/webviewScreenShot.png");
                            out = new FileOutputStream(file);

                            bitmap.compress(Bitmap.CompressFormat.PNG, 50, out);
                            out.flush();
                            out.close();
                            bitmap.recycle();
                         catch (Exception e) 
                            e.printStackTrace();
                        
                    
                
            , 1000);
        
    );

希望这会有所帮助..!

【讨论】:

这种方式只是帮助您捕获您的应用程序,对吧?如果我想捕获整个屏幕怎么办? 不幸的是,在捕获对话框之类的内容以及显示键盘时,这似乎不起作用。 :( Android Studio 给我错误:“找不到方法getBitmapFromView”,那是什么方法? 对于其他想知道相同的人,here 是getBitmapFromView 方法。 此方法适用于软件层视图。但它不适用于视频(VideoView 或 Webview 中的视频)。将捕获所有其他像素,但视频仅显示黑色。我认为原因是我们可以获得软件层位图,但无法获得视频的位图。有人知道如何用视频截取 Webview 的屏幕截图吗?【参考方案2】:

AFAIK,目前所有捕获 android 屏幕截图的方法都使用 /dev/graphics/fb0 帧缓冲区。这包括ddms。它确实需要 root 才能从此流中读取。 ddms 使用 adbd 请求信息,因此不需要 root,因为 adb 具有从 /dev/graphics/fb0 请求数据所需的权限。

帧缓冲区包含 2+“帧”的 RGB565 图像。如果您能够读取数据,则必须知道屏幕分辨率才能知道获取图像需要多少字节。每个像素为 2 字节,因此如果屏幕分辨率为 480x800,则您必须为图像读取 768,000 字节,因为 480x800 RGB565 图像有 384,000 像素。

【讨论】:

注意:并非所有帧缓冲区都是 RGB565。有不同的格式。 嗨,Ryan,你是绝对正确的,我非常感谢你的回答.. 请给我一个示例代码,描述如何从 /dev/graphics/fb0 捕获屏幕.. 是的,这是唯一的方法,不需要 root Ryan,您能否详细说明导致 ADBD 可以访问帧缓冲区的具体情况?我反编译了 DDMS 代码并尝试打包 DDMS 代码以在设备本身上进行屏幕捕获(作为应用程序的一部分),但我不知道如何访问帧缓冲区的权限。 就 Honeycomb 及以后的版本而言,此答案不正确且已过时。您不能保证所有屏幕内容最终都会出现在 fb0 中,因为可能会使用更直接的渲染方法,例如硬件覆盖。最好看看 Ducky Chen 提供的答案。【参考方案3】:

对于较新的Android平台,可以在/system/bin中执行系统实用程序screencap来获取屏幕截图,无需root权限。 你可以试试/system/bin/screencap -h看看如何在adb或任何shell下使用。

顺便说一下,我认为这种方法只适用于单张快照。 如果我们要捕获多个帧进行屏幕播放,它会太慢。 我不知道是否有任何其他方法可以更快地截屏。

【讨论】:

在 ICS 和 Honeycomb 上,就像魅力一样。 /system/bin/screencap 很棒,但似乎强制最大读取速率为每秒 3 个屏幕:( @AlexCohn:能否请您发布示例代码。谢谢 它在 adb shell 中运行良好。如何让它从代码运行?这不起作用: String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "outhope.png";进程proc = Runtime.getRuntime().exec("/system/bin/screencap -p" + path); 捕获屏幕的应用程序需要更高的权限。这是为了安全问题(防止后门)。结果,即使您重新编译源代码以拥有一个新应用程序,除非您以 root 身份运行它,否则您无法获得预期的结果。【参考方案4】:

[基于Android源码:]

在 C++ 端,SurfaceFlinger 实现了 captureScreen API。这暴露在 binder IPC 接口上,每次返回一个新的 ashmem 区域,其中包含来自屏幕的原始像素。实际截图是通过 OpenGL 截取的。

对于系统 C++ 客户端,接口通过 ScreenshotClient 类公开,定义在<surfaceflinger_client/SurfaceComposerClient.h> for Android 4.1 使用 <gui/SurfaceComposerClient.h>

在JB之前,要在C++程序中截图,这就够了:

ScreenshotClient ssc;
ssc.update();

有了JB和多显示器,就变得稍微复杂了:

ssc.update(
    android::SurfaceComposerClient::getBuiltInDisplay(
        android::ISurfaceComposer::eDisplayIdMain));

然后就可以访问了:

do_something_with_raw_bits(ssc.getPixels(), ssc.getSize(), ...);

使用 Android 源代码,您可以编译自己的共享库来访问该 API,然后通过 JNI 将其公开给 Java。要为您的应用创建屏幕截图,该应用必须具有READ_FRAME_BUFFER 权限。 但即便如此,显然您只能从系统应用程序创建屏幕截图,即使用与系统相同的密钥签名的应用程序。 (这部分我还是不太明白,因为我对Android Permissions系统不够熟悉。)

这是一段代码,适用于 JB 4.1 / 4.2:

#include <utils/RefBase.h>
#include <binder/IBinder.h>
#include <binder/MemoryHeapBase.h>
#include <gui/ISurfaceComposer.h>
#include <gui/SurfaceComposerClient.h>

static void do_save(const char *filename, const void *buf, size_t size) 
    int out = open(filename, O_RDWR|O_CREAT, 0666);
    int len = write(out, buf, size);
    printf("Wrote %d bytes to out.\n", len);
    close(out);


int main(int ac, char **av) 
    android::ScreenshotClient ssc;
    const void *pixels;
    size_t size;
    int buffer_index;

    if(ssc.update(
        android::SurfaceComposerClient::getBuiltInDisplay(
            android::ISurfaceComposer::eDisplayIdMain)) != NO_ERROR )
        printf("Captured: w=%d, h=%d, format=%d\n");
        ssc.getWidth(), ssc.getHeight(), ssc.getFormat());
        size = ssc.getSize();
        do_save(av[1], pixels, size);
    
    else
        printf(" screen shot client Captured Failed");
    return 0;

【讨论】:

是否可以在android应用程序中实现相同的功能。 这工作完美想在更新方法中添加错误检查,但我想为其他用户添加额外的 cmets。如果您想以不同的分辨率(缩小/放大)获取屏幕内容,只需在调用“更新”函数时将其作为参数,例如 ssc.update(displayID,width,height) eclipse 向我显示无效的参数'候选人是:void do_save(const char *, const void *, ?)' 有什么帮助吗? 我尝试编译这个,但我无法让它工作 - 我已经投入了一些时间。首先,大多数包含在 Android 源代码的不同存储库中。现在它无法编译!在 Cygwin 上尝试过,但缺少 stdatomic.h 并且使用 gcc-4.9 它在那里,但错误到处都是。请提供一个最小的工作示例和一些提示! 请注意,这些说明适用于 系统 应用程序。您无法轻松地将代码与 NDK 或可安装应用程序一起使用。通常您将系统应用程序构建为 Android 本身的一部分,例如就像编译 AOSP 或 Cyanogenmod 时一样。要将这些说明与 NDK 一起使用,您必须从 AOSP 复制相关源代码,并从中构建您自己的编译环境。我自己没试过,我做了一个系统应用程序。【参考方案5】:

您可以尝试以下库:Android Screenshot Library (ASL) 能够以编程方式从 Android 设备捕获屏幕截图,而无需拥有 root 访问权限。相反,ASL 使用在后台运行的本机服务,每次设备启动时通过 Android 调试桥 (ADB) 启动一次。

【讨论】:

从这个库中截取屏幕截图的任何示例??? 指定项目有太多组件:一个原生帧缓冲抓取器和一个java接口,原生组件仍然需要ROOT才能运行!所以总的来说,这个解决方案在没有 root 的情况下是行不通的。【参考方案6】:

根据this link,可以使用android sdk的tools目录下的ddms进行截屏。

要在应用程序中执行此操作(而不是在开发期间),也有应用程序可以执行此操作。但正如@zed_0xff 指出的那样,它肯定需要root。

【讨论】:

它在工具目录中更改了一点 ddms 说运行监视器。监视器有一个简洁的拍照图标 同意,这个答案现在已经严重过时了,特别是考虑到很多设备甚至提供了开箱即用的功能(例如 Nexus 设备上的 Power+VolDown)。我认为您可以将您的评论作为完整答案发布,但我怀疑最初的提问者会更改接受的答案(现在这个答案肯定不是最佳答案) 我想在启动移动设备和其他设备时查看当前任何屏幕,所以请告诉我那里的情况【参考方案7】:

Framebuffer 似乎是要走的路,它不会像 Ryan Conrad 提到的那样总是包含 2+ 帧。就我而言,它只包含一个。我想这取决于框架/显示尺寸。

我尝试连续读取帧缓冲区,但它似乎返回了固定数量的读取字节。在我的例子中是 (3 410 432) 字节,这足以存储 854*480 RGBA(3 279 360 字节)的显示帧。是的,从 fb0 输出的二进制帧在我的设备中是 RGBA这很可能因设备而异。这对你解码很重要=)

在我的设备 /dev/graphics/fb0 中,只有 rootgroup graphics 的用户可以读取 fb0。 graphics 是一个受限组,因此您可能只能使用 su 命令通过 root 手机访问 fb0。

Android 应用的用户 ID (uid) app_## 和组 ID (guid) app_##

adb shell 有 uid shellguid shell,它们比应用程序拥有更多的权限。 您实际上可以在 /system/permissions/platform.xml

中检查这些权限

这意味着您将能够在没有 root 的情况下在 adb shell 中读取 fb0,但在没有 root 的情况下您将无法在应用程序中读取它。

此外,在 AndroidManifest.xml 上授予 READ_FRAME_BUFFER 和/或 ACCESS_SURFACE_FLINGER 权限对于常规应用没有任何作用,因为这些仅适用于“签名”应用。

【讨论】:

你是怎么发现你的帧缓冲格式是RGBA的? 我用 GIMP 打开了一个原始帧缓冲图像。当我将其设置为 RGB 打开时,图像不正常,当我将其设置为 RGBA 时 - 正常 ;) 你能指出一个通过帧缓冲区捕获图像的例子吗?我正在研究 Android 源代码,所以生根/许可对我来说不是问题。这也适用于 SurfaceView 吗?我尝试了一些来自互联网的示例,但到目前为止,表面视图没有任何效果......我也尝试过使用 screencap,但它也返回了我在布局中存在 SurfaceViews 的黑色/空白部分跨度> 【参考方案8】:

如果您想从 Android 应用 AFAIK 中的 Java 代码截屏,您必须拥有 Root 权限。

【讨论】:

以上是关于如何捕获android设备屏幕内容? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

android上的设备特定屏幕捕获

如何以编程方式获取 android 设备屏幕截图? [复制]

如何捕获包含所有内容的 UIWebview 的屏幕截图?

如何在android中捕获屏幕并将其转换为图像[重复]

如何将屏幕设置为不旋转(android)? [复制]

如何使用 Eclipse 给 Android 模拟器截屏