web-dev-qa-db-ja.com

ヌガーでandroid.os.TransactionTooLargeException

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) 
69

最後に、私の問題はSaveInstanceに保存されるものにあり、次のアクティビティに送信されるものにはありませんでした。オブジェクト(ネットワークレスポンス)のサイズを制御できないすべての保存を削除し、現在は機能しています。

更新:

大きなデータの塊を保存するために、Googleはインスタンスを保持するフラグメントでそれを行うことを提案しています。アイデアは、すべての必要なフィールドを持つビューなしで空のフラグメントを作成することです。そうでなければ、バンドルに保存されます。 setRetainInstance(true);をFragmentのonCreateメソッドに追加します。そして、アクティビティのonDestroyのFragmentにデータを保存し、onCreateにロードします。アクティビティの例を次に示します。

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

詳細については、 こちら をご覧ください。

25

TransactionTooLargeExceptionが停止処理中のときにActivityが発生する場合は、Activityが保存された状態BundlesをシステムOSに送信しようとしていました。または送信したBundlesが大きすぎます。一度に発生するこのようなすべてのトランザクションには約1MBの最大制限があり、その制限を超える単一のBundleがなくても、その制限に到達できます。

ここでの主な原因は、一般に、onSaveInstanceStateまたはActivityによってホストされているFragmentsのいずれかのActivity内に大量のデータを保存していることです。通常、これは、Bitmapのような特に大きなものを保存するときに発生しますが、Parcelableオブジェクトのリストのような、小さなデータを大量に送信するときにも発生する可能性があります。 Androidチームは、onSavedInstanceStateに保存する必要があるビュー関連データはごく少量であることを何度も明確にしています。ただし、開発者はネットワークデータのページを保存して、同じデータを再度取得する必要がないように、構成の変更をできるだけスムーズに表示できるようにすることがよくあります。 Google I/O 2017の時点で、Androidチームは、Androidアプリの優先アーキテクチャがネットワークデータを保存することを明確にしました

  • メモリー内にあるため、構成の変更全体で簡単に再利用できます
  • プロセスの停止およびアプリセッション後に簡単に復元できるようにディスクに

新しいViewModelフレームワークとRoom永続ライブラリは、開発者がこのパターンに適合するのを支援することを目的としています。 onSaveInstanceStateに大量のデータを保存することに問題がある場合、これらのツールを使用してこのようなアーキテクチャに更新すると、問題が解決するはずです。

個人的に、その新しいパターンに更新する前に、既存のアプリを使用して、その間にTransactionTooLargeExceptionを回避したいと思います。それを行うためのクイックライブラリを作成しました: https://github.com/livefront/bridgeonSaveInstanceStateを介してすべての状態をOSに送信するのではなく、構成変更後のメモリおよびプロセス終了後のディスクから状態を復元するという同じ一般的な考え方を使用しますが、使用する既存のコードへの最小限の変更が必要です。ただし、これらの2つの目標に適合する戦略は、状態を保存する能力を犠牲にすることなく、例外を回避するのに役立ちます。

ここで最後の注意事項:Nougat +でこれが表示される唯一の理由は、もともとバインダートランザクションの制限を超えた場合、保存された状態をOSに送信するプロセスがLogcatにこのエラーのみを表示して黙って失敗することです:

!!!バインダー取引に失敗しました!!!

Nougatでは、そのサイレントな障害がハードクラッシュにアップグレードされました。彼らの功績として、これは開発チームが Nougatのリリースノート で文書化したものです。

多くのプラットフォームAPIがバインダートランザクションを介して送信される大きなペイロードのチェックを開始し、システムはそれらをサイレントにログに記録したり抑制したりする代わりに、TransactionTooLargeExceptionsをRuntimeExceptionsとして再スローするようになりました。 1つの一般的な例は、Activity.onSaveInstanceState()に大量のデータを保存することです。これにより、アプリがAndroid 7.0を対象とする場合、ActivityThread.StopInfoがRuntimeExceptionをスローします。

25
Brian Yencho

TransactionTooLargeExceptionによって約4か月間悩まされてきましたが、ついに問題を解決しました!

何が起こっていたかは、ViewPagerでFragmentStatePagerAdapterを使用していることでした。ユーザーはページをめくり、100以上のフラグメントを作成します(リーディングアプリケーション)。

