Search code examples
androidcanvasbitmappicasso

App crash when calling BitmapDrawable.draw(Canvas canvas)


I have a code like this:

@Override
public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
    drawGradient(canvas, parent);
    drawStages(canvas, parent);
}

private void drawStages(final Canvas canvas, RecyclerView parent) {
    final int parentLeft = parent.getPaddingLeft();
    final int parentRight = parentLeft + mLeftPadding + mIconSize + mRightPadding;

    int childCount = parent.getChildCount();
    for (int i = 0; i < childCount - 1; i++) {
        View child = parent.getChildAt(i);

        LineupLayoutManager.LayoutParams params =
                (LineupLayoutManager.LayoutParams) child.getLayoutParams();

        final int parentTop = child.getTop() + params.topMargin;
        final int parentBottom = child.getBottom() + params.bottomMargin;
        int height = parentBottom - parentTop;

        final int paddingVertical = Math.round((height - mIconSize) / 2);

        try {
            String link = findPlaceById(params.getPlaceId(),mStageList).getImg();
            Picasso.with(context).load(link).resize(150,150).into(new Target() {
                @Override
                public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
                    final Drawable drawable = new BitmapDrawable();
                    drawable.setBounds(parentLeft + mLeftPadding, parentTop + paddingVertical,
                            parentRight - mRightPadding, parentBottom - paddingVertical);
                    Canvas canvas1 = new Canvas(bitmap);
                    drawable.draw(canvas1);
                }

                @Override
                public void onBitmapFailed(Drawable errorDrawable) {

                }

                @Override
                public void onPrepareLoad(Drawable placeHolderDrawable) {

                }
            });
        } catch (IndexOutOfBoundsException e) {
            Log.e(LOG_TAG, Log.getStackTraceString(e));
        }
    }
}

Whole code is inside the Class extended from RecyclerView.ItemDecoration The problem is that when I execute it, app crashes with this error:

Fatal signal 11 (SIGSEGV), code 1, fault addr 0x58 in tid 17542

I also tried to put bitmap.draw into runOnUIThread() but no success.

After a bit of research, I discovered that Picasso is somehow interfering with the canvas. That means if I put the stuff outside picasso methods, everything works fine.


Solution

  • I'm only assuming right now, but it seems like you stored a reference to canvas sometime earlier. When you then receive the Bitmap, this Canvas might have been invalidated somehow already.

    Instead of your implementation, store a reference to the Drawable and request a redraw on your View. Then within onDraw() draw the Drawable to the Canvas provided as a parameter.

    Try to adapt the following implementation to you liking.

    BitmapDrawable drawable = null;
    
    private void drawStages(Canvas canvas, RecyclerView parent) {
        if (drawable != null) {
            int parentLeft = parent.getPaddingLeft();
            int parentRight = parentLeft + mLeftPadding + mIconSize + mRightPadding;
    
            int childCount = parent.getChildCount();
            for (int i = 0; i < childCount - 1; i++) {
                View child = parent.getChildAt(i);
    
                LineupLayoutManager.LayoutParams params =
                        (LineupLayoutManager.LayoutParams) child.getLayoutParams();
    
                int parentTop = child.getTop() + params.topMargin;
                int parentBottom = child.getBottom() + params.bottomMargin;
                int height = parentBottom - parentTop;
    
                int paddingVertical = Math.round((height - mIconSize) / 2);
    
                drawable.setBounds(parentLeft + mLeftPadding, parentTop + paddingVertical,
                        parentRight - mRightPadding, parentBottom - paddingVertical);
                drawable.draw(canvas);
        } else try {
            String link = findPlaceById(params.getPlaceId(),mStageList).getImg();
            Picasso.with(context).load(link).resize(150,150).into(new Target() {
                @Override
                public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
                    drawable = new BitmapDrawable();
                    parent.invalidate()
                }
    
                @Override
                public void onBitmapFailed(Drawable errorDrawable) {
                }
    
                @Override
                public void onPrepareLoad(Drawable placeHolderDrawable) {
                }
            });
        } catch (IndexOutOfBoundsException e) {
            Log.e(LOG_TAG, Log.getStackTraceString(e));
        }
    }
    

    Edit: Added the working code.

    private void drawStages(Canvas canvas, final RecyclerView parent) {
        final int parentLeft = parent.getPaddingLeft();
        final int parentRight = parentLeft + mLeftPadding + mIconSize + mRightPadding;
    
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount - 1; i++) {
            View child = parent.getChildAt(i);
    
            LineupLayoutManager.LayoutParams params =
                    (LineupLayoutManager.LayoutParams) child.getLayoutParams();
    
            final int parentTop = child.getTop() + params.topMargin;
            final int parentBottom = child.getBottom() + params.bottomMargin;
            int height = parentBottom - parentTop;
    
            final int paddingVertical = Math.round((height - mIconSize) / 2);
            BitmapDrawable drawable;
            if(drawables.size()!=0)drawable = drawables.get(i);
            else drawable = null;
            if(drawable!=null){
                drawable.setBounds(parentLeft + mLeftPadding, parentTop + paddingVertical,
                        parentRight - mRightPadding, parentBottom - paddingVertical);
                drawable.draw(canvas);
            } else {
                String link = findPlaceById(params.getPlaceId(),mStageList).getImg();
                Picasso.with(context).load(link).resize(150,150).into(new Target() {
                    @Override
                    public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
                        BitmapDrawable d = new BitmapDrawable(context.getResources(),bitmap);
                        drawables.add(d);
                        parent.invalidateItemDecorations();
                    }
    
                    @Override
                    public void onBitmapFailed(Drawable errorDrawable) {
                    }
    
                    @Override
                    public void onPrepareLoad(Drawable placeHolderDrawable) {
                    }
                });
            }
        }
    }