Android UnsupportedOperationException at Canvas.cl

2019-02-08 17:43发布

问题:

I am using the image crop library from here https://github.com/lvillani/android-cropimage in my project to crop images stored on device.

However, certain users are reporting crashes with the following stack trace

java.lang.UnsupportedOperationException
at android.view.GLES20Canvas.clipPath(GLES20Canvas.java:413)
at com.android.camera.HighlightView.draw(HighlightView.java:101)
at com.android.camera.CropImageView.onDraw(CropImage.java:783)
at android.view.View.draw(View.java:11006)
at android.view.View.getDisplayList(View.java:10445)
at android.view.ViewGroup.drawChild(ViewGroup.java:2850)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2489)
at android.view.View.getDisplayList(View.java:10443)
at android.view.ViewGroup.drawChild(ViewGroup.java:2850)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2489)
at android.view.View.getDisplayList(View.java:10443)
at android.view.ViewGroup.drawChild(ViewGroup.java:2850)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2489)
at android.view.View.getDisplayList(View.java:10443)
at android.view.ViewGroup.drawChild(ViewGroup.java:2850)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2489)
at android.view.View.getDisplayList(View.java:10443)
at android.view.ViewGroup.drawChild(ViewGroup.java:2850)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2489)
at android.view.View.draw(View.java:11009)
at android.widget.FrameLayout.draw(FrameLayout.java:450)
at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:2154)
at android.view.View.getDisplayList(View.java:10445)
at android.view.HardwareRenderer$GlRenderer.draw(HardwareRenderer.java:853)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:1961)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1679)
at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2558)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4697)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:787)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:554)
at dalvik.system.NativeStart.main(Native Method)

From searching I think it is caused by hardware acceleration on only certain devices. I have disable hardware acceleration in my manifest but the exception is still occurring. I have also found "A solid workaround is to identify the problematic operations in your code, draw those to a bitmap instead, then blit the bitmap to your accelerated canvas."

The problematic code according to the stack trace is

protected void draw(Canvas canvas) {
    if (mHidden) {
        return;
    }
    canvas.save();
    Path path = new Path();
    if (!hasFocus()) {
        mOutlinePaint.setColor(0xFF000000);
        canvas.drawRect(mDrawRect, mOutlinePaint);
    } else {
        Rect viewDrawingRect = new Rect();
        mContext.getDrawingRect(viewDrawingRect);
        if (mCircle) {
            float width  = mDrawRect.width();
            float height = mDrawRect.height();
            path.addCircle(mDrawRect.left + (width  / 2),
                           mDrawRect.top + (height / 2),
                           width / 2,
                           Path.Direction.CW);
            mOutlinePaint.setColor(0xFFEF04D6);
        } else {
            path.addRect(new RectF(mDrawRect), Path.Direction.CW);
            mOutlinePaint.setColor(0xFFFF8A00);
        }
        canvas.clipPath(path, Region.Op.DIFFERENCE);
        canvas.drawRect(viewDrawingRect,
                hasFocus() ? mFocusPaint : mNoFocusPaint);

        canvas.restore();
        canvas.drawPath(path, mOutlinePaint);

        if (mMode == ModifyMode.Grow) {
            if (mCircle) {
                int width  = mResizeDrawableDiagonal.getIntrinsicWidth();
                int height = mResizeDrawableDiagonal.getIntrinsicHeight();

                int d  = (int) Math.round(Math.cos(/*45deg*/Math.PI / 4D)
                        * (mDrawRect.width() / 2D));
                int x  = mDrawRect.left
                        + (mDrawRect.width() / 2) + d - width / 2;
                int y  = mDrawRect.top
                        + (mDrawRect.height() / 2) - d - height / 2;
                mResizeDrawableDiagonal.setBounds(x, y,
                        x + mResizeDrawableDiagonal.getIntrinsicWidth(),
                        y + mResizeDrawableDiagonal.getIntrinsicHeight());
                mResizeDrawableDiagonal.draw(canvas);
            } else {
                int left    = mDrawRect.left   + 1;
                int right   = mDrawRect.right  + 1;
                int top     = mDrawRect.top    + 4;
                int bottom  = mDrawRect.bottom + 3;

                int widthWidth   =
                        mResizeDrawableWidth.getIntrinsicWidth() / 2;
                int widthHeight  =
                        mResizeDrawableWidth.getIntrinsicHeight() / 2;
                int heightHeight =
                        mResizeDrawableHeight.getIntrinsicHeight() / 2;
                int heightWidth  =
                        mResizeDrawableHeight.getIntrinsicWidth() / 2;

                int xMiddle = mDrawRect.left
                        + ((mDrawRect.right  - mDrawRect.left) / 2);
                int yMiddle = mDrawRect.top
                        + ((mDrawRect.bottom - mDrawRect.top) / 2);

                mResizeDrawableWidth.setBounds(left - widthWidth,
                                               yMiddle - widthHeight,
                                               left + widthWidth,
                                               yMiddle + widthHeight);
                mResizeDrawableWidth.draw(canvas);

                mResizeDrawableWidth.setBounds(right - widthWidth,
                                               yMiddle - widthHeight,
                                               right + widthWidth,
                                               yMiddle + widthHeight);
                mResizeDrawableWidth.draw(canvas);

                mResizeDrawableHeight.setBounds(xMiddle - heightWidth,
                                                top - heightHeight,
                                                xMiddle + heightWidth,
                                                top + heightHeight);
                mResizeDrawableHeight.draw(canvas);

                mResizeDrawableHeight.setBounds(xMiddle - heightWidth,
                                                bottom - heightHeight,
                                                xMiddle + heightWidth,
                                                bottom + heightHeight);
                mResizeDrawableHeight.draw(canvas);
            }
        }
    }
}

How can I draw to a bitmap and then blit to the canvas?

Any help would be greatly appreciated!

回答1:

On ICS devices, there is a developer option to force hardware acceleration even if the app doesn't request it. That is what is causing the crashes. You should be able to use something like this to force it to use software rendering:

if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {
   myCusomView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}


回答2:

I guess you can draw your view to a off-screen bitmap and copy the bitmap to the on-screen canvas. For instance,

protected void draw(Canvas canvas) {
    if (mHidden) {
        return;
    }

    Bitmap bitmap = createOffScreenBitmap();
    canvas.drawa(bitmap,0f, 0f, null);

}


private Bitmap drawOffScreenBitmap(){

    // Draw whatever you want to draw right here.
}

Also, you can use PorterDuffxfermode that supports hardware acceleration, instead of clippath.



回答3:

myCustomView = view class name or use this

if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {
   this.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

I get the solution using that method.