Atlas框架源码简要分析(中)--Atlas中bundle的安装和初始化

Atlas框架源码简要分析(中)–Atlas中bundle的安装和初始化

在上一篇中大致的看了下Atlas整体框架的初始化及启动,下面我们以启动一个没有安装的子Bundle中的Activity为切入点,来跟踪一个Bundle是如何加载并启动在这个Bundle中的Activity的

1、写在之前:就一个组件化框架方案都需要解决的问题有哪些,以及对应的方案有哪些,这里大致提一下
1.1、首先组件化肯定是把一个完整的工程拆分为不同的子模块,各子模块之间相互隔离不耦合,同时能够有能力从一个模块中的组件拉起另外一个模块中组件的能力,从而实现按需加载相应的子模块,对于Android工程来说,使用DexClassLoader只要指定要加载的Class的Dex所在路径和该Class的全类名即可,那么对于各bundle中的类来说,我们需要知道它的具体路径以及要加载的全类名。

解决这个问题的方法简单的说就是建立一个Class全类名和其所在的路径关系表,我们在上一篇文章从反编译出的apk文件可以看出,Atlas下的APK在编译过程中会在FrameworkProperties中插入BundleInfo信息,而该BundleInfo信息中会分开记录所有bundle中对应的组件的全名及对应的包名。而同时各bundle在编译后,会把Bundle打包压缩,同时以对应包名的’.‘替换为’_‘之后前面加上’lib‘并会以so的为缀名放入lib目录下。这样在安装时就可以凭借要启动的组件名称,在FrameworkProperties的BundleInfo中找到对应的包名,进而找到其压缩文件的位置,之后会解压该so文件,并且会记录下解压到的路径,这样关于该Activity对应的Dex的位置已经资源位置就能和其全类名对应起来了。

1.2、对于Android工程中的四大组件来说还牵涉到其生命周期的管理,最重要的是其管理过程中为了安全性考虑而加入的安全校验机制,简单的说就是,只有在主manifest中声明的类才能在AMS中注册成功,进而开始对其生命周期的管理,然后开始实例化。所以除了我们需要知道其Class路径及全名外,同时我们还要能确保合法性校验能过的去。

而对于四大组件的合法性校验,最简单莫过于我们直接把需要用到的组件的信息都在主Manifest文件中进行声明,而Atlas框架也正是这样做的,它在编译过程中会合并各Bundle的manifest到主manifest中去。当然另外一种做法是只声明几个预留的占位组件在主manifest中,然后再根据开启这些占位组件的参数信息去替换为我们需要实际启动的Activity。但是这种方法相对要Hack更多的系统点,稳定性及可控性不是相对会下降。

1.3、另外对于Andoid工程来说,我们还需要能正确加载子Bundle中的对应资源文件

资源文件的寻找和加载是通过系统的Resources类和AssetManager来实现的,对于加载资源文件我们需要解决的是把我们子bundle中的资源文件加入到其寻找资源的路径中,从而保证能够找的到该资源,Atlas框架在解决的方法就是直接替换掉系统的Resource为DelegateResource,同时每安装一个Bundle,都会追加当前Bundle解压出来的路径即资源路径,至于如何追加会根据实际情况使用不同方案。

1.4、需要解决各子Bundle之间资源ID的重复导致的资源加载错误问题。

其在打包过程中各Bundle之间对应的Id值的前缀不同从而直接进行了隔离

2、Atlas框架下的app是如何启动一个单独Bundle中的Activity的以及该Bundle是如何安装和初始化的

对于Atlas框架下的APP运行,启动一个未安装的Bundle中的Activity,由于在打包中已经把其声明合并到了主manifest,所以其前面的校验及在AMS中的注册和普通工程的Activity并无两样,区别在于真正实例化该Activity时,主工程默认路径中并没有该Bundle对应的Dex,所以在真正实例化该Activity时,就需要用到我们上篇中在框架初始化之后就已经被替换了的DelegateClassLoader,至于该Activity实例化之后,就会又回到了相当于普通工程的流程中。

因此我们主要就是看的框架是如何找到该Activity对应的Bundle,然后是如何安装该Bundle,接着又是如何把它里面的该类的字节码加载起来的,当然还有对于其中的资源是如何处理的。

ps:此处关于我们启动一个Activity时AMS和一个Activity的实例的关系,以及通过什么来管理该Activity实例的,可以查阅相关的资料,这里只明确一点,就是AMS对Activity的管理不是以该Activity的实例对象的引用来管理的,而是以一个支持跨进成通信的Binder为Token来管理的,我们在启动一个Activity的时候是先建立了一个和Ams之间的特定通信通道,然后在实例化该Activity之后,会把该Activity和该通道相关连,这样AMS就能通过该通道直接找到该Activity进行通信,进而根据AMS中业务逻辑调用其相关的生命周期,赋予一个普通的Java对象一个完整的生命。

关于Bundle的安装及初始化过程的简要时序图如下(右键可以单独查看大图):
这里写图片描述

