OutOfMemoryError в анимации SharedTransitionElement - PullRequest
0 голосов
/ 11 марта 2019

Я использую sharedElementTransition в своем приложении, и я использовал собственный класс для sharedElementCallback для обновления представлений во время выполнения.Иногда это вызывало ошибки OutOfMemory, поэтому я искал об этом, и из этого решения я использовал код LeakFreeSupportSharedElementCallback, чтобы избежать сбоев, но я все еще очень часто получаю следующие журналы сбоев.

Fatal Exception: java.lang.OutOfMemoryError: Failed to allocate a 8357052 byte allocation with 953048 free bytes and 930KB until OOM
       at dalvik.system.VMRuntime.newNonMovableArray(VMRuntime.java)
       at android.graphics.Bitmap.nativeCopy(Bitmap.java)
       at android.graphics.Bitmap.copy(Bitmap.java:684)
       at com.fayvo.ui.main.home.post.PostDetailSharedElementCallback.onCreateSnapshotView(PostDetailSharedElementCallback.java:279)
       at android.support.v4.app.ActivityCompat$SharedElementCallback21Impl.onCreateSnapshotView(ActivityCompat.java:609)
       at android.app.ActivityTransitionCoordinator.createSnapshots(ActivityTransitionCoordinator.java:666)
       at android.app.EnterTransitionCoordinator.startSharedElementTransition(EnterTransitionCoordinator.java:400)
       at android.app.EnterTransitionCoordinator.-wrap4(EnterTransitionCoordinator.java)
       at android.app.EnterTransitionCoordinator$5$1$1.run(EnterTransitionCoordinator.java:475)
       at android.app.ActivityTransitionCoordinator.startTransition(ActivityTransitionCoordinator.java:836)
       at android.app.EnterTransitionCoordinator$5$1.onPreDraw(EnterTransitionCoordinator.java:472)
       at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:1013)
       at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2513)
       at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1522)
       at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7098)
       at android.view.Choreographer$CallbackRecord.run(Choreographer.java:927)
       at android.view.Choreographer.doCallbacks(Choreographer.java:702)
       at android.view.Choreographer.doFrame(Choreographer.java:638)
       at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:913)
       at android.os.Handler.handleCallback(Handler.java:751)
       at android.os.Handler.dispatchMessage(Handler.java:95)
       at android.os.Looper.loop(Looper.java:154)
       at android.app.ActivityThread.main(ActivityThread.java:6682)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)

Класс CustomCallback:

public class CustomSharedElementCallback extends SharedElementCallback {


    static final String BUNDLE_SNAPSHOT_BITMAP = "BUNDLE_SNAPSHOT_BITMAP";
    static final String BUNDLE_SNAPSHOT_IMAGE_SCALETYPE = "BUNDLE_SNAPSHOT_IMAGE_SCALETYPE";
    static final String BUNDLE_SNAPSHOT_IMAGE_MATRIX = "BUNDLE_SNAPSHOT_IMAGE_MATRIX";

    static final String BUNDLE_SNAPSHOT_TYPE = "BUNDLE_SNAPSHOT_TYPE";
    static final String BUNDLE_SNAPSHOT_TYPE_IMAGE_VIEW = "BUNDLE_SNAPSHOT_TYPE";
    private static int MAX_IMAGE_SIZE = (1024 * 1024);
    private List<View> mSharedElements;
    private int currentPosition = 0;
    private boolean enableChangeImageTransform;
    private SparseArray<Matrix> tempMatrixes;

    public PostDetailSharedElementCallback() {
        mSharedElements = new ArrayList<>();
        tempMatrixes = new SparseArray<>();
    }


