如何捕获安卓设备屏幕内容?

51
8个回答

43

使用以下代码:

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

这里的MyView是我们需要包含在屏幕中的View。您还可以通过这种方式(无需使用getRootView())从任何View获取DrawingCache


还有另一种方法...
如果我们的根视图是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);
        }
    });
希望这可以帮到你!

2
不幸的是,当捕获对话框和键盘显示时,似乎无法正常工作。 :( - LadyCailin
这种方法适用于软件层视图。但是对于视频(VideoView或Webview中的视频)则不起作用。所有其他像素都将被捕获,但视频只显示黑色。我认为原因是我们可以获取软件层位图,但无法获取视频的位图。有人知道如何截取带有视频的Webview的屏幕截图吗? - user2060386
@user2060386 我已经更新了我的答案,请查看。希望对你有所帮助! - Nirav Dangi
@NiravDangi,感谢您的更新!但是对于带有视频的Webview(例如YouTube页面),仍然无法正常工作。页面的其他静态部分被捕捉得很好,但视频是黑色的。 - user2060386
@user2060386 你能否现在检查一下?我已经更新了我的答案。我在onPageFinished()方法中添加了一个处理程序,它有1秒的延迟。所以,如果它不起作用,你可能需要增加延迟时间。 - Nirav Dangi
显示剩余5条评论

23
据我所知,目前所有截取Android屏幕截图的方法都使用/dev/graphics/fb0 frame buffer。这包括ddms。需要root权限才能从该流中读取数据。ddms使用adbd请求信息,因此不需要root权限,因为adb具有从/dev/graphics/fb0请求数据所需的权限。
framebuffer包含2个或更多的RGB565图像“帧”。如果您能够读取数据,您需要知道屏幕分辨率以了解需要多少字节来获取图像。每个像素占用2字节,因此如果屏幕分辨率为480x800,则需要读取768,000字节的图像,因为480x800 RGB565图像有380,400个像素。

5
注意:并非所有的帧缓冲都是RGB565格式,还存在其他不同的格式。 - ACT
1
这个答案已经过时了,对于Honeycomb及其以后的版本来说是不正确的。你不能保证所有屏幕内容都会最终呈现在fb0上,因为可能会使用更多直接渲染方法,比如硬件叠加。最好看看Ducky Chen提供的答案。 - tonylo
我认为这个链接 http://www.pocketmagic.net/2010/11/android-native-screen-capture-application-using-the-framebuffer/#.Ue5vj32XsUQ 对于同样的问题会很有帮助。 - Megharaj
但是解决方案在哪里?这不是代码或确切的解决方案。 - Anand Savjani
我不是来给你写代码的。我发布帖子是为了帮助解决问题。 - Ryan Conrad
显示剩余6条评论

23

对于更新的 Android 平台,可以在不需要 root 权限的情况下执行系统实用程序 screencap,以获取屏幕截图。该命令位于 /system/bin 目录下。 您可以尝试在 adb 或任何 shell 下使用 /system/bin/screencap -h 命令来了解如何使用它。

顺便说一句,我认为这种方法只适用于单个快照。如果我们想要捕获多个帧以进行屏幕播放,那将会太慢。我不知道是否存在其他更快的屏幕截取方法。


4
它在adb shell中运行得很好。我如何让它从代码中运行?这个不起作用:String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "outhope.png";Process proc = Runtime.getRuntime().exec( "/system/bin/screencap -p " + path); - gregm
@ocramot 这是在4.1.2 Cyanogenmod上的。 - barkside
屏幕截图的源代码可以在AOSP源代码树中找到。 - Ducky Chen
1
捕获屏幕的应用程序需要更高的权限。这是为了安全问题(防止后门)。结果,即使您重新编译源代码以获得新的应用程序,除非您以root权限运行它,否则无法获得预期的结果。 - Ducky Chen
如果使用平台构建器签名,则不需要 root 权限。 - Ducky Chen
显示剩余8条评论

18

[基于Android源代码:]

在C++端,SurfaceFlinger实现了captureScreen API。这通过binder IPC接口公开,每次返回一个包含屏幕原始像素的新ashmem区域。实际的屏幕截图是通过OpenGL进行的。

对于系统C++客户端,该接口通过ScreenshotClient类公开。 在Android<4.1中,该类定义在<surfaceflinger_client/SurfaceComposerClient.h>中; 对于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 权限系统不够熟悉。)