2.1 下面就以启动一个没有安装过的Bundle中的Activity来作为切入点。我们直接从ActivityThread中的performLaunchActivity()开始
2.1.1 此处拿取到的ClassLoader已经是我们在上篇中替换掉的DelegateClassLoader,即在上篇2.2.2和2.2.3处实例化并替换掉原有的ClassLoader的DelegateClassLoader
2.1.2 然后会调用Instrumentation中的newActivity()方法
2.1.3 进入Instrumentation 可以看到其直接使用传入的ClassLoader调用loadClass()加载该Activity的字节码并实例化,此处的ClassLoader就是DelegateClassLoader,下面我们进入DelegateClassLoader查看。
@ActivityThread
 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...     
    ActivityInfo aInfo = r.activityInfo;
    if (r.packageInfo == null) {
        r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                Context.CONTEXT_INCLUDE_CODE);
    }

    ComponentName component = r.intent.getComponent();
    if (component == null) {
        component = r.intent.resolveActivity(
            mInitialApplication.getPackageManager());
        r.intent.setComponent(component);
    }

    if (r.activityInfo.targetActivity != null) {
        component = new ComponentName(r.activityInfo.packageName,
                r.activityInfo.targetActivity);
    }

    Activity activity = null;
    try {
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();//-----2.1.1此处拿取到的ClassLoader已经是我们在上篇中替换掉的DelegateClassLoader
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);//------2.1.2最终会使用DelegateClassLoader来加载并实例化该Activity
        StrictMode.incrementExpectedActivityCount(activity.getClass());
        r.intent.setExtrasClassLoader(cl);
        r.intent.prepareToEnterProcess();
        if (r.state != null) {
            r.state.setClassLoader(cl);
        }
    } catch (Exception e) {
        if (!mInstrumentation.onException(activity, e)) {
            throw new RuntimeException(
                "Unable to instantiate activity " + component
                + ": " + e.toString(), e);
        }
    }

    ...//省略代码

    return activity;
}

@Instrumentation.java
 public Activity newActivity(ClassLoader cl, String className,
        Intent intent)
        throws InstantiationException, IllegalAccessException,
        ClassNotFoundException {
    return (Activity)cl.loadClass(className).newInstance();//-----2.1.3 实例化该Activity
}
2.2 DelegateClassLoader的loadClass()会先查找当前ClassLoader已经加载的类中是否有符合的,然后使用父类加载器去加载,如果依然没有就会调用到findClass(),很显然因为我们当前的场景是一个单独Bundle中的Activity,那么肯定会走到findClass(),逻辑及代码如下:

此处的findClass()是实现整个框架Bundle中的Class文件加载的关键点,因为在前面已经用该DelegateClassLoader替换了系统的ClassLoader,所以所有的Class加载都会走到此处,该DelegateClassLoader只是起到一个路由作用,在使用该ClassLoader去寻找Class时,实际就是一个检查对应Bundle是否已经安装,如果没有安装就会安装该Bundle,同时给该Bundle实例化一个单独的BundleClassLoader,并记录这些信息到BundleImpl中的过程。如果已经安装则会直接拿到其对应的ClassLoader进行加载

2.2.1 检查当前Class所对应的Bundle是否已经安装并准备完毕,该过程中如果该Activity对应的Bundle没有安装,则会安装并初始化该Bundle,具体我们在2.3中展开
2.2.2 经过2.2.1之后如果该Activity对应的Bundle存在则肯定是已经安装且准备好了的,此时直接调用loadFromInstalledBundles()加载该Activity的字节码。
2.2.3 据类名查找bundle名,这个主要就是依据apk编译过程中,在FrameworkProperties类中插入的BundleInfo相关信息,确定的
2.2.4 根据bundle名找到相应的BundleImpl,该BundleImpl是在Bundle安装过程中生成并完善的,主要保存了对应Bundle的详细身份信息及为其单独分配的BundleClassLoader
2.2.5 如果置该bundle的状态未启动状态,

注意:此处最终会触发拿取该Bundle对用的Application进行实例化并依次调用其attach()和onCreat()方法,保证了我们Bundle的开发也可以完全安装正常的组件开发一样

2.2.6 拿取其对应的BundleClassLoader,然后加载我们需要的Activity
2.2.7 使用该ClassLoader加载当前类
@DelegateClassLoader.java
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {//---此处是实现整个组件Class文件加载的路由关键点,因为在前面已经把该ClassLoader替换了系统的ClassLoader,所以所有的Class加载都会走到此处,该DelegateClassLoader只是起到一个路由作用,在使用该ClassLoader去寻找Class时,实际就是一个分发到各具体bundle在初始化时实例化的classLoader。当然首先会查找该Class对应的BundleImpl是否已经准备好,如果没有会先执行安装过程,如果找不到会安装安装对应的bundle,在安装bundle的时候会生生成该bundle自己的ClassLoader,如果已经安装则会直接拿到其对应的ClassLoader进行加载
    Class<?> clazz = null;
    if (Thread.currentThread().getId() != Looper.getMainLooper().getThread().getId()) {
        BundleUtil.checkBundleStateSyncOnChildThread(className);//---┐
    } else {                                                   //----│-----2.2.1此处如果该Class没有找到,则会先解析生成BundleImpl,同时安装该BundleImpl,此处两个方法实现实际并没有不同
        BundleUtil.checkBundleStateSyncOnUIThread(className);//------┘
    }
    clazz = loadFromInstalledBundles(className, true);//-----2.2.2到达此处如果该Class有对应的bundle则肯定已经被安装,此时通过此方法加载该Class到内存-此处到达我们常说的方法区即静态区
    if (clazz != null)
        return clazz;

    ComponentName comp = new ComponentName(RuntimeVariables.androidApplication.getPackageName(),className);
    if (isProvider(comp)){//--TODO 此处应该对应ContentProvider对应,使用代理的假的进行返回
        return Atlas.class.getClassLoader().loadClass("android.taobao.atlas.util.FakeProvider");
    }else if(isReceiver(comp)){//--TODO 此处应该对应BrodcastReceiver对应,使用一个假的进行返回
        return Atlas.class.getClassLoader().loadClass("android.taobao.atlas.util.FakeReceiver");
    }

    throw new ClassNotFoundException("Can't find class " + className + printExceptionInfo());
}

@DelegateClassLoader.java
static Class<?> loadFromInstalledBundles(String className,boolean safe)
        throws ClassNotFoundException {//从已安装的bundle中加载
    Class<?> clazz = null;
    List<Bundle> bundles = Framework.getBundles();
    String bundleName = AtlasBundleInfoManager.instance().getBundleForComponet(className);//------2.2.3根据类名查找bundle名
    BundleImpl bundle = (BundleImpl) Atlas.getInstance().getBundle(bundleName);//------2.2.4根据bundle名找到相应的BundleImpl
    if(bundle!=null){
        if(!Framework.isDeubgMode()) {
            bundle.optDexFile();
        }
        bundle.startBundle();//-----2.2.5如果该bundle已经注册为可用则会直接return
        ClassLoader classloader = bundle.getClassLoader();//------2.2.6拿取自己拥有的一个ClassLoader
        try {
            if (classloader != null) {
                clazz = classloader.loadClass(className);//------2.2.7使用该ClassLoader加载当前类
                if (clazz != null) {
                    return clazz;
                }
            }
        } catch (ClassNotFoundException e) {
        }
      ......
        if(safe) {//TODO 对应的Provider和Receiver
            ComponentName component = new ComponentName(RuntimeVariables.androidApplication.getPackageName(), className);
            if (isProvider(component)) {
                return Atlas.class.getClassLoader().loadClass("android.taobao.atlas.util.FakeProvider");
            } else if (isReceiver(component)) {
                return Atlas.class.getClassLoader().loadClass("android.taobao.atlas.util.FakeReceiver");
            } else {
               ....
            }
        }
    }

    ......
    return clazz;
}   
2.3 对应Bundle的安装及初始化

我们从上篇中可以看到一个Bundle安装的入口点是在框架初始化完成之后,会去安装autoStartBundles对应的bundle,此处也是一个入口安装的是我们要启动的Activity对应的Bundle,过程都一样。

对于2.2.1处的BundleUtil.checkBundleStateSyncOnChildThread(className)和BundleUtil.checkBundleStateSyncOnUIThread(className)我们可以看到两个方法其实并没有什么不同,经过几步判定后,如果该Class对用的bundle存在同时并没有安装的话,会调用BundleInstaller的installTransitivelySync()开始安装,代码如下:

2.3.1 拿到该ClassName进行检查看是否有对应的Bundle,如果没有直接返回false结束查找
2.3.2 如果有对应的bundle,但是该没有找到BundleImpl则说明该bundle还没有进行加载则开始准备加载
2.3.3 此处通过工厂类拿到一个BundleInstaller

此处生成BundleInstaller时使用的

2.3.4 使用BundleInstaller开始进行安装,此处会传入bundleName,而通过该bundlename可以找到该Bundle其实际对应压缩文件,然后会解压并返回该解压路径

*其实Bundle的安装过程就是找到对应bundle的压缩文件位置,然后解压解压,同时管理保存其状态信息,并为其单独创建一个和该解压路径相关的BundleClassLoader的过程*

@BundleUtil.java
public static boolean checkBundleStateSyncOnChildThread(String className){
    String bundleName;
    if (TextUtils.isEmpty(bundleName = AtlasBundleInfoManager.instance().getBundleForComponet(className))) {
        return false;//------2.3.1 此处先拿到该ClassName进行检查看是否有对应的Bundle,如果没有直接返回
    }
    BundleImpl impl = (BundleImpl)Atlas.getInstance().getBundle(bundleName);//-----2.3.2 如果有对应的bundle,但是该没有找到BundleImpl则说明该bundle还没有进行加载则开始准备加载
    if(impl==null || !impl.checkValidate()){
        BundleInstaller installer = BundleInstallerFetcher.obtainInstaller();//------2.3.3 此处通过工厂类拿到一个BundleInstaller
        installer.installTransitivelySync(new String[]{bundleName});//------2.3.4 使用BundleInstaller开始进行加载,此处会传入bundleName,而通过该bundlename可以找到其实际在APK中的位置,然后回解压最终返回该解压路径,实际的安装过程就是找到对应bundle在APK中的位置,并解压,同时管理保存其状态信息以及为其单独创建一个和其解压路径相关的ClassLoader的过程
    }
    return true;
}

public static boolean checkBundleStateSyncOnUIThread(String className){
    String bundleName;
    if (TextUtils.isEmpty(bundleName = AtlasBundleInfoManager.instance().getBundleForComponet(className))) {
        return false;
    }
    BundleImpl impl = (BundleImpl)Atlas.getInstance().getBundle(bundleName);
    if(impl==null || !impl.checkValidate()){
        BundleInstaller installer = BundleInstallerFetcher.obtainInstaller();
        installer.installTransitivelySync(new String[]{bundleName});
    }
    return true;
}
2.4 接上面2.3.4处,我们调用到BundleInstaller.installTransitivelySync(new String[]{bundleName})方法,其逻辑及代码如下:
2.4.1 调用installBundleInternal(true),注意此处传入的是true
2.4.2 此处条件为当前线程为专用安装线程时,sBundleHandler是在静态代码段中初始化的,其对应一个专用的Bundle安装子线程,从上面调用流程可知当前为主线程,不符和该条件
2.4.3 此处条件满足:不是在专用的安装线程中&&需要同步安装&&是主线程

我们一路从DelegateClassLoader中调用过来满足的就是此处的条件会直接调用call()

2.4.4 此处条件满足:不是在专用的安装线程中&&需要同步安装&&但是不是在主线程中,此时会使用sBundleHandler发送到专用的安装线程中调用call()
2.4.5 满足不是在专门的的安装线程中&&不需要同步,最终也会使用sBundleHandler发送到专用的子线程中调用call()

在上篇中安装autoStartBundles对应的Bundle时调用的方法undleInstaller.startDelayInstall()最终会满足该处条件。

综上不管怎么处理最终都会调用到call()方法,下面在2.5中我们跟进call()方法看具体实现
@BundleInstaller.java

 static{
    HandlerThread handlerThread = new HandlerThread("bundle_installer");//初始化一个单独的bundle安装线程
    handlerThread.start();//线程开启
    sBundleHandler = new Handler(handlerThread.getLooper());//生成该线程相关的sBundleHandler
}

public void installTransitivelySync(String[] location){
    if(location!=null){
        release();
        mTransitive = true;
        mLocation = location;
        try {
            installBundleInternal(true);//-----2.4.1调用installBundleInternal()
        }catch(Throwable e){
            e.printStackTrace();
            BundleInstallerFetcher.recycle(this);
        }
    }
}

//--------installBundleInternal()-----
private void installBundleInternal(boolean sync) throws Exception{
    if(Thread.currentThread().getId() == sBundleHandler.getLooper().getThread().getId()){//----2.4.2 满足当前线程为专门的的安装线程
        Log.e("BundleInstaller", Arrays.toString(mLocation));
        call();//该方法是安装budnle的方法下面的所有逻辑最终都会执行到此处
        if(mListener!=null){
            mListener.onFinished();
        }
        BundleInstallerFetcher.recycle(this);
    }else{//不是在专门的的安装线程中
        if(sync){//
            if(Thread.currentThread().getId() == Looper.getMainLooper().getThread().getId()){//-----2.4.3 满足:不是在专用的安装线程中&&需要同步安装&&是主线程
                call();
                BundleInstallerFetcher.recycle(this);
            }else {//-----2.4.4 满足:不是在专用的安装线程中&&需要同步安装&&但是不是在主线程中
                synchronized (this) {
                    deliveryTask(sync);//发送到sBundleHandler所在线程执行(即专门的安装线程中),最终依然会调用到call(),不过此时是在专用的线程中执行
                    Log.d("BundleInstaller", "call wait:" + this);
                    this.wait(30000);
                    BundleInstallerFetcher.recycle(this);
                }
            }
        }else{//2.4.5满足不是在专门的的安装线程中&&不需要同步,最终也会发送到专用的子线程中调用call()
            deliveryTask(sync);//实际该sync参数似乎没有使用
        }
    }
}       
2.5 对于BundleInstaller的call()方法实现及逻辑如下
2.5.1 是否传递依赖mTransitive=false,对应不安装该Bundle的依赖bundle,满足该条件的一个场景是对应升级的场景(代码不在详细展开),其源头可以追溯到FrameWork.update(),从该方法一路下来对应的mTransitive=false
2.5.2 对应我们上面一路走来的逻辑,从DelegateClassLoader一路过来此时mTransitive=true,会走到此处,即会解析同时安装对应的依赖bundle

此处的bundlesForInstall通过AtlasBundleInfoManager.instance().getBundleInfo(mLocation[x]).getTotalDependency()生成,该集合中会包含当前bundle以及该bundle所依赖的bundle,但是此处所说的依赖的bundle是指在FrameworkProperties类中插入的bundleInfo中所对应的dependency字段,一般是没有设置的。

2.5.3 如果通过该bundleName拿不到对应的Bundle信息,即当前bundle还没有安装,并且不是DexPatched中的calss且不是升级后的文件,则会走到2.5.4,否则跳过continue,继续循环找下一个
2.5.4 判断剩余空间是否够用
2.5.5 确定是否是内部bundle,即是否为打包时已经以so文件形式放入到lib下的bundle

