Android.OS.Transaction toolargeexception sur Nougat

J'ai mis à jour Nexus 5X vers Android N, et maintenant que j'installe l'application (débogage ou release) dessus je reçois TransactionTooLargeException sur chaque transition d'écran qui a Bundle en extras. L'application fonctionne sur tous les autres appareils. L'ancienne application qui est sur PlayStore et a la plupart du temps le même code fonctionne sur Nexus 5X. Est ce que quelqu'un ayant 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) 
65
demandé sur Vladimir Jovanović 2016-08-23 13:25:35

12 réponses

à la fin, mon problème était avec les choses qui étaient sauvées onssaveinstance, et non avec les choses qui étaient envoyées à la prochaine activité. J'ai supprimé toutes les sauvegardes où je ne peux pas contrôler une taille d'objets (réponses réseau), et maintenant ça fonctionne.

mise à jour:

pour préserver de gros morceaux de données, Google suggère de le faire avec Fragment qui retient l'instance. L'idée est de créer un Fragment vide sans une vue avec tous les champs nécessaires, qui autrement seraient sauvegardés dans Bundle. Ajouter setRetainInstance(true); à la méthode onCreate de Fragment. Puis sauvegardez les données dans Fragment Ontroy de Activity et chargez-les une fois créés. 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 .

22
répondu Vladimir Jovanović 2017-02-07 10:51:25

chaque fois que vous voyez TransactionTooLargeException se produire quand un Activity est en train de s'arrêter, cela signifie que le Activity essayait d'envoyer son état sauvé Bundles au système D'exploitation pour la sécurité de conservation pour la 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é étaient trop grands. Il y a une limite maximale d'environ 1MB pour toutes ces transactions à la fois et cette limite peut être atteinte même si aucune" dépasse cette limite.

le principal coupable ici est généralement de sauvegarder trop de données à l'intérieur de onSaveInstanceState du Activity ou de tout Fragments hébergé par le Activity . Cela se produit typiquement lors de l'enregistrement de quelque chose de particulièrement grand comme un Bitmap , mais cela peut aussi se produire lors de l'envoi de grandes quantités de données plus petites, comme des listes d'objets Parcelable . L'équipe Android a fait très clair à de nombreuses occasions que seulement de petites quantités de vue-liés les données doivent être sauvegardées dans onSavedInstanceState . Cependant, les développeurs ont souvent sauvegardé des pages de données réseau afin de rendre les modifications de configuration aussi fluides que possible en n'ayant pas à reconfigurer les mêmes données. À partir de Google I / O 2017, L'équipe Android a clairement fait savoir que l'architecture préférée pour une application Android sauvegarde les données réseau ""

  • en mémoire afin qu'il puisse être facilement réutilisé à travers les changements de configuration
  • de disque pour qu'il peut être facilement restauré après la mort du processus et app sessions

leur nouveau ViewModel framework et Room persistance bibliothèque sont destinés à aider les développeurs à adapter ce modèle. Si votre problème est d'enregistrer trop de données dans onSaveInstanceState , la mise à jour vers une architecture comme celle-ci en utilisant ces outils devrait corriger votre problème.

personnellement, avant de mettre à jour à ce nouveau modèle, je voudrais prendre mes applications existantes et juste se déplacer le TransactionTooLargeException en attendant. 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 de l'État à partir de la mémoire à travers les changements de configuration et à partir du disque après la mort du processus, plutôt que d'envoyer tout cet état à L'OS via onSaveInstanceState , mais nécessite des modifications très minimes à votre code existant à utiliser. Toute stratégie qui correspond à ces deux objectifs devrait vous aider à éviter l'exception, mais sans sacrifier votre capacité à sauver l'état.

sur la note finale ici : la seule raison pour laquelle vous voyez cela sur Nougat+ est qu'à l'origine si la limite de transaction binder était dépassée, le processus d'envoyer l'état sauvegardé à L'OS échouerait silencieusement avec seulement cette erreur se manifestant dans Logcat:

!!! TRANSACTION DE RELIURE RATÉE !!!

Dans le Nougat, que le silence de l'échec a été mis à niveau vers un crash. À leur crédit, c'est quelque chose que l' équipe de développement documentée dans les notes de version pour Nougat :

de nombreux API de plate-forme ont maintenant commencé à vérifier si des charges utiles importantes sont envoyées à travers des transactions de reliure, et le système repousse maintenant les transactions vers des exceptions plus importantes comme des exceptions RuntimeExceptions, au lieu de les enregistrer ou de les supprimer en silence. Un exemple courant est le stockage de trop de données dans Activity.onSaveInstanceState (), qui provoque ActivityThread.StopInfo de jeter un RuntimeException lorsque votre application cible Android 7.0.

19
répondu Brian Yencho 2017-06-24 16:30:10

la transaction Toolargeexception nous tourmente depuis environ 4 mois, et nous avons enfin résolu le problème!

ce qui se passait était que nous utilisions un FragmentStatePagerAdapter dans un ViewPager. L'utilisateur pageait et créait plus de 100 fragments (c'est une application de lecture).

bien que nous gérions correctement les fragments dans destroyItem (), dans androïdes implémentation de FragmentStatePagerAdapter il y a un bug, où il a gardé référence à la liste suivante:

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

et lorsque FragmentStatePagerAdapter de L'Android tente de sauver l'état, il appellera 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 voir, même si vous gérez correctement les fragments dans la sous-classe FragmentStatePagerAdapter, la classe de base stockera tout de même un Fragment.SavedState pour chaque fragment jamais créé. La transaction Toolargeexception se produirait quand ce tableau a été jeté à un parcelableArray et L'OS ne l'aimeraient pas 100 + articles.

