java.lang.IllegalStateException: 软件渲染不支持硬件位图

19
我需要获取一个视图的屏幕截图。我已经尝试了两种方法来完成这项工作。不幸的是,两种方法都产生了相同的错误。
下面是日志:
java.lang.IllegalArgumentException: Software rendering doesn't support hardware bitmaps
at android.graphics.BaseCanvas.null onHwBitmapInSwMode(null)(BaseCanvas.java:550)
at android.graphics.BaseCanvas.null throwIfHwBitmapInSwMode(null)(BaseCanvas.java:557)
at android.graphics.BaseCanvas.null throwIfCannotDraw(null)(BaseCanvas.java:69)
at android.graphics.BaseCanvas.null drawBitmap(null)(BaseCanvas.java:127)
at android.graphics.Canvas.null drawBitmap(null)(Canvas.java:1504)
at android.graphics.drawable.BitmapDrawable.null draw(null)(BitmapDrawable.java:545)
at android.widget.ImageView.null onDraw(null)(ImageView.java:1355)
at android.view.View.null draw(null)(View.java:20248)
at android.view.View.null draw(null)(View.java:20118)
at android.view.ViewGroup.null drawChild(null)(ViewGroup.java:4336)
at android.view.ViewGroup.null dispatchDraw(null)(ViewGroup.java:4115)
at android.view.ViewOverlay$OverlayViewGroup.null dispatchDraw(null)(ViewOverlay.java:251)
at android.view.View.null draw(null)(View.java:20251)
at android.view.View.null buildDrawingCacheImpl(null)(View.java:19516)
at android.view.View.null buildDrawingCache(null)(View.java:19379)
at android.view.View.null getDrawingCache(null)(View.java:19215)
at android.view.View.null getDrawingCache(null)(View.java:19166)
at com.omnipotent.free.videodownloader.pro.utils.ViewUtils.android.graphics.Bitmap captureView(android.view.View)(ViewUtils.java:70)
at com.omnipotent.free.videodownloader.pro.ui.main.MainActivity.com.omnipotent.free.videodownloader.pro.data.bean.TabBean getCurrentTabsData()(MainActivity.java:325)
at com.omnipotent.free.videodownloader.pro.ui.main.MainActivity.com.omnipotent.free.videodownloader.pro.data.bean.TabBean access$getCurrentTabsData(com.omnipotent.free.videodownloader.pro.ui.main.MainActivity)(MainActivity.java:84)
at com.omnipotent.free.videodownloader.pro.ui.main.MainActivity$onAddTab$1.void run()(MainActivity.java:628)
at android.os.Handler.null handleCallback(null)(Handler.java:873)
at android.os.Handler.null dispatchMessage(null)(Handler.java:99)
at android.os.Looper.null loop(null)(Looper.java:193)
at android.app.ActivityThread.null main(null)(ActivityThread.java:6936)
at java.lang.reflect.Method.null invoke(null)(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.null run(null)(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.null main(null)(ZygoteInit.java:870)

我已经仔细检查了代码并在互联网上查阅了相关文章。然而,我还没有解决它,这让我感到非常沮丧。 这个bug只出现在android O以上的版本。
以下是我尝试过的两种方法:
方法1:
public static Bitmap captureView(View view) {
    Bitmap tBitmap = Bitmap.createBitmap(
            view.getWidth(), view.getHeight(), Bitmap.Config.RGB_565);
    Canvas canvas = new Canvas(tBitmap);
    view.draw(canvas);
    canvas.setBitmap(null);
    return tBitmap;
}

方法二:

public static Bitmap captureView(View view) {

    if (view == null) return null;
    boolean drawingCacheEnabled = view.isDrawingCacheEnabled();
    boolean willNotCacheDrawing = view.willNotCacheDrawing();
    view.setDrawingCacheEnabled(true);
    view.setWillNotCacheDrawing(false);
    final Bitmap drawingCache = view.getDrawingCache();
    Bitmap bitmap;
    if (null == drawingCache) {
        view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
        view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
        view.buildDrawingCache();
        bitmap = Bitmap.createBitmap(view.getDrawingCache());
    } else {
        bitmap = Bitmap.createBitmap(drawingCache);
    }
    view.destroyDrawingCache();
    view.setWillNotCacheDrawing(willNotCacheDrawing);
    view.setDrawingCacheEnabled(drawingCacheEnabled);
    return bitmap;
}

需要提到的是,我已经为我的Activity设置了android:hardwareAccelerated="true",在其中调用了captureView方法。
7个回答

19

阅读Glide硬件位图文档,查找PixelCopy类,该类可以解决此错误。

在Android O及以上版本中使用PixelCopyview转换为Bitmap,在Android O以下版本中使用先前的方法。

这是我的代码:

fun captureView(view: View, window: Window, bitmapCallback: (Bitmap)->Unit) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        // Above Android O, use PixelCopy
        val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
        val location = IntArray(2)
        view.getLocationInWindow(location)
        PixelCopy.request(window,
            Rect(location[0], location[1], location[0] + view.width, location[1] + view.height),
            bitmap,
            {
                if (it == PixelCopy.SUCCESS) {
                    bitmapCallback.invoke(bitmap)
                }
            },
            Handler(Looper.getMainLooper()) )
    } else {
        val tBitmap = Bitmap.createBitmap(
            view.width, view.height, Bitmap.Config.RGB_565
        )
        val canvas = Canvas(tBitmap)
        view.draw(canvas)
        canvas.setBitmap(null)
        bitmapCallback.invoke(tBitmap)
    }
}

