What to do on TransactionTooLargeException

前端 未结 30 3368
盖世英雄少女心
盖世英雄少女心 2020-11-22 03:08

I got a TransactionTooLargeException. Not reproducible. In the docs it says

The Binder transaction failed because it was too large.

D

相关标签:
30条回答
  • 2020-11-22 03:47

    The TransactionTooLargeException has been plaguing us for about 4 months now, and we've finally resolved the issue!

    What was happening was we are using a FragmentStatePagerAdapter in a ViewPager. The user would page through and create 100+ fragments (its a reading application).

    Although we manage the fragments properly in destroyItem(), in Androids implementation of FragmentStatePagerAdapter there is a bug, where it kept a reference to the following list:

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

    And when the Android's FragmentStatePagerAdapter attempts to save the state, it will call the function

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

    As you can see, even if you properly manage the fragments in the FragmentStatePagerAdapter subclass, the base class will still store an Fragment.SavedState for every single fragment ever created. The TransactionTooLargeException would occur when that array was dumped to a parcelableArray and the OS wouldn't like it 100+ items.

    Therefore the fix for us was to override the saveState() method and not store anything for "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;
    }
    
    0 讨论(0)
  • 2020-11-22 03:47

    For those who bitterly disappointed in search of answer of why the TransactionTooLargeException apears, try to check how much information you save in instance state.

    On compile/targetSdkVersion <= 23 we have only internal warning about large size of saved state, but nothing is crashed:

    E/ActivityThread: App sent too much data in instance state, so it was ignored
        android.os.TransactionTooLargeException: data parcel size 713856 bytes
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(Binder.java:615)
        at android.app.ActivityManagerProxy.activityStopped(ActivityManagerNative.java:3604)
        at android.app.ActivityThread$StopInfo.run(ActivityThread.java:3729)
        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:6044)
        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)
    

    But on compile/targetSdkVersion >= 24 we have real RuntimeException crash in this case:

    java.lang.RuntimeException: android.os.TransactionTooLargeException: data parcel size 713860 bytes
        at android.app.ActivityThread$StopInfo.run(ActivityThread.java:3737)
        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:6044)
        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 713860 bytes
       at android.os.BinderProxy.transactNative(Native Method)
       at android.os.BinderProxy.transact(Binder.java:615)
       at android.app.ActivityManagerProxy.activityStopped(ActivityManagerNative.java:3604)
       at android.app.ActivityThread$StopInfo.run(ActivityThread.java:3729)
       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:6044) 
       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) 
    

    What to do?

    Save data in local database and keep only id's in instance state which you can use to retrieve this data.

    0 讨论(0)
  • 2020-11-22 03:47

    Try to use EventBus or ContentProvider like solution.

    If you are in the same process(normally all your activities would be), try to use EventBus, cause in process data exchange does NOT need a somewhat buffer, so you do not need to worry about your data is too large. (You can just use method call to pass data indeed, and EventBus hide the ugly things) Here is the detail:

    // one side
    startActivity(intentNotTooLarge);
    EventBus.getDefault().post(new FooEvent(theHugeData));
    
    // the other side
    @Subscribe public void handleData(FooEvent event) { /* get and handle data */ }
    

    If the two sides of Intent are not in the same process, try somewhat ContentProvider.


    See 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.

    0 讨论(0)
  • 2020-11-22 03:47

    Issue is resolved by:

     Bundle bundle = new Bundle();
      bundle.putSerializable("data", bigdata);
    ...
      CacheHelper.saveState(bundle,"DATA");
      Intent intent = new Intent(mActivity, AActivity.class);
      startActivity(intent, bb);// do not put data to intent.
    
    In Activity:
       @Override
       protected void onCreate(Bundle savedInstanceState) {
            Bundle bundle = CacheHelper.getInstance().loadState(Constants.DATA);
            if (bundle != null){
                Intent intent = getIntent();
                intent.putExtras(bundle);
            }
            getIntent().getExtra(..);
            ....
       }
       @Override
        protected void onDestroy() {
            super.onDestroy();
            CacheHelper.clearState("DATA");
        }
    
    public class CacheHelper {
    
        public static void saveState(Bundle savedInstanceState, String name) {
            Bundle saved = (Bundle) savedInstanceState.clone();
            save(name, saved);
        }
        public Bundle loadState(String name) {
    
            Object object = load(name);
            if (object != null) {
                Bundle bundle = (Bundle) object;
                return bundle;
            }
            return null;
        }
        private static void save(String fileName, Bundle object) {
            try {
                String path = StorageUtils.getFullPath(fileName);
                File file = new File(path);
                if (file.exists()) {
                    file.delete();
                }
                FileOutputStream fos = new FileOutputStream(path, false);
    
                Parcel p = Parcel.obtain(); //creating empty parcel object
                object.writeToParcel(p, 0); //saving bundle as parcel
                //parcel.writeBundle(bundle);
                fos.write(p.marshall()); //writing parcel to file
    
                fos.flush();
                fos.close();
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        private static Bundle load(String fileName) {
            try {
                String path = StorageUtils.getFullPath(fileName);
                FileInputStream fis = new FileInputStream(path);
    
                byte[] array = new byte[(int) fis.getChannel().size()];
                fis.read(array, 0, array.length);
    
                Parcel parcel = Parcel.obtain(); //creating empty parcel object
                parcel.unmarshall(array, 0, array.length);
                parcel.setDataPosition(0);
                Bundle out = parcel.readBundle();
                out.putAll(out);
    
                fis.close();
                parcel.recycle();
                return out;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    public static void clearState(Activity ac) {
        String name = ac.getClass().getName();
        String path = StorageUtils.getFullPath(name);
        File file = new File(path);
        if (file.exists()) {
            file.delete();
        }
    }
    }
    
    0 讨论(0)
  • 2020-11-22 03:48

    There isn't one specific cause of this problem.For me, in my Fragment class I was doing this:

    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        View rootView = inflater.inflate(R.layout.snacks_layout, container); //<-- notice the absence of the false argument
        return rootView;
    }
    

    instead of this:

    View rootView = inflater.inflate(R.layout.softs_layout, container, false);
    
    0 讨论(0)
  • 2020-11-22 03:48

    I got this in my syncadapter when trying to bulkInsert a large ContentValues[]. I decided to fix it as follows:

    try {
        count = provider.bulkInsert(uri, contentValueses);
    } catch (TransactionTooLarge e) {
        int half = contentValueses.length/2;
        count += provider.bulkInsert(uri, Arrays.copyOfRange(contentValueses, 0, half));
        count += provider.bulkInsert(uri, Arrays.copyOfRange(contentValueses, half, contentValueses.length));
    }
    
    0 讨论(0)
提交回复
热议问题