简体   繁体   English

android.os.TransactionTooLargeException的牛轧糖

[英]android.os.TransactionTooLargeException on Nougat

I updated Nexus 5X to Android N, and now when I install the app (debug or release) on it I am getting TransactionTooLargeException on every screen transition that has Bundle in extras. 我将Nexus 5X更新为Android N,现在当我在其上安装应用程序(调试或发行版)时,每次在具有Bundle附加功能的屏幕过渡上都得到TransactionTooLargeException。 The app is working on all other devices. 该应用程序可在所有其他设备上使用。 The old app that is on PlayStore and has mostly same code is working on Nexus 5X. 旧的应用程序,是在Play商店中,有大部分是相同的代码工作在Nexus 5X。 Is anyone having the same issue? 是具有相同问题的人?

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) 

Whenever you see TransactionTooLargeException happening when an Activity is in the process of stopping, that means that the Activity was trying to send its saved state Bundles to the system OS for safe keeping for restoration later (after a config change or process death) but that one or more of the Bundles it sent were too large. 每当你看到TransactionTooLargeException发生时的Activity是停止的过程,这意味着该Activity试图其保存的状态发送Bundles到系统OS保管的恢复后(一个配置改变或过程后死亡),但一个或更多发送的Bundles太大。 There is a maximum limit of about 1MB for all such transactions occurring at once and that limit can be reached even if no single Bundle exceeds that limit. 还有就是1MB左右发生的一次所有此类交易,甚至如果没有单是限制可达到的最大限制Bundle超过该限制。

The main culprit here is generally saving too much data inside onSaveInstanceState of either the Activity or any Fragments hosted by the Activity . 这里主要的罪魁祸首是一般里面保存的数据太多onSaveInstanceState任的Activity或任何Fragments由主办Activity Typically this happens when saving something particularly large like a Bitmap but it can also happen when sending large quantities of smaller data, like lists of Parcelable objects. 通常,在保存诸如Bitmap类的特别大的东西时会发生这种情况,但是在发送大量较小的数据(例如可Parcelable对象的列表)时也会发生这种情况。 The Android team has made very clear on numerous occasions that only small amounts of view-related data should be saved in onSavedInstanceState . Android团队在很多场合都非常明确地指出,只有少量与视图相关的数据应保存在onSavedInstanceState However, developers have often saved pages of network data in order to make configuration changes appear as smooth as possible by not having to refetch the same data again. 然而,开发商往往保存网络数据的页面,以进行配置更改看起来尽可能通过不必再重新获取相同的数据平滑。 As of Google I/O 2017, the Android team has made clear that the preferred architecture for an Android app saves networking data 作为谷歌I / O 2017年时,Android团队已经明确表示对Android应用的首选架构节省了网络数据

  • in memory so it can be easily reused across configuration changes 在内存中,因此可以轻松地在配置更改中重复使用
  • to disk so that it can be easily restored after process death and app sessions 到磁盘,以便在进程终止和应用程序会话后可以轻松还原它

Their new ViewModel framework and Room persistence library are meant to help developers fit this pattern. 他们的新ViewModel框架和Room持久性库是为了帮助开发人员适应这种模式。 If your problem is with saving too much data in onSaveInstanceState , updating to an architecture like this using those tools should fix your problem. 如果您的问题是在onSaveInstanceState保存了太多数据,那么使用那些工具更新到这样的体系结构应该可以解决您的问题。

Personally, before updating to that new pattern I'd like to take my existing apps and just get around the TransactionTooLargeException in the meantime. 就个人而言,在更新为该新模式之前,我想使用我现有的应用程序,同时避开TransactionTooLargeException I wrote a quick library to do just that: https://github.com/livefront/bridge . 我写了一个快速库来做到这一点: https://github.com/livefront/bridge It uses the same general ideas of restoring state from memory across configuration changes and from disk after process death, rather than sending all that state to the OS via onSaveInstanceState , but requires very minimal changes to your existing code to use. 它使用相同的一般思想,即通过配置更改从内存中恢复状态,以及在进程onSaveInstanceState后从磁盘中恢复状态,而不是通过onSaveInstanceState将所有状态发送到OS,但只需要对现有代码进行极少的更改即可使用。 Any strategy that fits those two goals should help you avoid the exception, though, without sacrificing your ability to save state. 任何策略适合这两个目标应该可以帮助您避免例外,不过,在不牺牲你的保存状态的能力。

