Android插件化(二):OpenAtlas插件安裝過程分析

Android插件化(二):OpenAtlas插件安裝過程分析

 
 
核心提示:在前一篇博客 Android插件化(一):OpenAtlas架構以及實現原理概要 中,咱們對應Android插件化存在的問題,實現原理,以及目前的實現方案進行了簡單的敘述。從這篇開始,咱們要深刻到OpenAtlas的源碼中進行插件安裝過程的分析。 插件的安裝分爲3種:宿主啓動時立

在前一篇博客 Android插件化(一):OpenAtlas架構以及實現原理概要 中,咱們對應Android插件化存在的問題,實現原理,以及目前的實現方案進行了簡單的敘述。從這篇開始,咱們要深刻到OpenAtlas的源碼中進行插件安裝過程的分析。html

插件的安裝分爲3種:宿主啓動時當即安裝,宿主啓動時延時安裝,使用時安裝,其中使用時安裝採用的是一種相似懶加載的機制。android

這3種方式只是前面的處理有所不一樣,最後安裝邏輯都是同樣的。限於篇幅,本文只分析宿主啓動時安裝,使用時安裝在下一篇分析。數據庫

因爲宿主啓動時安裝和宿主啓動時延時安裝的邏輯大致相同,因此放在一塊兒講解,它們的流程以下:json

Android插件化(二):OpenAtlas插件安裝過程分析

關鍵流程分析以下:數組

1.初始化分析

須要實現插件化,自定義的宿主Application就須要繼承AtlasApp,而在AtlasApp的attachBaseContext()中完成json文件的解析等初始化工做。在AtlasApp的onCreate()中調用OpenAtlasInitializer進行插件安裝等初始化工做,代碼以下:架構

@Override
public void onCreate() {
    super.onCreate();
    registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacksCompatImpl(this));
    this.mAtlasInitializer.startUp();
}

2.OpenAtlasInitizlizer.startup()分析

進入OpenAtlasInitializer.startup()方法中,在這個方法中有很是多的內容,先看代碼:app

public void startUp() {
        this.init = isMatchVersion();
        if (this.init) {
            killMe();
            ensureBaselineInfo();
        }
        Properties properties = new Properties();
        properties.put(PlatformConfigure.BOOT_ACTIVITY, PlatformConfigure.BOOT_ACTIVITY);
        properties.put(PlatformConfigure.COM_OPENATLAS_DEBUG_BUNDLES, "true");
        properties.put(PlatformConfigure.ATLAS_APP_DIRECTORY, this.mApp.getFilesDir().getParent());

        try {
            Field declaredField = Globals.class.getDeclaredField("sApplication");
            declaredField.setAccessible(true);
            declaredField.set(null, this.mApp);
            declaredField = Globals.class.getDeclaredField("sClassLoader");
            declaredField.setAccessible(true);
            declaredField.set(null, Atlas.getInstance().getDelegateClassLoader());
            //  this.d = new AwbDebug();
            if (this.mApp.getPackageName().equals(this.pkgName)) {
                if (verifyRumtime() || !ApkUtils.isRootSystem()) {
                    properties.put(PlatformConfigure.OPENATLAS_PUBLIC_KEY, SecurityFrameListener.PUBLIC_KEY);
                    Atlas.getInstance().addFrameworkListener(new SecurityFrameListener());
                }
                if (this.init) {
                    properties.put("osgi.init", "true");
                }
            }
            BundlesInstaller mBundlesInstaller = BundlesInstaller.getInstance();
            OptDexProcess mOptDexProcess = OptDexProcess.getInstance();
            if (this.mApp.getPackageName().equals(this.pkgName) && (this.init)) {
                mBundlesInstaller.init(this.mApp, isAppPkg);
                mOptDexProcess.init(this.mApp);
            }
            System.out.println("Atlas framework prepare starting in process " + this.pkgName + " " + (System.currentTimeMillis() - time) + " ms");
            Atlas.getInstance().setClassNotFoundInterceptorCallback(new ClassNotFoundInterceptor());
            try {
                Atlas.getInstance().startup(properties);
                installBundles(mBundlesInstaller, mOptDexProcess);
                System.out.println("Atlas framework end startUp in process " + this.pkgName + " " + (System.currentTimeMillis() - time) + " ms");
            } catch (Throwable e) {
                Log.e("AtlasInitializer", "Could not start up atlas framework !!!", e);
                throw new RuntimeException(e);
            }
        } catch (Throwable e2) {
            e2.printStackTrace();
            throw new RuntimeException("Could not set Globals !!!", e2);
        }
    }

