android.os.TransactionTooLargeException на Нуга


74

Я оновив Nexus 5X до Android N, і тепер, коли я встановлюю на ньому додаток (налагодження чи випуск), я отримую TransactionTooLargeException при кожному переході екрана, що містить пакет в додатках. Додаток працює на всіх інших пристроях. Стара програма, яка є на 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
Це проблема. в більшості випадків я надсилаю лише невеликий об'єкт, який можна парцелювати з кількома рядками. Я отримую цю помилку при кожному переході діяльності. На інших пристроях це працює без проблем.
Володимир Йованович

6
Про це повідомляється як про помилку, див. Code.google.com/p/android/issues/detail?id=212316
Ромен Піл,

Відповіді:


29

Всякий раз, коли ви бачите, що TransactionTooLargeExceptionвідбувається, коли an 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:

!!! ПОМИЛКА ТРАНСАКЦІЇ БІНДЕР !!!

У Нугаті цей тихий збій перетворився на сильний збій. На їх честь, це те, що команда розробників задокументувала у примітках до випуску для Нуги :

Багато API-платформ зараз почали перевіряти наявність великих корисних навантажень, що надсилаються через транзакції Binder, і система тепер відновлює TransactionTooLargeExceptions як RuntimeExceptions, замість того, щоб мовчки реєструвати або придушувати їх. Одним із поширених прикладів є зберігання забагато даних у Activity.onSaveInstanceState (), що призводить до того, що ActivityThread.StopInfo видає RuntimeException, коли ваша програма націлена на Android 7.0.


Я спробував впровадити вашу бібліотеку, але це не спрацювало. Все одно отримаєте той самий збій.
Хампел Елад

Мені було б цікаво побачити вашу реалізацію. Не соромтеся залишати деталі як випуск у бібліотеці. Я не мав жодних повідомлень про це при правильному використанні.
Брайан Єнчо

Я дотримувався вказівок на сторінці бібліотек github. Чи потрібно додавати щось інше в код?
Хампел Елад

Це залежить від того, як ви раніше економили стан. Якщо ви використовували щось на зразок Icepick, тоді ніяких додаткових кроків бути не повинно. Якщо ви цього не зробили, швидше за все, вам доведеться здійснити реорганізацію коду, щоб спочатку використовувати таку бібліотеку. Якщо ви опублікували трохи більше інформації із зразком коду як випуск у проекті Github, я міг би побачити, чи не буде щось у вашому налаштуванні.
Брайан Єнчо

2
Якщо ви ще не використовували Icepick (або щось подібне), цього недостатньо. Як згадувалося в документації: "Bridge призначений як простий обгортку для бібліотек збереження стану на основі анотацій, таких як Icepick, Android-State та Icekick". Існують способи змусити Bridge працювати, не використовуючи жодного з них, але це в кінцевому підсумку є більшою роботою, ніж просто оновлення програми для використання одного з них.
Брайан Єнчо

25

Врешті-решт, моя проблема полягала в речах, які зберігались у SaveStanstance, а не в речах, які надсилались до наступної діяльності. Я видалив усі збереження, де я не можу контролювати розмір об’єктів (мережеві відповіді), і тепер це працює.

Оновлення:

Щоб зберегти великі частини даних, Google пропонує зробити це за допомогою Fragment, який зберігає екземпляр. Ідея полягає в тому, щоб створити порожній фрагмент без подання з усіма необхідними полями, які в іншому випадку були б збережені в Bundle. Додати setRetainInstance(true);до методу onCreate фрагмента. А потім збережіть дані у фрагменті на Activity's onDestroy і завантажте їх на Create. Ось приклад діяльності:

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

Більше про це ви можете прочитати тут .


5
Правильна відповідь! Оскільки targetSdkVersion 24+, цей виняток видає замість "Програма надсилає забагато даних у стані екземпляра, тому її ігнорують". Деякі подробиці для тих, хто хоче знати більше: code.google.com/p/android/issues/detail?id=212316
Yazon2006,

2
Це не допомагає, коли програма просто фонова, і ViewState спричиняє це. Хтось знає, як з цим впоратися?
AllDayAmazing

Просто цікаво, а що, якщо у вас є статичне посилання на ці дані? Моя справа - це список об’єктів POJO.
X09

@ X09 Проблема такого підходу полягає в тому, що ви повинні очистити цю пам'ять, коли переходите до іншої діяльності, і це може бути складно (вам потрібно очистити її наDestory, але не onDestroy, коли ви обертаєте екран). В іншому випадку у всіх ваших діях будуть статичні варіації з деякими об’єктами, які вам не потрібні. Такий підхід може спрацювати, але у вас буде дуже брудна програма, тому я б не рекомендував її.
Володимир Йованович

18

TransactionTooLargeException переслідує нас вже близько 4 місяців, і ми нарешті вирішили проблему!

Те, що відбувалося, було тим, що ми використовуємо FragmentStatePagerAdapter у ViewPager. Користувач переглядає та створює 100+ фрагментів (це програма для читання).

Незважаючи на те, що ми належним чином управляємо фрагментами в killItem (), в реалізації FragmentStatePagerAdapter для Android існує помилка, де він зберігав посилання на такий список:

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

І коли FragmentStatePagerAdapter Android намагається зберегти стан, він викликає функцію

@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, і ОС не сподобається йому понад 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;
}

2
у saveState () комплект може бути нульовим, тому if (bundle! = null) bundle.putParcelableArray ("state", null);
Zbarcea Christian

@ IKK828 Велике спасибі за вашу відповідь, ви врятували мій день! Працює як шарм :)
jaumebd

18

Зробив удар і суд, і нарешті це вирішило мою проблему. Додайте це до свогоActivity

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

2
Дякую @Raj ... ти врятував мій час
varotariya vajsi

Ви нічого не економите таким чином. Видалення будь-якого "прогресу", досягнутого вашими користувачами, Activityє поганою практикою.
Н. Парк

Якщо ваш екземпляр важливий, і ви хочете зберегти їх, ви можете зберегти його перед очищенням, //Create instance variable, Bundle oldInstance; @Override protected void onSaveInstanceState(Bundle oldInstanceState) { super.onSaveInstanceState(oldInstanceState); //save your instance here, here oldInstance = oldInstanceState oldInstanceState.clear(); }
Радж Ядав,

11

Я стикаюся з цією проблемою і на своїх пристроях Нуга. У моїй програмі використовується фрагмент із пейджером перегляду, який містить 4 фрагменти. Я передав кілька великих аргументів конструкції 4 фрагментам, які спричинили проблему.

Я простежив розмір Bundleцього за допомогою TooLargeTool .

Нарешті, я вирішив це за putSerializableдопомогою об'єкта POJO, який реалізує Serializableзамість передачі великого необробленого Stringвикористання putStringпід час ініціалізації фрагментів. Це зменшено Bundleвдвічі і не кидає TransactionTooLargeException. Тому, будь ласка, переконайтесь, що ви не передаєте аргументи величезного розміру Fragment.

Проблема, пов’язана з PS, у програмі відстеження проблем Google: https://issuetracker.google.com/issues/37103380


3
Дякую Девід. Цей інструмент дійсно допоміг мені знайти першопричину проблеми. Ви рок :)
Нітін Места,

Ласкаво просимо @NitinMesta, :)
chub

8

Я стикаюся з подібною проблемою. Проблема та сценарій мало відрізняються, і я виправляю це наступним чином. Будь ласка, перевірте сценарій та рішення.

Сценарій: Я отримав дивну помилку від клієнта на пристрої Google Nexus 6P (7 ОС), оскільки моя програма вийде з ладу через 4 години роботи. Пізніше я виявив, що він кидає подібний (android.os.TransactionTooLargeException :) виняток.

Рішення: Журнал не вказував на якийсь конкретний клас у програмі, і пізніше я виявив, що це відбувається через збереження заднього стеку фрагментів. У моєму випадку 4 фрагменти неодноразово додаються до заднього стеку за допомогою автоматичної анімації руху екрана. Тож я замінюю 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();
        }
    }

Якщо стек перевищує обмеження, він автоматично переходить до початкового фрагмента. Сподіваюсь, хтось допоможе у цій відповіді, оскільки журнали винятків та стеків однакові. Отже, коли виникає ця проблема, перевірте кількість зворотного стека, якщо ви використовуєте фрагменти та зворотний стек.


Здається, це рішення обробляє винятки TransactionTooLargeException та FAILED BINDER TRANSACTION, які я маю. Хотіли б знати, що таке mCUrrentlyLoadedFragment і чому нам потрібно проаналізувати entry.getName () до нього. Дякую.
tfad334

6

У моєму випадку я отримав цей виняток усередині фрагмента, оскільки одним з його аргументів був дуже великий рядок, і я забув його видалити (я використовував лише цей великий рядок усередині методу 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  
}

Велике спасибі, працював у мене, давно шукав цю проблему, все пробував, і нарешті це спрацювало.
akashzincle

