Skip to content

Latest commit

 

History

History
164 lines (143 loc) · 6.94 KB

Android插件化之Fragment.md

File metadata and controls

164 lines (143 loc) · 6.94 KB

采用多个DexClassLoader去加载插件,插件中的fragment在宿主中Activity中使用,销毁重建,报错:

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)

追踪一下该问题,从FragmentActivity销毁重建过程开始。

查看FragmentActivity#onCreate():

FragmentActivity类

 protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) {
            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
            mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
        }
    }

接下来,查看FragmentController#restoreAllState()

FragmentController类:

   public void restoreAllState(Parcelable state, List<Fragment> nonConfigList) {
        mHost.mFragmentManager.restoreAllState(state,
                new FragmentManagerNonConfig(nonConfigList, null));
    }

接下来,查看FragmentManager#restoreAllState()

FragmentManager

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对象
                Fragment f = fs.instantiate(mHost, mContainer, mParent, childNonConfig);

            }
        }
     //.....
}

接下来,查看FragmentState#instantiate()

Fragment

 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
                // 通过PathClassLoader根据类名加载,关键点。
                clazz = context.getClassLoader().loadClass(fname);
                sClassMap.put(fname, clazz);
            }
            Fragment f = (Fragment)clazz.newInstance();
            if (args != null) {
                args.setClassLoader(f.getClass().getClassLoader());
                f.mArguments = 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);
        }
    }

宿主中的PathClassLoader中并没有加载插件的dex,因此会找不到对应的类。

解决方式:

  1. 采用合并式方案,单个ClassLoader
  2. 将加载插件的ClassLoader设置成应用程序的ClassLoader,进行替换。

方案二实现如下:

替换掉ContextImpl中的classloader:

  public  static class ContextImplHookManager {


        /**
         *  Hook 掉ContextImpl中ClassLoader ,替换成自己指定的。
         * @param context
         * @param substituteClassLoader
         */
        public static void hookClassLoader(Context context,ClassLoader  substituteClassLoader){
            try {
                Class<?> contextImplClass=Class.forName("android.app.ContextImpl");
                Field[] fields= contextImplClass.getDeclaredFields();
                Field mClassLoaderField=null,mPackageInfoField=null;
                for (Field field:fields){
                    if (field.getName().equals("mClassLoader")){
                        mClassLoaderField=field;
                    }else if (field.getName().equals("mPackageInfo")){
                        mPackageInfoField=field;
                    }
                }
                if (mClassLoaderField!=null){
                    try {
                        mClassLoaderField.setAccessible(true);
                        mClassLoaderField.set(context,substituteClassLoader);
                    }catch ( Exception e){
                        e.printStackTrace();
                    }
                    return;
                }
                if (mPackageInfoField!=null){
                    try {
                        mPackageInfoField.setAccessible(true);
                        Object LoadedApk =  mPackageInfoField.get(context);
                        Class<?> LoadedApkClass=LoadedApk.getClass();
                        mClassLoaderField= LoadedApkClass.getDeclaredField("mClassLoader");
                        mClassLoaderField.setAccessible(true);
                        mClassLoaderField.set(LoadedApk,substituteClassLoader);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

fragment#onAttach() : 进行hook替换

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        // 屏幕旋转,重建,可能导致 找不到当前的Fragment
        ContextImplHookManager.hookClassLoader(getActivity().getBaseContext(),MessageDialogFragment.class.getClassLoader());
    }