牛轧糖上的 TransactionTooLargeException

我更新了 Nexus 5X 到 Android N,现在当我在它上面安装应用程序(调试或发布)的时候,我在每一个屏幕转换上都得到了 TransactionTooLargeException,这些屏幕转换都有 Bundle 的额外功能。这个应用程序适用于所有其他设备。在 PlayStore 上的旧应用程序和 Nexus5X 上的代码大致相同。 有人有同样的问题吗?

java.lang.RuntimeException: android.os.TransactionTooLargeException: data parcel size 592196 bytes
at android.app.ActivityThread$StopInfo.run(ActivityThread.java:3752)
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:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)
Caused by: android.os.TransactionTooLargeException: data parcel size 592196 bytes
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(Binder.java:615)
at android.app.ActivityManagerProxy.activityStopped(ActivityManagerNative.java:3606)
at android.app.ActivityThread$StopInfo.run(ActivityThread.java:3744)
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:6077) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755) 
51873 次浏览

最后,我的问题在于保存在 SaveInstance 上的东西,而不是发送到下一个活动的东西。我删除了所有无法控制对象大小(网络响应)的保存,现在它正在工作。

更新2:

Google 现在提供了 AndroidX ViewModel,它基于与保留碎片相同的技术,但更容易使用。现在 ViewModel 是首选的方法。

更新1:

为了保留大块数据,Google 建议使用保留实例的片段。想法是创建一个没有所有必要字段的视图的空片段,否则将保存在 Bundle 中。将 setRetainInstance(true);添加到片段的 onCreate 方法。 然后将数据保存在活动的 onDestroy 碎片中,并将它们加载到 Create 上。 下面是一个活动的例子:

public class MyActivity extends Activity {


private DataFragment dataFragment;


@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);


// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
dataFragment = (DataFragment) fm.findFragmentByTag(“data”);


// create the fragment and data the first time
if (dataFragment == null) {
// add the fragment
dataFragment = new DataFragment();
fm.beginTransaction().add(dataFragment, “data”).commit();
// load the data from the web
dataFragment.setData(loadMyData());
}


// the data is available in dataFragment.getData()
...
}


@Override
public void onDestroy() {
super.onDestroy();
// store the data in the fragment
dataFragment.setData(collectMyLoadedData());
}
}

碎片的一个例子:

public class DataFragment extends Fragment {


// data object we want to retain
private MyDataObject data;


// this method is only called once for this fragment
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}


public void setData(MyDataObject data) {
this.data = data;
}


public MyDataObject getData() {
return data;
}
}

关于它,你可以阅读 给你

只要在你的活动中重写这个方法:

@Override
protected void onSaveInstanceState(Bundle outState) {
// below line to be commented to prevent crash on nougat.
// http://blog.sqisland.com/2016/09/transactiontoolargeexception-crashes-nougat.html
//
//super.onSaveInstanceState(outState);
}

https://code.google.com/p/android/issues/detail?id=212316#makechanges了解更多信息。

我也面临同样的问题。这个问题和场景几乎没有什么不同,我用下面的方法修复它。请检查场景和解决方案。

场景: 我从 Google Nexus 6P 设备(7 OS)的客户那里得到了一个奇怪的 bug,因为我的应用程序在工作4小时后就会崩溃。后来我发现它抛出了类似的(android.os)。TransactionToLargeException:)异常。

解决方案: 日志并没有指向应用程序中的任何特定类,后来我发现这是因为保留了片段的回栈。在我的例子中,通过自动屏幕移动动画的帮助,4个片段被反复添加到后面的堆栈中。所以我重写了 onBackstackChanged () ,如下所述。

 @Override
