我正在尝试通过代码截取我的游戏的屏幕截图,并通过Intent分享它。我能够做到这些,但是屏幕截图始终显示为黑色。以下是与共享屏幕截图有关的代码:

View view = MainActivity.getView();
view.setDrawingCacheEnabled(true);
Bitmap screen = Bitmap.createBitmap(view.getDrawingCache(true));
.. save Bitmap


这是在MainActivity中:

view = new GameView(this);
view.setLayoutParams(new RelativeLayout.LayoutParams(
            RelativeLayout.LayoutParams.FILL_PARENT,
            RelativeLayout.LayoutParams.FILL_PARENT));

public static SurfaceView getView() {
    return view;
}


而视图本身:

public class GameView extends SurfaceView implements SurfaceHolder.Callback {
private static SurfaceHolder surfaceHolder;
...etc


这就是我绘制所有内容的方式:

Canvas canvas = surfaceHolder.lockCanvas(null);
        if (canvas != null) {
                Game.draw(canvas);
...


好吧,根据一些答案,我构建了以下内容:

public static void share() {
    Bitmap screen = GameView.SavePixels(0, 0, Screen.width, Screen.height);
    Calendar c = Calendar.getInstance();
    Date d = c.getTime();
    String path = Images.Media.insertImage(
            Game.context.getContentResolver(), screen, "screenShotBJ" + d
                    + ".png", null);
    System.out.println(path + " PATH");
    Uri screenshotUri = Uri.parse(path);
    final Intent emailIntent = new Intent(
            android.content.Intent.ACTION_SEND);
    emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    emailIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
    emailIntent.setType("image/png");
    Game.context.startActivity(Intent.createChooser(emailIntent,
            "Share High Score:"));
}


Gameview包含以下方法:

public static Bitmap SavePixels(int x, int y, int w, int h) {
    EGL10 egl = (EGL10) EGLContext.getEGL();
    GL10 gl = (GL10) egl.eglGetCurrentContext().getGL();
    int b[] = new int[w * (y + h)];
    int bt[] = new int[w * h];
    IntBuffer ib = IntBuffer.wrap(b);
    ib.position(0);
    gl.glReadPixels(x, 0, w, y + h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, ib);
    for (int i = 0, k = 0; i < h; i++, k++) {
        for (int j = 0; j < w; j++) {
            int pix = b[i * w + j];
            int pb = (pix >> 16) & 0xff;
            int pr = (pix << 16) & 0x00ff0000;
            int pix1 = (pix & 0xff00ff00) | pr | pb;
            bt[(h - k - 1) * w + j] = pix1;
        }
    }

    Bitmap sb = Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);
    return sb;
}


屏幕截图仍为黑色。

我尝试了几种不同的方法来截取屏幕截图,但没有一个起作用:
上面代码中显示的一个是最常见的建议之一。但这似乎不起作用。
使用SurfaceView是否有问题?如果是这样,为什么不能使用view.getDrawingCache(true)甚至存在,如何解决?

我的代码:

public static void share() {
    // GIVES BLACK SCREENSHOT:
    Calendar c = Calendar.getInstance();
    Date d = c.getTime();

    Game.update();
    Bitmap.Config conf = Bitmap.Config.RGB_565;
    Bitmap image = Bitmap.createBitmap(Screen.width, Screen.height, conf);
    Canvas canvas = GameThread.surfaceHolder.lockCanvas(null);
    canvas.setBitmap(image);
    Paint backgroundPaint = new Paint();
    backgroundPaint.setARGB(255, 40, 40, 40);
    canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(),
            backgroundPaint);
    Game.draw(canvas);
    Bitmap screen = Bitmap.createBitmap(image, 0, 0, Screen.width,
            Screen.height);
    canvas.setBitmap(null);
    GameThread.surfaceHolder.unlockCanvasAndPost(canvas);

    String path = Images.Media.insertImage(
            Game.context.getContentResolver(), screen, "screenShotBJ" + d
                    + ".png", null);
    System.out.println(path + " PATH");
    Uri screenshotUri = Uri.parse(path);
    final Intent emailIntent = new Intent(
            android.content.Intent.ACTION_SEND);
    emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    emailIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
    emailIntent.setType("image/png");
    Game.context.startActivity(Intent.createChooser(emailIntent,
            "Share High Score:"));
}


