Android.os.TransactionTooLargeException on Nougat
Я обновил Nexus 5X до Android N, и теперь, когда я устанавливаю приложение (отладка или выпуск), я получаю TransactionTooLargeException на каждом экране, который имеет Bundle в дополнительных приложениях. Приложение работает на всех других устройствах. Старое приложение, которое находится в PlayStore и имеет в основном тот же код, работает с Nexus 5X.
Кто-нибудь имеет такую же проблему?
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)
Ответы
Ответ 1
В конце концов, моя проблема заключалась в вещах, которые сохранялись вSaveInstance, а не в вещах, которые отправлялись в следующую активность. Я удалил все сэвы, где я не могу контролировать размер объектов (сетевые ответы), и теперь он работает.
Update:
Чтобы сохранить большие куски данных, Google предлагает сделать это с помощью фрагмента, который сохраняет экземпляр. Идея состоит в том, чтобы создать пустой фрагмент без представления со всеми необходимыми полями, которые в противном случае были бы сохранены в Bundle. Добавьте setRetainInstance(true);
в метод фрагмента onCreate.
А затем сохраните данные в Fragment on Activity onDestroy и загрузите их onCreate.
Вот пример Activity:
public class MyActivity extends Activity {
private DataFragment dataFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
dataFragment = (DataFragment) fm.findFragmentByTag("data");
// create the fragment and data the first time
if (dataFragment == null) {
// add the fragment
dataFragment = new DataFragment();
fm.beginTransaction().add(dataFragment, "data").commit();
// load the data from the web
dataFragment.setData(loadMyData());
}
// the data is available in dataFragment.getData()
...
}
@Override
public void onDestroy() {
super.onDestroy();
// store the data in the fragment
dataFragment.setData(collectMyLoadedData());
}
}
И пример фрагмента:
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;
}
}
Подробнее об этом вы можете прочитать здесь здесь.
Ответ 2
Всякий раз, когда вы видите, что TransactionTooLargeException
происходит, когда Activity
находится в процессе остановки, это означает, что Activity
пытался отправить сохраненное состояние Bundles
в системную ОС для безопасного сохранения для восстановления позже ( после изменения конфигурации или смерти процесса), но один или несколько отправленных Bundles
были слишком большими. Максимальный предел около 1 МБ для всех таких транзакций происходит сразу, и этот предел может быть достигнут, даже если ни один Bundle
превышает этот предел.
Основной причиной здесь является сохранение слишком большого количества данных внутри onSaveInstanceState
либо Activity
, либо любого Fragments
, размещенного Activity
. Обычно это происходит при сохранении чего-то особо крупного типа Bitmap
, но оно также может возникать при отправке больших объемов меньших данных, например списков объектов Parcelable
. Команда Android неоднократно заявляла, что в onSavedInstanceState
следует сохранить только небольшие данные, связанные с просмотром. Тем не менее, разработчики часто сохраняли страницы сетевых данных, чтобы изменения конфигурации отображались как можно более гладкими, так как не приходилось повторно получать одни и те же данные. Начиная с Google I/O 2017, команда Android четко указала, что предпочтительная архитектура приложения для Android сохраняет сетевые данные
- в памяти, поэтому его можно легко использовать повторно при изменении конфигурации.
- на диск, чтобы его можно было легко восстановить после завершения процесса и сеансов приложений.
Их новая инфраструктура ViewModel
и библиотека сохранения Room
призваны помочь разработчикам соответствовать этому шаблону. Если ваша проблема связана с сохранением слишком большого количества данных в onSaveInstanceState
, обновление этой архитектуры, используемой этими инструментами, должно устранить вашу проблему.
Лично перед обновлением к этому новому шаблону я хотел бы использовать мои существующие приложения и просто обойти TransactionTooLargeException
тем временем. Я написал быструю библиотеку, чтобы сделать именно это: https://github.com/livefront/bridge. Он использует те же общие идеи о восстановлении состояния из памяти при изменении конфигурации и с диска после смерти процесса, а не передает все это состояние ОС через onSaveInstanceState
, но требует очень минимальных изменений в вашем существующем коде для использования. Любая стратегия, которая соответствует этим двум целям, должна помочь вам избежать исключения, но, не жертвуя своей способностью сохранять состояние.
В заключение отметим: единственная причина, по которой вы видите это в Nougat +, - это то, что изначально, если предел пересылки связующего был превышен, процесс отправки сохраненного состояния в ОС будет терпеть неудачу, только эта ошибка появляется в Logcat:
!!! НЕПРАВИЛЬНАЯ СДЕЛКА СДЕЛКИ!!!
В Нуге, этот тихий провал был повышен до тяжелой аварии. К их чести, это то, что команда разработчиков задокументировала в примечаниях к выпуску для Nougat:
Теперь многие API-интерфейсы платформ начали проверять, что большие транзакции передаются по транзакциям Binder, и теперь система обновляет TransactionTooLargeExceptions как RuntimeExceptions, вместо того, чтобы тихо регистрировать или подавлять их. Одним из распространенных примеров является хранение слишком большого количества данных в Activity.onSaveInstanceState(), что приводит к тому, что ActivityThread.StopInfo выдает исключение RuntimeException, когда ваше приложение нацелено на Android 7.0.
Ответ 3
TransactionTooLargeException преследует нас около 4 месяцев, и мы, наконец, решили проблему!
Что происходит, мы использовали FragmentStatePagerAdapter в ViewPager. Пользователь будет просматривать страницы и создавать более 100 фрагментов (приложение для чтения).
Хотя мы правильно управляем фрагментами в destroyItem(), в андроидах
при реализации FragmentStatePagerAdapter появляется ошибка, в которой содержится ссылка на следующий список:
private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
И когда Android FragmentStatePagerAdapter попытается сохранить состояние, он вызовет функцию
@Override
public Parcelable saveState() {
Bundle state = null;
if (mSavedState.size() > 0) {
state = new Bundle();
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
mSavedState.toArray(fss);
state.putParcelableArray("states", fss);
}
for (int i=0; i<mFragments.size(); i++) {
Fragment f = mFragments.get(i);
if (f != null && f.isAdded()) {
if (state == null) {
state = new Bundle();
}
String key = "f" + i;
mFragmentManager.putFragment(state, key, f);
}
}
return state;
}
Как вы можете видеть, даже если вы правильно управляете фрагментами в подклассе FragmentStatePagerAdapter, базовый класс по-прежнему будет хранить Fragment.SavedState для каждого отдельного фрагмента, когда-либо созданного. TransactionTooLargeException произойдет, когда этот массив будет сброшен в parcelableArray, и OS не понравится более 100 элементов.
Поэтому исправление для нас состояло в том, чтобы переопределить метод saveState() и не хранить ничего для "состояний".
@Override
public Parcelable saveState() {
Bundle bundle = (Bundle) super.saveState();
bundle.putParcelableArray("states", null); // Never maintain any states from the base class, just null it out
return bundle;
}
Ответ 4
Был хит и испытание, и, наконец, это решило мою проблему.
Добавьте это в свой Activity
@Override
protected void onSaveInstanceState(Bundle oldInstanceState) {
super.onSaveInstanceState(oldInstanceState);
oldInstanceState.clear();
}
Ответ 5
Я сталкиваюсь с этой проблемой также на моих устройствах Nougat. Мое приложение использует фрагмент с пейджером просмотра, который содержит 4 фрагмента. Я передал несколько больших аргументов конструкции 4 фрагментам, которые вызвали проблему.
Я проследил размер Bundle
вызвав это с помощью TooLargeTool.
Наконец, я решил это, используя putSerializable
для объекта POJO, который реализует Serializable
вместо передачи большой необработанной String
с использованием putString
во время инициализации фрагмента. Это уменьшает размер Bundle
вдвое и не вызывает исключения TransactionTooLargeException
. Поэтому, пожалуйста, убедитесь, что вы не передаете аргументы огромного размера в Fragment
.
Проблема с PS в системе отслеживания проблем Google: https://issuetracker.google.com/issues/37103380
Ответ 6
Я сталкиваюсь с подобной проблемой. Проблема и сценарий немного отличаются, и я исправляю это следующим образом. Проверьте сценарий и решение.
Сценарий:
У меня возникла странная ошибка у клиента в устройстве Google Nexus 6P (7 ОС), так как мое приложение будет аварийно завершено через 4 часа работы. Позже я обнаружил, что он бросает подобное (исключение android.os.TransactionTooLargeException:).
Решение:
Журнал не указывал какой-либо конкретный класс в приложении, и позже я обнаружил, что это происходит из-за сохранения заднего стопа фрагментов. В моем случае четыре фрагмента добавляются в задний стек несколько раз с помощью анимации движения с автоматическим экраном. Поэтому я переопределяю onBackstackChanged(), как упоминается ниже.
@Override
public void onBackStackChanged() {
try {
int count = mFragmentMngr.getBackStackEntryCount();
if (count > 0) {
if (count > 30) {
mFragmentMngr.popBackStack(1, FragmentManager.POP_BACK_STACK_INCLUSIVE);
count = mFragmentMngr.getBackStackEntryCount();
}
FragmentManager.BackStackEntry entry = mFragmentMngr.getBackStackEntryAt(count - 1);
mCurrentlyLoadedFragment = Integer.parseInt(entry.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
Если стек превышает предел, он автоматически пойдет в исходный фрагмент. Я надеюсь, что кто-то поможет в этом ответе, потому что журналы отслеживания исключений и стека одинаковы. Поэтому всякий раз, когда возникает эта проблема, проверьте счетчик обратного стека, если вы используете фрагменты и задний стек.
Ответ 7
В моем случае я получил это исключение внутри фрагмента, потому что одним из его аргументов была очень большая строка, которую я забыл удалить (я использовал только эту большую строку внутри метода onViewCreated()). Итак, чтобы решить это, я просто удалил этот аргумент. В вашем случае вы должны очистить или аннулировать любое подозрительное поле перед вызовом onPause().
Код активности
Fragment fragment = new Fragment();
Bundle args = new Bundle();
args.putString("extremely large string", data.getValue());
fragment.setArguments(args);
Код фрагмента
@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
}
Ответ 8
Проблема в моем приложении заключалась в том, что я пытался слишком много экономить на saveInstanceState, решение заключалось в том, чтобы точно определить, какие данные должны быть сохранены в нужное время. В основном внимательно посмотрите на свой onSaveInstanceState, чтобы убедиться, что вы не растягиваете его:
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user current state
// Check carefully what you're adding into the savedInstanceState before saving it
super.onSaveInstanceState(savedInstanceState);
}
Ответ 9
Просто переопределите этот метод в своей деятельности:
@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);
}
Для получения дополнительной информации перейдите к https://code.google.com/p/android/issues/detail?id=212316#makechanges.
Ответ 10
Я столкнулся с той же проблемой.
Мое обходное решение отключает saveInstanceState для файлов в каталоге dir.
Я сделал следующий класс утилиты.
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;
}
}
Полные коды: https://github.com/cattaka/AndroidSnippets/pull/37
Я беспокоюсь о том, что Parcel # marshall не следует использовать для постоянных.
Но у меня нет другой идеи.
Ответ 11
Ни один из вышеперечисленных ответов не работал у меня, причина этой проблемы была довольно простой, поскольку некоторые из них я использовал FragmentStatePagerAdapter, а метод saveState сохраняет состояние фрагментов, потому что один из моих фрагментов был довольно большим, поэтому сохранение этого фрагмента приводит к этому TransactionTooLargeExecption.
Я попытался переопределить метод saveState в моей реализации пейджера, как указано в @IK828, но это не могло решить проблему.
В моем фрагменте был EditText, который использовался для хранения очень большого текста, который был виновником проблемы в моем случае, поэтому просто в onPause() фрагмента я установил текст edittext в пустую строку.
то есть:
@Override
public void onPause() {
edittext.setText("");
}
Теперь, когда FragmentStatePagerAdapter попытается сохранить состояние, этот большой кусок текста не будет там, чтобы потреблять большую его часть, следовательно, разрешает сбой.
В вашем случае вам нужно найти то, что является виновником, это может быть ImageView с некоторым растровым изображением, TextView с огромным фрагментом текста или любым другим высоким потреблением памяти, вам нужно освободить память, вы можете установить изображение .setImageResource(null) или аналогичный в onPause() вашего фрагмента.
update: onSaveInstanceState лучше подходит для этой цели, прежде чем вызывать супер:
@Override
public void onSaveInstanceState(Bundle outState) {
edittext.setText("");
super.onSaveInstanceState(outState);
}
или, как указано @Vladimir, вы можете использовать android: saveEnabled = "false" или view.setSaveEnabled(false); в представлении или пользовательском представлении и убедитесь, что он установлен в onResume, иначе он будет пуст, когда действие возобновится.
Ответ 12
Поскольку Android N изменяет поведение и бросает TransactionTooLargeException вместо регистрации ошибки.
try {
if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Reporting activity stopped: " + activity);
ActivityManagerNative.getDefault().activityStopped(
activity.token, state, persistentState, description);
} catch (RemoteException ex) {
if (ex instanceof TransactionTooLargeException
&& activity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) {
Log.e(TAG, "App sent too much data in instance state, so it was ignored", ex);
return;
}
throw ex.rethrowFromSystemServer();
}
Мое решение - подключить экземпляр ActivityMangerProxy и попытаться поймать метод activityStopped.
Вот код:
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);
}
}
И класс вспомогательного рефлекса
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;
}
}
}
Ответ 13
В моем случае я использовал TooLargeTool, чтобы отследить, откуда возникла проблема, и я обнаружил ключ android:support:fragments
в Bundle
из моего onSaveInstanceState
использовался почти до 1 МБ при сбое приложения. Таким образом, решение было похоже на:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.remove("android:support:fragments");
}
Делая это, я избегал сохранять состояния всех фрагментов и сохранял другие вещи, которые должны быть сохранены.