此处我们默认所有的子Bundle都是在在打包时候已经放入了APK的lib目录下,没有考虑对应远程下载该Bundle的情景

2.5.6 直接使用bundleName安装,该BundleName对应的就是该Bundle声明的包名
2.5.7 在该过程中会根据bundleName试图解析出对应的bundle压缩文件的全路径,并保存在mTmpBundleSourceFile字段

该处的代码逻辑和根据该bundle包名生成其对应压缩文件名的的逻辑完全相同

2.5.8 拿到了该bundle对应的压缩文件的路径位置生成的FIle,此时开始真正的安装该Bundle
@Override
public synchronized Void call() throws Exception {//bundle ---正式安装
    Bundle bundle = null;
    if(!mTransitive) {//-----2.5.1对应升级的场景处理
        ....
    }else{//mTransitive = true
        for(int x=0; x<mLocation.length; x++) {
            List<String> bundlesForInstall = AtlasBundleInfoManager.instance().getBundleInfo(mLocation[x]).getTotalDependency();//-----2.5.2从DelegateClassLoader一路过来此时mTransitive为true,会走到此处
            Log.e("BundleInstaller",mLocation[x]+"-->"+bundlesForInstall.toString());
            for (String bundleName : bundlesForInstall) {
                if ((bundle = getInstalledBundle(bundleName)) == null) {//----2.5.3 如果通过该bundleName拿不到对应的Bundle信息,即当前bundle还没有安装
                    if((BaselineInfoManager.instance().isDexPatched(bundleName) || BaselineInfoManager.instance().isUpdated(bundleName))){//------2.5.3判断是否是DexPatched中的calss以及是不是升级后的文件,如果是则跳过不再使用此旧bundle中的文件
                        continue;
                    }
                    if (FileUtils.getUsableSpace(Environment.getDataDirectory()) >= 5) {//------2.5.4 判断剩余空间是否够用
                        //has enough space
                        if(AtlasBundleInfoManager.instance().isInternalBundle(bundleName)) {//------2.5.5 再次确定是否是内部bundle,即打包时已经以so文件形式放入到lib下的bundle
                            bundle = installBundleFromApk(bundleName);//--###--2.5.6直接使用bundle位置安装,已经打包的bundle则都是使用该种模式##-----
                            if (bundle != null) {
                                ((BundleImpl) bundle).optDexFile();
                            }
                        }
                    } else {
                        throw new LowDiskException("no enough space");
                    }
                } else {
                    if (bundle!=null && ((BundleImpl) bundle).getArchive()!=null && !((BundleImpl) bundle).getArchive().isDexOpted()) {
                        ((BundleImpl) bundle).optDexFile();
                    }
                }
            }
        }
    }
    return null;
}

//经过此处之后转入Framework.installNewBundle(bundleName,mTmpBundleSourceFile);
private Bundle installBundleFromApk(String bundleName) throws Exception{//---从APK或者直接可以认为是从我们最终打包后,各bundle在APK的lib目录中生成的so(实际是arr)文件加载
    Bundle bundle = null;
    findBundleSource(bundleName);//------2.5.7在该过程中会试图解析出对应的bundle压缩文件的全路径,并保存在mTmpBundleSourceFile字段
    if(mTmpBundleSourceFile!=null){//正常该mTmpBundleSourceFile不为null,bundle是在lib下放的
        bundle = Framework.installNewBundle(bundleName,mTmpBundleSourceFile);//------2.5.8根据该文件名开始安装
    }else if(mTmpBundleSourceInputStream!=null){
        bundle = Framework.installNewBundle(bundleName,mTmpBundleSourceInputStream);
    }else{
        IOException e = new IOException("can not find bundle source file");
        Map<String, Object> detail = new HashMap<>();
        detail.put("installBundleFromApk",bundleName);
        AtlasMonitor.getInstance().report(AtlasMonitor.CONTAINER_BUNDLE_SOURCE_MISMATCH, detail, e);
        throw e;
    }
    return bundle;
}
2.6 根据当前bundle的压缩文件开始安装该Bundle,接上面2.5.8处逻辑Framework.installNewBundle(bundleName,mTmpBundleSourceFile),源码及逻辑如下:
2.6.1 创建解压路径,然后生成该路径
2.6.2 再次拿取该Bundle对应的BundleInfo,对应的依旧是FrameworkProperties中的bundleinfo的对应信息
2.6.3 实例化实际的Bundle管理类可以说是所有和该bundle相关的东西在安装之后都可以通过该BundleImpl来获取,也可以说它是这个bundle的一个简单的上下文

至此对于我们在code时的各Bundle的业务代码模块,转换成了一个BundleImpl对象,之后的对应该Bundle下的class文件的加载都会由该BundleImpl直接开始,里面有我们需要的BundleClassloader

@Framework.java
static BundleImpl installNewBundle(final String location, final File file) throws BundleException {//-----真真真的要开始安装了-----
    File bundleDir = null;

    try {
        BundleLock.WriteLock(location);
        installingBundles.put(location,0);
        bundleDir = createBundleStorage(location);//----2.6.1 创建解压路径,然后生成该路径
        if(!bundleDir.exists()){
            bundleDir.mkdirs();//构建该路径
        }
        AtlasFileLock.getInstance().LockExclusive(bundleDir);
        final BundleImpl cached;
        if ((cached = (BundleImpl) getBundle(location)) != null) {
            return cached;//如果有缓存直接返回
        }
        Log.e("BundleInstaller","real install " + location);
        BundleImpl bundle = null;

        BundleListing.BundleInfo info = AtlasBundleInfoManager.instance().getBundleInfo(location);//------2.6.2 再次拿取该Bundle对应的BundleInfo,对应的依旧是FrameworkProperties中的bundleinfo的对应信息
        bundle = new BundleImpl(bundleDir, location, null, file,info.getUnique_tag(),true,-1l);//------2.6.3实例化实际的Bundle管理类可以说是所有和该bundle的资源都可以通过他获取,也可以说它是这个bundle的一个简单的上下文
        return bundle;
    } catch (IOException e) {
        ....
    } catch (BundleException e) {
      ....
    } finally {
       ....
    }
}
2.7 我们下面再看一下关于BundleImpl的实例化中都具体做了哪些工作
2.7.1 该处实例化BundleArchive的功能有版本记录,同时在实例化中会解压bundle对应的so,到在2.6.1处生成的bundleDir中去,并记录该信息到BundleArchive中的loaction字段

解压最终调用的是 ApkUtils.copyInputStreamToFile(new FileInputStream(file), bundleFile);,其中file就是对应的so文件,bundleFile对应的就是解压之后的文件

2.7.2 更改当前Bundle的状态为INSTALLED
2.7.3 此处解析该bundle依赖的bundle文件并记录,同时在此时生成该bundle的专用ClassLoader,另外此处涉及到Bundle资源的处理,[我们在2.8中展开]
2.7.4 至此已经解压完毕并生成了classloader,此时把该BundleImpl放入到全局的bundle列表中,该bundle已经是整体的一部分可以随时调用
2.7.5 Bundle生命周期管理 实际并没有做什么处理
@BundleImpl.java
BundleImpl(final File bundleDir, final String location, final InputStream stream,
           final File file, String unique_tag, boolean installForCurrentVersion, long dexPatchVersion) throws BundleException, IOException{
    this.location = location;
    this.bundleDir = bundleDir;
    if(installForCurrentVersion) {
        Framework.notifyBundleListeners(BundleEvent.BEFORE_INSTALL, this);
    }
    if (stream != null) {
        this.archive = new BundleArchive(location,bundleDir, stream,unique_tag, dexPatchVersion);
    } else if (file != null) {
        this.archive = new BundleArchive(location,bundleDir, file,unique_tag, dexPatchVersion);//-----2.7.1此处解压bundle对应的so,到目标文件中去,并记录其信息
    }
    this.state = INSTALLED;//-----2.7.2 更改当前Bundle的状态为INSTALLED
    if (installForCurrentVersion) {
        resolveBundle();//-----2.7.3此处解析该bundle依赖的bundle文件并记录,同时在此时生成该bundle的专用ClassLoader,在此处由于是解析的依赖的bundle信息,后面我们在找class文件时,会先直接找到对应的bundle,在此bundle内找,然后一次是该bundle依赖的bundle,此时会出现循环依赖,在找的时候可能循环
        Framework.bundles.put(location, this);//-----2.7.4解压完并生成了classloader此时放入到全局的bundle列表中,此时该bundle已经是整体的一部分可以调用
        // notify the listeners
        Framework.notifyBundleListeners(BundleEvent.INSTALLED, this);//-----2.7.5Bundle生命周期管理
    }
}
2.8 对于对应Bundle的BundleClassLoader的生成和该Bundle资源的处理都在2.7.3中处理,代码及逻辑如下
2.8.1 此处实例化BundleClassLoader,后面用来加载该bundle的Class文件

注意此处的BundClassLoader还不是最终加载我们要加在的Class的ClassLoader,其中的findClass(),会先调用findOwnClass(className),而该方法又会调用在2.7.1处实例化的BundleArchive的findClass(),进而会使用对应的BundleArchiveRevision中的findClass(),而此处会再次新建一个DexClassLoader,而该DexClassLoader是用当前Bundle当前版本的路径初始化的一个ClassLoader,具体我们在后面整个bundle安装完毕回到DelegateClassLaoder中的2.2.2处的逻辑时再详细看一下流程

2.8.2 更改当前Bundle的状态
2.8.3 此时解析完了bundle,也生成了对应的Classloader,class文件的加载已经没有问题,此处通知框架当前Bundle为安装完毕状态,并最终会调用到BundleLifecycleHandler.laoded()方法,具体在2.9中展开

该处调用之后开始bundle资源插入 这个地方的state=0会对应BundleLifecycleHandler里面的CMD:0 最终会调用到BundleLifecycleHandler.laoded()方法,此时该bundle的位置已经确定

@BundleImpl.java
private synchronized void resolveBundle() throws BundleException {//

    if (this.archive == null) {
        throw new BundleException("Not a valid bundle: " + location);
    }

    if (this.state == RESOLVED){
        return;
    }

    if ( this.classloader == null){
        // create the bundle classloader
        List<String> dependencies = AtlasBundleInfoManager.instance().getDependencyForBundle(location);
        String nativeLibDir = getArchive().getCurrentRevision().mappingInternalDirectory().getAbsolutePath()+"/lib"+":"
                + RuntimeVariables.androidApplication.getApplicationInfo().nativeLibraryDir+":"
                +System.getProperty("java.library.path");
        if(dependencies!=null) {
            for (String str : dependencies) {
                BundleImpl impl = (BundleImpl) Atlas.getInstance().getBundle(str);
                if (impl != null) {
                    nativeLibDir += ":";
                    File dependencyLibDir = new File(impl.getArchive().getCurrentRevision().mappingInternalDirectory(), "lib");
                    nativeLibDir += dependencyLibDir;
                }
            }
        }
        this.classloader = new BundleClassLoader(this,dependencies,nativeLibDir);//------2.8.1实例化各自bundle的Classloader,后面在启动寻找一个Class文件时,会首先在DelegateCalssLoader中进行寻找,然后回按照名字直接到拿到其bundle信息,如果bundle信息没有,就会安装综合bundle,bundle安装之后,就会在拿这个bundleImpl中的classloader去加载该Class文件,并返回
    }
    state = RESOLVED;//-----2.8.2 修改当前Bundle的状态
    // notify the listeners
    Framework.notifyBundleListeners(0 /*LOADED*/, this);//------2.8.3 解析完bundle生成Classloader,之后开始bundle资源插入  这个地方的state=0会对应BundleLifecycleHandler里面的CMD:0 最终会调用到BundleLifecycleHandler.laoded()方法,此时该bundle的位置已经确定
}
2.9 bundle中资源文件的处理,接上面从2.8.3Framework.notifyBundleListeners(0 /LOADED/, this)开始我们找到其代码位置,代码及逻辑如下:
2.9.1 调用BudleListener的bundleChanged()方法

此处的BundleListener[]就是FrameWork中的syncBundleListeners字段,而该字段的值在上篇中的2.3.5处放入的,方便查看已贴出该处代码,所以最终,此处最终调用的是BundleLifecycleHandler.bundleChanged(event),其中event对应的state==0
@Framework.java
static void notifyBundleListeners(final int state, final Bundle bundle) {
if (syncBundleListeners.isEmpty() && bundleListeners.isEmpty()) {
return;
}

    final BundleEvent event = new BundleEvent(state, bundle);

    // inform the synchrounous bundle listeners first ...
    final BundleListener[] syncs = (BundleListener[]) syncBundleListeners.toArray(new BundleListener[syncBundleListeners.size()]);

    for (int i = 0; i < syncs.length; i++) {
        syncs[i].bundleChanged(event);//-----2.9.1调用BudleListener的bundleChanged()方法
    }

        //-------start------Atlas.java中的代码,为了方便看出此处的syncBundleListeners来源贴此处代码到此处--------start-------------
        //bundleLifecycleHandler = new BundleLifecycleHandler();-----<上篇>2.3.5 初始化Bundle声明周期的监听回调,并放入Framework的syncBundleListeners中去,以便在后面进行调用
        //Framework.syncBundleListeners.add(bundleLifecycleHandler);
        //-------end------Atlas.java中的代码,为了方便看出此处的syncBundleListeners来源贴此处代码到此处-------end--------------
    .....
}
2.10 接2.9.1我们看BundleLifecycleHandler中的代码及逻辑如下:
2.10.1 对应2.9.1处state==0时调用loaded()
2.10.2 调用DelegateResources的addBundleResource(),添加当前bundle的资源到系统中去
——–对于具体资源如何add到系统中去,我们在下一篇文章中在从此处再做展开——–
public class BundleLifecycleHandler implements SynchronousBundleListener {
@SuppressLint("NewApi") @Override
public void bundleChanged(final BundleEvent event){
    switch (event.getType()) {
        case 0:/* LOADED */
            loaded(event.getBundle());//------2.10.1 对应state==0时调用loaded()
            break;
       ....//省略代码
    }
}

private void loaded(Bundle bundle) {//在BundleImpl建立之后,此时bundle解压的位置,及其中的ClassLoader已经初始化完毕,则会发送CMD=0的指令到达bundleChanged,进而调用此处开始:资源加载起始点
    BundleImpl b = (BundleImpl) bundle;

    try {
          DelegateResources.addBundleResources(b.getArchive().getArchiveFile().getAbsolutePath(),
                  b.getArchive().getCurrentRevision().getDebugPatchFilePath());//-------2.10.2 DelegateResources的addBundleResource(),添加当前bundle的资源到系统中去
    } catch (Exception e) {
        e.printStackTrace();
    }
}
.....//省略代码
}

至此撇开Bundle资源插入的具体实现,我们从2.1.1处开始的Bundle的安装及初始化的步骤完成了,整理以上的流程中时序如下2.1(顺序执行)->2.2->2.2.1(开始Bundle的安装)->2.3(顺序执行)->2.4(顺序执行)->2.5(顺序执行)->2.6(顺序执行)->2.7->2.7.1->2.7.2->2.7.3->2.8->2.9->2.10(2.7.3处逻辑执行完毕)->2.7.4-2.7.5(Bundle安装完毕)->2.2.2(开始加载该Bundle中的Class)->2.2.3->2.2.4->2.2.5->2.2.6->2.2.7

2.11 从上面整理的时序可以看到2.2.7中最终使用BundleImpl中的ClassLoader加载该类,此时该类对应的Bundle已经安装完成,其在2.2.6处拿取的ClassLoader,正是在安装过程2.8.1处生成的BundleClassLoader,但是此时加载其该Bundle中class时最终依然不是使用的该BundleClassLoader,我们继续看BundlerClassLoader的代码如下:
2.11.1 我们知道2.2.7处的调用classloader.loadClass(className)是ClassLoader类的方法,最终会调用到实际子类BundleClassLoader中的findclass方法,如代码所示
2.11.2 调用findOwnClass()
2.11.3 调用archive的findClass(),此处的archive是在该BundleClassLoader实例化时传入的BundleImpl中的archive,参考2.8.1处的代码可知该archive正是在2.7.1处生成的BundleArchive
@BundleClassLoader.java
  BundleClassLoader(final BundleImpl bundle,List<String> dependencies,String nativeLibPath) throws BundleException {
    super(".",null,nativeLibPath,Object.class.getClassLoader());

    this.bundle = bundle;
    this.archive = bundle.archive;//初始化archive,在2.11.3处会使用到
    this.location = bundle.location;

    this.dependencies = dependencies;
}

//-----2.11.1 父类ClassLoader中的loadClass()会调用到此处
 protected Class<?> findClass(final String classname) throws ClassNotFoundException {

    Class<?> clazz;
    // okay, check if it is in the scope of this classloader
    clazz = findOwnClass(classname);//------2.11.2  调用findOwnClass()
    .....
    ....//省略对依赖bundle的处理一般不用
}

 private Class<?> findOwnClass(final String classname) {
    try {
        Class<?> clazz = archive.findClass(classname, this);//-----2.11.3 调用archive的findClass(),此处的archive
        return clazz;
    } catch (Exception e) {
        if (e instanceof BundleArchiveRevision.DexLoadException) {
            throw (BundleArchiveRevision.DexLoadException) e;
        }
    }
    return null;
}
2.12 BundleArchive中的findClass()最终会调用到BundleArchiveRevision的findClass(),我们直接看BundleArchiveRevision的findClass()中的逻辑及代码如下
2.12.1 如果当前的dexClassLoader为null,则实例化一个DexClassLoader
2.12.2 最终使用该ClassLoader加载对应的Class
    Class<?> findClass(String className, ClassLoader cl) throws ClassNotFoundException {
    try {
        Class<?> clazz = null;
        if (AtlasHacks.LexFile != null && AtlasHacks.LexFile.getmClass() != null) {
            if (dexClassLoader == null) {
                File libDir = new File(RuntimeVariables.androidApplication.getFilesDir().getParentFile(),"lib");
                dexClassLoader = new DexClassLoader(bundleFile.getAbsolutePath(), revisionDir.getAbsolutePath(), libDir.getAbsolutePath(), cl){//----2.12.1 如果当前的dexClassLoader为null,则实例化一个DexClassLoader
                    @Override
                    public String findLibrary(String name){
                        String path = super.findLibrary(name);
                        if(TextUtils.isEmpty(path)){
                            String fileName = System.mapLibraryName(name);
                            File soFile = findSoLibrary(fileName);
                            if(soFile!=null && soFile.exists()){
                                return soFile.getAbsolutePath();
                            }
                            try {
                                return (String) AtlasHacks.ClassLoader_findLibrary.invoke(Framework.getSystemClassLoader(), name);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            return null;
                        }else{
                            return path;
                        }
                    }
                };
            }
            clazz = (Class<?>) AtlasHacks.DexClassLoader_findClass.invoke(dexClassLoader, className);//-----2.12.2 最终使用该ClassLoader加载对应的Class
            return clazz;
        }

        if (isDexOpted() == false){
            optDexFile();
        }

        if (dexFile == null){
            optDexFile();
        }
        if(Framework.isDeubgMode()){
            clazz = findPatchClass(className,cl);
            if(clazz!=null){
                return clazz;
            }
            if(patchDexFileForDebug!=null){
                return clazz;
            }
        }
        clazz = dexFile.loadClass(className, cl);
        return clazz;
    } 
    ....
    return null;
}
2.13至此就加载到了一个一开始还没有安装的bundle中的Activity的class文件,加载完毕之后此时代码逻辑可以回到2.1.3处,2.1.3处的代码执行完毕之后,然后回到2.1.2处,则此时该Activity就被实例化出来,接着就可以按照正常的Activity的启动了
以上分析了启动一个没有安装过的Bundle中的activity时,从hook的DelegateClassLoader的laodClass()开始,然后历经该Activity对应的Bundle安装,以及Class文件的加载,然后又回到正常的Activity启动的流程中的整个过程。当然中间省去了2.10.2处涉及的资源的处理过程
在下一篇文章中在单独从2.10.2处DelegateResources.addBundleResource()切入,简单的看一下Atlas框架是如何处理bundle的资源文件以及如何保证能够正常拿到资源的
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值