android.os.TransactionTooLargeException auf Nougat

74

Ich habe Nexus 5X auf Android N aktualisiert und jetzt, wenn ich die App darauf installiere (Debug oder Release), erhalte ich TransactionTooLargeException für jeden Bildschirmübergang, der Bundle in Extras enthält. Die App funktioniert auf allen anderen Geräten. Die alte App, die im PlayStore verfügbar ist und größtenteils denselben Code enthält, funktioniert unter Nexus 5X. Hat jemand das gleiche Problem?

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
Vladimir Jovanović
quelle
Schreibklasse wo mit erscheinen. Es bedeutet, dass Sie eine Klasse mit Parces mit vielen Daten haben.
Vyacheslav
1
Das ist ein Problem. In den meisten Fällen sende ich nur ein kleines Paketobjekt mit wenigen Zeichenfolgen. Ich erhalte diesen Fehler bei jedem Aktivitätsübergang. Auf anderen Geräten funktioniert es ohne Probleme.
Vladimir Jovanović
6
Es wurde als Fehler gemeldet, siehe code.google.com/p/android/issues/detail?id=212316
Romain Piel

Antworten:

29

Wenn Sie sehen TransactionTooLargeException, dass ein Ereignis Activitygerade gestoppt wird, bedeutet dies, dass Activityversucht wurde, seinen gespeicherten Status Bundlesan das Systembetriebssystem zu senden , um ihn später (nach einer Konfigurationsänderung oder einem Prozesstod) sicher wiederherzustellen, aber einer oder mehrere der Bundleses schickte waren zu groß. Es gibt ein maximales Limit von ungefähr 1 MB für alle derartigen Transaktionen, die gleichzeitig stattfinden, und dieses Limit kann erreicht werden, selbst wenn kein Einzelner Bundledieses Limit überschreitet.

Der Hauptschuldige hier ist im Allgemeinen das Speichern zu vieler Daten innerhalb onSaveInstanceStatedes Activityoder eines Fragmentsvon dem gehosteten Activity. In der Regel geschieht dies beim Speichern von besonders großen BitmapDaten wie a, aber auch beim Senden großer Mengen kleinerer Daten, z. B. von ParcelableObjektlisten. Das Android-Team hat mehrfach deutlich gemacht, dass nur geringe Mengen an ansichtsbezogenen Daten gespeichert werden sollten onSavedInstanceState. Entwickler haben jedoch häufig Seiten mit Netzwerkdaten gespeichert, um Konfigurationsänderungen so reibungslos wie möglich zu gestalten, da dieselben Daten nicht erneut abgerufen werden müssen. Ab Google I / O 2017 hat das Android-Team klargestellt, dass die bevorzugte Architektur für eine Android-App Netzwerkdaten speichert

  • im Speicher, so dass es bei Konfigurationsänderungen problemlos wiederverwendet werden kann
  • auf die Festplatte, damit es nach Prozessabschluss und App-Sitzungen problemlos wiederhergestellt werden kann

Das neue ViewModelFramework und die RoomPersistenzbibliothek sollen Entwicklern dabei helfen, dieses Muster anzupassen. Wenn Ihr Problem darin besteht, zu viele Daten zu speichern onSaveInstanceState, sollte das Problem durch Aktualisieren auf eine Architektur wie diese mit diesen Tools behoben werden.

Persönlich möchte ich vor dem Aktualisieren auf dieses neue Muster meine vorhandenen Apps übernehmen und mich TransactionTooLargeExceptionin der Zwischenzeit einfach darum kümmern . Ich habe eine schnelle Bibliothek geschrieben, um genau das zu tun: https://github.com/livefront/bridge . Es verwendet dieselben allgemeinen Ideen zum Wiederherstellen des Status aus dem Speicher bei Konfigurationsänderungen und von der Festplatte nach dem Prozessabschluss, anstatt den gesamten Status über an das Betriebssystem zu senden onSaveInstanceState, erfordert jedoch nur minimale Änderungen an Ihrem vorhandenen Code. Jede Strategie, die zu diesen beiden Zielen passt, sollte Ihnen jedoch helfen, die Ausnahme zu vermeiden, ohne Ihre Fähigkeit zu beeinträchtigen, den Status zu speichern.

Zum Schluss hier: Der einzige Grund, warum Sie dies bei Nougat + sehen, ist, dass der Prozess zum Senden des gespeicherten Status an das Betriebssystem ursprünglich fehlschlug, wenn nur das Binder-Transaktionslimit überschritten wurde und nur dieser Fehler in Logcat angezeigt wurde:

