Programing

Nougat의 android.os.TransactionTooLargeException

crosscheck 2020. 10. 30. 07:45
반응형

Nougat의 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) 

이 ( 가) 중지 TransactionTooLargeException중일 때 발생하는 것을 볼 때마다 Activity나중에 (구성 변경 또는 프로세스 종료 후) 복원을 위해 안전하게 보관하기 위해 시스템 OS에 Activity저장된 상태 Bundles보내려고 했지만 하나 이상의 Bundles너무 큽니다. 한 번에 발생하는 모든 트랜잭션에 대한 최대 한도는 약 1MB이며 한도를 Bundle초과 하지 않더라도이 한도에 도달 할 수 있습니다 .

여기에 주요 원인은 일반적으로 너무 많은 데이터를 내부에 저장되어 onSaveInstanceState중 하나의 ActivityFragments에 의해 호스팅을 Activity. 일반적으로 이것은 a와 같이 특히 큰 것을 저장할 때 발생 Bitmap하지만 Parcelable객체 목록과 같이 많은 양의 작은 데이터를 보낼 때도 발생할 수 있습니다 . Android 팀은보기 관련 데이터가 적은 양만 onSavedInstanceState. 그러나 개발자는 동일한 데이터를 다시 가져올 필요없이 가능한 한 원활하게 구성 변경을 표시하기 위해 네트워크 데이터 페이지를 저장하는 경우가 많습니다. Google I / O 2017에서 Android 팀은 Android 앱에 선호되는 아키텍처가 네트워킹 데이터를 절약한다는 점을 분명히했습니다.

  • 구성 변경시 쉽게 재사용 할 수 있도록 메모리에 저장
  • 프로세스 종료 및 앱 세션 후 쉽게 복원 할 수 있도록 디스크에

새로운 ViewModel프레임 워크와 Room지속성 라이브러리는 개발자가이 패턴에 적합하도록 돕기위한 것입니다. 에 너무 많은 데이터를 저장하는 것이 문제인 경우 onSaveInstanceState이러한 도구를 사용하여 이와 같은 아키텍처로 업데이트하면 문제가 해결됩니다.

개인적으로 새로운 패턴으로 업데이트하기 전에 기존 앱 TransactionTooLargeException을 사용하고 그 동안 둘러보고 싶습니다 . 이를 위해 빠른 라이브러리를 작성했습니다 : https://github.com/livefront/bridge . 를 통해 모든 상태를 OS로 전송하는 대신 구성 변경시 메모리에서 상태를 복원하고 프로세스가 종료 된 후 디스크에서 상태를 복원하는 것과 동일한 일반적인 아이디어를 사용 onSaveInstanceState하지만 사용할 기존 코드에 최소한의 변경 만 필요합니다. 이 두 가지 목표에 맞는 전략은 상태를 저장하는 능력을 희생하지 않고도 예외를 피하는 데 도움이됩니다.

마지막 참고 사항 : Nougat +에서 이것을 볼 수있는 유일한 이유는 원래 바인더 트랜잭션 제한이 초과 된 경우 저장된 상태를 OS로 보내는 프로세스가 Logcat에 표시되는이 오류와 함께 자동으로 실패하기 때문입니다.

!!! 바인더 거래 실패 !!!

Nougat에서는 그 조용한 실패가 하드 크래시로 업그레이드되었습니다. 그들의 신용에 대해, 이것은 개발 팀 이 Nougat의 릴리스 노트에 문서화 한 것입니다 .

많은 플랫폼 API가 이제 Binder 트랜잭션을 통해 전송되는 대용량 페이로드를 확인하기 시작했으며 시스템은 이제 트랜잭션을 자동으로 로깅하거나 억제하는 대신 RuntimeExceptions로 TransactionTooLargeExceptions를 다시 발생시킵니다. 한 가지 일반적인 예는 Activity.onSaveInstanceState ()에 너무 많은 데이터를 저장하는 것입니다. 이로 인해 앱이 Android 7.0을 대상으로 할 때 ActivityThread.StopInfo에서 RuntimeException이 발생합니다.


