web-dev-qa-db-fra.com

android.os.TransactionTooLargeException sur Nougat

J'ai mis à jour Nexus 5X vers Android N, et maintenant, lorsque j'y installe l'application (débogage ou publication), j'obtiens TransactionTooLargeException sur chaque transition d'écran comportant Bundle dans des extras. L'application fonctionne sur tous les autres appareils. L'ancienne application qui est sur PlayStore et qui a essentiellement le même code fonctionne sur Nexus 5X. Est-ce que quelqu'un a le même problème?

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) 
71
Vladimir Jovanović

À la fin, mon problème concernait les éléments sauvegardés dans OnSaveInstance et non les éléments envoyés à l'activité suivante. J'ai supprimé toutes les sauvegardes pour lesquelles je ne peux pas contrôler la taille des objets (réponses réseau), et maintenant cela fonctionne.

Mise à jour:

Pour préserver de gros volumes de données, Google suggère de le faire avec Fragment qui conserve l'instance. L'idée est de créer un fragment vide sans une vue avec tous les champs nécessaires, qui seraient autrement sauvegardés dans Bundle. Ajoutez setRetainInstance(true); à la méthode onCreate de Fragment. Et puis enregistrez les données dans Fragment on Activity's onDestroy et chargez-les onCreate. Voici un exemple d'activité:

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

Et exemple de 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;
    }
}

Pour en savoir plus, vous pouvez lire ici .

25
Vladimir Jovanović

Chaque fois que vous voyez TransactionTooLargeException se produire lorsqu'un Activity est en train de s'arrêter, cela signifie que le Activity essayait d'envoyer son état enregistré Bundles au système d'exploitation pour plus de sécurité. conserver pour restauration plus tard (après un changement de configuration ou la mort du processus) mais qu'un ou plusieurs des Bundles qu'il a envoyés étaient trop volumineux. Il existe une limite maximale d'environ 1 Mo pour toutes les transactions de ce type qui se produisent en même temps et cette limite peut être atteinte même si aucun Bundle ne dépasse cette limite.

Le principal coupable ici est généralement d’enregistrer trop de données dans onSaveInstanceState de la Activity ou de n’importe quel Fragments hébergé par la Activity. Cela se produit généralement lorsque vous enregistrez quelque chose de particulièrement important, tel que Bitmap, mais également lors de l'envoi de grandes quantités de données plus petites, telles que des listes d'objets Parcelable. L’équipe Android a indiqué à de nombreuses reprises que seules de petites quantités de données relatives aux vues devaient être enregistrées dans onSavedInstanceState. Toutefois, les développeurs ont souvent sauvegardé des pages de données réseau afin de rendre les modifications de configuration aussi fluides que possible, sans avoir à récupérer les mêmes données. À compter de Google I/O 2017, l'équipe Android a clairement indiqué que l'architecture préférée pour une application Android enregistre les données de réseau.

  • en mémoire afin qu'il puisse être facilement réutilisé lors de changements de configuration
  • sur le disque afin qu'il puisse être facilement restauré après la mort du processus et les sessions d'application

Leur nouvelle structure ViewModel et leur bibliothèque de persistance Room sont conçues pour aider les développeurs à s’adapter à ce modèle. Si votre problème est lié à la sauvegarde de trop de données dans onSaveInstanceState, la mise à jour vers une architecture comme celle-ci à l'aide de ces outils devrait résoudre votre problème.

Personnellement, avant de mettre à jour ce nouveau modèle, j'aimerais prendre mes applications existantes et contourner la TransactionTooLargeException entre-temps. J'ai écrit une bibliothèque rapide pour faire exactement cela: https://github.com/livefront/bridge . Il utilise les mêmes idées générales de restauration d'état à partir de la mémoire lors des modifications de configuration et du disque après la mort du processus, au lieu d'envoyer tout cet état au système d'exploitation via onSaveInstanceState, mais nécessite des modifications très minimes de votre code existant. Toute stratégie qui correspond à ces deux objectifs devrait toutefois vous aider à éviter l'exception, sans sacrifier votre capacité à sauvegarder l'état.