On final note here : the only reason you see this on Nougat+ is that originally if the binder transaction limit was exceeded, the process to send the saved state to the OS would fail silently with only this error showing up in Logcat: 最后要注意的是:在Nougat +上看到此消息的唯一原因是,最初如果超过了绑定器事务限制,则将已保存状态发送到OS的过程将静默失败,并且仅在Logcat中显示此错误:

!!! !!! FAILED BINDER TRANSACTION !!! 绑定交易失败!!!

In Nougat, that silent failure was upgraded to a hard crash. 在牛轧糖中,这种无声的失败升级为硬崩溃。 To their credit, this is something the development team documented in 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. 现在,许多平台API已开始检查跨Binder事务发送的大型有效负载,并且系统现在将TransactionTooLargeExceptions抛出为RuntimeExceptions,而不是静默记录或禁止它们。 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.onSaveInstanceState()中存储了太多数据,当您的应用程序针对Android 7.0时,这会导致ActivityThread.StopInfo抛出RuntimeException。

At the end, my problem was with things that were being saved onSaveInstance, and not with things that were being sent to next activity. 最后,我的问题是与正在保存onSaveInstance,而不是与被发送到下一个活动的事情的东西。 I removed all saves where I can't control a size of objects (network responses), and now it's working. 我删除了无法控制对象大小(网络响应)的所有保存,现在可以使用了。

Update: 更新:

To preserve big chunks of data, Google is suggesting to do it with Fragment that retains instance. 为了保持数据的大块大块的,谷歌是在暗示与片段做保留实例。 Idea is to create empty Fragment without a view with all necessary fields, that would otherwise be saved in Bundle. 想法是创建一个空的片段,不带所有必填字段的视图,否则将其保存在Bundle中。 Add setRetainInstance(true); 添加setRetainInstance(true); to Fragment's onCreate method. 到碎片的onCreate方法。 And then save data in Fragment on Activity's onDestroy and load them onCreate. 然后再保存活动的中的onDestroy片段数据的onCreate加载它们。 Here is an example of 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());
    }
}

And example of Fragment: 片段和例子:

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

More about it, you can read here . 更多关于它,你可以阅读这里

The TransactionTooLargeException has been plaguing us for about 4 months now, and we've finally resolved the issue! TransactionTooLargeException已经困扰我们大约4个月了,我们终于解决了这个问题!

What was happening was we are using a FragmentStatePagerAdapter in a ViewPager. 发生了什么事是我们所使用的ViewPager一个FragmentStatePagerAdapter。 The user would page through and create 100+ fragments (its a reading application). 用户将通过页面并创建100多个片段(其一款阅读应用)。

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: 虽然我们在destroyItem()妥善管理的片段中,机器人会执行FragmentStatePagerAdapter的有一个bug,它保持在下面的列表的引用:

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

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. 正如你所看到的,即使你正确地管理在FragmentStatePagerAdapter子的片段,基类仍然会存储有史以来创造的每一个片段的Fragment.SavedState。 The TransactionTooLargeException would occur when that array was dumped to a parcelableArray and the OS wouldn't like it 100+ items. 当阵列转储到parcelableArray和OS不会喜欢它100+项目将发生TransactionTooLargeException。

Therefore the fix for us was to override the saveState() method and not store anything for "states". 因此,对于我们的定盘覆盖的“国家”的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;
}

Did a hit and trial, and finally this solved my issue. 做了命中和试验,终于解决了这个问题,我的。 Add this to your Activity 将此添加到您的Activity

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

I face this issue as well on my Nougat devices. 我面对这个问题,也是对我的牛轧糖设备。 My app uses a fragment with a view pager which contains 4 fragments. 我的应用程序使用与含有4个片段的视图寻呼机的片段。 I passed some large construction arguments to the 4 fragments which caused the problem. 我通过一些大型建筑的参数给4个片段引起的问题。

I traced the size of Bundle causing this with the help of TooLargeTool . 我跟踪的大小Bundle造成这个的帮助下TooLargeTool

Finally, I resolved it using putSerializable on a POJO object which implements Serializable instead of passing a large raw String using putString during fragment initialization. 最后,我决定用它putSerializable一个POJO对象,它实现的Serializable ,而不是通过大量原始String使用putString片段在初始化过程中。 This reduced size of Bundle by half and does not throw the TransactionTooLargeException . Bundle的大小减小一半,并且不会引发TransactionTooLargeException Therefore, please make sure you do not pass huge size arguments to Fragment . 因此,请确保您没有通过庞大的体型参数Fragment

PS related issue in Google issue tracker: https://issuetracker.google.com/issues/37103380 在谷歌问题跟踪PS相关的问题: https://issuetracker.google.com/issues/37103380

I face the similar issue. 我面临类似的问题。 The issue and scenario are little different and I fix it in the following way. 这个问题和情景都有点不同,我修复它以下列方式。 Please check the scenario and solution. 请检查方案和解决方案。

Scenario: I got a weird bug from the customer in the Google Nexus 6P device(7 OS) as my application will crash after 4 hours of working. 场景:我在Google Nexus 6P设备(7操作系统)上从客户那里收到一个奇怪的错误,因为我的应用程序在工作4个小时后会崩溃。 Later I identify that it's throwing the similar (android.os.TransactionTooLargeException:) exception. 后来我确定它抛出类似(android.os.TransactionTooLargeException :)例外。

Solution: The log was not pointing any particular class in the application and later I found that this is happening because of keeping the back stack of fragments. 解决方案:日志没有指向应用程序中的任何特定类,后来我发现这是由于保留了片段的后退堆栈而发生的。 In my case, 4 fragments are added to the back stack repeatedly with the help of an auto screen movement animation. 在我的情况下,借助自动屏幕移动动画将4个片段重复添加到后堆栈中。 So I override the onBackstackChanged() as mention below. 所以我重写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();
        }
    }

If the stack exceeds the limit, it will automatically pop to initial fragment. 如果堆栈超出限制,它将自动弹出到初始片段。 I hope somebody will help this answer because the exception and stack trace logs are same. 我希望有人会帮助这个答案,因为异常和堆栈跟踪日志是一样的。 So whenever this issue happens, please check the back stack count, if you are using Fragments and back stack. 因此,每当发生此问题时,如果您正在使用Fragments和Back Stack,请检查Back Stack计数。

In my case, I got that exception inside a fragment because one of its arguments was a very large string that I forgot to delete it (I only used that large string inside the onViewCreated() method). 就我而言,我在片段中得到了该异常,因为它的参数之一是一个很大的字符串,我忘了删除它(我只在onViewCreated()方法中使用了那个大字符串)。 So, to solve this, i simply deleted that argument. 因此,要解决这个问题,我只是删除了这样的说法。 In your case, you have to clear or nullify any suspicious field before call onPause(). 你的情况,你必须清除或调用的onPause()之前消除任何可疑的领域。

Activity code 活动代码

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

Fragment code 片段代码

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

The problem in my app was that I was trying to save too much into the savedInstanceState, the solution was to identify exactly which data should be saved at the right time. 在我的应用程序的问题是,我是想太多保存到savedInstanceState,该解决方案是确定究竟哪些数据应在正确的时间保存。 Basically look carefully into your onSaveInstanceState to make sure you don't stretch it: 基本上仔细查看您的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);
}

I faced the same issue. 我遇到了同样的问题。 My workaround offloads savedInstanceState to files in cache dir. 我的解决方法将savedInstanceState卸载到缓存目录中的文件中。

I made the following utility 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;
    }
}

Full codes: https://github.com/cattaka/AndroidSnippets/pull/37 全码: https://github.com/cattaka/AndroidSnippets/pull/37

I worry about that Parcel#marshall should not be used for persistent. 我担心包裹#马歇尔不应该用于持久。 But, I don't have any other idea. 但是,我没有任何其他的想法。

None of the above answers worked for me, the reason of the issue was quite simple as stated by some I was using FragmentStatePagerAdapter and its saveState method saves the state of the fragments, because one of my fragment was quite large , so saving of this fragment leads to this TransactionTooLargeExecption. 上面的答案都不对我有用,这个问题的原因很简单,正如我使用FragmentStatePagerAdapter的某些人所说的那样,它的saveState方法保存了片段的状态,因为我的一个片段很大,因此保存此片段导致此TransactionTooLargeExecption。