谢谢。

评论

我的代码基于该帖子的已接受答案。仍然无法使用

单击此处获取答案。它将为您提供帮助:)
@TastyLemons Pls删除单词“工作代码:”。它会误导读者。

#1 楼

对此有很多困惑,还有一些正确答案。

这是要解决的问题:


SurfaceView有两个部分,Surface和视图。 Surface与所有View UI元素位于完全独立的图层上。 getDrawingCache()方法仅在View层上有效,因此它无法捕获Surface上的任何内容。
缓冲区队列具有生产者-消费者API,并且只能有一个生产者。帆布是一个生产商,GLES是另一个生产商。您不能使用Canvas进行绘制,也不能使用GLES读取像素。 (从技术上讲,当您读取像素时,如果Canvas使用的是GLES,并且当前使用的是正确的EGL上下文,则可以这样做,但是不能保证。在任何发行版本的Android中,Canvas渲染到Surface的速度都不会加快,所以现在没有希望。)
(与您的情况无关,但出于完整性考虑,我将其提及:) Surface不是帧缓冲区,而是缓冲区队列。当您使用GLES提交缓冲区时,缓冲区消失了,无法再从中读取。因此,如果要使用GLES进行渲染并使用GLES进行捕获,则需要在调用eglSwapBuffers()之前先读取像素。

使用Canvas渲染,“捕获” Surface内容的最简单方法是简单地绘制它两次。创建一个屏幕大小的位图,从该位图创建一个Canvas,并将其传递给您的draw()函数。

使用GLES渲染,可以在缓冲区交换之前使用glReadPixels()来捕获像素。 Grafika中有一个抓取代码的实现(比问题中的代码便宜)。请参阅EglSurfaceBase中的saveFrame()

如果您将视频直接发送到Surface(通过MediaPlayer),将无法捕获帧,因为您的应用程序永远无法访问它们-它们直接从mediaserver到达合成器(SurfaceFlinger)。但是,您可以通过SurfaceTexture路由传入的帧,然后从您的应用程序渲染两次,一次进行显示,一次进行捕获。有关更多信息,请参见此问题。

一种替代方法是用TextureView替换SurfaceView,该TextureView可以像其他任何Surface一样绘制。然后,您可以使用getBitmap()调用之一来捕获帧。 TextureView比SurfaceView效率低,因此不建议在所有情况下都使用它,但是这样做很简单。

如果您希望获得包含Surface内容和View UI内容的复合屏幕截图,您将需要捕获上述的Canvas,使用常规的图形缓存技巧捕获View,然后手动将两者进行合成。请注意,这不会占用系统部分(状态栏,导航栏)。

更新:在Lollipop及更高版本(API 21+)上,您可以使用MediaProjection类来捕获整个屏幕。虚拟显示。这种方法需要权衡取舍,例如您正在捕获渲染的屏幕,而不是捕获发送到Surface的帧,因此您可能会放大或缩小以适应窗口。另外,这种方法涉及到活动切换,因为您必须创建一个意图(通过在ProjectionManager对象上调用createScreenCaptureIntent)并等待其结果。

如果您想了解更多有关这些东西的信息可以使用,请参见Android系统级图形体系结构文档。

评论


因此,为了澄清起见,我如何准确地将画布存储到位图中?

–user3011902
2015年1月8日在2:25

您创建一个位图,然后将其传递给Canvas构造函数-developer.android.com/reference/android/graphics/…

–淡淡
2015年1月8日在6:14



我有一个相对的布局,在该布局下有一个调用相机的表面视图,在它的顶部有一个可以增加相机的图像视图。如何拍摄整个场景的屏幕截图?

– navinpai
15年8月30日在12:01

@navinpai请通过创建新问题而不是在评论中添加评论来提问。参见stackoverflow.com/questions/26545970/…

–淡淡
15年8月30日在17:44

所以如何在给定活动根视图的情况下截取该死的屏幕截图

–艾哈迈德·阿里(Ahmed Ali)
18/12/6在16:25

#2 楼

我知道它的回复很晚,但是对于那些遇到相同问题的人,我们可以使用PixelCopy来获取快照。在API level 24及更高版本中可用

PixelCopy.request(surfaceViewObject,BitmapDest,listener,new Handler());