public void onBackStackChanged() {
try {
int count = mFragmentMngr.getBackStackEntryCount();
if (count > 0) {
if (count > 30) {
mFragmentMngr.popBackStack(1, FragmentManager.POP_BACK_STACK_INCLUSIVE);
count = mFragmentMngr.getBackStackEntryCount();
}
FragmentManager.BackStackEntry entry = mFragmentMngr.getBackStackEntryAt(count - 1);
mCurrentlyLoadedFragment = Integer.parseInt(entry.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}

如果堆栈超出限制,它将自动弹出到初始片段。我希望有人能够帮助解决这个问题,因为异常和堆栈跟踪日志是相同的。因此,无论何时发生这个问题,如果您正在使用片段和回堆栈,请检查回堆栈计数。

我也面临同样的问题。 我的工作区卸载将 InstanceState 保存到缓存目录中的文件。

我做了下面的实用类。

package net.cattaka.android.snippets.issue;


import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;


import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;


/**
* To parry BUG of Android N. https://code.google.com/p/android/issues/detail?id=212316
* <p>
* Created by cattaka on 2017/01/12.
*/
public class Issue212316Parrier {
public static final String DEFAULT_NAME = "Issue212316Parrier";
private static final String KEY_STORED_BUNDLE_ID = "net.cattaka.android.snippets.issue.Issue212316Parrier.KEY_STORED_BUNDLE_ID";


private String mName;
private Context mContext;
private String mAppVersionName;
private int mAppVersionCode;
private SharedPreferences mPreferences;
private File mDirForStoredBundle;


public Issue212316Parrier(Context context, String appVersionName, int appVersionCode) {
this(context, appVersionName, appVersionCode, DEFAULT_NAME);
}


public Issue212316Parrier(Context context, String appVersionName, int appVersionCode, String name) {
mName = name;
mContext = context;
mAppVersionName = appVersionName;
mAppVersionCode = appVersionCode;
}


public void initialize() {
mPreferences = mContext.getSharedPreferences(mName, Context.MODE_PRIVATE);


File cacheDir = mContext.getCacheDir();
mDirForStoredBundle = new File(cacheDir, mName);
if (!mDirForStoredBundle.exists()) {
mDirForStoredBundle.mkdirs();
}


long lastStoredBundleId = 1;
boolean needReset = true;
String fingerPrint = (Build.FINGERPRINT != null) ? Build.FINGERPRINT : "";
needReset = !fingerPrint.equals(mPreferences.getString("deviceFingerprint", null))
|| !mAppVersionName.equals(mPreferences.getString("appVersionName", null))
|| (mAppVersionCode != mPreferences.getInt("appVersionCode", 0));
lastStoredBundleId = mPreferences.getLong("lastStoredBundleId", 1);


if (needReset) {
clearDirForStoredBundle();


mPreferences.edit()
.putString("deviceFingerprint", Build.FINGERPRINT)
.putString("appVersionName", mAppVersionName)
.putInt("appVersionCode", mAppVersionCode)
.putLong("lastStoredBundleId", lastStoredBundleId)
.apply();
}
}


/**
* Call this from {@link android.app.Activity#onCreate(Bundle)}, {@link android.app.Activity#onRestoreInstanceState(Bundle)} or {@link android.app.Activity#onPostCreate(Bundle)}
*/
public void restoreSaveInstanceState(@Nullable Bundle savedInstanceState, boolean deleteStoredBundle) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_STORED_BUNDLE_ID)) {
long storedBundleId = savedInstanceState.getLong(KEY_STORED_BUNDLE_ID);
File storedBundleFile = new File(mDirForStoredBundle, storedBundleId + ".bin");
Bundle storedBundle = loadBundle(storedBundleFile);
if (storedBundle != null) {
savedInstanceState.putAll(storedBundle);
}
if (deleteStoredBundle && storedBundleFile.exists()) {
storedBundleFile.delete();
}
}
}
}


/**
* Call this from {@link android.app.Activity#onSaveInstanceState(Bundle)}
*/
public void saveInstanceState(Bundle outState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (outState != null) {
long nextStoredBundleId = mPreferences.getLong("lastStoredBundleId", 1) + 1;
mPreferences.edit().putLong("lastStoredBundleId", nextStoredBundleId).apply();
File storedBundleFile = new File(mDirForStoredBundle, nextStoredBundleId + ".bin");
saveBundle(outState, storedBundleFile);
outState.clear();
outState.putLong(KEY_STORED_BUNDLE_ID, nextStoredBundleId);
}
}
}