!!! FEHLGESCHLAGENE BINDERTRANSAKTION !!!

In Nougat wurde dieser stille Fehler zu einem harten Absturz aufgewertet. Dies ist etwas, was das Entwicklungsteam in den Versionshinweisen für Nougat dokumentiert hat :

Viele Plattform-APIs haben jetzt begonnen, nach großen Nutzdaten zu suchen, die über Binder-Transaktionen gesendet werden, und das System setzt TransactionTooLargeExceptions jetzt als RuntimeExceptions neu, anstatt sie stillschweigend zu protokollieren oder zu unterdrücken. Ein häufiges Beispiel ist das Speichern zu vieler Daten in Activity.onSaveInstanceState (), wodurch ActivityThread.StopInfo eine RuntimeException auslöst, wenn Ihre App auf Android 7.0 abzielt.

Brian Yencho
quelle
Ich habe versucht, Ihre Bibliothek zu implementieren, aber es hat nicht funktioniert. Immer noch den gleichen Absturz bekommen.
Hampel Előd
Ich wäre gespannt auf Ihre Implementierung. Fühlen Sie sich frei, Details als Problem in der Bibliothek zu hinterlassen. Ich habe keine Berichte darüber erhalten, wenn sie richtig verwendet wurden.
Brian Yencho
Ich habe die Anweisungen auf der Github-Seite der Bibliotheken befolgt. Muss ich dem Code noch etwas hinzufügen?
Hampel Előd
Das hängt davon ab, wie Sie den Status zuvor gespeichert haben. Wenn Sie so etwas wie Icepick verwendet haben, sollten keine zusätzlichen Schritte erforderlich sein. Wenn dies nicht der Fall ist, müssen Sie wahrscheinlich einige Code-Reorganisationen durchführen, um zuerst eine solche Bibliothek zu verwenden. Wenn Sie weitere Informationen mit einem Codebeispiel als Problem im Github-Projekt veröffentlicht haben, konnte ich feststellen, ob mit Ihrem Setup etwas nicht stimmt.
Brian Yencho
2
Wenn Sie Icepick (oder ähnliches) noch nicht verwendet haben, reicht das nicht aus. Wie in der Dokumentation erwähnt: "Bridge ist als einfacher Wrapper für annotationsbasierte State-Save-Bibliotheken wie Icepick, Android-State und Icekick gedacht." Es gibt Möglichkeiten, Bridge zum Laufen zu bringen, ohne eine davon zu verwenden, aber es ist am Ende mehr Arbeit als nur eine Aktualisierung Ihrer App, um eine davon zu verwenden.
Brian Yencho
25

Am Ende war mein Problem mit Dingen, die auf SaveInstance gespeichert wurden, und nicht mit Dingen, die an die nächste Aktivität gesendet wurden. Ich habe alle Speicherungen entfernt, bei denen ich die Größe von Objekten (Netzwerkantworten) nicht steuern kann, und jetzt funktioniert es.

Aktualisieren:

Um große Datenmengen zu erhalten, empfiehlt Google, dies mit Fragment zu tun, das die Instanz beibehält. Die Idee ist, ein leeres Fragment ohne Ansicht mit allen erforderlichen Feldern zu erstellen, die andernfalls im Bundle gespeichert würden. In setRetainInstance(true);zu onCreate Methode des Fragments. Speichern Sie dann die Daten in Fragment on onestest von Activity und laden Sie sie onCreate. Hier ist ein Beispiel für Aktivität:

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

Und Beispiel eines Fragments:

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

Mehr dazu können Sie hier lesen .

Vladimir Jovanović
quelle
5
Das ist die richtige Antwort! Seit targetSdkVersion 24+ löst diese Ausnahme anstelle von "App hat zu viele Daten im Instanzstatus gesendet, daher wurde sie ignoriert" aus. Einige Details für diejenigen, die mehr wissen wollen: code.google.com/p/android/issues/detail?id=212316
Yazon2006
2
Dies hilft nicht, wenn die App einfach im Hintergrund ist und der ViewState dies verursacht. Hat jemand eine Idee, wie man damit umgeht?
AllDayAmazing
Nur neugierig, was ist, wenn Sie einen statischen Verweis auf die fraglichen Daten haben? Mein Fall ist eine Arrayliste von POJO-Objekten.
X09
@ X09 Das Problem bei diesem Ansatz ist, dass Sie diesen Speicher löschen sollten, wenn Sie zu einer anderen Aktivität wechseln. Dies kann schwierig sein (Sie müssen ihn inDestory bereinigen, aber nicht inDestroy, wenn Sie einen Bildschirm drehen). Andernfalls haben alle Ihre Aktivitäten statische Variablen mit einigen Objekten, die Sie nicht benötigen. Dieser Ansatz kann funktionieren, aber Sie werden eine wirklich unordentliche App haben, daher würde ich sie nicht empfehlen.
Vladimir Jovanović
18

Die TransactionTooLargeException plagt uns seit ungefähr 4 Monaten und wir haben das Problem endlich gelöst!

Was geschah, war, dass wir einen FragmentStatePagerAdapter in einem ViewPager verwenden. Der Benutzer blättert durch und erstellt mehr als 100 Fragmente (es ist eine Leseanwendung).

Obwohl wir die Fragmente in destroyItem () ordnungsgemäß verwalten, gibt es in der Androids-Implementierung von FragmentStatePagerAdapter einen Fehler, bei dem auf die folgende Liste verwiesen wird:

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

Und wenn der FragmentStatePagerAdapter von Android versucht, den Status zu speichern, wird die Funktion aufgerufen

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

Wie Sie sehen können, speichert die Basisklasse auch dann, wenn Sie die Fragmente in der Unterklasse FragmentStatePagerAdapter ordnungsgemäß verwalten, ein Fragment.SavedState für jedes einzelne jemals erstellte Fragment. Die TransactionTooLargeException würde auftreten, wenn dieses Array in ein parcelableArray kopiert wurde und das Betriebssystem nicht mehr als 100 Elemente mag.

Daher bestand die Lösung für uns darin, die saveState () -Methode zu überschreiben und nichts für "Zustände" zu speichern.

@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;
}
IK828
quelle
2
im saveState () kann das Bundle null sein, also wenn (bundle! = null) bundle.putParcelableArray ("States", null);
Zbarcea Christian
@ IKK828 Vielen Dank für Ihre Antwort, Sie haben meinen Tag gerettet! Funktioniert wie ein Zauber :)
jaumebd
18

Habe einen Treffer und einen Versuch gemacht, und schließlich hat dies mein Problem gelöst. Fügen Sie dies Ihrem hinzuActivity

@Override
protected void onSaveInstanceState(Bundle oldInstanceState) {
    super.onSaveInstanceState(oldInstanceState);
    oldInstanceState.clear();
}
Raj Yadav
quelle
2
Danke @Raj ... du hast mir Zeit gespart
varotariya vajsi
Auf diese Weise sparen Sie nichts. Es Activityist eine schlechte Praxis, alle "Fortschritte" zu löschen, die Benutzer in Ihrem Unternehmen gemacht haben .
N. Park
Wenn Ihre Instanz wichtig ist und Sie sie speichern möchten, können Sie sie vor dem Löschen speichern. //Create instance variable, Bundle oldInstance; @Override protected void onSaveInstanceState(Bundle oldInstanceState) { super.onSaveInstanceState(oldInstanceState); //save your instance here, here oldInstance = oldInstanceState oldInstanceState.clear(); }
Raj Yadav
11

Ich habe dieses Problem auch auf meinen Nougat-Geräten. Meine App verwendet ein Fragment mit einem Ansichtspager, der 4 Fragmente enthält. Ich habe einige große Konstruktionsargumente an die 4 Fragmente übergeben, die das Problem verursacht haben.

Ich habe die Größe Bundleder Ursache mithilfe von TooLargeTool ermittelt .

Schließlich habe ich es mit putSerializableeinem POJO-Objekt gelöst , das während der Fragmentinitialisierung implementiert, Serializableanstatt ein großes Raw zu übergeben . Diese reduzierte Größe um die Hälfte und wirft die nicht . Stellen Sie daher sicher, dass Sie keine Argumente mit großer Größe an übergeben .StringputStringBundleTransactionTooLargeExceptionFragment

PS-bezogenes Problem im Google Issue Tracker: https://issuetracker.google.com/issues/37103380

Döbel
quelle
3
Danke David. Dieses Tool hat mir wirklich geholfen, die Hauptursache des Problems zu finden. Du rockst :)
Nitin Mesta
Willkommen @NitinMesta, :)
Döbel
8

