前言:
android插件化是这几年比较流行的技术,可以实现热更新,可以动态某些某块工程。
使用插件化开发项目的时候,插件项目中创建了一个DialogFramgent子类。
当屏幕旋转后,重新创建fragment,报错:
Caused by: android.app.Fragment$InstantiationException: Unable to instantiate fragment com.xingen.plugin.dialog.MessageDialogFragment:
make sure class name exists, is public, and has an empty constructor that is public
at android.app.Fragment.instantiate(Fragment.java:618)
at android.app.FragmentState.instantiate(Fragment.java:104)
at android.app.FragmentManagerImpl.restoreAllState(FragmentManager.java:1777)
at android.app.Activity.onCreate(Activity.java:953)
at com.matchvs.union.ad.demo.MainActivity.onCreate(MainActivity.java:17)
源码探究原因
先从Activity.onCreate(),开始看起。
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.fragments : null);
}
mFragments.dispatchCreate();
}
接下来,源码走向到,FragmentController.restoreAllState():
public void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
mHost.mFragmentManager.restoreAllState(state, nonConfig);
}
接下来,源码走向到,FragmentManager.restoreAllState():
void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
//.....
for (int i=0; i<fms.mActive.length; i++) {
FragmentState fs = fms.mActive[i];
if (fs != null) {
FragmentManagerNonConfig childNonConfig = null;
if (childNonConfigs != null && i < childNonConfigs.size()) {
childNonConfig = childNonConfigs.get(i);
}
Fragment f = fs.instantiate(mHost, mContainer, mParent, childNonConfig);
}
}
//.....
}
接下来,源码走向到,FragmentState.instantiate:
public Fragment instantiate(FragmentHostCallback host, FragmentContainer container,
Fragment parent, FragmentManagerNonConfig childNonConfig) {
if (mInstance == null) {
//......
if (container != null) {
mInstance = container.instantiate(context, mClassName, mArguments);
} else {
mInstance = Fragment.instantiate(context, mClassName, mArguments);
}
//......
}
mInstance.mChildNonConfig = childNonConfig;
return mInstance;
}
最终锁定在,fragment.newInstance():
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);
}
Fragment f = (Fragment) clazz.getConstructor().newInstance();
if (args != null) {
args.setClassLoader(f.getClass().getClassLoader());
f.setArguments(args);
}
return f;
} catch (ClassNotFoundException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
} catch (java.lang.InstantiationException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
} catch (IllegalAccessException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
} catch (NoSuchMethodException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": could not find Fragment constructor", e);
} catch (InvocationTargetException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": calling Fragment constructor caused an exception", e);
}
}
通过查看源码可知,Activity在销毁重建时候,通过反射创建Fragment对象。因此,编写Fragment子类的时候,最好创建一个默认的构造方法。
在插件项目中,DialogFramgent子类添加默认的构造方法:
public class MessageDialogFragment extends DialogFragment {
public static final String TAG=MessageDialogFragment.class.getSimpleName();
public MessageDialogFragment (){}
}
满心欢喜的运行项目,依旧报错同样的错误。这时候,你就想到fragment.newInstance()源码中的ClassLoader加载不到,找不到插件中的MessageDialogFragment。
这时候,也许会想这是一个无解的bug,但好在,天无绝人之路,出现了Hook这种黑科技。
Android Hook大法
思路:
Hook一个插件的ClassLoader替代宿主的ClassLoader。
查找Hook点:
众所周知,Context的实现操作逻辑在ContextIml中,查看其获取ClassLoader的方法:
/**
* Common implementation of Context API, which provides the base
* context object for Activity and other application components.
*/
class ContextImpl extends Context {
final @NonNull LoadedApk mPackageInfo;
private @Nullable ClassLoader mClassLoader;
@Override
public ClassLoader getClassLoader() {
return mClassLoader != null ? mClassLoader : (mPackageInfo != null ? mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader());
}
}
从上面可知,classLoader可能来源于ContextImpl中classLoader或者LoadedApk的classLoader。
接下来,再找到一个,可以修改ContextImpl中classloadeer的hook点:
public class Instrumentation {
/**
* Perform calling of an activity's {@link Activity#onCreate}
* method. The default implementation simply calls through to that method.
*
* @param activity The activity being created.
* @param icicle The previously frozen state (or null) to pass through to onCreate().
*/
public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
activity.performCreate(icicle);
postPerformCreate(activity);
}
}
开始编写Hook代码:
1. 先创建一个HookManager,编写反射获取ActivityThread和替换Instrumentation对象:
public class HookManager {
public static void init(){
try {
//先获取当前的ActivityThread对象
Class<?> activityThreadClass=Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod= activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread=currentActivityThreadMethod.invoke(null);
//拿到原始的Instrumentation
Field mInstrumentationField=activityThreadClass.getDeclaredField("mInstrumentation");
mInstrumentationField.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
//替换成Hook中的 Instrumentation
InstrumentationFilter instrumentationFilter=new InstrumentationFilter();
mInstrumentationField.set(currentActivityThread,instrumentationFilter);
}catch (Exception e){
e.printStackTrace();
}
}
}
2. 在Instrumentation的callActivityOnCreate()中:
public class InstrumentationFilter extends Instrumentation {
private static final String TAG=InstrumentationFilter.class.getSimpleName();
@Override
public void callActivityOnCreate(Activity activity, Bundle icicle) {
try {
Class<?> mClass = Class.forName("android.app.ContextImpl");
Field[] fields = mClass.getDeclaredFields();
Field mClassLoader = null;
Field mPackageInfo = null;
for (Field field : fields) {
if (field.getName().equals("mClassLoader")) {
mClassLoader = field;
}
if (field.getName().equals("mPackageInfo")) {
mPackageInfo = field;
}
}
if (mClassLoader != null) {
mClassLoader.setAccessible(true);
//将系统的加载Activity的ClassLoader替换成,插件的ClassLoader
mClassLoader.set(activity.getBaseContext(), InstrumentationFilter.class.getClassLoader());
return;
}
if (mPackageInfo != null) {
mPackageInfo.setAccessible(true);
Class<?> loadedApk=Class.forName("android.app.LoadedApk");
mClassLoader= loadedApk.getDeclaredField("mClassLoader");
mClassLoader.setAccessible(true);
//将系统中LoadedApk的ClassLoader
mClassLoader.set(mPackageInfo.get(activity.getBaseContext()),InstrumentationFilter.class.getClassLoader());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
super.callActivityOnCreate(activity, icicle);
}
}
}
反射替换掉ContextImpl中classLoader。
项目运行,效果如下:
项目地址:https://github.com/13767004362/HookDemo
资源参考: