技术总结(一)

上一篇分析中我们分析了Replugin框架Host端的一些核心概念,还梳理了Activity启动的流程,但是有两个重要部分没有提及或者详细讲述,那就是Plugin的加载过程,Plugin端的初始化,所以本篇会重点看看这两个方面的内容。

目录
  • Plugin加载的详细过程
  • Plugin端初始化
  • Plugin中启动Activity

Plugin加载的详细过程

要讲解这个过程,我们得从Plugin.doLoad函数开始,在上一篇分析中其实已经提到过,只是当时并没有分析它的细节。这个函数做了三件事情:

  • 释放插件文件到相应的目录,比如so库,dex文件
  • 加载dex文件,比如组件信息,组件属性,资源等
  • 要运行Plugin,还需要初始化Plugin的运行环境

释放文件这一步很简单,就是将APK包打开以后将相应的文件放到不同的目录中,有兴趣的同学可以取跟以下代码,这里就不赘述了。

来看看dex文件的加载,这是通过Loader.loadDex函数实现的。这个方法比较长,我们拆开来看。

第一步,获取PackageInfo并缓存插件相关的信息,比如组件,组件属性,资源等,下次就可以直接从缓存中读取。

if (mPackageInfo == null) {
    // 通过PackageManager获取PackageInfo
    mPackageInfo = pm.getPackageArchiveInfo(mPath,
            PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS | PackageManager.GET_META_DATA);
    ......
    // 添加针对SO库的加载
    PluginInfo pi = mPluginObj.mInfo;
    File ld = pi.getNativeLibsDir();
    mPackageInfo.applicationInfo.nativeLibraryDir = ld.getAbsolutePath();
    // 缓存表: pkgName -> pluginName
    synchronized (Plugin.PKG_NAME_2_PLUGIN_NAME) {
        Plugin.PKG_NAME_2_PLUGIN_NAME.put(mPackageInfo.packageName, mPluginName);
    }
    // 缓存表: pluginName -> fileName
    synchronized (Plugin.PLUGIN_NAME_2_FILENAME) {
        Plugin.PLUGIN_NAME_2_FILENAME.put(mPluginName, mPath);
    }
    // 缓存表: fileName -> PackageInfo
    synchronized (Plugin.FILENAME_2_PACKAGE_INFO) {
        Plugin.FILENAME_2_PACKAGE_INFO.put(mPath, new WeakReference<PackageInfo>(mPackageInfo));
    }
}

第二步,解析组件信息,注册Plugin的Manifest中声明的BroadcastReceiver。

if (mComponents == null) {
    mComponents = new ComponentList(mPackageInfo, mPath, mPluginObj.mInfo);
    // 动态注册插件中声明的 receiver
    regReceivers();
    // 缓存表Components
    synchronized (Plugin.FILENAME_2_COMPONENT_LIST) {
        Plugin.FILENAME_2_COMPONENT_LIST.put(mPath, new WeakReference<>(mComponents));
    }
    ......
}

这一步用到了ComponentList类,在它的构造函数中完成了对Plugin的Manifest文件的解析,调用的是Minifestparser类。有兴趣的同学可以跟一跟这里的代码,并不复杂。读取到Manifest文件以后,使用ManifestParser.INS.parse函数调用XmlHandler解析,把解析以后的信息缓存到ManifestParser中。

public void parse(PluginInfo pli, String manifestStr) {
    XmlHandler handler = parseManifest(manifestStr);

    Map<String, List<IntentFilter>> activityFilterMap = new HashMap<>();
    putToMap(mPluginActivityInfoMap, activityFilterMap, pli);
    parseComponent(pli.getName(), activityFilterMap, handler.getActivities(), mActivityActionPluginsMap);

    Map<String, List<IntentFilter>> serviceFilterMap = new HashMap<>();
    putToMap(mPluginServiceInfoMap, serviceFilterMap, pli);
    parseComponent(pli.getName(), serviceFilterMap, handler.getServices(), mServiceActionPluginsMap);

    Map<String, List<IntentFilter>> receiverFilterMap = new HashMap<>();
    putToMap(mPluginReceiverInfoMap, receiverFilterMap, pli);
    parseComponent(pli.getName(), receiverFilterMap, handler.getReceivers(), null);
}

第三步,获取Plugin的资源,先查找缓存,如果找不到就通过PackageManager创建Resources对象,但这里做了一个多余的赋值操作是为了修复一个BUG,不必在意。最后将资源对象缓存下来。

mPkgResources = Plugin.queryCachedResources(mPath);
if (mPkgResources == null) {
    try {
        if (BuildConfig.DEBUG) {
            // 如果是Debug模式的话,防止与Instant Run冲突,资源重新New一个
            Resources r = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
            mPkgResources = new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration());
        } else {
            mPkgResources = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
        }
    } catch (NameNotFoundException e) {
        return false;
    }
    // 缓存Resources
    synchronized (Plugin.FILENAME_2_RESOURCES) {
        Plugin.FILENAME_2_RESOURCES.put(mPath, new WeakReference<>(mPkgResources));
    }
}

第四步,注意咯,这里就是创建Plugin的PluginDexClassLoader的地方,将它缓存起来,不必每次都创建一个新的。

if (mClassLoader == null) {
    String out = mPluginObj.mInfo.getDexParentDir().getPath();
    ......
    String soDir = mPackageInfo.applicationInfo.nativeLibraryDir;
    // 创建PluginDexClassLoader对象
    mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPath, out, soDir, parent);   
    ......
    synchronized (Plugin.FILENAME_2_DEX) {
        Plugin.FILENAME_2_DEX.put(mPath, new WeakReference<>(mClassLoader)); // 缓存ClassLoader
    }
}

第五步, 为Plugin创建一个全局的PluginContext,并用上面创建的ClassLoader以及Resources作为参数。而这个PluginContext对象会被赋值给Plugin的Application对象(后面会讲到)。其实每一个Plugin的Activity都会创建一个PluginContext对象,并使用相同的ClassLoaderResources,因此在Plugin中就可以加载相关的类和使用资源了,跟原生程序一样。

mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);

到这里,Plugin的加载就算是全部完成了!

Plugin端初始化

Dex文件加载完成以后,要运行Plugin还需要初始化Plugin的运行环境相关的类,在Plugin.doLoad的最后阶段调用了loadEntryLocked函数,这个函数负责初始化Plugin的运行环境。

private boolean loadEntryLocked(PluginCommImpl manager) {
    if (mDummyPlugin) {
        ......
    } else {
        ......
        } else if (mLoader.loadEntryMethod3()) {  // 通过反射拿到Entry的create函数
            if (!mLoader.invoke2(manager)) {
                return false;
            }
        } else {
            return false;
        }
    }
    return true;
}

Loader.loadEntryMethod3通过反射将Plugin中的Entry类的create函数对象得到并保存在mCreateMethod2中。注意这里的mClassLoader是插件的PluginDexClassLoader,所以才能得到插件中的类。

final boolean loadEntryMethod3() {
    try {
        String className = Factory.REPLUGIN_LIBRARY_ENTRY_PACKAGE_PREFIX + "." + Factory.PLUGIN_ENTRY_CLASS_NAME;
        Class<?> c = mClassLoader.loadClass(className);
        mCreateMethod2 = c.getDeclaredMethod(Factory.PLUGIN_ENTRY_EXPORT_METHOD_NAME, Factory.PLUGIN_ENTRY_EXPORT_METHOD2_PARAMS);
    } catch (Throwable e) {
    }
    return mCreateMethod2 != null;
}

接着Loader.invoke2函数会用反射的方式调用上面拿到的create函数。

final boolean invoke2(PluginCommImpl x) {
    try {
        IBinder manager = null;
        final ClassLoader classLoader = getClass().getClassLoader();
        IBinder b = (IBinder) mCreateMethod2.invoke(null, mPkgContext, classLoader, manager);   // 调用create函数
        ......
        mBinderPlugin = new ProxyPlugin(b);
        mPlugin = mBinderPlugin;
    } catch (Throwable e) {
        return false;
    }
    return true;
}

注意,这里我们将会进入到Plugin端的代码中,来看看Entry.create做了什么。这里有一个参数是ClassLoader,这个ClassLoader是Host中的RepluginClassLoader,有了它,Plugin才能找到Host当中的类并调用这些类的方法。

public static final IBinder create(Context context, ClassLoader cl, IBinder manager) {
    RePluginFramework.init(cl); // 初始化一些类,这些类用于调用Host中类的方法
    RePluginEnv.init(context, cl, manager);

    return new IPlugin.Stub() {
        @Override
        public IBinder query(String name) throws RemoteException {
            return RePluginServiceManager.getInstance().getService(name);
        }
    };
}

