我得到了一个TransactionTooLargeException。无法复制的。文件里说

The Binder transaction failed because it was too large. During a remote procedure call, the arguments and the return value of the call are transferred as Parcel objects stored in the Binder transaction buffer. If the arguments or the return value are too large to fit in the transaction buffer, then the call will fail and TransactionTooLargeException will be thrown. ... There are two possible outcomes when a remote procedure call throws TransactionTooLargeException. Either the client was unable to send its request to the service (most likely if the arguments were too large to fit in the transaction buffer), or the service was unable to send its response back to the client (most likely if the return value was too large to fit in the transaction buffer). ...

在某个地方,我传递或接收的参数超出了未知的限制。在哪里?

stacktrace没有显示任何有用的东西:

java.lang.RuntimeException: Adding window failed
at android.view.ViewRootImpl.setView(ViewRootImpl.java:548)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:406)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:320)
at android.view.WindowManagerImpl$CompatModeWrapper.addView(WindowManagerImpl.java:152)
at android.view.Window$LocalWindowManager.addView(Window.java:557)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2897)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2245)
at android.app.ActivityThread.access$600(ActivityThread.java:139)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1262)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:4977)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
at dalvik.system.NativeStart.main(Native Method)
Caused by: android.os.TransactionTooLargeException
at android.os.BinderProxy.transact(Native Method)
at android.view.IWindowSession$Stub$Proxy.add(IWindowSession.java:569)
at android.view.ViewRootImpl.setView(ViewRootImpl.java:538)
... 16 more
android.os.TransactionTooLargeException
at android.os.BinderProxy.transact(Native Method)
at android.view.IWindowSession$Stub$Proxy.add(IWindowSession.java:569)
at android.view.ViewRootImpl.setView(ViewRootImpl.java:538)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:406)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:320)
at android.view.WindowManagerImpl$CompatModeWrapper.addView(WindowManagerImpl.java:152)
at android.view.Window$LocalWindowManager.addView(Window.java:557)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2897)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2245)
at android.app.ActivityThread.access$600(ActivityThread.java:139)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1262)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:4977)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
at dalvik.system.NativeStart.main(Native Method)

这似乎和观点有关?这与远程过程调用有什么关系?

可能重要的是:Android版本:4.0.3,设备:HTC One X


当前回答

@Vaiden的回答帮助了我。我不明白为什么短列表会引发这种异常。我有许多片段和一对夫妇的ViewPagers与列表。所以,每次我开始另一个活动或停止一个程序(关闭屏幕),我捕捉到这个异常(通常在小米)。

我发现所有的片段调用他们的onSaveInstanceState()事件,并在最后托管活动调用onSaveInstanceState()之前onStop()。之后发生了撞车事故。因此,我理解许多短列表(大小为10-100 Kb)可以引发TransactionTooLargeException异常。

您可以通过将数据写入数据库,然后通过它们的id获取项来解决这个问题。通过这种方式,你可以将一个id列表传递给一个Activity/Fragment。

但如果你想要一个临时的快速方法,就这么做。

1)创建一个保留实例片段,该片段将在活动重新创建期间存活。

class SavedInstanceFragment : Fragment() {

    // This map will hold bundles from different sources (tag -> bundle).
    private lateinit var bundleMap: HashMap<String, Bundle?>


    // This method is only called once for this fragment.
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        retainInstance = true
        bundleMap = HashMap()
    }

    fun pushData(key: String, bundle: Bundle): SavedInstanceFragment {
        if (bundleMap[key] == null) {
            bundleMap[key] = bundle
        } else {
            bundleMap[key]!!.putAll(bundle)
        }
        return this
    }

    fun popData(key: String): Bundle? {
        val data = bundleMap[key]
        bundleMap[key] = null
        return data
    }

    fun removeData(key: String) {
        bundleMap.remove(key)
    }


    companion object {

        private val TAG = SavedInstanceFragment::class.java.simpleName

        // Create the fragment with this method in `onCreate()` of an activity.
        // Then you can get this fragment with this method again.
        fun getInstance(fragmentManager: FragmentManager): SavedInstanceFragment {
            var out = fragmentManager.findFragmentByTag(TAG) as SavedInstanceFragment?

            if (out == null) {
                out = SavedInstanceFragment()
                fragmentManager.beginTransaction().add(out, TAG).commit()
            }
            return out
        }
    }
}

2)在你的活动的onCreate()持有问题片段,创建这个片段。它将在活动娱乐中存活。您应该在其他片段添加到FragmentManager之前创建它,因为这个操作是异步的。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity)

    if (savedInstanceState == null) {
        SavedInstanceFragment.getInstance(supportFragmentManager)
    }

    ...
}

3)在每个问题片段中添加这些行。你应该在任何地方使用getInstance(activity!!. supportfragmentmanager)访问一个保留实例片段,这样你就可以在FragmentManager中找到它。如果使用childFragmentManager作为getInstance()的参数,那么您将创建另一个片段并崩溃。

也可以在读取参数并设置包后清除参数,例如,在onSaveInstanceState: arguments中?.clear()。这是非常重要的,因为当你创建一个新的片段时,参数会留在内存中。当你稍后返回到当前片段并旋转屏幕时,数据不会丢失,因为它们已经包含在一个bundle中,并将在onCreate中读取。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val bundle = if (savedInstanceState == null) {
        // Remove old data in order not to show wrong information.
        SavedInstanceFragment.getInstance(activity!!.supportFragmentManager).removeData(TAG)
        null
    } else {
        SavedInstanceFragment.getInstance(activity!!.supportFragmentManager).popData(TAG)
    }
    (bundle ?: arguments)?.run { // I access 'bundle' or 'arguments', depending if it is not first or first call of 'onCreate()'.
        token = getString(ARG_TOKEN)!!
        id = getInt(ARG_ID)
        items = getParcelableArrayList(ARG_ITEMS)
    }
}