其中,

surfaceViewObject是表面视图的对象

BitmapDest是要保存图像且不能为null的位图对象

listener为OnPixelCopyFinishedListener

有关更多信息,请参考-https://developer.android.com/reference/ android / view / PixelCopy

评论


您对24年前可以使用的类似产品有任何建议吗?

– hellowill89
19年1月11日,1:50

不,我尝试了很多解决方案,但是在表面视图的情况下,只有这种方法有效。

–安贾尼·米塔尔(Anjani Mittal)
19年1月14日在11:59

因此,这将适用于单个Surface视图吗?如果当前的显示由表面视图和普通的Android UI层组成,该怎么办?

–迪兹曼狂热
19年1月17日在2:27

这仅用于表面视图,对于表面视图和其他内容,我没有这种情况。

–安贾尼·米塔尔(Anjani Mittal)
19年1月17日在4:43

@AnjaniMittal在第26级添加了一个新的api,您可以捕获一个窗口developer.android.com/reference/android/view/…

– Alijandro
19年4月6日在6:59

#3 楼

这是因为SurfaceView使用OpenGL线程绘制并直接绘制到硬件缓冲区。您必须使用glReadPixels(可能还需要使用GLWrapper)。

查看线程:Android OpenGL屏幕截图

评论


大链接。接受答案中的代码似乎要求以下条件:GL10 gl在我的SurfaceView中的哪里创建此对象?

–user3011902
2015年1月7日,10:50

当我阅读有关您的问题的信息时,我深信您的方法应该有效。在stackoverflow上有关于它的链接:stackoverflow.com/questions/4742868/…我的方法使用GLSurfaceView和GLWrapper。请参阅:stackoverflow.com/questions/5979069/…

–Zielony
15年1月7日,11:05

更新的代码,请参阅问题。它仍然不能正常工作。

–user3011902
15年1月7日,11:53

使用Canvas渲染到Surface永远不会加速硬件(从5.0开始),因此尝试使用GLES捕获像素没有帮助。如果启用了硬件加速,则渲染到视图将使用GLES,但是惯用的图形缓存方法更加可靠。

–淡淡
2015年1月7日在16:47

#4 楼

在API 28中不推荐使用Update 2020 view.setDrawingCacheEnabled(true)。如果您使用的是普通View,则可以使用指定的位图创建画布。然后,要求视图在画布上绘制并返回由Canvas填充的位图。或者您可以在使用视图绘制画布之前用默认颜色填充画布: br />
 /**
     * Copy View to Canvas and return bitMap
     */
    fun getBitmapFromView(view: View): Bitmap? {
        var bitmap =
            Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        view.draw(canvas)
        return bitmap
    }


上述方法不适用于将其绘制为屏幕截图中的孔的表面视图。

要从Android 24开始使用曲面视图,您需要使用Pixel Copy。

    /**
     * Copy View to Canvas and return bitMap and fill it with default color
     */
    fun getBitmapFromView(view: View, defaultColor: Int): Bitmap? {
        var bitmap =
            Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
        var canvas = Canvas(bitmap)
        canvas.drawColor(defaultColor)
        view.draw(canvas)
        return bitmap
    }


这种方法可以截取Surface Vie的任何子类的屏幕快照,例如VideoView

    /**
     * Pixel copy to copy SurfaceView/VideoView into BitMap
     */
     fun usePixelCopy(videoView: SurfaceView,  callback: (Bitmap?) -> Unit) {
        val bitmap: Bitmap = Bitmap.createBitmap(
            videoView.width,
            videoView.height,
            Bitmap.Config.ARGB_8888
        );
        try {
        // Create a handler thread to offload the processing of the image.
        val handlerThread = HandlerThread("PixelCopier");
        handlerThread.start();
        PixelCopy.request(
            videoView, bitmap,
            PixelCopy.OnPixelCopyFinishedListener { copyResult ->
                if (copyResult == PixelCopy.SUCCESS) {
                    callback(bitmap)
                }
                handlerThread.quitSafely();
            },
            Handler(handlerThread.looper)
        )
        } catch (e: IllegalArgumentException) {
            callback(null)
            // PixelCopy may throw IllegalArgumentException, make sure to handle it
            e.printStackTrace()
        }
    }