這個方法主要作了如下事情:dom

  • 首先,調用isMatchVersion()檢查版本號是否匹配,若是版本號匹配則init爲true,此時會檢查包名,若是包名不匹配則直接殺死進程;
  • 若是版本號匹配,進行平臺屬性的設置;
  • 利用反射將Globals中的sApplication替換爲當前的Application對象,將Globals中的sClassLoader替換爲DelegateClassLoader對象;
  • 初始化BundlesInstaller,OptDexProcess對象,而後調用Atlas.getInstance().startup(properties);進行初始話工做,主要是屬性的設置和獲取;
  • 最後調用installBundles(mBundlesInstaller,mOptDexProcess);開始插件的安裝;

3.條件判斷與設置

OpenAtlassInitializer中的installBundles()方法比較簡單,就是若是InstallSolutionConfig.install_when_oncreate_auto爲true,則發佈異步任務進行插件的安裝,其中InstallSolutionConfig中的各個屬性能夠由開發者進行配置; 若是InstallSolutionConfig.install_when_oncreate_auto爲true,則會在啓動時遍歷AtlasConfig中的AUTO數組,安裝AUTO數組中的全部插件;異步

4.安裝條件檢查與真正進入安裝流程

進入BundleInstaller.process()方法中,這個方法其實很簡單:先是從zipFile(路徑相似/data/app/XX-1.apk)中獲取全部lib/armeabi/下以libcom_爲前綴,.so爲後綴的插件文件路徑。以後檢查空間是否足夠,若是足夠則進入安裝階段,不然彈出Toast提示.另外,就是在這裏區分當即安裝和延時安裝。代碼以下:ide

public synchronized void process(boolean installAuto, boolean updatePackageVersion) {
        if (!this.isinitialized) {
            Log.e("BundlesInstaller", "Bundle Installer not initialized yet, process abort!");
        } else if (!this.isInstalled || updatePackageVersion) {   //isInstalled和updatePackageVersion通常都爲false
            ZipFile zipFile = null;
            try {   //bundleList相似{"lib/armeabi/libcom_lizhangqu_test.so","lib/armeabi/libcom_lizhangqu_zxing.so"}
                zipFile = new ZipFile(this.mApplication.getApplicationInfo().sourceDir);
                List<String> bundleList = fetchBundleFileList(zipFile, "lib/" + AtlasConfig.PRELOAD_DIR + "/libcom_", ".so");
                if (bundleList != null && bundleList.size() > 0 && getAvailableSize() < (((bundleList.size() * 2) * 4096) * 4096)) {
                    new Handler(Looper.getMainLooper()).post(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(RuntimeVariables.androidApplication, "Ops 可用空間不足!", 1).show();


                        }
                    });
                }
                if (installAuto) {  //installAuto通常爲true
                    List<String> arrayList = new ArrayList<String>();
                    for (String str : bundleList) {  //bundleList是相似{"lib/armeabi/libcom_lizhangqu_test.so","lib/armeabi/libcom_lizhangqu_zxing.so"}
                        for (String replace : AtlasConfig.AUTO) {
                            if (str.contains(replace.replace(".", "_"))) {   //將可能存在的"."替換爲"_",替換完後arrayList爲{"lib/armeabi/liccom_lizhangqu_test.so","lib/armeabi/libcom_lizhangqu_zxing.so"}
                                arrayList.add(str);
                            }
                        }
                    }  //在processAutoStartBundles()中會進行autostart類型的插件的安裝
                    processAutoStartBundles(zipFile, arrayList, this.mApplication);
                } else {
                    installDelayBundles(zipFile, bundleList, this.mApplication);
                }
                if (!updatePackageVersion) {
                    Utils.UpdatePackageVersion(this.mApplication);
                }
                if (zipFile != null) {
                    try {
                        zipFile.close();
                    } catch (IOException e2) {
                        e2.printStackTrace();
                    }
                }
            } catch (IOException e5) {
                //isInstalled = e5;

                Log.e("BundlesInstaller", "IOException while processLibsBundles >>>", e5);

                if (updatePackageVersion) {
                    this.isInstalled = true;
                }
            } catch (Throwable th2) {
                th2.printStackTrace();

                if (zipFile != null) {
                    try {
                        zipFile.close();
                    } catch (IOException e1) {
                        // TODO Auto-generated catch block
                        e1.printStackTrace();
                    }
                }

            }
            if (updatePackageVersion) {
                this.isInstalled = true;
            }
        }
    }

因爲這裏bunnyblue對於延時安裝實現的並很差,並且到了ACDD的時候沒有這個功能了,因此這裏就再也不分析延時安裝,直接進入到後面的安裝過程。

5.安裝過程

進入BundlesInstaller.processAutoStartBundles()方法中,代碼以下:

public void processAutoStartBundles(ZipFile zipFile, List<String> list, Application application) {
        for (String a : list) {
            installBundle(zipFile, a, application);
        }
        if (autoStart) {
            for (String bundle : AtlasConfig.AUTO) {
                Bundle bundle2 = Atlas.getInstance().getBundle(bundle);
                if (bundle2 != null) {
                    try {
                        bundle2.start();
                    } catch (Throwable e) {
                        Log.e("BundlesInstaller", "Could not auto start bundle: " + bundle2.getLocation(), e);
                    }
                }
            }
        }
    }

顯然是先安裝插件再啓動。而安裝插件的代碼以下:

//packageName相似"lib/armeabi/libcom_lizhangqu_test.so",zipFile相似"data/app/cn.edu.zafu.atlasdemo-1.apk"這樣的文件
    private boolean installBundle(ZipFile zipFile, String packageName, Application application) {
        System.out.println("processLibsBundle entryName " + packageName);
        //this.a.a(str);  //fileNameFromEntryName相似"libcom_lizhangqu_test.so",packageNameFromEntryName相似"com.lizhangqu.test"
        String fileNameFromEntryName = Utils.getFileNameFromEntryName(packageName);
        String packageNameFromEntryName = Utils.getPackageNameFromEntryName(packageName);
        if (packageNameFromEntryName == null || packageNameFromEntryName.length() <= 0) {
            return false;
        }   //file相似"/data/data/cn.edu.zafu.atlasdemo/lib/libcom_lizhangqu_test.so"這樣的文件
        File file = new File(new File(application.getFilesDir().getParentFile(), "lib"), fileNameFromEntryName);
        if (Atlas.getInstance().getBundle(packageNameFromEntryName) != null) {
            return false;
        }
        try {
            if (file.exists()) { //最終仍是走到了這個安裝邏輯
                Atlas.getInstance().installBundle(packageNameFromEntryName, file);
            } else {
                Atlas.getInstance().installBundle(packageNameFromEntryName, zipFile.getInputStream(zipFile.getEntry(packageName)));
            }
            System.out.println("Succeed to install bundle " + packageNameFromEntryName);
            return true;
        } catch (Throwable e) {
            Log.e("BundlesInstaller", "Could not install bundle.", e);
            return false;
        }
    }

注意zipFile是相似/data/app/cn.edu.zafu.atlasdemo-1.apk這樣的壓縮文件,而file則是相似/data/data/cn.edu.zafu.atlasdemo/lib/libcom_lizhangqu_test.so這樣的文件,實際上是因爲/data/app/下存放第三方軟件;而/data/data存放全部軟件(包括/system/app和/data/app以及/mnt/asec中的軟件)的一些lib和xml文件等數據信息. 也就是說安裝完宿主APK以後,lib會解壓到/data/data/pckageName/lib下面.可是,若是這個文件不存在(例如不當心被刪除了),那麼就須要從zipFile這個文件中讀出咱們須要的插件文件了,如根據"lib/armeabi/libcom_lizhangqu_test.so"就能夠讀取到libcom_lizhangqu_test.so這個文件。

通常file是存在的,進入Atlas.installBundle()進行分析。

6.Framework.installNewBundle()分析

Atlas.installBundle(String,File)直接調用Framework進行安裝工做,可見Atlas實際上是使用了裝飾模式,真正完成工做的是Framework.進入Framework.installNewBundle(String,File)中分析,代碼以下:

static BundleImpl installNewBundle(String location, File apkFile) throws BundleException {
        BundleImpl bundleImpl;
        File mBundleArchiveFile = null;
        try {   //注意:要從第四行打斷點才行,前面兩行都是被編譯器優化了
            BundleLock.WriteLock(location);
            bundleImpl = (BundleImpl) Framework.getBundle(location);
            if (bundleImpl != null) {
                BundleLock.WriteUnLock(location);
            } else {  //STORAGE_LOCATION相似"/data/data/cn.edu.zafu.atlasdemo/files/storage/",mBundleArchiveFile相似"/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test"
                mBundleArchiveFile = new File(STORAGE_LOCATION, location);

                OpenAtlasFileLock.getInstance().LockExclusive(mBundleArchiveFile);
                if (mBundleArchiveFile.exists()) {
                    bundleImpl = restoreFromExistedBundle(location, mBundleArchiveFile);
                    if (bundleImpl != null) {
                        BundleLock.WriteUnLock(location);
                        if (mBundleArchiveFile != null) {
                            OpenAtlasFileLock.getInstance().unLock(mBundleArchiveFile);
                        }
                    }
                }
                //這裏是有可能重複建立吧!當mBundleArchiveFile.exists()爲true時,會重複建立.apkFile相似"/data/data/cn.edu.zafu.altasdemo/lib/libcom_lizhangqu_test.so"這樣的文件
                bundleImpl = new BundleImpl(mBundleArchiveFile, location, new BundleContextImpl(), null, apkFile, true);
                storeMetadata();
                BundleLock.WriteUnLock(location);
                if (mBundleArchiveFile != null) {
                    OpenAtlasFileLock.getInstance().unLock(mBundleArchiveFile);
                }
            }
        } catch (Throwable e) {

            e.printStackTrace();
            BundleLock.WriteUnLock(location);
            throw new BundleException(e.getMessage());
        }

        return bundleImpl;
    }

顯然,這裏利用了線程鎖,首次安裝時Framework.getBundle(location);的結果爲空,因此進入到else分支,以後會先判斷mBundleArchiveFile這個插件檔案文件是否存在(路徑相似"/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test"),若是存在,就能夠由這個存檔文件直接生成BundleImpl對象.

不過這裏有一個小bug,就是調用restoreFromExistedBundle()生成BundleImpl對象以後,其實到了BundleLock.WriteLock(location);以後,能夠直接返回的,如今的邏輯是到了下面還會建立一個BundleImpl對象,顯然不對。

那麼這個mBundleArchiveFile究竟是什麼呢?其實它是一個相似"/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test"這樣的目錄,在這個目錄下面保存着與插件相關的數據。那麼到底有哪些數據呢?

咱們只要先看一下mBundleArchiveFile不存在時安裝插件的情形,就能夠發如今這個過程當中新建了哪些文件。