private void saveBundle(@NonNull Bundle bundle, @NonNull File storedBundleFile) {
byte[] blob = marshall(bundle);
OutputStream out = null;
try {
out = new GZIPOutputStream(new FileOutputStream(storedBundleFile));
out.write(blob);
out.flush();
out.close();
} catch (IOException e) {
// ignore
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
// ignore
}
}
}
}


@Nullable
private Bundle loadBundle(File storedBundleFile) {
byte[] blob = null;
InputStream in = null;
try {
in = new GZIPInputStream(new FileInputStream(storedBundleFile));
ByteArrayOutputStream bout = new ByteArrayOutputStream();
int n;
byte[] buffer = new byte[1024];
while ((n = in.read(buffer)) > -1) {
bout.write(buffer, 0, n);   // Don't allow any extra bytes to creep in, final write
}
bout.close();
blob = bout.toByteArray();
} catch (IOException e) {
// ignore
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// ignore
}
}
}


try {
return (blob != null) ? (Bundle) unmarshall(blob) : null;
} catch (Exception e) {
return null;
}
}


private void clearDirForStoredBundle() {
for (File file : mDirForStoredBundle.listFiles()) {
if (file.isFile() && file.getName().endsWith(".bin")) {
file.delete();
}
}
}




@NonNull
private static <T extends Parcelable> byte[] marshall(@NonNull final T object) {
Parcel p1 = Parcel.obtain();
p1.writeValue(object);


byte[] data = p1.marshall();
p1.recycle();
return data;
}


@SuppressWarnings("unchecked")
@NonNull
private static <T extends Parcelable> T unmarshall(@NonNull byte[] bytes) {
Parcel p2 = Parcel.obtain();
p2.unmarshall(bytes, 0, bytes.length);
p2.setDataPosition(0);
T result = (T) p2.readValue(Issue212316Parrier.class.getClassLoader());
p2.recycle();
return result;
}
}

密码: https://github.com/cattaka/AndroidSnippets/pull/37

我担心,包裹 # 马歇尔不应该用于持久性。 但是,我没有别的办法了。

我的应用程序存在的问题是,我试图在保存的 InstanceState 中保存太多数据,解决方案是确定在正确的时间应该保存哪些数据。基本上要仔细查看 onSaveInstanceState,以确保不会延伸它:

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current state
// Check carefully what you're adding into the savedInstanceState before saving it
super.onSaveInstanceState(savedInstanceState);
}

TransactionToLargeException 已经困扰我们大约4个月了,我们终于解决了这个问题!

我们正在使用 ViewPager 中的 FragmentStatePagerAdapter。用户将浏览并创建100多个片段(它是一个阅读应用程序)。

尽管我们可以正确地管理 demuryItem ()中的片段,但是在 Android 中 有一个 bug,它保存了一个对以下列表的引用:

private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();

当 Android 的 FragmentStatePagerAdapter 尝试保存状态时,它将调用该函数

@Override
public Parcelable saveState() {
Bundle state = null;
if (mSavedState.size() > 0) {
state = new Bundle();
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
mSavedState.toArray(fss);
state.putParcelableArray("states", fss);
}
for (int i=0; i<mFragments.size(); i++) {
Fragment f = mFragments.get(i);
if (f != null && f.isAdded()) {
if (state == null) {
state = new Bundle();
}
String key = "f" + i;
mFragmentManager.putFragment(state, key, f);
}
}
return state;
}

正如您所看到的,即使正确地管理了 FragmentStatePagerAdapter 子类中的片段,基类仍然会存储一个片段。保存所有已经创建的片段。当该数组被转储到 parcelableArray 并且操作系统不喜欢它超过100个项时,会发生 TransactionTooLargeException。

因此,我们的解决方案是覆盖 saveState ()方法,不为“ state”存储任何内容。

@Override
public Parcelable saveState() {
Bundle bundle = (Bundle) super.saveState();
bundle.putParcelableArray("states", null); // Never maintain any states from the base class, just null it out
return bundle;
}