Чи не будуть аргументи зрозумілими після видалення фрагмента ??? Чому ця частина логіки не фрагментована ...
Мор Ельмаліач,

2

Проблема в моєму додатку полягала в тому, що я намагався заощадити занадто багато в saveInstanceState, рішенням було визначити, які саме дані слід зберігати в потрібний момент. В основному уважно перегляньте свій onSaveInstanceState, щоб переконатися, що ви його не розтягуєте:

@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
У мене була та ж проблема, і нарешті я її виправив, видаливши деякі дані, які я зберігав, у saveInstanceState. Будьте обережні, особливо з outState.putParcelableArrayList (... або об'єктами, що піддаються парцеляції, що містять списки, що піддаються парцеляції.
anthorlop

1

Я зіткнувся з тим же питанням. Моє обхідне рішення розвантажує saveInstanceState до файлів у папці кешу.

Я зробив наступний клас корисності.

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

Мене турбує те, що Парцел # маршалл не слід використовувати для наполегливих. Але я не маю жодної іншої ідеї.


Чи можете ви поглянути на мою оновлену відповідь. Я випадково знайшов це рішення. На перший погляд це виглядає дивно, але працює дуже приємно.
Володимир Йованович

1
Ваше рішення - виглядає непогано. Але я думаю, що це не може відновити, коли процес було вбито системою. Однак це не може бути великою проблемою, оскільки це крайній випадок.
Такао Сумітомо

1

Жоден із наведених вище відповідей не спрацював для мене, причина проблеми була досить проста, як стверджували деякі, я використовував FragmentStatePagerAdapter, а метод saveState зберігає стан фрагментів, оскільки один із моїх фрагментів був досить великим, тому збереження цього фрагмента веде до цього TransactionTooLargeExecption.

Я спробував замінити метод saveState у своїй реалізації пейджера, як зазначено @ IK828, але це не могло вирішити аварію.

У моєму фрагменті був EditText, який містив дуже великий текст, що в моєму випадку було винуватцем проблеми, тому просто в onPause () фрагмента я встановив текст edittext на порожній рядок. тобто:

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

Тепер, коли FragmentStatePagerAdapter спробує зберегти State, цього великого шматка тексту не буде там, щоб спожити більшу його частину, отже, вирішує збій.

У вашому випадку вам потрібно знайти все, що є винуватцем, це може бути ImageView з деяким растровим зображенням, TextView з величезним шматком тексту або будь-який інший вид, який вимагає великої пам'яті, вам потрібно звільнити його пам'ять, ви можете встановити imageview.setImageResource ( null) або подібний у onPause () вашого фрагмента.

оновлення: onSaveInstanceState - найкраще місце для цієї мети перед викликом супер-типу:

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

або як вказав @Vladimir, ви можете використовувати android: saveEnabled = "false" або view.setSaveEnabled (false); у поданні або користувацькому поданні та переконайтеся, що текст знову встановлено у onResume, інакше він буде порожнім, коли активність відновиться.


3
Кращим рішенням буде використання android:saveEnabled="false"на певному поданні. Детальніше про це ви можете прочитати тут .
Володимир Йованович

0

Просто перевизначте цей спосіб у своїй діяльності:

@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 .


2
це може допомогти, якщо ви зможете додати трохи більше інформації, яку користувачі зможуть побачити, перш ніж натиснути на посилання
Амануель Нега

3
Ця відповідь сама по собі не корисна
grantespo

actiontoolargeexception виникає при збереженні великих даних у SaveInstanceState. За допомогою цього коду він запобіжить збереженню даних.
Ameer

Це не є гарним рішенням, тому що вам, можливо, доведеться зберігати щось із вашої діяльності
Леннон Спірределлі,

0

Оскільки 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;
    }
}
}

0

У моєму випадку я використовував TooLargeTool, щоб відстежувати, звідки надходить проблема, і я дізнався, що android:support:fragmentsключ у Bundleмоєму onSaveInstanceStateколись досягав майже 1 Мб, коли програма аварійно завершила роботу. Тож рішення було таким:

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

Роблячи це, я уникав збереження станів усіх фрагментів і зберігав з іншими речами, які потрібно зберегти.


Куди ви додали вищезазначений фрагмент коду? У батьківській діяльності або самому фрагменті. Крім того, чи працює вищевказане рішення для вас, щоб усунути занадто великий виняток угоди?
Jatin Jha

@JatinJha Я додав цей код в діяльність, і це заважало мати TransactionTooLargeException. Якщо у вас це не вийшло, краще перевіряйте, що робите, використовуючи github.com/guardian/toolargetool
Lennon Spirlandelli
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.