此時會調用BundleImpl(File,String,BundleContextImpl,InputStream,File,boolean)這個構造方法:

//archiveFile是相似指向"/data/data/cn.edu.zafu.atlasdemo/lib/libcom_lizhangqu_test.so"的File,bundleDir相似"/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test"
    BundleImpl(File bundleDir, String location, BundleContextImpl bundleContextImpl,
               InputStream archiveInputStream, File archiveFile, boolean isInstall)
            throws BundleException, IOException {
        this.persistently = false;
        this.domain = null;
        this.registeredServices = null;
        this.registeredFrameworkListeners = null;
        this.registeredBundleListeners = null;
        this.registeredServiceListeners = null;
        this.staleExportedPackages = null;
        long currentTimeMillis = System.currentTimeMillis();
        this.location = location;
        bundleContextImpl.bundle = this;
        this.context = bundleContextImpl;
        this.currentStartlevel = Framework.initStartlevel;
        this.bundleDir = bundleDir;
        if (archiveInputStream != null) {
            //  try {
            this.archive = new BundleArchive(location, bundleDir, archiveInputStream);
//            } catch (Throwable e) {
//                Framework.deleteDirectory(bundleDir);
//                throw new BundleException("Could not install bundle " + location, e);
//            }
        } else if (archiveFile != null) {
            try {
                this.archive = new BundleArchive(location, bundleDir, archiveFile);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        this.state = BundleEvent.STARTED;

        updateMetadata();
        if (isInstall) {
            Framework.bundles.put(location, this);
            resolveBundle(false);
            Framework.notifyBundleListeners(1, this);
        }

        if (Framework.DEBUG_BUNDLES && log.isInfoEnabled()) {
            log.info("Framework: Bundle " + toString() + " created. "
                    + (System.currentTimeMillis() - currentTimeMillis) + " ms");
        }
    }

這裏因爲archiveInputStream爲null,故調用this.archive=new BundleArchive(location,bundleDir,archiveFile);建立BundleArchive對象,而對應的構造方法以下:

public BundleArchive(String location, File bundleDir, File archiveFile) throws IOException {
        this.revisions = new TreeMap<Long, BundleArchiveRevision>();
        this.bundleDir = bundleDir;
        BundleArchiveRevision bundleArchiveRevision = new BundleArchiveRevision(
                location, 1, new File(bundleDir, "version." + String.valueOf(1)), archiveFile);
        this.revisions.put(Long.valueOf(1), bundleArchiveRevision);
        this.currentRevision = bundleArchiveRevision;
    }

其中的location實際上是包名,相似"com.lizhangqu.test",而bundleDir相似指向/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test這樣的目錄,archiveFile實際上是插件文件,相似/data/data/cn.edu.zafu.atlasdemo/lib/libcom_lizhangqu_test.so這樣的,可見在BundleArchive()中會建立revisions這個TreeMap對象,並將版本號以及新建的BundleArchiveRevision對象保存到revisions中。下面看一下BundleArchiveRevision對應的構造方法:

//revisionNum的值相似爲1,revisionDir的值相似爲"/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test/version.1",packageName相似"com.lizhangqu.test",archiveDir相似"/data/data/cn.edu.zafu.atlasdemo/lib/libcom_lizhangqu_test.so"的文件
    BundleArchiveRevision(String packageName, long revisionNum, File revisionDir, File archiveFile)
            throws IOException {
        boolean hasSO = false;
        this.revisionNum = revisionNum;
        this.revisionDir = revisionDir;
        BundleInfoList instance = BundleInfoList.getInstance();
        if (instance == null || !instance.getHasSO(packageName)) {

        } else {
            hasSO = true;
        }
        if (!this.revisionDir.exists()) {
            this.revisionDir.mkdirs();
        }//archiveFile通常不可寫
        if (archiveFile.canWrite()) {
            if (isSameDriver(revisionDir, archiveFile)) {
                this.revisionLocation = FILE_PROTOCOL;
                this.bundleFile = new File(revisionDir, BUNDLE_FILE_NAME);
                archiveFile.renameTo(this.bundleFile);
            } else {
                this.revisionLocation = FILE_PROTOCOL;
                this.bundleFile = new File(revisionDir, BUNDLE_FILE_NAME);
                ApkUtils.copyInputStreamToFile(new FileInputStream(archiveFile),
                        this.bundleFile);
            }
            if (hasSO) {
                installSoLib(this.bundleFile);
            }
        } else if (Build.HARDWARE.toLowerCase().contains("mt6592")
                && archiveFile.getName().endsWith(".so")) {
            this.revisionLocation = FILE_PROTOCOL;
            this.bundleFile = new File(revisionDir, BUNDLE_FILE_NAME);
            Runtime.getRuntime().exec(
                    String.format("ln -s %s %s",
                            new Object[]{archiveFile.getAbsolutePath(),
                                    this.bundleFile.getAbsolutePath()}));
            if (hasSO) {
                installSoLib(archiveFile);
            }
        } else if (OpenAtlasHacks.LexFile == null
                || OpenAtlasHacks.LexFile.getmClass() == null) {  //通常會走這個分支
            this.revisionLocation = REFERENCE_PROTOCOL
                    + archiveFile.getAbsolutePath();//revisionLocation相似"reference:/data/data/cn.edu.zafu.atlasdemo/lib/libcom_lizhangqu_test.so"
            this.bundleFile = archiveFile;   //bundleFile相似"/data/data/cn.edu.zafu.atlasdemo/lib/libcom_lizhangqu_test.so"
            if (hasSO) {
                installSoLib(archiveFile);
            }
        } else {
            this.revisionLocation = FILE_PROTOCOL;
            this.bundleFile = new File(revisionDir, BUNDLE_FILE_NAME);
            ApkUtils.copyInputStreamToFile(new FileInputStream(archiveFile),
                    this.bundleFile);
            if (hasSO) {
                installSoLib(this.bundleFile);
            }
        }
        updateMetadata();
    }

首先是記錄版本號和當前插件版本的目錄,revisionDir相似"/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test/version.1"這樣,顯然,這個其實就是bundleDir+「/version.」+revisionNum生成的,archiveFile仍然是相似/data/data/cn.edu.zafu.atlasdemo/lib/libcom_lizhangqu_test.so這樣的文件。

BundleInfoList是在AtlasApp的attachBaseContext()中解析assets中的json文件得到的插件信息,因此能夠經過包名來獲取對應的插件信息。

以後是對一些特殊ROM等作兼容,好比對於第一種狀況,會判斷是否爲同一個路徑(有的ROM可能在安裝時解壓路徑比較奇怪),若是是則直接rename便可;不然將解壓後的插件文件(如/data/data/cn.edu.zafu.atlasdemo/lib/libcom_lizhangqu_test.so)複製到bundleFile中便可,而bundleFile的路徑相似"/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test/version.1/bundle.zip";

第二種狀況則是對於mt6592這個奇葩ROM,須要創建軟連接;

大多數狀況會走到第三個分支,此時不須要複製插件文件,只是revisionLocation變爲REFERENCE_PROTOCOL+archiveFile.getAbsolutePath(),以後安裝插件中的so庫(若是有的話).

最後一種狀況則是對YunOS進行兼容(YunOS使用的是阿里本身的虛擬機,運行的文件爲.lex文件而非.dex文件)也是複製插件文件;

最後,調用updateMetadata()更新元數據:

//revisionDir是相似"/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test/version.1"這樣的路徑,metaFile是相似"/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test/version.1/meta"這樣的文件
    void updateMetadata() throws IOException {

        File metaFile = new File(this.revisionDir, "meta");
        DataOutputStream dataOutputStream = null;
        try {
            if (!metaFile.getParentFile().exists()) {
                metaFile.getParentFile().mkdirs();
            }
            dataOutputStream = new DataOutputStream(new FileOutputStream(metaFile));
            //revisionLocation的值相似"reference:/data/data/cn.edu.zafu.atlasdemo/lib/libcom_lizhangqu_test.so"這樣的值
            dataOutputStream.writeUTF(this.revisionLocation);
            dataOutputStream.flush();
            {
                try {
                    dataOutputStream.close();
                    return;
                } catch (IOException e) {
                    e.printStackTrace();
                    return;
                }
            }


        } catch (IOException e) {

            throw new IOException("Could not save meta data " + metaFile.getAbsolutePath(), e);
        } finally {

            if (dataOutputStream != null) {
                try {
                    dataOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

其中的註釋已經寫得很清楚,metaFile就是相似/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test/version.1/meta這樣的文件,updateMetadata()就是往其中寫入revisionLocation這個字符串,顯然metaFile這個文件就是用於記錄文件協議和插件文件位置的。

BundleArchive和BundleArchiveRevision的對象建立都分析完以後,發現其實到這裏爲止就建立了一個相似/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test/version.1/meta 這樣的元數據文件。

再回到BundleImpl()中,看創建BundleArchive對象以後的部分:

...
      this.state = BundleEvent.STARTED;

        updateMetadata();
        if (isInstall) {
            Framework.bundles.put(location, this);
            resolveBundle(false);
            Framework.notifyBundleListeners(1, this);
        }

        if (Framework.DEBUG_BUNDLES && log.isInfoEnabled()) {
            log.info("Framework: Bundle " + toString() + " created. "
                    + (System.currentTimeMillis() - currentTimeMillis) + " ms");
        }

主要就是狀態變爲BundleEvent.STARTED,以後調用updateMetadata()更新數據,因爲是安裝,isInstall爲true,故會將當前對象插入到Framework.bundles這個Map中,key是包名;

下面就看一下這裏的updateMetadata()作了什麼:

void updateMetadata() {
    File file = new File(this.bundleDir, "meta");   //file相似指向"/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test/meta"
    DataOutputStream dataOutputStream = null;
    try {
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        dataOutputStream = new DataOutputStream(fileOutputStream);
        dataOutputStream.writeUTF(this.location);//location通常爲"com.lizhangqu.test"這樣的插件包名
        dataOutputStream.writeInt(this.currentStartlevel);  //currentStartLevel通常爲1
        dataOutputStream.writeBoolean(this.persistently);   //persistently通常爲false
        dataOutputStream.flush();
        fileOutputStream.getFD().sync();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (dataOutputStream != null) {
            try {
                dataOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

其中location爲插件的包名,如"com.lizhangqu.test",currentStartlevel爲Framework.initStartlevel,這個值通常爲1;而persistently表示當前插件的對應的BundleImpl對象是否已經啓動,啓動後則爲true,不然爲false;因此在每次persistently變更以後,都須要調用updateMetadata()進行數據更新;

而這個file對應的路徑相似"/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test/meta",顯然和BundleArchiveRevision中的metaFile的路徑是不一樣的,做用也是不一樣的,這個是與插件的版本無關的,直接在插件數據目錄下的;

可是文件的I/O是很是低效的,若是改用數據庫來進行數據的持久化,效率要高不少,這也是OpenAtlas的一個不足之處.可能也是手淘啓動時卡頓的緣由!

再回到前面那個問題,其實對於大部分ROM來講,創建BundleImpl過程當中新建了兩個meta文件,一個直接在插件目錄下,裏面保存了插件的包名,啓動level和當前啓動狀態;另外一個在插件對應版本的目錄下,裏面保存了文件協議和插件文件的位置。

那麼Framework中restoreFromExistedBundle()是如何依據這個就創建插件對象(BundleImpl對象)的呢?

這個放到下一篇博客分析.

相關文章
相關標籤/搜索