问题是我不得不使用回调函数,而我并不是很喜欢它。

希望它能够正常工作。


它工作得非常好,但我不能使用SurfaceView,并且我想要保存为透明的。但是,这种方法始终以白色背景保存。我尝试了一些东西,但它没有起作用。该怎么办? - Umut ADALI
请问您能告诉我如何在Java代码中实现它吗? - shashank singh bisht

8
您可以尝试将硬件类型位图复制到所需的类型位图中,例如:
Bitmap targetBmp = originBmp.copy(Bitmap.Config.ARGB_8888, false);

我能知道这背后的原因是什么吗? - GvHarish

5
如果您正在转换一个包含ImageView的视图,并且使用Glide或Coil等图像加载库加载其图像,则必须禁用硬件位图的使用。这些库仅将像素数据存储在图形内存中,适用于仅将位图绘制到屏幕的情况。
如果您正在使用Coil,则必须执行以下操作:
val imageLoader = ImageLoader.Builder(context).allowHardware(false).build()
imageView.load(imageUrl, imageLoader)

4

根据@wang willway的回复,我创建了这个 Kotlin View 扩展。

在 Compose 中使用它:

LocalView.current.toBitmap(
onBitmapReady = { bitmap: Bitmap ->
// TODO - use generated bitmap
},
onBitmapError = { exception: Exception ->
// TODO - handle exception
}
)

// start of extension.
fun View.toBitmap(onBitmapReady: (Bitmap) -> Unit, onBitmapError: (Exception) -> Unit) {

    try {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

            val temporalBitmap = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.ARGB_8888)

            // Above Android O, use PixelCopy due
            // https://dev59.com/r1MH5IYBdhLWcg3wyyot
            val window: Window = (this.context as Activity).window

            val location = IntArray(2)

            this.getLocationInWindow(location)

            val viewRectangle = Rect(location[0], location[1], location[0] + this.width, location[1] + this.height)

            val onPixelCopyListener: PixelCopy.OnPixelCopyFinishedListener = PixelCopy.OnPixelCopyFinishedListener { copyResult ->

                if (copyResult == PixelCopy.SUCCESS) {

                    onBitmapReady(temporalBitmap)
                } else {

                    error("Error while copying pixels, copy result: $copyResult")
                }
            }

            PixelCopy.request(window, viewRectangle, temporalBitmap, onPixelCopyListener, Handler(Looper.getMainLooper()))
        } else {

            val temporalBitmap = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.RGB_565)

            val canvas = android.graphics.Canvas(temporalBitmap)

            this.draw(canvas)

            canvas.setBitmap(null)

            onBitmapReady(temporalBitmap)
        }

    } catch (exception: Exception) {

        onBitmapError(exception)
    }
}