결국 내 문제는 다음 활동으로 보내지는 것이 아니라 SaveInstance에 저장되는 것들에 관한 것이 었습니다. 객체 (네트워크 응답)의 크기를 제어 할 수없는 모든 저장을 제거했으며 이제 작동 중입니다.

최신 정보:

많은 양의 데이터를 보존하기 위해 Google은 인스턴스를 유지하는 Fragment로이를 수행 할 것을 제안합니다. 아이디어는 필요한 모든 필드가있는 뷰없이 빈 조각을 만드는 것입니다. 그렇지 않으면 Bundle에 저장됩니다. 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;
    }
}

그것에 대한 자세한 내용은 여기에서 읽을 수 있습니다 .


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

히트와 재판을했고 마침내 이것은 내 문제를 해결했습니다. 이것을 당신의Activity

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

Nougat 기기에서도이 문제에 직면합니다. 내 앱은 4 개의 조각이 포함 된 뷰 페이저가있는 조각을 사용합니다. 문제를 일으킨 4 개의 조각에 몇 가지 큰 구성 인수를 전달했습니다.

TooLargeToolBundle 의 도움으로 이것을 일으키는 크기를 추적했습니다 .

마지막으로 조각 초기화 중에 큰 원시 전달하는 대신 putSerializable구현하는 POJO 객체에서 사용하여 해결했습니다 . 이 크기는 절반으로 줄어들고 . 따라서에 거대한 크기 인수를 전달하지 않도록하십시오 .SerializableStringputStringBundleTransactionTooLargeExceptionFragment

Google 문제 추적기의 PS 관련 문제 : https://issuetracker.google.com/issues/37103380


비슷한 문제에 직면 해 있습니다. 문제와 시나리오는 약간 다르며 다음과 같이 수정합니다. 시나리오 및 솔루션을 확인하십시오.

시나리오 : Google Nexus 6P 기기 (7 OS)에서 고객으로부터 이상한 버그를 받았습니다. 내 애플리케이션이 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 및 백 스택을 사용하는 경우 백 스택 수를 확인하십시오.


제 경우에는 인수 중 하나가 삭제하는 것을 잊은 매우 큰 문자열이기 때문에 조각 내부에 예외가 발생했습니다 (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

Parcel # marshall을 지속적으로 사용해서는 안된다는 점이 걱정입니다. 하지만 다른 생각은 없습니다.


위의 답변 중 어느 것도 저에게 효과가 없었습니다. FragmentStatePagerAdapter를 사용하고 있었으며 saveState 메서드가 조각 중 하나가 상당히 크기 때문에 조각의 상태를 저장한다고 말한 것처럼 문제의 원인은 매우 간단했습니다. 이 TransactionTooLargeExecption으로 이어집니다.

@ IK828에 명시된대로 호출기 구현에서 saveState 메서드를 재정의하려고 시도했지만 충돌을 해결할 수 없습니다.

내 프래그먼트에는 매우 큰 텍스트를 보유하는 데 사용되는 EditText가 있었는데, 이는 내 경우 문제의 원인이되었으므로 간단히 조각의 onPause ()에서 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에 설정해야합니다. 그렇지 않으면 활동이 다시 시작될 때 비어있게됩니다.


활동에서이 메서드를 재정의하십시오.

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

그리고 reflect 도우미 클래스는

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을 문제가에서 오는 된 위치 추적하고 내가 발견 android:support:fragments의 키 Bundle에서 내 onSaveInstanceState응용 프로그램이 충돌 할 때 거의 1메가바이트에 도달하는 데 사용됩니다. 그래서 해결책은 다음과 같습니다.

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

그렇게함으로써 모든 조각의 상태를 저장하는 것을 피하고 저장해야하는 다른 것들과 함께 보관했습니다.

참고 URL : https://stackoverflow.com/questions/39098590/android-os-transactiontoolargeexception-on-nougat

반응형