Ich stehe vor einem ähnlichen Problem. Das Problem und das Szenario unterscheiden sich kaum und ich behebe es folgendermaßen. Bitte überprüfen Sie das Szenario und die Lösung.

Szenario: Der Kunde hat einen seltsamen Fehler im Google Nexus 6P-Gerät (7 OS) erhalten, da meine Anwendung nach 4 Stunden Arbeit abstürzt. Später stelle ich fest, dass es die ähnliche Ausnahme (android.os.TransactionTooLargeException :) auslöst.

Lösung: Das Protokoll zeigte nicht auf eine bestimmte Klasse in der Anwendung, und später stellte ich fest, dass dies geschieht, weil der hintere Stapel von Fragmenten beibehalten wurde. In meinem Fall werden 4 Fragmente mithilfe einer automatischen Bildschirmbewegungsanimation wiederholt zum hinteren Stapel hinzugefügt. Also überschreibe ich das onBackstackChanged () wie unten erwähnt.

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

Wenn der Stapel das Limit überschreitet, wird er automatisch zum ursprünglichen Fragment verschoben. Ich hoffe, jemand wird diese Antwort unterstützen, da die Ausnahme- und Stack-Trace-Protokolle identisch sind. Wenn dieses Problem auftritt, überprüfen Sie bitte die Anzahl der Rückstapel, wenn Sie Fragmente und Rückstapel verwenden.

Nithinjith
quelle
Diese Lösung scheint die Ausnahmen TransactionTooLargeException und FAILED BINDER TRANSACTION zu behandeln. Möchten Sie wissen, was mCUrrentlyLoadedFragment ist und warum wir entry.getName () darauf analysieren müssen. Vielen Dank.
tfad334
6

In meinem Fall habe ich diese Ausnahme in einem Fragment erhalten, weil eines seiner Argumente eine sehr große Zeichenfolge war, die ich vergessen habe, zu löschen (ich habe nur diese große Zeichenfolge in der onViewCreated () -Methode verwendet). Um dies zu lösen, habe ich dieses Argument einfach gelöscht. In Ihrem Fall müssen Sie verdächtige Felder löschen oder aufheben, bevor Sie onPause () aufrufen.

Aktivitätscode

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

Fragmentcode

@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  
}
andres.dev
quelle
Vielen Dank, arbeitete für mich, suchte seit langem nach diesem Problem, versuchte alles und schließlich funktionierte dies.
Akashzincle
Würden die Argumente nicht klar sein, nachdem das Fragment entfernt wurde? Warum ist dieser Teil der Logik nicht in Fragmenten ...
Mor Elmaliach
2

Das Problem in meiner App war, dass ich versucht habe, zu viel im savedInstanceState zu speichern. Die Lösung bestand darin, genau zu identifizieren, welche Daten zum richtigen Zeitpunkt gespeichert werden sollten. Schauen Sie sich Ihren onSaveInstanceState genau an, um sicherzustellen, dass Sie ihn nicht dehnen:

