環境
- Replugin插件框架
問題原因
問題原因的分析這段文字沒有經過潤色,純粹是初稿,不一定易於閱讀,這段看不下去的同學可以直接看文章最後的解決方案。
ReportFragment
是android.arch.lifecycle
組件中的類,lifecycle
組件中的ProcessLifecycleOwnerInitializer
繼承自ContentProvider
,利用ContentProvider#onCreate()
方法初始化。
在初始化時,通過Application#registerActivityLifecycleCallbacks()
註冊Activity
的生命週期監聽,在onActivityCreated()
回調中爲每一個Activity
注入一個ReportFragment
public static void injectIfNeededIn(Activity activity) {
// ProcessLifecycleOwner should always correctly work and some activities may not extend
// FragmentActivity from support lib, so we use framework fragments for activities
android.app.FragmentManager manager = activity.getFragmentManager();
if (manager.findFragmentByTag(REPORT_FRAGMENT_TAG) == null) {
manager.beginTransaction().add(new ReportFragment(), REPORT_FRAGMENT_TAG).commit();
// Hopefully, we are the first to make a transaction.
manager.executePendingTransactions();
}
}
注入ReportFragment
之後,ProcessLifecycleOwner
中也會監聽Activity
的生命週期,並且在onActivityCreated()
回調中通過ReportFragment.get()
方法獲取ReportFragment
。
static ReportFragment get(Activity activity) {
return (ReportFragment) activity.getFragmentManager().findFragmentByTag(
REPORT_FRAGMENT_TAG);
}
出錯的代碼就是get()
方法中的轉型。
ReportFragment
爲什麼不能被轉型成ReportFragment
呢?因爲它們的ClassLoader
不同,但是從ReportFragment
的注入方法:injectIfNeededIn()
和使用方法:get()
來看,這兩處代碼都是ProcessLifecycleOwnerInitializer
在初始化時註冊的Activity
的生命週期監聽中回調的。又因爲在類A中通過new
關鍵字創建類B,類B的ClassLoader就是類A的ClassLoader,那麼這兩處代碼中的ReportFragment
的Classloader應該是同一個,爲什麼會出現ClassLoader不一致的情況呢?
這就要從FragmentActivity
的狀態保存說起,來看代碼,FragmentActivity#onSaveInstanceState()
中,this.mFragments.saveAllState
會保存Fragment
。
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
this.markFragmentsCreated();
Parcelable p = this.mFragments.saveAllState();
if (p != null) {
outState.putParcelable("android:support:fragments", p);
}
...
}
}
最終通過new FragmentState()
將Fragment
序列化。
Parcelable saveAllState() {
...
if (this.mActive != null && this.mActive.size() > 0) {
int N = this.mActive.size();
FragmentState[] active = new FragmentState[N];
boolean haveFragments = false;
for(int i = 0; i < N; ++i) {
Fragment f = (Fragment)this.mActive.valueAt(i);
if (f != null) {
if (f.mIndex < 0) {
this.throwException(new IllegalStateException("Failure saving state: active " + f + " has cleared index: " + f.mIndex));
}
haveFragments = true;
FragmentState fs = new FragmentState(f);
active[i] = fs;
...
而序列化時,只保存了Fragment
的類名,ClassLoader
信息就丟失了。
FragmentState(Fragment frag) {
this.mClassName = frag.getClass().getName();
this.mIndex = frag.mIndex;
this.mFromLayout = frag.mFromLayout;
this.mFragmentId = frag.mFragmentId;
this.mContainerId = frag.mContainerId;
this.mTag = frag.mTag;
this.mRetainInstance = frag.mRetainInstance;
this.mDetached = frag.mDetached;
this.mArguments = frag.mArguments;
this.mHidden = frag.mHidden;
}
當後臺App由於內存不足被kill後,從最近使用的App列表中返回App,系統會恢復切後臺時所處的Activity
,在Activity#onCreate()
中通過this.mFragments.restoreAllState
恢復Fragment
。
protected void onCreate(@Nullable Bundle savedInstanceState) {
this.mFragments.attachHost((Fragment)null);
super.onCreate(savedInstanceState);
FragmentActivity.NonConfigurationInstances nc = (FragmentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();
if (nc != null && nc.viewModelStore != null && this.mViewModelStore == null) {
this.mViewModelStore = nc.viewModelStore;
}
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable("android:support:fragments");
this.mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
if (savedInstanceState.containsKey("android:support:next_request_index")) {
...
之前序列化了Fragment
信息的FragmentState
類,通過FragmentState#instantiate()
方法,將Context
, ClassName
傳遞給Fragment#instantiate()
方法,最終通過Context
的ClassLoader
加載Fragment
的Class
。
public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
try {
Class<?> clazz = sClassMap.get(fname);
if (clazz == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = context.getClassLoader().loadClass(fname);
if (!Fragment.class.isAssignableFrom(clazz)) {
throw new InstantiationException("Trying to instantiate a class " + fname
+ " that is not a Fragment", new ClassCastException());
}
sClassMap.put(fname, clazz);
}
...
這個Context
是Activity#mFragments
中的HostCallbacks
通過onGetHost
獲取的,就是Activity
自身。
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
class HostCallbacks extends FragmentHostCallback<Activity> {
@Override
public Activity onGetHost() {
return Activity.this;
}
}
下面瞭解一下Replugin的ClassLoader機制,Replugin中有宿主和插件之分,通過RePluginClassLoader
加載宿主的類,通過PluginDexClassLoader
加載插件的類,插件中的Activity
就是PluginDexClassLoader
加載的。如上所述,在內存不足的情況下,Activity
中的ReportFragment
是在狀態恢復時,由PluginDexClassLoader
加載的。
再回到出問題的代碼,此時findfragmentByTag
得到的ReportFragment
就是由PluginDexClassLoader
加載的,而轉型後的ReportFragment
是由系統的ClassLoader加載的,ClassLoader不同,自然就不能轉型。
static ReportFragment get(Activity activity) {
return (ReportFragment) activity.getFragmentManager().findFragmentByTag(
REPORT_FRAGMENT_TAG);
}
解決方案
知道了問題的來龍去脈,就好找解決方案了,只要保證LifeCycle使用的ReportFragment都是由LifeCycle組件加載,就不會有轉型問題,但是Activity的狀態保存步驟又不能去除,綜上得到的方案就是:在Activity創建了ReportFragment之後,LifeCycle插入ReportFragment之前,移除Activity創建的ReportFragment,這樣LifeCycle就會自行創建ReportFragment,就能保證LifeCycle使用的ReportFragment都是自己加載的。
方案一
在所有Activity的公共父類的onCreate
中執行removeReportTag
方法移除ReportFragment。
private static void removeReportTag(Activity activity) {
try {
XLLog.d(TAG, "removeReportTag");
FragmentManager manager = activity.getFragmentManager();
if (manager != null) {
Fragment fragment = manager.findFragmentByTag(REPORT_FRAGMENT_TAG);
XLLog.d(TAG, "removeReportTag--fragment=" + fragment);
if (fragment != null) {
XLLog.d(TAG, "removeReportTag--fragment.classLoader=" + fragment.getClass().getClassLoader());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
manager.beginTransaction().remove(fragment).commitNow();
}
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
這種方法的缺點是,第三方的Activity不會繼承我們的公共父類,無法執行這段代碼,那就看方案二。
方案二
在Application#onCreate
中執行HookActivityLifecycle#hook
方法,通過反射在Activity生命週期的監聽器list的第一位中插入一個監聽器,在監聽器中的onActivityCreated
回調中移除ReportFragment。這個方案的缺點是,Activity
中的FragmentManager
中的commitNow
方法在api 24及以上版本才實現,如果不用commitNow
方法,在LifeCycle判斷是否插入ReportFragment時,ReportFragment還沒來得及被移除。
public class HookActivityLifeCycle {
private static final String TAG = "HookActivityLifeCycle";
private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
+ ".LifecycleDispatcher.report_fragment_tag";
public static void hook(Application application) {
try {
Field[] fields = Application.class.getDeclaredFields();
for (Field field : fields) {
XLLog.d(TAG, "field=" + field.getName());
}
Field mActivityLifecycleCallbacksField = Application.class.getDeclaredField("mActivityLifecycleCallbacks");
mActivityLifecycleCallbacksField.setAccessible(true);
List list = (List) mActivityLifecycleCallbacksField.get(application);
if (list != null) {
list.add(0, new hook());
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
static class hook implements Application.ActivityLifecycleCallbacks {
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
removeReportTag(activity);
}
@Override
public void onActivityStarted(@NonNull Activity activity) {
}
@Override
public void onActivityResumed(@NonNull Activity activity) {
}
@Override
public void onActivityPaused(@NonNull Activity activity) {
}
@Override
public void onActivityStopped(@NonNull Activity activity) {
}
@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
}
@Override
public void onActivityDestroyed(@NonNull Activity activity) {
}
}
private static void removeReportTag(Activity activity) {
if (activity instanceof FragmentActivity) {
removeReportTagV4((FragmentActivity) activity);
} else {
try {
XLLog.d(TAG, "removeReportTag");
FragmentManager manager = activity.getFragmentManager();
if (manager != null) {
Fragment fragment = manager.findFragmentByTag(REPORT_FRAGMENT_TAG);
XLLog.d(TAG, "removeReportTag--fragment=" + fragment);
if (fragment != null) {
XLLog.d(TAG, "removeReportTag--fragment.classLoader=" + fragment.getClass().getClassLoader());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
manager.beginTransaction().remove(fragment).commitNow();
}
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
/**
* FragmentActivity中的FragmentManager的commitNow方法沒有版本限制。
*/
private static void removeReportTagV4(FragmentActivity activity) {
try {
XLLog.d(TAG, "removeReportTagV4");
android.support.v4.app.FragmentManager manager = activity.getSupportFragmentManager();
if (manager != null) {
android.support.v4.app.Fragment fragment = manager.findFragmentByTag(REPORT_FRAGMENT_TAG);
XLLog.d(TAG, "removeReportTagV4--fragment=" + fragment);
if (fragment != null) {
XLLog.d(TAG, "removeReportTagV4--fragment.classLoader=" + fragment.getClass().getClassLoader());
manager.beginTransaction().remove(fragment).commitNow();
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
測試驗證
這個問題的復現場景就是內存不足,那麼如果模擬內存不足的情況呢,參考模擬內存不足