Nougat上的android.os.TransactionTooLargeException

Vla*_*vić 71 android android-7.0-nougat

我将Nexus 5X更新为Android N,现在当我在其上安装应用程序(调试或发布)时,我在每个具有附加功能的Bundle的屏幕转换上都会收到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) 
Run Code Online (Sandbox Code Playgroud)

Bri*_*cho 26

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

这里的主要罪魁祸首通常是在onSaveInstanceState其中托管的Activity任何内容中保存太多数据.通常,这会在保存像a这样特别大的东西时发生,但在发送大量较小数据(如对象列表)时也会发生这种情况.Android团队在很多场合都非常明确地表示只应保存少量与视图相关的数据.但是,开发人员经常保存网络数据页面,以便通过不必再次重新获取相同数据来使配置更改显得尽可能平滑.从Google I/O 2017开始,Android团队已经明确表示Android应用程序的首选架构可以节省网络数据FragmentsActivityBitmapParcelableonSavedInstanceState

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

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

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

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

!失败的粘合剂交易!

在Nougat,这种沉默的失败升级为严重的崩溃.值得赞扬的是,这是开发团队在Nougat的发行说明中记录的内容:

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

  • 如果您尚未使用Icepick(或类似工具),那还不够。如文档中所述:“ Bridge旨在用作基于批注的状态保存库(如Icepick,Android-State和Icekick)的简单包装。” 有多种方法可以使Bridge在不使用其中任何一种的情况下工作,但最终要比仅更新您的应用程序以使用其中一种进行更多的工作。 (2认同)

Vla*_*vić 25

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

更新:

为了保留大块数据,Google建议使用保留实例的Fragment来实现.想法是创建空片段而不包含所有必需字段的视图,否则将保存在Bundle中.添加setRetainInstance(true);到Fragment的onCreate方法.然后将数据保存在Activity的onDestroy上的Fragment中并将它们加载到onCreate上.以下是活动的示例:

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

片段的例子:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

更多关于它,你可以在这里阅读.

  • 这是正确的答案!由于targetSdkVersion 24+此异常抛出而不是"App在实例状态中发送了太多数据,因此被忽略了".想要了解更多信息的用户的一些详细信息:https://code.google.com/p/android/issues/detail?id = 212B16 (5认同)
  • 当应用只是在后台运行并且ViewState导致此问题时,这无济于事。有谁知道如何处理吗? (2认同)

小智 18

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

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

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

private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
Run Code Online (Sandbox Code Playgroud)

当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;
}
Run Code Online (Sandbox Code Playgroud)

如您所见,即使您正确管理FragmentStatePagerAdapter子类中的片段,基类仍将为创建的每个片段存储Fragment.SavedState.当该数组被转储到parcelableArray并且操作系统不喜欢100多个项目时,就会发生TransactionTooLargeException.

因此,我们的修复是覆盖saveState()方法而不是为"状态"存储任何内容.

@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;
}
Run Code Online (Sandbox Code Playgroud)

  • 在saveState()中,bundle可以为null,所以if(bundle!= null)bundle.putParcelableArray("states",null); (2认同)

Raj*_*dav 15

受到了打击和审判,最后这解决了我的问题.将此添加到您的Activity

@Override
protected void onSaveInstanceState(Bundle oldInstanceState) {
    super.onSaveInstanceState(oldInstanceState);
    oldInstanceState.clear();
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢@Raj ...你节省了我的时间 (2认同)

Dav*_*ung 11

我在Nougat设备上也遇到了这个问题.我的应用程序使用带有视图寻呼机的片段,其中包含4个片段.我将一些大的构造参数传递给造成问题的4个碎片.

我在TooLargeToolBundle的帮助下追踪了导致这个问题的大小.

最后,我决定它使用putSerializable它实现了一个POJO对象上Serializable,而不是通过大量的原始String使用putString片段在初始化过程中.这减小Bundle了一半,并没有抛出TransactionTooLargeException.因此,请确保您没有传递巨大的参数Fragment.

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

  • 谢谢大卫.这个工具确实帮助我找到了问题的根本原因.你摇滚:) (3认同)

Nit*_*ith 8

我面临类似的问题.问题和情况略有不同,我通过以下方式解决它.请检查方案和解决方案.

场景: 我在Google Nexus 6P设备(7操作系统)中遇到了一个奇怪的错误,因为我的应用程序将在工作4小时后崩溃.后来我发现它正在抛出类似的(android.os.TransactionTooLargeException :)异常.

解决方案: 日志没有指向应用程序中的任何特定类,后来我发现这是因为保留了后端堆栈的碎片.在我的例子中,借助自动屏幕移动动画,将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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

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


小智 6

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

活动代码

Fragment fragment = new Fragment();
Bundle args = new Bundle();
args.putString("extremely large string", data.getValue());
fragment.setArguments(args);
Run Code Online (Sandbox Code Playgroud)

片段代码

@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  
}
Run Code Online (Sandbox Code Playgroud)