Note finale ici: la seule raison pour laquelle vous voyez ceci sur Nougat + est qu’à l’origine, si la limite de transaction du classeur était dépassée, le processus d’envoi de l’état sauvegardé au système d’exploitation échouerait en silence, seule cette erreur s’affichant dans Logcat:

!!! ÉCHEC DE LA TRANSACTION DE LIANT !!!

À Nougat, cet échec silencieux a été transformé en un crash dur. À leur crédit, l'équipe de développement a documenté les notes de publication pour Nougat :

De nombreuses API de plate-forme ont maintenant commencé à vérifier si de grandes charges utiles sont envoyées via des transactions de classeur et le système réenvoie désormais TransactionTooLargeExceptions en tant qu'exceptions RuntimeExceptions, au lieu de les consigner ou de les supprimer en mode silencieux. Un exemple courant est de stocker trop de données dans Activity.onSaveInstanceState (), ce qui permet à ActivityThread.StopInfo de générer une exception RuntimeException lorsque votre application cible Android 7.0.

26
Brian Yencho

L'exception TransactionTooLargeException nous tourmente depuis environ 4 mois et nous avons finalement résolu le problème!

Ce qui se passait, c’est que nous utilisons un FragmentStatePagerAdapter dans un ViewPager. L'utilisateur peut parcourir et créer plus de 100 fragments (c'est une application de lecture).

Bien que nous gérions les fragments correctement dans destroyItem (), dans l'implémentation de FragmentStatePagerAdapter dans Androids, il y a un bogue, qui garde une référence à la liste suivante:

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

Et lorsque FragmentStatePagerAdapter d'Android tente de sauvegarder l'état, il appelle la fonction

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

Comme vous pouvez le constater, même si vous gérez correctement les fragments de la sous-classe FragmentStatePagerAdapter, la classe de base conservera tout de même un Fragment.SavedState pour chaque fragment créé. L'exception TransactionTooLargeException se produirait lorsque ce tableau serait vidé dans un tableau parcelable et que le système d'exploitation ne l'aimerait pas plus de 100 éléments.

Par conséquent, le correctif pour nous était de remplacer la méthode saveState () et de ne rien stocker pour des "états".

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

Fait un coup et un procès, et finalement cela a résolu mon problème. Ajoutez ceci à votre Activity

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

Je suis également confronté à ce problème sur mes appareils Nougat. Mon application utilise un fragment avec un pageur de vue contenant 4 fragments. J'ai passé de gros arguments de construction aux 4 fragments qui ont causé le problème.

J'ai tracé la taille de Bundle provoquant ceci à l'aide de TooLargeTool .

Finalement, je l'ai résolu en utilisant putSerializable sur un objet POJO qui implémente Serializable au lieu de passer un gros raw String en utilisant putString lors de l'initialisation du fragment. Cette taille réduite de Bundle de moitié et ne jette pas le TransactionTooLargeException. Par conséquent, veillez à ne pas transmettre d’arguments de taille énorme à Fragment.

P.S. problème lié dans le suivi des problèmes de Google: https://issuetracker.google.com/issues/3710338

10
David Cheung

Je suis confronté au même problème. Le problème et le scénario sont peu différents et je le résout de la manière suivante. Veuillez vérifier le scénario et la solution.

Scénario: J'ai reçu un étrange bug du client dans le périphérique Google Nexus 6P (7 OS) car mon application se planterait après 4 heures de travail. Plus tard, j’identifie qu’il déclenche une exception similaire (Android.os.TransactionTooLargeException :).

Solution: Le journal ne pointait aucune classe en particulier dans l'application et j'ai découvert par la suite que cela se produisait parce que la pile de fragments restante était conservée. Dans mon cas, 4 fragments sont ajoutés à la pile arrière à plusieurs reprises à l'aide d'une animation de mouvement d'écran automatique. Donc, je remplace le onBackstackChanged () comme mentionné ci-dessous.

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