下面是针对 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应用程序中实现相同的功能是否可能? - Megharaj
这段代码运行得非常完美,但我想在更新方法中添加一个错误检查。此外,我还想为其他用户添加额外的注释。如果您想以不同的分辨率(缩小/放大)获取屏幕内容,请在调用“update”函数时将它们作为参数传递,例如 ssc.update(displayID,width,height)。 - amIT
我尝试编译这个程序,但是无法使其工作 - 我已经投入了相当多的时间。 首先,大部分包含在不同的Android源代码存储库中。 现在它无法编译!在Cygwin上尝试过,但stdatomic.h缺失,在gcc-4.9中存在,但错误到处都是。 请提供一个最小的可工作示例和一些提示! - ThE_-_BliZZarD
@KiranParmar,你正在使用哪个版本的Android? EGL是哪个版本? 在更新的Android中,EGL的作用比以前更大。例如,SurfaceView是单独的SurfaceFlinger窗口,通常使用OpenGL直接绘制到屏幕上。 根据你的评论,似乎“ScreenshotClient”没有更新以处理直接的OpenGL窗口,例如那些用于SurfaceView的窗口。我认为媒体播放器窗口也是如此,但我没有检查过。 - Pekka Nikander
@PekkaNikander:我用的是Android 4.3。我不知道EGL的版本。 实际上,我通过fb1而不是fb0成功地捕获了surfaceview,因为这是一个定制版的Android,我不知道这个内部修改。我所要做的就是cat fb1 > some_file & pull这个文件,并使用ffmpeg将其从自定义格式转换成png / jpg :) - Kiran Parmar
显示剩余3条评论

15
你可以尝试使用以下库:Android Screenshot Library (ASL),它可以在不需要root访问权限的情况下从Android设备中以编程方式捕获屏幕截图。相反,ASL利用在后台运行的本地服务,每次设备启动时通过Android Debug Bridge (ADB)启动一次。

这个库如何截屏?有示例吗? - Shreyash Mahajan
1
该项目有两个组件:本地帧缓冲抓取器和Java接口,本地组件仍需要ROOT才能运行!因此,总体而言,如果没有ROOT,这个解决方案将无法工作。 - radhoo

13
根据此链接,可以使用Android SDK中工具目录中的ddms来捕获屏幕截图。要在应用程序内执行此操作(而不是在开发期间),也有相应的应用程序可供使用。但正如@zed_0xff所指出的那样,这肯定需要root权限。

在工具目录中,DDMS 有所改变,建议运行 Monitor。Monitor 有一个很棒的图标可以拍照。 - danny117
同意,这个答案现在已经严重过时了,特别是考虑到很多设备甚至已经默认提供了这个功能(例如 Nexus 设备上的 Power+VolDown)。我认为你可以把你的评论发表为一个完整的答案,但我有些怀疑原来的提问者会不会更改已接受的答案(这个答案现在肯定不是最好的答案)。 - Joubarc
我想在移动设备和其他设备启动时获取当前屏幕,请告诉我如何实现。 - Ramani Hitesh
链接已失效,请修复。 - Dominic Cerisano

7

Framebuffer似乎是一个不错的选择,但它不总是像Ryan Conrad所说的那样包含2个或以上的帧。在我的情况下,它仅包含一个帧。我猜这取决于帧/显示尺寸。

我试图连续读取framebuffer,但它似乎只返回固定数量的字节。在我的情况下,这是(3,410,432)字节,足以存储854 * 480 RGBA(3,279,360字节)的显示帧。是的,从fb0输出的帧在我的设备上是RGBA二进制格式这很可能因设备而异。这对您来解码它很重要=)

在我的设备上,/dev/graphics/fb0权限只有root用户组图形(graphics)的用户可以读取fb0。graphics是受限制的组,因此您可能只能使用su命令在已root的手机上访问fb0。

Android应用程序具有用户ID(uid) app_##和组ID(guid) app_##

adb shell具有uid shellguid shell,它比应用程序具有更多的权限。您实际上可以在/system/permissions/platform.xml中检查这些权限。

这意味着您可以在adb shell中读取fb0而无需root,但是您将无法在应用程序中读取它。

此外,在AndroidManifest.xml中授予READ_FRAME_BUFFER和/或ACCESS_SURFACE_FLINGER权限对于常规应用程序无效,因为这些权限仅适用于“签名”应用程序。


你是怎么发现你的帧缓存格式是RGBA的? - Guilherme Torres Castro
我使用GIMP打开了一个原始的帧缓冲图像。当我将其设置为以RGB格式打开时,图像不正常,但当我将其设置为RGBA格式时,它就正常了 ;) - Rui Marques
你能指出一个通过帧缓冲捕获图像的例子吗?我正在处理Android源代码,所以获取root权限/权限对我来说不是问题。这对SurfaceView也适用吗?我尝试了一些来自互联网的示例,但到目前为止,对于surface-view什么都没用...我确实尝试使用screencap,但它也会返回我布局中存在SurfaceViews的黑色/空白部分。 - Kiran Parmar

5

如果您想在Android应用程序中从Java代码进行屏幕截图,据我所知,您必须具有Root权限。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接