这里RepluginFramework.init初始化了一些代理类,这些代理类可以在Plugin中调用Host中的函数。

private static boolean initLocked(ClassLoader cl) {
        ......
        try {
            RePluginInternal.ProxyRePluginInternalVar.initLocked(cl);
            RePlugin.ProxyRePluginVar.initLocked(cl);
            PluginLocalBroadcastManager.ProxyLocalBroadcastManagerVar.initLocked(cl);
            PluginProviderClient.ProxyRePluginProviderClientVar.initLocked(cl);
            PluginServiceClient.ProxyRePluginServiceClientVar.initLocked(cl);
            IPC.ProxyIPCVar.initLocked(cl);

            mHostInitialized = true;

        } catch (final Throwable e) {
        }
        return mHostInitialized;
    }

我们拿RePlugin.ProxyRePluginVar.initLocked作为例子来讲解。这里还是通过反射,在Plugin中去获取Host中Replugin的相关方法并保存在一系列的MethodInvoker对象中,比如Replugin.installReplugin.preloadReplugin.startActivity等。

static void initLocked(final ClassLoader classLoader) {
        // 初始化Replugin的相关方法
        final String rePlugin = "com.qihoo360.replugin.RePlugin";
        install = new MethodInvoker(classLoader, rePlugin, "install", new Class<?>[]{String.class});
        preload = new MethodInvoker(classLoader, rePlugin, "preload", new Class<?>[]{String.class});
        ......
        startActivity = new MethodInvoker(classLoader, rePlugin, "startActivity", new Class<?>[]{Context.class, Intent.class});
        startActivity2 = new MethodInvoker(classLoader, rePlugin, "startActivity", new Class<?>[]{Context.class, Intent.class, String.class, String.class});
       ......
    }
}

当我们在Plugin中使用Replugin.startActivity时,实际上是通过MethodInvoker.call函数去执行Host中Replugin对应的startActivity函数。所以Plugin中的Replugin类的实现就只是一个空壳代理而已,你可以看到它的实现如下:

public static boolean startActivity(Context context, Intent intent) {
        if (!RePluginFramework.mHostInitialized) {
            return false;
        }
        try {  // 用反射的方式调用Host中Replugin的startActivity函数来启动Activity
            Object obj = ProxyRePluginVar.startActivity.call(null, context, intent);
            if (obj != null) {
                return (Boolean) obj;
            }
        } catch (Exception e) {
            if (LogDebug.LOG) {
                e.printStackTrace();
            }
        }
        return false;
    }

看到这里,你就跟上一篇中 插件Activity启动流程 这一节连起来啦!

插件中启动Activity

Android中ActivityContext都有startActivity函数,为了在插件中能正常启动Activity,我们要将这些函数都屏蔽掉,转而使用Replugin提供的startActivity

为什么一定要用Replugin提供startActivity方法呢?因为我们插件的组件在Host的Manifest中是没有声明的,

只能通过坑位来启动,而启动坑位的动作只能在Host中完成。

Replugin是怎么做的呢?来看看吧。

将官方Demo中的插件APK反编译出来,会发现所有继承了Activity的类都被强制修改成继承PluginActivity,这个工作是由replugin-plugin-gradle在编译阶段完成的。

实际上在replugin-plugin-lib中的com.qihoo360.replugin.loader.a包中,你可以找到更多的Activity关的类。

首先PluginActivity重写了startActivity函数,并在其中通过反射调用Host中的Replugin.startActivity函数,这样就能做到在插件中启动Activity了。这里的反射调用指的就是调用前面讲过的RePlugin.ProxyRePluginVar类在初始化时得到的函数。

public void startActivity(Intent intent) {
    if (RePluginInternal.startActivity(this, intent)) {
        return;
    }
    super.startActivity(intent);
}

然后,PluginActivity重写了attachBaseContext函数,同样在代理类中通过反射调用Host中Factory2.createActivityContext函数创建一个PluginContext对象,并用这个对象替换掉原生的Context对象。

protected void attachBaseContext(Context newBase) {
    newBase = RePluginInternal.createActivityContext(this, newBase); //创建PluginContext对象
    super.attachBaseContext(newBase); //替换原生的Context
}

那么这个PluginContext又做了什么呢?原来PluginContext也重写了startActivity函数,并且在其中调用了Factory2.startActivity函数,接着又会调用PluginLibraryInternalProxy.startActivity。后面的事情如果读过第一篇分析文章,你应该都知道啦!

