android.os.TransactionTooLargeException 牛轧糖

android.os.TransactionTooLargeException on Nougat

我将 Nexus 5X 更新为 Android N,现在当我在其上安装应用程序(调试或发布)时,我在每个具有 Bundle in extras 的屏幕转换中都收到 TransactionTooLargeException。该应用程序适用于所有其他设备。 PlayStore 上的旧应用程序具有大部分相同的代码,可在 Nexus 5X 上运行。 有人遇到同样的问题吗?

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) 

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

更新二:

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

更新 1:

为了保留大块数据,Google 建议使用保留实例的 Fragment 来实现。想法是创建一个没有视图的空片段,其中包含所有必要的字段,否则将保存在捆绑包中。在 Fragment 的 onCreate 方法中添加 setRetainInstance(true);。 然后将数据保存在 Activity 的 onDestroy 上的 Fragment 中,并在 onCreate 上加载它们。 这是 Activity 的示例:

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;
    }
}

更多内容,您可以阅读here

只需在您的 activity 上覆盖此方法即可:

@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) 的客户那里得到了一个奇怪的错误,因为我的应用程序在工作 4 小时后会崩溃。后来我发现它抛出了类似的 (android.os.TransactionTooLargeException:) 异常。

解法: 日志没有指向应用程序中的任何特定 class,后来我发现这是因为保留了碎片的后堆栈而发生的。在我的例子中,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();
        }
    }

如果堆栈超过限制,它会自动弹出到初始片段。我希望有人会帮助这个答案,因为异常和堆栈跟踪日志是相同的。因此,每当发生此问题时,如果您使用的是 Fragments 和返回堆栈,请检查返回堆栈计数。

我遇到了同样的问题。 我的解决方法将 savedInstanceState 卸载到缓存目录中的文件。

我制作了以下实用程序 class。

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

我担心 Parcel#marshall 不应该用于持久化。 但是,我没有别的想法。

我的应用程序中的问题是我试图将太多数据保存到 savedInstanceState 中,解决方案是准确识别应在正确时间保存哪些数据。基本上仔细查看您的 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);
}

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

发生的事情是我们在 ViewPager 中使用 FragmentStatePagerAdapter。用户将翻页并创建 100 多个片段(它是一个阅读应用程序)。

虽然我们在 destroyItem() 中正确管理了片段,但在 Androids FragmentStatePagerAdapter 的实现存在一个错误,它保留了对以下列表的引用:

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 子 class 中正确管理片段,基础 class 仍将为曾经创建的每个片段存储一个 Fragment.SavedState。当该数组被转储到 parcelableArray 并且 OS 不喜欢它 100 多个项目时,将发生 TransactionTooLargeException。

因此,我们的解决方法是覆盖 saveState() 方法并且不为 "states" 存储任何内容。

@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 更改行为并抛出 TransactionTooLargeException 而不是记录错误。

     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 实例并尝试捕获 activityStopped 方法。

代码如下:

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);
    }
}

反射助手class是

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;
    }
}
}

我的 Nougat 设备也遇到了这个问题。我的应用程序使用一个片段和一个包含 4 个片段的视图寻呼机。我将一些大型构造参数传递给导致问题的 4 个片段。

我在 TooLargeTool 的帮助下追踪了 Bundle 的大小。

最后,我在实现 Serializable 的 POJO 对象上使用 putSerializable 解决了它,而不是在片段初始化期间使用 putString 传递大型原始 String。这将 Bundle 的大小减少了一半并且不会抛出 TransactionTooLargeException。因此,请确保您没有将过大的参数传递给 Fragment.

P.S。 Google 问题跟踪器中的相关问题:https://issuetracker.google.com/issues/37103380

每当您看到 TransactionTooLargeExceptionActivity 正在停止的过程中发生时,这意味着 Activity 正在尝试将其保存的状态 Bundles 发送到系统 OS 用于安全保存以便稍后恢复(在配置更改或进程终止后),但是它发送的一个或多个 Bundles 太大了。一次发生的所有此类事务的最大限制约为 1MB,即使没有单个 Bundle 超过该限制,也可以达到该限制。

这里的罪魁祸首通常是在 ActivityActivity 托管的任何 FragmentsonSaveInstanceState 中保存了太多数据。通常这种情况发生在保存像 Bitmap 这样特别大的东西时,但它也可能发生在发送大量较小的数据时,比如 Parcelable 对象的列表。 Android 团队已经多次明确表示,onSavedInstanceState 中只应保存少量与视图相关的数据。然而,开发人员经常保存网络数据页面,以便通过不必再次重新获取相同数据来使配置更改看起来尽可能顺利。截至 Google I/O 2017 年,Android 团队明确表示 Android 应用程序的首选架构可节省网络数据

  • 在内存中,因此可以在配置更改时轻松重用
  • 到磁盘,以便在进程死亡和应用程序会话后可以轻松恢复

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

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

这里最后要注意的是:你在 Nougat+ 上看到这个的唯一原因是,最初如果超过活页夹交易限制,将保存的状态发送到 OS 的过程将无提示地失败,只有这个错误出现在 Logcat:

!!!失败的 BINDER 交易 !!!

在 Nougat 中,无声故障升级为硬故障。值得赞扬的是,这是开发团队在 the release notes for Nougat:

中记录的内容

Many platform APIs have now started checking for large payloads being sent across Binder transactions, and the system now rethrows TransactionTooLargeExceptions as RuntimeExceptions, instead of silently logging or suppressing them. One common example is storing too much data in Activity.onSaveInstanceState(), which causes ActivityThread.StopInfo to throw a RuntimeException when your app targets Android 7.0.

点击试用,终于解决了我的问题。 将此添加到您的 Activity

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

None 上面的答案对我有用,问题的原因很简单,正如我使用 FragmentStatePagerAdapter 的一些人所说的那样,它的 saveState 方法保存了片段的状态,因为我的片段之一非常大,因此保存此片段会导致此 TransactionTooLargeExecption。

我尝试按照@IK828 所述在寻呼机实现中重写 saveState 方法,但这无法解决崩溃问题。

我的片段有一个 EditText 用来保存非常大的文本,这是我的问题的罪魁祸首,所以只需在片段的 onPause() 中,我将 edittext 文本设置为空字符串。 即:

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

现在,当 FragmentStatePagerAdapter 将尝试保存状态时,这一大块文本将不会在那里消耗更大的部分,因此解决了崩溃问题。

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

更新:onSaveInstanceState 是在调用 super 之前更好的地方:

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

或者正如@Vladimir 所指出的,您可以使用 android:saveEnabled="false" 或 view.setSaveEnabled(false);在视图或自定义视图上,并确保在 onResume 中重新设置文本,否则当 Activity 恢复时它将为空。

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

Activity代码

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  
}

在我的例子中,我使用 TooLargeTool 来跟踪问题的来源,我发现 Bundle 中的 android:support:fragments 键来自我使用的 onSaveInstanceState当应用程序崩溃时达到近 1mb。所以解决方案就像:

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

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