Подтвердить что ты не робот

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) 
4b9b3361

Ответ 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");
}

Делая это, я избегал сохранять состояния всех фрагментов и сохранял другие вещи, которые должны быть сохранены.