@Override
public void startActivity(Intent intent) {
    if (!Factory2.startActivity(this, intent)) { 
        if (mContextInjector != null) {
            mContextInjector.startActivityBefore(intent);
        }
        super.startActivity(intent);
        if (mContextInjector != null) {
            mContextInjector.startActivityAfter(intent);
        }
    }
}

到这里,你无论是使用getContext().startActivity还是直接在Activity中使用startActivity都会走Replugin的启动流程。但是事情并没有完,还有一个地方需要修改,那就是Application中的Context

PluginApplicationClient为插件创建了Application对象,在PluginApplicationClient.callAttachBaseContext函数中通过反射调用Applicaiton.attach函数,用一个PluginContext对象替换掉原来的Context对象。

这里的PluginContext对象就是在前面讲到的Dex加载过程中创建的,作为Plugin的全局Context。

public void callAttachBaseContext(Context c) {
    try {
        sAttachBaseContextMethod.setAccessible(true);   
        sAttachBaseContextMethod.invoke(mApplication, c);  // 将PluginContext对象传递给Application对象
    } catch (Throwable e) {
        if (BuildConfig.DEBUG) {
            e.printStackTrace();
        }
    }
}

OK,到这里我们在插件中就能正常的使用Replugin的启动流程在插件中启动Activity了!!!

注意

实际上在Plugin中调用getApplication获取到的是Host的Application对象,因为别忘了我们是利用坑位原理运行插件组件的,所以在插件中我们用到的都是Host的Application。但是万一Plugin的Application中有客户定制的任何动作要完成呢?所以在加载 Plugin之后,Host也会通过反射创建Plugin的Application对象,并反射调用它的attachcreate函数。

Plugin.load

final boolean load(int load, boolean useCache) {
   PluginInfo info = mInfo;
   boolean rc = loadLocked(load, useCache); //Plugin加载完成

   if (load == LOAD_APP && rc) {
       callApp();   // 关于Plugin的Application的操作都在这里了
   }
     ......
   return rc;
}

Plugin.callAppLocked

private void callAppLocked() {
    // 获取并调用Application的几个核心方法
    if (!mDummyPlugin) {
          ......
        mApplicationClient = PluginApplicationClient.getOrCreate(
                mInfo.getName(), mLoader.mClassLoader, mLoader.mComponents, mLoader.mPluginObj.mInfo);  // 创建Plugin的Appication对象

        if (mApplicationClient != null) { // 调用Application.attach
            mApplicationClient.callAttachBaseContext(mLoader.mPkgContext);
            mApplicationClient.callOnCreate(); //调用Application.onCreate
        }
    } else {
    }
}

总结:

通过这两篇文章,我们总结一下一个插件Activity启动的流程大致是:

  • 加载目标Activity信息

    开始查找Activity信息 —> 找到对应的Pugin信息 —> 解压APK文件 —> 加载Dex文件内容 —> 创建Application对象 —> 创建Entry对象初始化Plugin环境

  • 寻找坑位

    查找坑位 —> 将目标Activity与找到的坑位以ActivityState的形式缓存在PluginContainers中 —> 启动坑位Activity —> 系统调用RepluginClassLoader —> 调用PluginDexClassLoader加载坑位Activity类 —> 通过PluginContainers找到坑位对应的目标Activity类 —> 系统调用PluginActivityattachBaseContext函数 —> 创建PluginContext对象并替换 —> 目标Activity的正常启动流程(onCreateonStartonResume.....)

如果你已经读完了这两篇分析文章,但还没有将Replugin的里面如此多的类的关系理清楚,那么下面这张图也许可以帮到你。不过这张图目前还不完整,只针对目前已经讲到的部分,后面会逐渐补全。

replugin_1.jpg

绿色的部分是replugin-plugin-lib,蓝色和红色部分都是是replugin-host-lib包中的类,但是蓝色部分是运行在UI进程中,而红色部分是运行在Persistent进程中。

绿色部分和蓝色部分之间的调用都是通过反射来实现的,所以用类虚线箭头,同样蓝色部分和红色部分是通过Binder进程间通信机制来调用的,也用虚线箭头表示。

下一篇Replugin 全面解析 (4)我们会讲解Service以及进程相关内容!



作者:神罗天征_39a0
链接:https://www.jianshu.com/p/8465585b3507
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值