DestroyItem()でフラグメントを適切に管理しますが、Androidの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;
}
18
IK828

ヒットとトライアルを行い、最終的にこれで問題が解決しました。これをActivityに追加します

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

Nougatデバイスでもこの問題に直面しています。私のアプリは、4つのフラグメントを含むビューページャーを持つフラグメントを使用しています。問題の原因となった4つのフラグメントにいくつかの大きな構築引数を渡しました。

TooLargeTool を使用して、Bundleのサイズをトレースし、これを引き起こしています。

最後に、フラグメントの初期化中にputSerializableを使用して大きな生のSerializableを渡す代わりに、Stringを実装するPOJOオブジェクトでputStringを使用して解決しました。これにより、Bundleのサイズが半分に縮小され、TransactionTooLargeExceptionがスローされなくなりました。したがって、Fragmentに巨大なサイズの引数を渡さないようにしてください。

追伸Google課題トラッカーの関連課題: https://issuetracker.google.com/issues/3710338

10
David Cheung

同様の問題に直面しています。問題とシナリオはほとんど変わりません。次の方法で修正します。シナリオと解決策を確認してください。

シナリオ:アプリケーションが4時間動作するとクラッシュするため、Google Nexus 6Pデバイス(7 OS)のお客様から奇妙なバグを受け取りました。後で、同様の(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();
        }
    }

スタックが制限を超えると、最初のフラグメントに自動的にポップします。例外ログとスタックトレースログは同じなので、誰かがこの答えを手伝ってくれることを願っています。したがって、この問題が発生するたびに、フラグメントとバックスタックを使用している場合は、バックスタックカウントを確認してください。

8
Nithinjith

私の場合、その引数の1つが削除するのを忘れた非常に大きな文字列であるため、フラグメント内で例外が発生しました(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  
}
5
andres.dev

私のアプリの問題は、savedInstanceStateに多くのデータを保存しようとしていたことでした。解決策は、適切なタイミングで保存すべきデータを正確に特定することでした。基本的に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);
}
2
Golan Shay

私は同じ問題に直面しました。私の回避策は、savedInstanceStateをキャッシュディレクトリ内のファイルにオフロードします。

次のユーティリティクラスを作成しました。

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を永続的に使用しないでください。しかし、私は他のアイデアを持っていません。

1
Takao Sumitomo

アクティビティでこのメソッドをオーバーライドするだけです:

@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 にアクセスしてください。

1

上記の答えは私にはうまくいきませんでした、FragmentStatePagerAdapterを使用していた人が述べたように、問題の理由は非常に簡単で、そのsaveStateメソッドはフラグメントの状態を保存しますこのTransactionTooLargeExecptionにつながります。

@ IK828で述べられているように、ページャーの実装でsaveStateメソッドをオーバーライドしようとしましたが、クラッシュを解決できませんでした。

私のフラグメントは非常に大きなテキストを保持していたEditTextを持っていましたが、これは私の場合の問題の原因でした。すなわち:

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

FragmentStatePagerAdapterがsaveStateを試行するとき、この大きなテキストチャンクはその大部分を消費するために存在しないため、クラッシュを解決します。

あなたの場合、犯人が何であるかを見つける必要があります。それはビットマップを持つImageView、テキストの巨大なチャンクを持つTextView、または他のメモリ消費量の多いビューである可能性があり、メモリを解放する必要があり、imageview.setImageResource( null)またはフラグメントのonPause()で同様。

update:onSaveInstanceStateは、次のようなsuperを呼び出す前の目的に適した場所です。

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

または@Vladimirが指摘したように、Android:saveEnabled = "false"またはview.setSaveEnabled(false);を使用できます。ビューまたはカスタムビューで、onResumeにテキストを設定します。そうしないと、アクティビティの再開時に空になります。

1
Ankush Chugh

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
liubaoyua

私の場合、 TooLargeTool を使用して問題の発生元を追跡し、アプリがクラッシュしたときにほぼ1mbに達したBundleからonSaveInstanceStateAndroid:support:fragmentsキーを見つけました。したがって、解決策は次のようなものでした:

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

そうすることで、すべてのフラグメントの状態を保存することを避け、保存する必要がある他のものと一緒に保管しました。

0
Lennon Petrick