在前一篇博客 Android插件化(一):OpenAtlas架构以及实现原理概要 中,咱们对应Android插件化存在的问题,实现原理,以及目前的实现方案进行了简单的叙述。从这篇开始,咱们要深刻到OpenAtlas的源码中进行插件安装过程的分析。html
插件的安装分为3种:宿主启动时当即安装,宿主启动时延时安装,使用时安装,其中使用时安装采用的是一种相似懒加载的机制。android
这3种方式只是前面的处理有所不一样,最后安装逻辑都是同样的。限于篇幅,本文只分析宿主启动时安装,使用时安装在下一篇分析。数据库
因为宿主启动时安装和宿主启动时延时安装的逻辑大致相同,因此放在一块儿讲解,它们的流程以下:json
关键流程分析以下:数组
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对象)的呢?
这个放到下一篇博客分析.