簡體   English   中英

android.os.TransactionTooLargeException的牛軋糖

[英]android.os.TransactionTooLargeException on Nougat

我將Nexus 5X更新為Android N,現在當我在其上安裝應用程序(調試或發行版)時,每次在具有Bundle附加功能的屏幕過渡上都得到TransactionTooLargeException。 該應用程序可在所有其他設備上使用。 舊的應用程序,是在Play商店中,有大部分是相同的代碼工作在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) 

每當你看到TransactionTooLargeException發生時的Activity是停止的過程,這意味着該Activity試圖其保存的狀態發送Bundles到系統OS保管的恢復后(一個配置改變或過程后死亡),但一個或更多發送的Bundles太大。 還有就是1MB左右發生的一次所有此類交易,甚至如果沒有單是限制可達到的最大限制Bundle超過該限制。

這里主要的罪魁禍首是一般里面保存的數據太多onSaveInstanceState任的Activity或任何Fragments由主辦Activity 通常,在保存諸如Bitmap類的特別大的東西時會發生這種情況,但是在發送大量較小的數據(例如可Parcelable對象的列表)時也會發生這種情況。 Android團隊在很多場合都非常明確地指出,只有少量與視圖相關的數據應保存在onSavedInstanceState 然而,開發商往往保存網絡數據的頁面,以進行配置更改看起來盡可能通過不必再重新獲取相同的數據平滑。 作為谷歌I / O 2017年時,Android團隊已經明確表示對Android應用的首選架構節省了網絡數據

  • 在內存中,因此可以輕松地在配置更改中重復使用
  • 到磁盤,以便在進程終止和應用程序會話后可以輕松還原它

他們的新ViewModel框架和Room持久性庫是為了幫助開發人員適應這種模式。 如果您的問題是在onSaveInstanceState保存了太多數據,那么使用那些工具更新到這樣的體系結構應該可以解決您的問題。

就個人而言,在更新為該新模式之前,我想使用我現有的應用程序,同時避開TransactionTooLargeException 我寫了一個快速庫來做到這一點: https://github.com/livefront/bridge 它使用相同的一般思想,即通過配置更改從內存中恢復狀態,以及在進程onSaveInstanceState后從磁盤中恢復狀態,而不是通過onSaveInstanceState將所有狀態發送到OS,但只需要對現有代碼進行極少的更改即可使用。 任何策略適合這兩個目標應該可以幫助您避免例外,不過,在不犧牲你的保存狀態的能力。

最后要注意的是:在Nougat +上看到此消息的唯一原因是,最初如果超過了綁定器事務限制,則將已保存狀態發送到OS的過程將靜默失敗,並且僅在Logcat中顯示此錯誤:

!!! 綁定交易失敗!!!

在牛軋糖中,這種無聲的失敗升級為硬崩潰。 為了他們的信用,這是后話了開發團隊中記錄了牛軋糖的發行說明

現在,許多平台API已開始檢查跨Binder事務發送的大型有效負載,並且系統現在將TransactionTooLargeExceptions拋出為RuntimeExceptions,而不是靜默記錄或禁止它們。 一個常見的示例是在Activity.onSaveInstanceState()中存儲了太多數據,當您的應用程序針對Android 7.0時,這會導致ActivityThread.StopInfo拋出RuntimeException。

最后,我的問題是與正在保存onSaveInstance,而不是與被發送到下一個活動的事情的東西。 我刪除了無法控制對象大小(網絡響應)的所有保存,現在可以使用了。

更新:

為了保持數據的大塊大塊的,谷歌是在暗示與片段做保留實例。 想法是創建一個空的片段,不帶所有必填字段的視圖,否則將其保存在Bundle中。 添加setRetainInstance(true); 到碎片的onCreate方法。 然后再保存活動的中的onDestroy片段數據的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;
    }
}

更多關於它,你可以閱讀這里

TransactionTooLargeException已經困擾我們大約4個月了,我們終於解決了這個問題!

發生了什么事是我們所使用的ViewPager一個FragmentStatePagerAdapter。 用戶將通過頁面並創建100多個片段(其一款閱讀應用)。

雖然我們在destroyItem()妥善管理的片段中,機器人會執行FragmentStatePagerAdapter的有一個bug,它保持在下面的列表的引用:

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。 當陣列轉儲到parcelableArray和OS不會喜歡它100+項目將發生TransactionTooLargeException。

因此,對於我們的定盤覆蓋的“國家”的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;
}

做了命中和試驗,終於解決了這個問題,我的。 將此添加到您的Activity

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

我面對這個問題,也是對我的牛軋糖設備。 我的應用程序使用與含有4個片段的視圖尋呼機的片段。 我通過一些大型建築的參數給4個片段引起的問題。

我跟蹤的大小Bundle造成這個的幫助下TooLargeTool

最后,我決定用它putSerializable一個POJO對象,它實現的Serializable ,而不是通過大量原始String使用putString片段在初始化過程中。 Bundle的大小減小一半,並且不會引發TransactionTooLargeException 因此,請確保您沒有通過龐大的體型參數Fragment

在谷歌問題跟蹤PS相關的問題: https://issuetracker.google.com/issues/37103380

我面臨類似的問題。 這個問題和情景都有點不同,我修復它以下列方式。 請檢查方案和解決方案。

場景:我在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();
        }
    }

如果堆棧超出限制,它將自動彈出到初始片段。 我希望有人會幫助這個答案,因為異常和堆棧跟蹤日志是一樣的。 因此,每當發生此問題時,如果您正在使用Fragments和Back Stack,請檢查Back Stack計數。

就我而言,我在片段中得到了該異常,因為它的參數之一是一個很大的字符串,我忘了刪除它(我只在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  
}

在我的應用程序的問題是,我是想太多保存到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);
}

我遇到了同樣的問題。 我的解決方法將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

我擔心包裹#馬歇爾不應該用於持久。 但是,我沒有任何其他的想法。

上面的答案都不對我有用,這個問題的原因很簡單,正如我使用FragmentStatePagerAdapter的某些人所說的那樣,它的saveState方法保存了片段的狀態,因為我的一個片段很大,因此保存此片段導致此TransactionTooLargeExecption。

如@ IK828所述,我嘗試在尋呼機的實現中覆蓋saveState方法,但這無法解決崩潰問題。

我的片段有一個EditText,該文本曾經用於保存非常大的文本,這是我所遇到的問題的根源,因此,在片段的onPause()中,我將edittext文本設置為空字符串。 即:

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

現在,當FragmentStatePagerAdapter將嘗試saveState和,這個大文本塊不會在那里消耗它的更大的一部分,因此解決了崩潰。

在您的情況下,您需要找到任何罪魁禍首,它可能是帶有某些位圖的ImageView,帶有大量文本的TextView或任何其他占用大量內存的視圖,您需要釋放其內存,可以設置imageview.setImageResource(空)或在onPause()的片段相似。

更新:onSaveInstanceState更好地用於此目的,然后再調用super,例如:

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

或@Vladimir指出,您可以使用android:saveEnabled =“ false”或view.setSaveEnabled(false); 在視圖或自定義視圖,並確保設置的onResume背課文,否則這將是空當活動恢復。

只重寫您的活動這種方法:

@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獲取更多信息。

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

就我而言,我使用TooLargeTool跟蹤問題的出處 ,並從onSaveInstanceState中發現Bundleandroid:support:fragments鍵,當應用程序崩潰時,該鍵曾經達到近1mb。 因此,解決辦法是這樣的:

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

通過這樣做,我避免了保存所有片段的狀態,並保留了其他需要保存的內容。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM