I'm using sharedElementTransition in my app and i've used a custom class for sharedElementCallback to update the views at run time. It was causing OutOfMemory errors at sometimes, so i searched about it and from this solution i used code of LeakFreeSupportSharedElementCallback to avoid crashes but i still get following crash logs very often.
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 class:
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);
}
}
In your manifest file under application tag add the following line
That is not a very good approach but this will resolve your issue or you have to find a way to manage your memory allocation. But these days devices are coming with large memories. So this will not affect noticeable performance. You are good to go with this.
For further information regarding memory allocation do visit this link: performance and memory