当 Android N 更改行为并抛出 TransactionToLargeException 而不是记录错误时。

     try {
if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Reporting activity stopped: " + activity);
ActivityManagerNative.getDefault().activityStopped(
activity.token, state, persistentState, description);
} catch (RemoteException ex) {
if (ex instanceof TransactionTooLargeException
&& activity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) {
Log.e(TAG, "App sent too much data in instance state, so it was ignored", ex);
return;
}
throw ex.rethrowFromSystemServer();
}

我的解决方案是钩住 ActivityMangerProxy 实例并尝试捕获 activityStop 方法。

密码如下:

private boolean hookActivityManagerNative() {
try {
ClassLoader loader = ClassLoader.getSystemClassLoader();
Field singletonField = ReflectUtils.findField(loader.loadClass("android.app.ActivityManagerNative"), "gDefault");
ReflectUtils.ReflectObject singletonObjWrap = ReflectUtils.wrap(singletonField.get(null));
Object realActivityManager = singletonObjWrap.getChildField("mInstance").get();
Object fakeActivityManager = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{loader.loadClass("android.app.IActivityManager")}, new ActivityManagerHook(realActivityManager));
singletonObjWrap.setChildField("mInstance", fakeActivityManager);
return true;
} catch (Throwable e) {
AppHolder.getThirdPartUtils().markException(e);
return false;
}
}


private static class ActivityManagerHook implements InvocationHandler {


private Object origin;


ActivityManagerHook(Object origin) {
this.origin = origin;
}


public Object getOrigin() {
return origin;
}


@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
switch (method.getName()) {
//ActivityManagerNative.getDefault().activityStopped(activity.token, state, persistentState, description);
case "activityStopped": {
try {
return method.invoke(getOrigin(), args);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
return method.invoke(getOrigin(), args);
}
}

反射助手类是

public class ReflectUtils {


private static final HashMap<String, Field> fieldCache = new HashMap<>();
private static final HashMap<String, Method> methodCache = new HashMap<>();


public static Field findField(Class<?> clazz, String fieldName) throws Throwable {
String fullFieldName = clazz.getName() + '#' + fieldName;


if (fieldCache.containsKey(fullFieldName)) {
Field field = fieldCache.get(fullFieldName);
if (field == null)
throw new NoSuchFieldError(fullFieldName);
return field;
}


try {
Field field = findFieldRecursiveImpl(clazz, fieldName);
field.setAccessible(true);
fieldCache.put(fullFieldName, field);
return field;
} catch (NoSuchFieldException e) {
fieldCache.put(fullFieldName, null);
throw new NoSuchFieldError(fullFieldName);
}
}




private static Field findFieldRecursiveImpl(Class<?> clazz, String fieldName) throws NoSuchFieldException {
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
while (true) {
clazz = clazz.getSuperclass();
if (clazz == null || clazz.equals(Object.class))
break;


try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException ignored) {
}
}
throw e;
}
}




public static Method findMethodExact(Class<?> clazz, String methodName, Class<?>... parameterTypes) throws Throwable {
String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#exact";


if (methodCache.containsKey(fullMethodName)) {
Method method = methodCache.get(fullMethodName);
if (method == null)
throw new NoSuchMethodError(fullMethodName);
return method;
}


try {
Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
method.setAccessible(true);
methodCache.put(fullMethodName, method);
return method;
} catch (NoSuchMethodException e) {
methodCache.put(fullMethodName, null);
throw new NoSuchMethodError(fullMethodName);
}
}




/**
* Returns an array of the given classes.
*/
public static Class<?>[] getClassesAsArray(Class<?>... clazzes) {
return clazzes;
}


private static String getParametersString(Class<?>... clazzes) {
StringBuilder sb = new StringBuilder("(");
boolean first = true;
for (Class<?> clazz : clazzes) {
if (first)
first = false;
else
sb.append(",");


if (clazz != null)
sb.append(clazz.getCanonicalName());
else
sb.append("null");
}
sb.append(")");
return sb.toString();
}


/**
* Retrieve classes from an array, where each element might either be a Class
* already, or a String with the full class name.
*/
private static Class<?>[] getParameterClasses(ClassLoader classLoader, Object[] parameterTypes) throws ClassNotFoundException {
Class<?>[] parameterClasses = null;
for (int i = parameterTypes.length - 1; i >= 0; i--) {
Object type = parameterTypes[i];
if (type == null)
throw new ClassNotFoundException("parameter type must not be null", null);


if (parameterClasses == null)
parameterClasses = new Class<?>[i + 1];


if (type instanceof Class)
parameterClasses[i] = (Class<?>) type;
else if (type instanceof String)
parameterClasses[i] = findClass((String) type, classLoader);
else
throw new ClassNotFoundException("parameter type must either be specified as Class or String", null);
}


// if there are no arguments for the method
if (parameterClasses == null)
parameterClasses = new Class<?>[0];


return parameterClasses;
}


public static Class<?> findClass(String className, ClassLoader classLoader) throws ClassNotFoundException {
if (classLoader == null)
classLoader = ClassLoader.getSystemClassLoader();
return classLoader.loadClass(className);
}




public static ReflectObject wrap(Object object) {
return new ReflectObject(object);
}




public static class ReflectObject {


private Object object;


private ReflectObject(Object o) {
this.object = o;
}


public ReflectObject getChildField(String fieldName) throws Throwable {
Object child = ReflectUtils.findField(object.getClass(), fieldName).get(object);
return ReflectUtils.wrap(child);
}


public void setChildField(String fieldName, Object o) throws Throwable {
ReflectUtils.findField(object.getClass(), fieldName).set(object, o);
}


public ReflectObject callMethod(String methodName, Object... args) throws Throwable {
Class<?>[] clazzs = new Class[args.length];
for (int i = 0; i < args.length; i++) {
clazzs[i] = args.getClass();
}
Method method = ReflectUtils.findMethodExact(object.getClass(), methodName, clazzs);
return ReflectUtils.wrap(method.invoke(object, args));
}


public <T> T getAs(Class<T> clazz) {
return (T) object;
}


public <T> T get() {
return (T) object;
}
}
}