    @Override
    public void onSharedElementStart(List<String> sharedElementNames,
                                     List<View> sharedElements,
                                     List<View> sharedElementSnapshots) {

        /*AppLogger.d("usm_shared_callback_0.1", "onSharedElementStart: " + sharedElementNames.get(0)
                + " ,enableChangeImageTransform= " + enableChangeImageTransform
        );*/
        boolean allowTransform = enableChangeImageTransform && sharedElements != null && sharedElements.size() > 0;

        if (allowTransform && sharedElements.get(0) instanceof CustomPhotoView) {
            ((CustomPhotoView) sharedElements.get(0)).setScaleType(ImageView.ScaleType.CENTER_CROP);
        } else if (allowTransform && sharedElements.get(0) instanceof ImageView) {
            ((ImageView) sharedElements.get(0)).setScaleType(ImageView.ScaleType.FIT_CENTER);
        }
    }

    @Override
    public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
       /*AppLogger.d("usm_shared_callback_0.2", "onSharedElementEnd: " + sharedElementNames.get(0)
                + " ,enableChangeImageTransform= " + enableChangeImageTransform
        );*/
        boolean allowTransform = enableChangeImageTransform && sharedElements != null && sharedElements.size() > 0;

        if (allowTransform && sharedElements.get(0) instanceof CustomPhotoView) {
            ((CustomPhotoView) sharedElements.get(0)).setScaleType(ImageView.ScaleType.FIT_CENTER);

            // updateCloudChipBoxTagsView(sharedElementNames, sharedElements);
            updateBoxIconView(sharedElementNames, sharedElements);
            updateUserNameView(sharedElementNames, sharedElements);
        } else if (allowTransform && sharedElements.get(0) instanceof ImageView) {
            ((ImageView) sharedElements.get(0)).setScaleType(ImageView.ScaleType.CENTER_CROP);
        }

    }

    private void updateCloudChipBoxTagsView(List<String> sharedElementNames, List<View> sharedElements) {
        int i = 0;
        for (String sharedName : sharedElementNames) {

            if (sharedName.contains(PostTags.TRANSITION_NAME_TAGS)) {
                if (sharedElements.get(i) instanceof ChipCloud) {
                    ChipCloud chipCloud = ((ChipCloud) sharedElements.get(i));

                    int textColor = ContextCompat.getColor(chipCloud.getContext(), R.color.fayvo_color);
                    // adding modifyChips method to make animation transition experience a bit better
                    chipCloud.modifyChips(chipCloud.getContext().getResources().getDimension(R.dimen.hint_text_size), textColor);
                    break;
                }
            }
            i++;
        }
    }

    private void updateBoxIconView(List<String> sharedElementNames, List<View> sharedElements) {
        int i = 0;
        for (String sharedName : sharedElementNames) {

            if (sharedName.contains(PostTags.TRANSITION_NAME_BOX_ICON)) {
                if (sharedElements.get(i) instanceof ImageView) {
                    ImageView ivBoxIcon = (ImageView) sharedElements.get(i);
                    int color = ContextCompat.getColor(ivBoxIcon.getContext(), R.color.fayvo_color);
                    ivBoxIcon.setColorFilter(color);
                    break;
                }
            }
            i++;
        }
    }

    private void updateUserNameView(List<String> sharedElementNames, List<View> sharedElements) {
        int i = 0;
        for (String sharedName : sharedElementNames) {

            if (sharedName.contains(PostTags.TRANSITION_NAME_USERNAME)) {
                if (sharedElements.get(i) instanceof TextView) {
                    TextView textView = (TextView) sharedElements.get(i);
                    int color = ContextCompat.getColor(textView.getContext(), R.color.black);
                    float textSizePx = textView.getContext().getResources().getDimension(R.dimen.et_text_size);
                    textView.setTextColor(color);
                    textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx);
                    break;
                }
            }
            i++;
        }
    }

    @Override
    public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
        removeObsoleteElements(names, sharedElements, mapObsoleteElements(names));

        // update transition names
        setTransitionNames();

        for (int i = 0; i < mSharedElements.size(); i++)
            mapSharedElement(names, sharedElements, mSharedElements.get(i));
    }

    public int getCurrentPosition() {
        return currentPosition;
    }

    public void setCurrentPosition(int position) {
        // AppLogger.d("usm_shared_callback_1", "currentPosition= " + position);
        this.currentPosition = position;
    }

    public void setEnableChangeImageTransform(boolean enableTransform) {
        this.enableChangeImageTransform = enableTransform;
    }

    /**
     * This method is used to set Transition name of shared views.
     * Note: Keep the sequence exactly same in which the views are
     * passed for transition.
     */
    public void setTransitionNames() {
        //  AppLogger.d("usm_shared_callback_2", "Setting transition names: " + currentPosition);
        if (mSharedElements.size() > 0)
            ViewCompat.setTransitionName(mSharedElements.get(0), PostTags.TRANSITION_NAME_POST_BODY + currentPosition);
        if (mSharedElements.size() > 1)
            ViewCompat.setTransitionName(mSharedElements.get(1), PostTags.TRANSITION_NAME_USER_PIC + currentPosition);
        if (mSharedElements.size() > 2)
            ViewCompat.setTransitionName(mSharedElements.get(2), PostTags.TRANSITION_NAME_USERNAME + currentPosition);
        if (mSharedElements.size() > 3)
            ViewCompat.setTransitionName(mSharedElements.get(3), PostTags.TRANSITION_NAME_TAGS + currentPosition);
        if (mSharedElements.size() > 4)
            ViewCompat.setTransitionName(mSharedElements.get(4), PostTags.TRANSITION_NAME_BOX_ICON + currentPosition);

    }

    public List<View> getSharedViews() {
        // AppLogger.d("usm_shared_callback_4", "setSharedViews is called");
        if (mSharedElements != null)
            return mSharedElements;
        return null;
    }

    public void setSharedViews(@NonNull View... sharedViews) {
        // AppLogger.d("usm_shared_callback_4", "setSharedViews is called");
        clearSharedViews();
        for (View view : sharedViews) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                mSharedElements.add(view);
            }
        }
    }

    public void clearSharedViews() {
        mSharedElements.clear();
        tempMatrixes.clear();
    }

    /**
     * Maps all views that don't start with "android" namespace.
     *
     * @param names All shared element names.
     * @return The obsolete shared element names.
     */
    @NonNull
    private List<String> mapObsoleteElements(List<String> names) {
        List<String> elementsToRemove = new ArrayList<>(names.size());
        for (String name : names) {
            if (name.startsWith("android")) continue;
            elementsToRemove.add(name);
        }
        return elementsToRemove;
    }

    /**
     * Removes obsolete elements from names and shared elements.
     *
     * @param names            Shared element names.
     * @param sharedElements   Shared elements.
     * @param elementsToRemove The elements that should be removed.
     */
    private void removeObsoleteElements(List<String> names,
                                        Map<String, View> sharedElements,
                                        List<String> elementsToRemove) {
        if (elementsToRemove.size() > 0) {
            names.removeAll(elementsToRemove);
            for (String elementToRemove : elementsToRemove) {
                sharedElements.remove(elementToRemove);
            }
        }
    }

    /**
     * Puts a shared element to transitions and names.
     *
     * @param names          The names for this transition.
     * @param sharedElements The elements for this transition.
     * @param view           The view to add.
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void mapSharedElement(List<String> names, Map<String, View> sharedElements, View view) {
        String transitionName = view.getTransitionName();
        names.add(transitionName);
        sharedElements.put(transitionName, view);
    }

    /**
     * This method is overridden to avoid memory leaks
     * @param context
     * @param snapshot
     * @return
     */
    @Override
    public View onCreateSnapshotView(Context context, Parcelable snapshot) {

        // AppLogger.d("usm_shared_element_call_back", "onCreateSnapshotView: mSharedElements= " + mSharedElements.size());

        View view = null;
        if (snapshot instanceof Bundle) {
            Bundle bundle = (Bundle) snapshot;
            Bitmap bitmap = bundle.getParcelable(BUNDLE_SNAPSHOT_BITMAP);

            if (bitmap == null) {
                bundle.clear();
                return null;
            }

            // Curiously, this is required to have the bitmap be GCed almost immediately after transition ends
            // otherwise, garbage-collectable mem will still build up quickly
            bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false);

            if (bitmap == null) {
                return null;
            }

            if (BUNDLE_SNAPSHOT_TYPE_IMAGE_VIEW.equals(((Bundle) snapshot).getString(BUNDLE_SNAPSHOT_TYPE))) {
                ImageView imageView = new ImageView(context);
                view = imageView;
                imageView.setImageBitmap(bitmap);
                imageView.setScaleType(
                        ImageView.ScaleType.valueOf(bundle.getString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE)));
                if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) {
                    float[] values = bundle.getFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX);
                    Matrix matrix = new Matrix();
                    matrix.setValues(values);
                    imageView.setImageMatrix(matrix);
                }
            } else {
                view = new View(context);
                Resources resources = context.getResources();
                view.setBackground(new BitmapDrawable(resources, bitmap));
            }
            bundle.clear();
        }

        return view;
        //  return super.onCreateSnapshotView(context, snapshot);
    }


    /**
     * This method is overridden to avoid memory leaks
     * @param sharedElement
     * @param viewToGlobalMatrix
     * @param screenBounds
     * @return
     */
    @Override
    public Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix, RectF screenBounds) {
        //AppLogger.d("usm_shared_element_call_back", "onCaptureSharedElementSnapshot: mSharedElements= " + mSharedElements.size()
        //        + " ,sharedElement name= " + sharedElement.getTransitionName() + " ,id= " + sharedElement.getId());


        if (sharedElement instanceof ImageView) {
            ImageView imageView = ((ImageView) sharedElement);
            Drawable d = imageView.getDrawable();
            Drawable bg = imageView.getBackground();
            if (d != null && (bg == null || bg.getAlpha() == 0)) {
                Bitmap bitmap = TransitionUtils.createDrawableBitmap(d);
                if (bitmap != null) {
                    Bundle bundle = new Bundle();
                    bundle.putParcelable(BUNDLE_SNAPSHOT_BITMAP, bitmap);
                    bundle.putString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE,
                            imageView.getScaleType().toString());
                    if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) {
                        Matrix matrix = imageView.getImageMatrix();
                        float[] values = new float[9];
                        matrix.getValues(values);
                        bundle.putFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX, values);
                    }

                    bundle.putString(BUNDLE_SNAPSHOT_TYPE, BUNDLE_SNAPSHOT_TYPE_IMAGE_VIEW);

                    return bundle;
                }
            }
        }
        if (tempMatrixes.get(sharedElement.getId(), null) == null) {
            tempMatrixes.put(sharedElement.getId(), new Matrix(viewToGlobalMatrix));
        } else {
            tempMatrixes.get(sharedElement.getId()).set(viewToGlobalMatrix);
        }

        Bundle bundle = new Bundle();
        Bitmap bitmap = TransitionUtils.createViewBitmap(sharedElement, tempMatrixes.get(sharedElement.getId()), screenBounds);
        bundle.putParcelable(BUNDLE_SNAPSHOT_BITMAP, bitmap);

        return bundle;
        // return super.onCaptureSharedElementSnapshot(sharedElement, viewToGlobalMatrix, screenBounds);
    }
}

1 Ответ

0 голосов
/ 11 марта 2019

В вашем файле манифеста под тегом приложения добавьте следующую строку

<application
    android:largeHeap="true"
>

Это не очень хороший подход, но он решит вашу проблему, или вы должны найти способ управлять распределением памяти. Но в наши дни устройства приходят с большими воспоминаниями. Так что это не повлияет на заметную производительность. Вы хороши, чтобы пойти с этим.

Для получения дополнительной информации о распределении памяти перейдите по этой ссылке: производительность и память

...