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