“VIEW_LOCATION_SIZE” 有什么值? - rosu alin
代码已更新,现在是2。 - Akhha8

0

这可能会帮助未来的某个人,在我的情况下,是因为给评分栏设置了固定高度,同时将样式设置如下:

style="@style/Widget.AppCompat.RatingBar.Small"

我的解决方法是将其高度包装起来,而不是给它固定的大小。

0

我用它来截取 Composables 的屏幕截图,但你也可以在没有 Compose 的情况下使用它。它会将你可能遇到的每个异常作为包装错误返回到 ImageResult 中。

fun View.screenshot(bitmapCallback: (ImageResult) -> Unit) {

try {

    val bitmap = Bitmap.createBitmap(
        width,
        height,
        Bitmap.Config.ARGB_8888,
    )

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

        PixelCopy.request(
            (this.context as Activity).window,
            bounds.toAndroidRect(),
            bitmap,
            {
                when (it) {
                    PixelCopy.SUCCESS -> {
                        bitmapCallback.invoke(ImageResult.Success(bitmap))
                    }
                    PixelCopy.ERROR_DESTINATION_INVALID -> {
                        bitmapCallback.invoke(
                            ImageResult.Error(
                                Exception(
                                    "The destination isn't a valid copy target. " +
                                            "If the destination is a bitmap this can occur " +
                                            "if the bitmap is too large for the hardware to " +
                                            "copy to. " +
                                            "It can also occur if the destination " +
                                            "has been destroyed"
                                )
                            )
                        )
                    }
                    PixelCopy.ERROR_SOURCE_INVALID -> {
                        bitmapCallback.invoke(
                            ImageResult.Error(
                                Exception(
                                    "It is not possible to copy from the source. " +
                                            "This can happen if the source is " +
                                            "hardware-protected or destroyed."
                                )
                            )
                        )
                    }
                    PixelCopy.ERROR_TIMEOUT -> {
                        bitmapCallback.invoke(
                            ImageResult.Error(
                                Exception(
                                    "A timeout occurred while trying to acquire a buffer " +
                                            "from the source to copy from."
                                )
                            )
                        )
                    }
                    PixelCopy.ERROR_SOURCE_NO_DATA -> {
                        bitmapCallback.invoke(
                            ImageResult.Error(
                                Exception(
                                    "The source has nothing to copy from. " +
                                            "When the source is a Surface this means that " +
                                            "no buffers have been queued yet. " +
                                            "Wait for the source to produce " +
                                            "a frame and try again."
                                )
                            )
                        )
                    }
                    else -> {
                        bitmapCallback.invoke(
                            ImageResult.Error(
                                Exception(
                                    "The pixel copy request failed with an unknown error."
                                )
                            )
                        )
                    }
                }

            },
            Handler(Looper.getMainLooper())
        )
    } else {
        val canvas = Canvas(bitmap)
            .apply {
                translate(-bounds.left, -bounds.top)
            }
        this.draw(canvas)
        canvas.setBitmap(null)
        bitmapCallback.invoke(ImageResult.Success(bitmap))
    }
} catch (e: Exception) {
    bitmapCallback.invoke(ImageResult.Error(e))
}

}

sealed class ImageResult {
    object Initial : ImageResult()
    data class Error(val exception: Exception) : ImageResult()
    data class Success(val data: Bitmap) : ImageResult()
}

使用方法


-1

使用此代码来捕获视图(适用于Android 8.0):

class CaptureView{

    static Bitmap capture(View view){
        view.setDrawingCacheEnabled(true);
        view.buildDrawingCache();

        return view.getDrawingCache(); //Returns a bitmap
    }
}

Bitmap bitmap = CaptureView.capture(yourView) // Pass in the view to capture

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