@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);
}
Golan Shay
quelle
1
Ich hatte das gleiche Problem und schließlich wurde es behoben, indem einige Daten, die ich gespeichert hatte, in den savedInstanceState entfernt wurden. Seien Sie besonders vorsichtig mit outState.putParcelableArrayList (... oder Parcelable-Objekten, die Parcelable-Listen enthalten.
Anthorlop
1

Ich stand vor dem gleichen Problem. Durch meine Problemumgehung werden savedInstanceState in Dateien im Cache-Verzeichnis ausgelagert.

Ich habe die folgende Utility-Klasse erstellt.

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

Vollständige Codes: https://github.com/cattaka/AndroidSnippets/pull/37

Ich mache mir Sorgen, dass Parcel # Marshall nicht für dauerhafte verwendet werden sollte. Aber ich habe keine andere Idee.

Takao Sumitomo
quelle
Können Sie sich bitte meine aktualisierte Antwort ansehen? Ich habe diese Lösung versehentlich gefunden. Auf den ersten Blick sieht es seltsam aus, aber es funktioniert wirklich gut.
Vladimir Jovanović
1
Ihre Lösung sieht gut aus. Aber ich denke, es kann nicht wiederhergestellt werden, wenn der Prozess vom System beendet wurde. Es kann jedoch kein großes Problem sein, da es sich um einen Randfall handelt.
Takao Sumitomo
1

Keine der oben genannten Antworten hat bei mir funktioniert. Der Grund für das Problem war recht einfach, wie von einigen angegeben, die ich mit FragmentStatePagerAdapter verwendet habe. Die saveState-Methode speichert den Status der Fragmente, da eines meiner Fragmente ziemlich groß war, sodass dieses Fragment gespeichert wurde führt zu dieser TransactionTooLargeExecption.

Ich habe versucht, die saveState-Methode in meiner Implementierung von Pager zu überschreiben, wie von @ IK828 angegeben, aber dies konnte den Absturz nicht beheben.

Mein Fragment hatte einen EditText, der früher sehr großen Text enthielt, was in meinem Fall der Grund für das Problem war. In onPause () des Fragments habe ich den edittext-Text einfach auf eine leere Zeichenfolge gesetzt. dh:

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

Wenn FragmentStatePagerAdapter nun versucht, State zu speichern, ist dieser große Textblock nicht mehr vorhanden, um einen größeren Teil davon zu verbrauchen, wodurch der Absturz behoben wird.

In Ihrem Fall müssen Sie herausfinden, was der Schuldige ist. Dies kann eine ImageView mit einer Bitmap, eine TextView mit einem großen Textblock oder eine andere Ansicht mit hohem Speicherbedarf sein. Sie müssen den Speicher freigeben. Sie können imageview.setImageResource ( null) oder ähnliches in onPause () Ihres Fragments.

Update: onSaveInstanceState ist ein besserer Ort für den Zweck, bevor Sie super aufrufen wie:

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

oder wie von @Vladimir gezeigt, können Sie android verwenden: saveEnabled = "false" oder view.setSaveEnabled (false); Stellen Sie in der Ansicht oder in der benutzerdefinierten Ansicht sicher, dass der Text wieder in onResume festgelegt ist. Andernfalls ist er leer, wenn die Aktivität fortgesetzt wird.

Ankush Chugh
quelle
3
Eine bessere Lösung wäre die Verwendung android:saveEnabled="false"in einer bestimmten Ansicht. Mehr dazu lesen Sie hier .
Vladimir Jovanović
0

Überschreiben Sie diese Methode einfach für Ihre Aktivität:

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

Weitere Informationen finden Sie unter https://code.google.com/p/android/issues/detail?id=212316#makechanges .

Étienne Théodore
quelle
2
Es könnte hilfreich sein, wenn Sie weitere Informationen hinzufügen könnten, die die Benutzer sehen könnten, bevor sie auf den Link klicken
Amanuel Nega
3
Diese Antwort allein ist nicht hilfreich
Grantespo
Beim Speichern großer Datenmengen in SaveInstanceState tritt eine Transaktions-Toolarge-Ausnahme auf. Durch diesen Code wird das Speichern von Daten verhindert.
Ameer
Es ist keine gute Lösung, weil Sie möglicherweise etwas von Ihrer Aktivität speichern müssen
Lennon Spirlandelli
0

Wenn Android N das Verhalten ändert und TransactionTooLargeException auslöst, anstatt den Fehler zu protokollieren.

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

Meine Lösung besteht darin, die ActivityMangerProxy-Instanz zu verknüpfen und die activityStopped-Methode abzufangen.

Hier ist der 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);
    }
}

Und die Reflect Helper Klasse ist

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;
    }
}
}
liubaoyua
quelle
0

In meinem Fall habe ich TooLargeTool verwendet, um zu verfolgen, woher das Problem stammt, und ich habe herausgefunden, dass der android:support:fragmentsSchlüssel in Bundlemeinem onSaveInstanceStateverwendet wurde, um fast 1 MB zu erreichen, als die App abstürzte. Die Lösung war also wie folgt:

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

Auf diese Weise habe ich vermieden, die Zustände aller Fragmente zu speichern, und mich an andere Dinge gehalten, die gespeichert werden müssen.

Lennon Spirlandelli
quelle
Wo haben Sie das obige Code-Snippet hinzugefügt? In der übergeordneten Aktivität oder im Fragment selbst. Funktioniert die oben genannte Lösung auch für Sie, um die Ausnahme "Transaktion zu groß" zu beseitigen?
Jatin Jha
@JatinJha Ich habe diesen Code in die Aktivität eingefügt und es wurde verhindert, dass TransactionTooLargeException. Wenn das bei Ihnen nicht funktioniert hat, überprüfen Sie besser, was mit github.com/guardian/toolargetool
Lennon Spirlandelli