override fun onSaveInstanceState(outState: Bundle) {
    // Create a copy of savedInstanceState and push to the retain-instance fragment.
    val bundle = (outState.clone() as Bundle).apply {
        putString(ARG_TOKEN, token)
        putInt(ARG_ID, id)
        putParcelableArrayList(ARG_ITEMS, items)
    }
    SavedInstanceFragment.getInstance(activity!!.supportFragmentManager).pushData(TAG, bundle)
    arguments?.clear() // Avoids an exception "java.lang.RuntimeException: android.os.TransactionTooLargeException: data parcel size xxxxxxx bytes".
    super.onSaveInstanceState(outState)
}


companion object {

    private val TAG = YourFragment::class.java.simpleName

    private const val ARG_TOKEN = "ARG_TOKEN"
    private const val ARG_ID = "ARG_ID"
    private const val ARG_ITEMS = "ARG_ITEMS"

    fun newInstance(token: String, id: Int, items: ArrayList<Item>?) =
        YourFragment().apply {
            arguments = Bundle().apply {
                putString(ARG_TOKEN, token)
                putInt(ARG_ID, id)
                putParcelableArrayList(ARG_ITEMS, items)
            }
        }
}

你可以使用Singleton代替持久片段。也可以阅读https://medium.com/@mdmasudparvez/android-os-transactiontoolargeexception-on-nougat-solved-3b6e30597345了解更多关于库和解决方案的信息。

我认为,如果在启动其他活动后删除了活动,则此解决方案将不起作用。您可以在打开“不将活动保留在开发人员选项中”复选框时勾选此行为(不要忘记在之后取消勾选)。在这种情况下,getFragmentManager()将是新的,您将不会得到旧的数据。当您从new activity返回时,您将在一个bundle中获得null。

其他回答

我发现了这个问题的根本原因(我们得到了“添加窗口失败”和文件描述符泄漏mvds说)。

Android 4.4的BitmapFactory.decodeFileDescriptor()有一个bug。 只有当BitmapOptions的inPurgeable和inInputShareable设置为true时才会发生。这在与文件交互的许多地方造成了许多问题。

注意,该方法也是从MediaStore.Images.Thumbnails.getThumbnail()调用的。

通用图像加载器受此问题影响。毕加索和格莱德似乎没有受到影响。 https://github.com/nostra13/Android-Universal-Image-Loader/issues/1020

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

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

虽然我们在destroyItem()中正确地管理片段,但在android中 FragmentStatePagerAdapter的实现有一个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子类中的片段,基类仍然会存储一个片段。SavedState用于创建的每个片段。TransactionTooLargeException会在数组被转储到parcelableArray时发生,而操作系统不希望它有100+项。

因此,我们的解决方案是重写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;
}

对我来说,这个错误是在演示程序中出现的。我对onResume做了评论,并在onStart中写了相同的代码,它对我有用。

 @Override
    public void onStart() {
        super.onStart();
        Goal goal = Session.getInstance(getContext()).getGoalForType(mMeasureType);
        if (goal != null && goal.getValue() > 0) {
            mCurrentValue = (int) goal.getValue();
            notifyPropertyChanged(BR.currentValue);
            mIsButtonEnabled.set(true);
        }
    }
   /* @Override
    public void onResume() {
        super.onResume();
        Goal goal = Session.getInstance(getContext()).getGoalForType(mMeasureType);
        if (goal != null && goal.getValue() > 0) {
            mCurrentValue = (int) goal.getValue();
            notifyPropertyChanged(BR.currentValue);
            mIsButtonEnabled.set(true);
        }
    }*/

我也在三星S3上得到了这个例外。 我认为有两个根本原因,

如果你有位图,它会占用太多内存,使用缩小 你在drawable-_dpi文件夹中丢失了一些drawables, android在drawable中寻找它们,并调整它们的大小,使你的setContentView突然跳转并使用大量内存。

使用DDMS并在你播放你的应用程序时查看你的堆,这将给你一些关于哪个setcontentview正在创建问题的指示。

我复制了所有文件夹中的所有图纸,以摆脱问题2。

问题解决了。

重要的是要理解,无论设备功能或应用程序,事务缓冲区被限制为1mb。这个缓冲区用于你所做的每个API调用,并在应用程序当前运行的所有事务之间共享。

我相信它还包含一些特定的对象,如包裹和such (package . get()),因此始终将每个get()与recycle()匹配是很重要的。

这个错误很容易发生在返回大量数据的API调用上,即使返回的数据小于1 MB(如果其他事务仍在运行)。

例如,PackageManager.getInstalledApplication()调用返回已安装的所有应用程序的列表。添加特定的标志可以检索大量额外的数据。这样做很可能会失败,所以建议不要检索任何额外的数据,并在每个应用程序的基础上检索这些数据。

然而,调用仍然可能失败,因此重要的是用一个捕获来包围它,并在必要时能够重试。

据我所知,除了重新尝试并确保检索尽可能少的信息之外,没有解决这个问题的方法。