我在自己的牛轧糖设备上也面临着这个问题。我的应用程序使用了一个包含4个片段的视图页导航器片段。我将一些大的构造参数传递给了引起问题的4个片段。

工具大工具的帮助下,我追踪了造成这种情况的 Bundle的大小。

最后,我在实现 Serializable的 POJO 对象上使用 putSerializable解决了这个问题,而不是在片段初始化期间使用 putString传递一个大的原始 String。这减少了一半的大小的 Bundle和不抛出的 TransactionTooLargeException。因此,请确保不要向 Fragment传递大型参数。

附注: Google 问题跟踪器中的相关问题: https://issuetracker.google.com/issues/37103380

每当你看到 TransactionTooLargeException发生时,一个 Activity正在停止的过程中,这意味着 Activity试图发送其保存的状态 Bundles到系统操作系统的安全保管恢复以后(在配置改变或进程死亡) ,但一个或多个的 Bundles发送太大。对于同时发生的所有此类事务,最大限制约为1MB,即使没有一个 Bundle超过这个限制,也可以达到这个限制。

这里的罪魁祸首通常是在 ActivityActivity承载的任何 FragmentsonSaveInstanceState内部保存了太多的数据。这种情况通常发生在保存特别大的数据(如 Bitmap)时,但也可能发送大量小数据(如 Parcelable对象列表)时。Android 团队已经在许多场合明确表示,onSavedInstanceState中只应该保存少量与视图相关的数据。但是,开发人员经常保存网络数据页,以便通过不必重新获取相同的数据,使配置更改看起来尽可能顺利。从2017年 Google I/O 大会开始,Android 团队已经明确表示,Android 应用程序的首选架构可以节省网络数据

  • 这样就可以很容易地在配置更改之间重用它
  • 到磁盘,以便在进程终止和应用程序会话之后可以轻松地恢复

他们新的 ViewModel框架和 Room持久性库旨在帮助开发人员适应这种模式。如果您的问题是在 onSaveInstanceState中保存了太多的数据,那么使用这些工具更新到这样的体系结构应该可以解决您的问题。

就个人而言,在更新到那个新模式之前,我想使用我现有的应用程序,同时绕过 TransactionTooLargeException。为此,我编写了一个快速库: https://github.com/livefront/bridge。它使用了相同的通用思想,即通过配置更改从内存恢复状态,并在进程终止后从磁盘恢复状态,而不是通过 onSaveInstanceState将所有状态发送到操作系统,但是只需对现有代码进行非常小的更改即可使用。但是,任何符合这两个目标的策略都应该能够帮助您避免异常,而不会牺牲保存状态的能力。

最后一点: 你在 Nougat + 上看到这个的唯一原因是,最初如果绑定器事务限制被超过,发送保存状态到操作系统的过程将无声无息地失败,只有这个错误出现在 Logcat:

文件夹交易失败!

在牛轧糖中,这种无声的失败升级为严重的崩溃。值得赞扬的是,这是开发团队在 牛轧糖的发行说明中记录的内容:

许多平台 API 现在已经开始检查通过 Binder 事务发送的大型有效负载,系统现在将 TransactionTooLargeExceptions 重新抛出为 RuntimeExceptions,而不是静默地记录或抑制它们。一个常见的例子是在 Activity.onSaveInstanceState ()中存储太多数据,这会导致 ActivityThread。当你的应用针对 Android 7.0时,StopInfo 会抛出一个 RuntimeException 异常。

杀人审判,终于解决了我的问题。 把这个加到你的 Activity

@Override
protected void onSaveInstanceState(Bundle oldInstanceState) {
super.onSaveInstanceState(oldInstanceState);
oldInstanceState.clear();
}

上面的答案对我来说都不起作用,问题的原因很简单,正如一些人所说,我使用的是 FragmentStatePagerAdapter,它的 saveState 方法保存了片段的状态,因为我的一个片段非常大,所以保存这个片段导致了这个 TransactionToLargeExeception。

如@IK828所述,我试图在寻呼机的实现中重写 saveState 方法,但这不能解决崩溃问题。

我的片段拥有一个 EditText,它用于保存非常大的文本,这是我的例子中问题的罪魁祸首,所以简单地在片段的 onPuse ()中,我将 edtext 文本设置为空字符串。 即:

@Override
public void onPause() {
edittext.setText("");
}

现在,当 FragmentStatePagerAdapter 试图保存 State 时,这个大块文本将不会占用它的更大部分,因此解决了崩溃问题。

在你的情况下,你需要找到罪魁祸首,它可能是一个带有一些位图的 ImageView,一个带有大块文本的 TextView 或任何其他高内存消耗视图,你需要释放它的内存,你可以在片段的 onPace ()中设置 ImageView.setImageResource (null)或类似的值。

Update: onSaveInstanceState 在调用 super 之前最好放在以下位置:

@Override
public void onSaveInstanceState(Bundle outState) {
edittext.setText("");
super.onSaveInstanceState(outState);
}

或者像@Vladimir 指出的那样,你可以在视图或自定义视图中使用 android: saveEnable = “ false”或 view.setSaveEnable (false) ; 并确保在 onResume 中设置文本,否则当 Activity 恢复时它将是空的。

在我的例子中,我在一个片段中得到了这个异常,因为它的一个参数是一个非常大的字符串,我忘了删除它(我只在 onViewCreated ()方法中使用了这个大字符串)。所以,为了解决这个问题,我简单地删除了那个参数。在您的情况下,必须在调用 Puse ()之前清除或取消任何可疑字段。

行动代码

Fragment fragment = new Fragment();
Bundle args = new Bundle();
args.putString("extremely large string", data.getValue());
fragment.setArguments(args);

碎片代码

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {


String largeString = arguments.get("extremely large string");
//Do Something with the large string
arguments.clear() //I forgot to execute this
}

在我的案例中,我使用 工具大工具来跟踪问题来自哪里,我发现 Bundle中的 android:support:fragments密钥来自我的 onSaveInstanceState,当应用程序崩溃时,android:support:fragments密钥几乎达到1mb。所以解决办法就是:

@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.remove("android:support:fragments");
}

通过这样做,我避免了保存所有片段的状态,并保留了需要保存的其他内容。