Si la pile dépasse la limite, le fragment initial s'affichera automatiquement. J'espère que quelqu'un aidera cette réponse, car les journaux de trace des exceptions et de la pile sont les mêmes. Par conséquent, chaque fois que ce problème se produit, vérifiez le nombre de piles de retour si vous utilisez des fragments et une pile de retour.

8
Nithinjith

Dans mon cas, j'ai eu cette exception dans un fragment parce que l'un de ses arguments était une très grande chaîne que j'ai oublié de supprimer (je n'ai utilisé que cette grande chaîne dans la méthode onViewCreated ()). Donc, pour résoudre ce problème, j'ai simplement supprimé cet argument. Dans votre cas, vous devez effacer ou annuler tout champ suspect avant d'appeler onPause ().

Code d'activité

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

Code de fragment

@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  
}
5
andres.dev

Le problème dans mon application était que j'essayais de sauver trop de choses dans saveInstanceState. La solution consistait à identifier exactement les données à sauvegarder au bon moment. En gros, examinez attentivement votre onSaveInstanceState pour vous assurer de ne pas l'étirer:

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

J'ai fait face au même problème. Ma solution de contournement savedInstanceState est sauvegardée dans les fichiers du répertoire de cache.

J'ai fait la classe d'utilitaire suivante.

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

Codes complets: https://github.com/cattaka/AndroidSnippets/pull/37

Je m'inquiète de ce que la parcelle # marshall ne doit pas être utilisée pour des tâches persistantes. Mais je n'ai aucune autre idée.

1
Takao Sumitomo

Aucune des réponses ci-dessus n'a fonctionné pour moi, la raison du problème était assez simple, comme l'ont indiqué certains que j'utilisais FragmentStatePagerAdapter et que sa méthode saveState enregistre l'état des fragments, car l'un de mes fragments était assez volumineux, donc la sauvegarde de ce fragment mène à cette transaction TransactionTooLargeExecption.

J'ai essayé de surcharger la méthode saveState dans mon implémentation de pager, comme indiqué par @ IK828, mais cela ne pouvait pas résoudre le blocage.

Mon fragment avait un EditText qui contenait autrefois un texte très volumineux, qui était le coupable du problème dans mon cas. Donc, simplement dans onPause () du fragment, je règle le texte edittext sur chaîne vide. c'est à dire:

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

Désormais, lorsque FragmentStatePagerAdapter essaiera de sauvegarder, ce gros bloc de texte ne sera pas là pour en absorber une plus grande partie, ce qui résout le problème.

Dans votre cas, vous devez trouver le coupable. Il peut s’agir d’un ImageView avec un bitmap, d’un TextView avec d’énormes morceaux de texte ou de toute autre vue consommant beaucoup de mémoire, vous devez libérer sa mémoire, vous pouvez définir imageview.setImageResource ( null) ou similaire dans onPause () de votre fragment.

update: onSaveInstanceState est le meilleur endroit pour cela avant d'appeler super comme:

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

ou comme l'a souligné @ Vladimir, vous pouvez utiliser Android: saveEnabled = "false" ou view.setSaveEnabled (false); sur la vue ou la vue personnalisée et assurez-vous de redéfinir le texte dans onResume, sinon il sera vide à la reprise de l'activité.

1
Ankush Chugh

En tant que Android N, modifiez le comportement et lève l'exception TransactionTooLargeException au lieu de consigner l'erreur.

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

ma solution consiste à connecter l'instance ActivityMangerProxy et à intercepter la méthode activityStopped.

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

Et la classe d'assistance de réflexion est

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

Substituez simplement cette méthode à votre activité:

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

Allez à https://code.google.com/p/Android/issues/detail?id=212316#makechanges pour plus d'informations.

0
Étienne Théodore

Dans mon cas, j’ai utilisé TooLargeTool pour déterminer l’origine du problème et j’ai découvert la clé Android:support:fragments dans la Bundle de mon onSaveInstanceState utilisé pour atteindre près de 1 Mo quand l'application s'est écrasée. La solution était donc la suivante:

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

En faisant cela, j'ai évité de sauvegarder tous les fragments et de conserver d'autres éléments à sauvegarder.

0
Lennon Petrick