donc le correctif pour nous était de surcharger la méthode saveState() et ne pas stocker quoi que ce soit pour"É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;
}
15
répondu IK828 2017-04-03 20:14:30

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

@Override
protected void onSaveInstanceState(Bundle oldInstanceState) {
    super.onSaveInstanceState(oldInstanceState);
    oldInstanceState.clear();
}
10
répondu Raj Yadav 2017-09-23 06:39:19

je fais face à cette question ainsi que sur mes dispositifs Nougat. Mon application utilise un fragment avec un pager de vue qui contient 4 fragments. J'ai passé quelques grands arguments de construction aux 4 fragments qui ont causé le problème.

j'ai tracé la taille de Bundle ce qui cause cela à l'aide de TooLargeTool .

enfin, je l'ai résolu en utilisant putSerializable sur un objet POJO qui met en œuvre Serializable au lieu de passer un grand raw String utilisant putString lors de l'initialisation des fragments. Cette taille réduite de Bundle de moitié et ne jette pas le TransactionTooLargeException . Par conséquent, s'il vous plaît assurez-vous de ne pas passer des arguments de taille énorme à Fragment .

P.S. question connexe dans Google issue tracker: https://issuetracker.google.com/issues/37103380

8
répondu David Cheung 2017-08-01 06:50:34

je fais face à la même question. Le problème et le scénario sont un peu différents et je le règle de la façon suivante. Veuillez vérifier le scénario et la solution.

scénario: J'ai reçu un bug bizarre de la part du client dans le dispositif Google Nexus 6P(7 OS) que mon application va planter après 4 heures de travail. Plus tard, j'identifie qu'il lance le similaire (androïde.OS.Transaction toolargeexception:) exception.

Solution: Le journal ne pointait aucune classe particulière dans l'application et plus tard j'ai trouvé que cela se produit à cause de garder la pile arrière des fragments. Dans mon cas, 4 fragments sont ajoutés à la pile arrière à plusieurs reprises à l'aide d'une animation de mouvements d'écran automatique. Ainsi, j'annule l'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, elle passe automatiquement au fragment initial. J'espère que quelqu'un va l'aider cette réponse parce que les journaux de trace d'exception et de pile sont les mêmes. Donc, à chaque fois que ce problème se produit, s'il vous plaît vérifier le compte de la pile arrière, si vous utilisez des Fragments et la pile arrière.

6
répondu Nithinjith 2016-12-23 11:27:47

dans mon cas, j'ai eu cette exception à l'intérieur d'un fragment parce que l'un de ses arguments était une très grande chaîne que j'ai oublié de supprimer (j'ai seulement utilisé cette grande chaîne à l'intérieur de 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 toute suspect champ 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 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  
}
3
répondu andres.dev 2018-04-02 05:57:42

il suffit de modifier cette méthode sur 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);
}

passer à https://code.google.com/p/android/issues/detail?id=212316#makechanges pour plus d'information.

1
répondu Étienne Théodore 2016-12-13 13:53:22

j'ai fait face à la même question. Ma solution décharge savedInstanceState de fichiers dans le cache dir.

j'ai fait la classe d'utilité 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#marshall ne devrait pas être utilisé pour persistant. Mais, je n'ai pas d'autre idée.

1
répondu Takao Sumitomo 2017-01-12 06:25:39

Le problème dans mon application que j'essayais de mettre trop dans le savedInstanceState, la solution a été d'identifier exactement quelles données doivent être enregistrées au bon moment. Fondamentalement, regardez attentivement dans votre onSaveInstanceState pour vous assurer que vous ne l'étirez pas:

@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);
}
1
répondu Golan Shay 2017-07-31 17:12:08

aucune des réponses ci-dessus n'a fonctionné pour moi, la raison de la question était très simple comme l'ont déclaré certains j'utilisais FragmentStatePagerAdapter et sa méthode saveState sauve l'état des fragments, parce que l'un de mes fragments était assez grand , de sorte que la sauvegarde de ce fragment conduit à cette transaction à une plus grande excrétion.

j'ai essayé de passer outre la méthode saveState dans mon implémentation de pager comme indiqué par @IK828, mais cela n'a pas pu résoudre le crash.

mon fragment avait un texte édité qui contenait du Très gros texte, ce qui était le coupable du problème dans mon cas, donc simplement dans onPause() de fragment, j'ai placé le texte édité en chaîne vide. c'est à dire:

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

maintenant quand FragmentStatePagerAdapter va essayer de saveState, ce grand morceau de texte ne sera pas là pour en consommer une plus grande partie, donc résout le crash.

dans votre cas, vous devez trouver ce qui est le coupable, il peut-être un ImageView avec un peu de bitmap, un TextView avec un énorme morceau de texte ou n'importe quelle autre vue de haute consommation de mémoire, vous devez libérer sa mémoire, vous pouvez définir imageview.setImageResource (null) ou similaire dans onPause () de votre fragment.

mise à jour: onSaveInstanceState est meilleur endroit pour le but avant d'appeler super like:

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

ou comme indiqué par @Vladimir vous pouvez utiliser android:saveEnabled=" false " ou view.setSaveEnabled( faux); sur le visualisez ou visualisez sur mesure et assurez-vous de remettre le texte dans onResume, sinon il sera vide lorsque l'activité reprendra.

1
répondu Ankush Chugh 2017-11-22 18:16:00

comme L'Android n changer le comportement et jeter TransactionTooLargeException au lieu de la journalisation de 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 est d'accrocher L'instance ActivityMangerProxy et essayer d'attraper 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 reflect helper 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
répondu liubaoyua 2017-04-21 01:46:49