web-dev-qa-db-fra.com

Comment réparer "toile: essayer d'utiliser une erreur bitmap recyclé"?

Je crée une RecyclerView pour afficher une grille d'images. Lors de la sélection de l'un d'entre eux, une nouvelle activité doit être ouverte avec une transition.

J'utilise la bibliothèque Glide pour charger les images et la transition est affreuse, car elle recharge l'image dans la nouvelle activité. J'ai donc dû l'enregistrer en cache, puis l'utiliser pour la transition.

J'ai le code, mais parfois, si l'image ne se charge pas, une exception Canvas RuntimeException est levée.

Ceci est le journal:

07-03 15:19:58.633  28461-28461/jahirfiquitiva.project E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: jahirfiquitiva.project, PID: 28461
    Java.lang.RuntimeException: Canvas: trying to use a recycled bitmap Android.graphics.Bitmap@29f09d20
            at Android.graphics.Canvas.throwIfCannotDraw(Canvas.Java:1282)
            at Android.view.GLES20Canvas.drawBitmap(GLES20Canvas.Java:599)
            at Android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.Java:538)
            at Android.widget.ImageView.onDraw(ImageView.Java:1176)
            at Android.view.View.draw(View.Java:15239)
            at Android.view.View.updateDisplayListIfDirty(View.Java:14175)
            at Android.view.View.getDisplayList(View.Java:14197)
            at Android.view.GhostView.onDraw(GhostView.Java:52)
            at Android.view.View.draw(View.Java:15239)
            at Android.view.View.updateDisplayListIfDirty(View.Java:14175)
            at Android.view.View.getDisplayList(View.Java:14197)
            at Android.view.View.draw(View.Java:14967)
            at Android.view.ViewGroup.drawChild(ViewGroup.Java:3406)
            at Android.view.ViewGroup.dispatchDraw(ViewGroup.Java:3199)
            at Android.view.View.updateDisplayListIfDirty(View.Java:14170)
            at Android.view.View.getDisplayList(View.Java:14197)
            at Android.view.View.draw(View.Java:14967)
            at Android.view.ViewGroup.drawChild(ViewGroup.Java:3406)
            at Android.view.ViewGroup.dispatchDraw(ViewGroup.Java:3199)
            at Android.view.ViewOverlay$OverlayViewGroup.dispatchDraw(ViewOverlay.Java:219)
            at Android.view.View.draw(View.Java:15248)
            at Android.widget.FrameLayout.draw(FrameLayout.Java:598)
            at com.Android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.Java:2906)
            at Android.view.View.updateDisplayListIfDirty(View.Java:14175)
            at Android.view.View.getDisplayList(View.Java:14197)
            at Android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.Java:273)
            at Android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.Java:279)
            at Android.view.ThreadedRenderer.draw(ThreadedRenderer.Java:318)
            at Android.view.ViewRootImpl.draw(ViewRootImpl.Java:2536)
            at Android.view.ViewRootImpl.performDraw(ViewRootImpl.Java:2352)
            at Android.view.ViewRootImpl.performTraversals(ViewRootImpl.Java:1982)
            at Android.view.ViewRootImpl.doTraversal(ViewRootImpl.Java:1061)
            at Android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.Java:5891)
            at Android.view.Choreographer$CallbackRecord.run(Choreographer.Java:767)
            at Android.view.Choreographer.doCallbacks(Choreographer.Java:580)
            at Android.view.Choreographer.doFrame(Choreographer.Java:550)
            at Android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.Java:753)
            at Android.os.Handler.handleCallback(Handler.Java:739)
            at Android.os.Handler.dispatchMessage(Handler.Java:95)
            at Android.os.Looper.loop(Looper.Java:135)
            at Android.app.ActivityThread.main(ActivityThread.Java:5289)
            at Java.lang.reflect.Method.invoke(Native Method)
            at Java.lang.reflect.Method.invoke(Method.Java:372)
            at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:904)
            at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:699)

C'est le code pour ouvrir l'autre activité et enregistrer l'image en cache:

private void openViewer(WallpapersAdapter.WallsHolder wallsHolder, int index, final HashMap<String, String> data) {

        final Intent intent = new Intent(wallsActivity, ViewerActivity.class);
        intent.putExtra("wallUrl", data.get(WallpapersActivity.WALL));
        intent.putExtra("wallName", data.get(WallpapersActivity.NAME));
        intent.putExtra("transitionName", ViewCompat.getTransitionName(wallsHolder.wall));

        //save image from drawable
        //get its path and send it to activity
        Bitmap bitmap = drawableToBitmap(wallsHolder.wall.getDrawable());
        //Convert to byte array and send to the other activity

        Log.e("Resolution", bitmap.getWidth() + "x" + bitmap.getHeight());
        try {
            //Write file
            String filename = "bitmap.png";
            FileOutputStream stream = this.openFileOutput(filename, Context.MODE_PRIVATE);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);

            //Cleanup
            stream.close();
            bitmap.recycle();

            //Pop intent
            intent.putExtra("image", filename);
        } catch (Exception e) {
            e.printStackTrace();
        }

        ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
                this, wallsHolder.wall, ViewCompat.getTransitionName(wallsHolder.wall));
        startActivity(intent, options.toBundle());

    }

    public static Bitmap drawableToBitmap (Drawable drawable) {
        Bitmap bitmap = null;
        if (drawable instanceof BitmapDrawable) {
            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
            if(bitmapDrawable.getBitmap() != null) {
                return bitmapDrawable.getBitmap();
            }
        }

        if(drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
            bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel
        } else {
            bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        }

        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        drawable.draw(canvas);
        return bitmap;
    }

Que puis-je faire pour résoudre ce problème? Merci d'avance.

7
Jahir Fiquitiva

Je soupçonne que de temps en temps votre bitmap passe à l'état recyclé juste avant que la variable Canvas ait la possibilité de l'utiliser ici drawable.draw(canvas);.

Une solution rapide devrait être de ne pas appeler bitmap.recycle();, qui n'est pas strictement requis pour Android> 2.3.3 . Si vous souhaitez toujours récupérer cette mémoire avec force, vous devrez trouver un moyen de vérifier si le bitmap n'est plus nécessaire (c'est-à-dire que Canvas a eu la possibilité de terminer ses opérations de dessin).

12
AndroidEx

Déplacez bitmap.recycle(); vers un autre endroit du code où ce bitmap n'est vraiment plus nécessaire.

6
sakiM

Je ne connais pas beaucoup de choses sur canvas (je n'utilise pas souvent d'animations), mais si vous ne trouvez aucun moyen de résoudre ce problème, vous pouvez utiliser cette bibliothèque à la place: https://github.com/codepath/Android_guides/wiki/shared-element-activity-transition

0
James Fenn