I tried overriding the saveState method in my implementation of pager as stated by @IK828, but this couldn't resolve the crash. 如@ IK828所述,我尝试在寻呼机的实现中覆盖saveState方法,但这无法解决崩溃问题。

My fragment was having an EditText which used to hold very large text, which was the culprit of the issue in my case, so simply in onPause() of fragment, I set the edittext text to empty string. 我的片段有一个EditText,该文本曾经用于保存非常大的文本,这是我所遇到的问题的根源,因此,在片段的onPause()中,我将edittext文本设置为空字符串。 ie: 即:

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

Now when FragmentStatePagerAdapter will try to saveState, this large chunk of text will not be there to consume bigger part of it, hence resolves the crash. 现在,当FragmentStatePagerAdapter将尝试saveState和,这个大文本块不会在那里消耗它的更大的一部分,因此解决了崩溃。

In your case you need to find whatever is the culprit, it could be an ImageView with some bitmap, a TextView with huge chunk of text or any other high memory consuming view, you need to free it's memory, you may set imageview.setImageResource(null) or similar in onPause() of your fragment. 在您的情况下,您需要找到任何罪魁祸首,它可能是带有某些位图的ImageView,带有大量文本的TextView或任何其他占用大量内存的视图,您需要释放其内存,可以设置imageview.setImageResource(空)或在onPause()的片段相似。

update : onSaveInstanceState is better place for the purpose before calling super like: 更新:onSaveInstanceState更好地用于此目的,然后再调用super,例如:

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

or as pointed by @Vladimir you can use android:saveEnabled="false" or view.setSaveEnabled(false); 或@Vladimir指出,您可以使用android:saveEnabled =“ false”或view.setSaveEnabled(false); on the view or custom view and make sure to set the text back in onResume otherwise it will be empty when Activity resumes. 在视图或自定义视图,并确保设置的onResume背课文,否则这将是空当活动恢复。

Just Override this method on your 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);
}

Go to https://code.google.com/p/android/issues/detail?id=212316#makechanges for more info. 转到https://code.google.com/p/android/issues/detail?id=212316#makechanges获取更多信息。

As the Android N change the behavior and throw TransactionTooLargeException instead of logging the error. 随着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();
        }

my solution is to hook the ActivityMangerProxy instance and try catch the activityStopped method. 我的解决方案是挂接ActivityMangerProxy实例,并尝试捕获activityStopped方法。

Here is the code: 这是代码:

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

And the reflect helper class is 而反射助手类是

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

In my case, I used TooLargeTool to track where the issue was coming from and I found out the android:support:fragments key in the Bundle from my onSaveInstanceState used to reach almost 1mb when the app crashed. 就我而言,我使用TooLargeTool跟踪问题的出处 ,并从onSaveInstanceState中发现Bundleandroid:support:fragments键,当应用程序崩溃时,该键曾经达到近1mb。 So the solution was like: 因此,解决办法是这样的:

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

By doing that, I avoided to save all fragments' states and kept with other things that need to be saved. 通过这样做,我避免了保存所有片段的状态,并保留了其他需要保存的内容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 android.os.TransactionTooLargeException:数据包大小NOUGAT错误 - android.os.TransactionTooLargeException: data parcel size NOUGAT ERROR 启动器中的android.os.transactiontoolargeexception - android.os.transactiontoolargeexception in Launcher Android中的android.os.TransactionTooLargeException错误 - android.os.TransactionTooLargeException error in Android android.os.TransactionTooLargeException检索已安装的应用程序 - android.os.TransactionTooLargeException retrieving installed applications ForegroundService通知的android.os.TransactionTooLargeException - android.os.TransactionTooLargeException for ForegroundService notification 执行queryIntentActivities时android.os.TransactionTooLargeException - android.os.TransactionTooLargeException when executing queryIntentActivities AppWidgetHost崩溃android.os.TransactionTooLargeException - AppWidgetHost crash android.os.TransactionTooLargeException 如何避免得到“android.os.TransactionTooLargeException” - how to avoid getting the “android.os.TransactionTooLargeException” 随机抛出android.os.TransactionTooLargeException - android.os.TransactionTooLargeException thrown randomly AppWidgetManager.updateAppWidget中的android.os.TransactionTooLargeException - android.os.TransactionTooLargeException in AppWidgetManager.updateAppWidget
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM