1. 程式人生 > >Atlas框架原始碼簡要分析(中)--Atlas中bundle的安裝和初始化

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的資原始檔以及如何保證能夠正常拿到資源的