Android插件化(三):OpenAtlas的插件重建以及使用时安装

   2017-02-05 0
核心提示:在上一篇博客 Android插件化(二):OpenAtlas插件安装过程分析 中深入分析了OpenAtlas的随宿主启动的插件安装过程,本文将对插件的重建和使用时插件的安装过程进行分析,其中使用时安装这是我自己的定义,它类似懒加载机制,比如在需要用到插件中的某个组件时,

在上一篇博客 Android插件化(二):OpenAtlas插件安装过程分析 中深入分析了OpenAtlas的随宿主启动的插件安装过程,本文将对插件的重建和使用时插件的安装过程进行分析,其中"使用时安装"这是我自己的定义,它类似懒加载机制,比如在需要用到插件中的某个组件时,会先check一下,如果发现有插件存在该组件,并且插件还未安装,就会先安装该插件。

1.插件的重建

Framework.restoreFromExistedBundle()方法如下:

private static BundleImpl restoreFromExistedBundle(String location, File file) {

        try {
            return new BundleImpl(file, new BundleContextImpl());
        } catch (Throwable e) {
            OpenAtlasMonitor.getInstance().trace(Integer.valueOf(-1), "", "", "restore bundle failed " + location + e);
            log.error("restore bundle failed" + location, e);
            return null;
        }
    }

其中的file其实是插件数据目录,类似"/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test",进入到对应的BundleImpl(File,BundleContextImpl)构造方法中:

//file是类似"/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test"这样的目录
    BundleImpl(File file, BundleContextImpl bundleContextImpl) throws Exception {
        long currentTimeMillis = System.currentTimeMillis();
        DataInputStream dataInputStream = new DataInputStream(new FileInputStream(new File(file, "meta")));
        this.location = dataInputStream.readUTF();
        this.currentStartlevel = dataInputStream.readInt();
        this.persistently = dataInputStream.readBoolean();
        dataInputStream.close();
        bundleContextImpl.bundle = this;
        this.context = bundleContextImpl;
        this.bundleDir = file;
        this.state = BundleEvent.STARTED;
        try {
            this.archive = new BundleArchive(this.location, file);
            resolveBundle(false);
            Framework.bundles.put(this.location, this);
            Framework.notifyBundleListeners(1, this);
            if (Framework.DEBUG_BUNDLES && log.isInfoEnabled()) {
                log.info("Framework: Bundle " + toString() + " loaded. " + (System.currentTimeMillis() - currentTimeMillis) + " ms");
            }
        } catch (Exception e) {
            throw new BundleException("Could not load bundle " + this.location, e.getCause());
        }
    }

显然,通过读取/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test目录下的meta文件,获取了包名,启动级别,启动状态信息。之后也是要创建BundleArchive对象,但是调用的构造方法和前面的不同,代码如下:

//bundleDir类似"data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test"
public BundleArchive(String location, File bundleDir) throws IOException {
    this.revisions = new TreeMap<Long, BundleArchiveRevision>();
    File[] listFiles = bundleDir.listFiles();
    String currentProcessName = OpenAtlasUtils.getProcessNameByPID(android.os.Process.myPid());
    if (listFiles != null) {
        for (File file : listFiles) {
            if (file.getName().startsWith(REVISION_DIRECTORY)) {
                if (new File(file, DEPRECATED_MARK).exists()) {
                    try {
                        if (!TextUtils.isEmpty(currentProcessName) && currentProcessName.equals(RuntimeVariables.androidApplication.getPackageName())) {
                            for (File delete : file.listFiles()) {
                                delete.delete();
                            }
                            file.delete();
                        }
                    } catch (Exception e) {
                    }
                } else {
                    long parseLong = Long.parseLong(StringUtils.substringAfter(file.getName(), "."));
                    if (parseLong > 0) {
                        this.revisions.put(Long.valueOf(parseLong), null);
                    }
                }
            }
        }
    }
    if (this.revisions.isEmpty()) {
        try {
            if (!TextUtils.isEmpty(currentProcessName) && currentProcessName.equals(RuntimeVariables.androidApplication.getPackageName())) {

                for (File file : listFiles) {
                    file.delete();
                }

                bundleDir.delete();
            }
        } catch (Exception e2) {
        }
        throw new IOException("No valid revisions in bundle archive directory: " + bundleDir);
    }
    this.bundleDir = bundleDir;
    long longValue = this.revisions.lastKey().longValue();
    BundleArchiveRevision bundleArchiveRevision = new BundleArchiveRevision(location, longValue, new File(bundleDir, "version." + String.valueOf(longValue)));
    this.revisions.put(Long.valueOf(longValue), bundleArchiveRevision);
    this.currentRevision = bundleArchiveRevision;
    //remove  old version
    for (int i = 1; i < longValue; i++) {  //mBundleDir类似"/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test/version.1"
        File mBundleDir = new File(bundleDir, "version." + String.valueOf(i));
        if (mBundleDir.isDirectory()) {
            File[] listFilesSub = mBundleDir.listFiles();
            for (File file : listFilesSub) {
                file.delete();
            }

            mBundleDir.delete();
        }
        log.info("remove old  bundle@" + mBundleDir.getAbsolutePath() + " last version : " + currentRevision);

    }
    //remove old version
}

代码有点长,但是其实不难。

  • 同样的,新建了revisions这个TreeMap对象;
  • 获取插件目录bundleDir(类似"/data/data/cn.edu.zafu.atalasdemo/files/storage/com.lizhangqu.test")下所有文件,如果是以"version"开头,则进行判断,如果这个版本的目录下有DEPRECATED_MARK文件存在,则说明是不推荐的版本,为了节约空间,直接将这个版本的插件目录以及其中的文件删除;否则将对应版本号的值置为null,即this.revisions.put(Long.valueOf(parseLong),null);这样做的原因是不能有两个版本的插件共存,需要安装(或重建)当前插件的话,就需要先把其他版本的插件从内存中消除;
  • 如果revisions为空,则对进行这个操作的进程进行检查,保证是宿主进程在进行插件的重建工作,这样是为了防止Hacker将自己的插件安装到宿主中进行破坏活动;
  • 注意longValue=this.revisions.lastKey().longValue();这里,会返回键值最大的那个,也就是版本号最大的那个;可以保证获取到最后一次安装时的版本号,而这个版本号就是我们要重建的插件版本号;
  • 之后类似的,也要创建BundleArchiveRevision对象,但是调用的是不同的构造方法:
BundleArchiveRevision(String location, long revisionNum, File revisionDir) throws IOException {
    File metaFile = new File(revisionDir, "meta");
    if (metaFile.exists()) {
        DataInputStream dataInputStream = new DataInputStream(
                new FileInputStream(metaFile));
        this.revisionLocation = dataInputStream.readUTF();
        dataInputStream.close();

        this.revisionNum = revisionNum;
        this.revisionDir = revisionDir;
        if (!this.revisionDir.exists()) {
            this.revisionDir.mkdirs();
        }

        if (StringUtils
                .startWith(this.revisionLocation, REFERENCE_PROTOCOL)) {
            this.bundleFile = new File(StringUtils.substringAfter(
                    this.revisionLocation, REFERENCE_PROTOCOL));
            return;
        } else {
            this.bundleFile = new File(revisionDir, BUNDLE_FILE_NAME);
            return;
        }
    }
    throw new IOException("Could not find meta file in "
            + revisionDir.getAbsolutePath());
}

这个构造方法就是记录包名,版本号和版本目录;

之后从安装时创建的meta file中读出插件文件的位置,并且用bundleFile记录下来。这个bundleFile非常重要,在之后加载类,读取Manifest中的信息,以及加载插件中的资源都会用到。

到这里,记录插件信息的BundleImpl对象中的属性都已经初始化完毕,所以重建完毕。

到这里,插件的安装过程就完成了。之后,在BundleImpl中会调用Framework.bundles.put(location,this);将插件对象插入到Framework.bundles这个map中;之后调用resolveBundle(false);主要是新建了classLoader对象和将state该为4(BundleEvent.STOPPED),并通知BundleListeners,不过这里状态是BundleEvent.LOADED,表示当前插件已经加载完毕;最后调用Framework.notifyBundleListeners通知BundleListener,通知的状态为BundleEvent.INSTALLED,表示当前插件已经安装完毕。

再回到Framework中的installNewBundle(String,File)方法,之后会调用storeMetadata(),该方法如下:

static void storeMetadata() {

    try {
        File metaFile = new File(STORAGE_LOCATION, "meta");

        DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream(metaFile));
        dataOutputStream.writeInt(startlevel);
        String join = StringUtils.join(writeAheads.toArray(), ",");
        if (join == null) {
            join = "";
        }
        dataOutputStream.writeUTF(join);
        dataOutputStream.flush();
        dataOutputStream.close();

    } catch (IOException e) {
        OpenAtlasMonitor.getInstance().trace(Integer.valueOf(OpenAtlasMonitor.WRITE_META_FAIL), "", "", "storeMetadata failed ", e);
        log.error("Could not save meta data.", e);
    }
}

这个方法非常简单,就是将startLevel和writeAheads(首个插件安装时startlevel为-1,writeAheads为空),写入到类似/data/data/cn.edu.atlasdemo/files/storage/meta这样的文件中。

需要注意的是,到这里并没有完成全部工作,回到BundleInstaller.processAutoStartBundles()中,发现如果是随宿主启动的话,就会立即开启各个BundleImpl对象,BundleImpl.start()的代码如下:

     @Override
    public synchronized void start() throws BundleException {
        this.persistently = true;
        //因为persistently是metaFile中的一部分,persistently变化了,当然metaFile也要更新.
        updateMetadata();
        if (this.currentStartlevel <= Framework.startlevel) {  //"com.lizhangqu.test"时currentStartLevel==1,Framework.startLevel==-1
            startBundle();
        }
    }

可见这里主要就是更改persistently的状态,而且当当前插件的启动level比总体的启动level低时,就需要startBundle(),不过首次安装插件时this.currentStartlevel>Framework.startlevel,所以不会执行startBundle();

再一路回到OpenAtlasInitializer中的installBundles()方法中,安装完插件之后调用的是mOptDexProcess.processPackage(false,false);方法:

     /**
     * 处理Bundles
     *
     * @param optAuto
     *            是否只处理安装方式为AUTO的Bundle
     * @param notifyResult
     *            通知UI安装结果
     * ******/
    public synchronized void processPackages(boolean optAuto, boolean notifyResult) {
        if (!this.isInitialized) {
            Log.e("OptDexProcess", "Bundle Installer not initialized yet, process abort!");
        } else if (!this.isExecuted || notifyResult) {
            long currentTimeMillis;
            if (optAuto) {  //optAuto一般为true
                currentTimeMillis = System.currentTimeMillis();
                optAUTODex();
                if (!notifyResult) {
                    finishInstalled();
                }
                Log.e("debug", "dexopt auto start bundles cost time = " + (System.currentTimeMillis() - currentTimeMillis) + " ms");
            } else {
                currentTimeMillis = System.currentTimeMillis();
                optStoreDex();
                Log.e("debug", "dexopt bundles not delayed cost time = " + (System.currentTimeMillis() - currentTimeMillis) + " ms");

                if (!notifyResult) {
                    finishInstalled();
                }
                currentTimeMillis = System.currentTimeMillis();
                getInstance().optStoreDex2();
                Log.e("debug", "dexopt delayed bundles cost time = " + (System.currentTimeMillis() - currentTimeMillis) + " ms");
            }
            if (!notifyResult) {
                this.isExecuted = true;
            }
        }
    }

这里的逻辑其实有点混乱,到了ACDD就修改好了.

由于optAuto和notifyResult都为false,故执行optStoreDex()方法:

     /**** 对已安装并且安装方式不为STORE的Bundle进行dexopt操作 ****/
    private void optStoreDex() {
        for (Bundle bundle : Atlas.getInstance().getBundles()) {
            if (!(bundle == null || contains(AtlasConfig.STORE, bundle.getLocation()))) {
                try {
                    ((BundleImpl) bundle).optDexFile();
                } catch (Throwable e) {
                    if (e instanceof DexLoadException) {
                        throw ((RuntimeException) e);
                    }
                    Log.e("OptDexProcess", "Error while dexopt >>>", e);
                }
            }
        }
    }
 /**** 对全部安装方式为Store的Bundle进行dexopt操作 ***/
    private void optStoreDex2() {
        for (String bundle : AtlasConfig.STORE) {
            Bundle bundle2 = Atlas.getInstance().getBundle(bundle);
            if (bundle2 != null) {
                try {
                    ((BundleImpl) bundle2).optDexFile();
                } catch (Throwable e) {
                    if (e instanceof DexLoadException) {
                        throw ((RuntimeException) e);
                    }
                    Log.e("OptDexProcess", "Error while dexopt >>>", e);
                }
            }
        }
    }

这两个综合起来就是对已经安装或者安装方式为STORE的插件进行dex优化工作,BundleImpl的optDexFile()方法如下:

 public synchronized void optDexFile() {
        getArchive().optDexFile();
    }

显然,就是调用BundleArchive的optDexFile()方法,而BundleArchive的optDexFile()又是调用BundleArchiveRevision的optDexFile()方法:

public synchronized void optDexFile() {
    if (!isDexOpted()) {
        if (OpenAtlasHacks.LexFile == null
                || OpenAtlasHacks.LexFile.getmClass() == null) {
            File oDexFile = new File(this.revisionDir, BUNDLE_ODEX_FILE);
            long currentTimeMillis = System.currentTimeMillis();
            try {
                if (!OpenAtlasFileLock.getInstance().LockExclusive(oDexFile)) {
                    log.error("Failed to get file lock for "
                            + this.bundleFile.getAbsolutePath());
                }
                if (oDexFile.length() <= 0) {
                    InitExecutor.optDexFile(
                            this.bundleFile.getAbsolutePath(),
                            oDexFile.getAbsolutePath());
                    loadDex(oDexFile);
                    OpenAtlasFileLock.getInstance().unLock(oDexFile);
                    // "bundle archieve dexopt bundle " +
                    // this.bundleFile.getAbsolutePath() + " cost time = " +
                    // (System.currentTimeMillis() - currentTimeMillis) +
                    // " ms";
                }
            } catch (Throwable e) {
                log.error(
                        "Failed optDexFile '"
                                + this.bundleFile.getAbsolutePath()
                                + "' >>> ", e);
            } finally {
                OpenAtlasFileLock mAtlasFileLock = OpenAtlasFileLock.getInstance();
                mAtlasFileLock.unLock(oDexFile);
            }
        } else {
            DexClassLoader dexClassLoader = new DexClassLoader(
                    this.bundleFile.getAbsolutePath(),
                    this.revisionDir.getAbsolutePath(), null,
                    ClassLoader.getSystemClassLoader());
        }
    }
}

显然,这里就是进行dex优化工作,优化后的目标文件位置类似"/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test/version.1/bundle.dex",先是调用了InitExecutor.optDexFile()进行了优化工作:

/****
 * 在低于Android 4.4的系统上调用dexopt进行优化Bundle
 ****/
public static boolean optDexFile(String srcDexPath, String oDexFilePath) {
    try {
        if (sDexOptLoaded) {
            if (isART&& AtlasConfig.optART) {
                dexopt(srcDexPath, oDexFilePath, true, defaultInstruction);
            } else {
                dexopt(srcDexPath, oDexFilePath, false, "");
            }


            return true;
        }
    } catch (Throwable e) {
        log.error("Exception while try to call native dexopt >>>", e);
    }
    return false;
}

由于ART虚拟机和Dalvik虚拟机的优化方式不同,所以需要区分,而dexopt是个native方法:

@SuppressWarnings("JniMissingFunction")
private static native void dexopt(String srcZipPath, String oDexFilePath, boolean runtime, String defaultInstruction);

它对应的C++方法如下:

int dexopt(const char *zipName, const char *odexName,bool isART, const char *defaultInstuction) {

    int zipFd, odexFd;

    /*
     * Open the zip archive and the odex file, creating the latter (and
     * failing if it already exists).  This must be done while we still
     * have sufficient privileges to read the source file and create a file
     * in the target directory.  The "classes.dex" file will be extracted.
     */
    zipFd = open(zipName, O_RDONLY, 0);
    if (zipFd < 0) {
#ifdef OPENATLAS_DEXOPT_DEBUG
        LOGE("Unable to open '%s': %s\n", zipName, strerror(errno));
#endif
        return 1;
    }

    odexFd = open(odexName, O_RDWR | O_CREAT | O_EXCL, 0644);
    if (odexFd < 0) {
#ifdef OPENATLAS_DEXOPT_DEBUG
        LOGE("Unable to create '%s': %s\n", odexName, strerror(errno));
#endif
        close(zipFd);
        return 1;
    }
#ifdef OPENATLAS_DEXOPT_DEBUG
    LOGI("--- BEGIN '%s' (bootstrap=%d) ---\n", zipName, 0);
#endif

    pid_t pid = fork();
    if (pid == 0) {


        /* lock the input file */
        if (flock(odexFd, LOCK_EX | LOCK_NB) != 0) {
#ifdef OPENATLAS_DEXOPT_DEBUG
            LOGE("Unable to lock '%s': %s\n",odexName, strerror(errno));
#endif
            exit(65);
        }

       if(isART){//run dex2oat  vm safe is false
           run_dex2oat(zipFd, odexFd, zipName,odexName,defaultInstuction,false);
       } else{//
           run_dexopt(zipFd, odexFd, zipName, "v=n,o=v");
       }

        exit(67);                   /* usually */
    } else {
        /* parent -- wait for child to finish */
#ifdef OPENATLAS_DEXOPT_DEBUG
        LOGI("--- waiting for verify+opt, pid=%d\n", (int) pid);
#endif
        int status, oldStatus;
        pid_t gotPid;

        close(zipFd);
        close(odexFd);

        /*
         * Wait for the optimization process to finish.
         */
        while (true) {
            gotPid = waitpid(pid, &status, 0);
            if (gotPid == -1 && errno == EINTR) {
                #ifdef OPENATLAS_DEXOPT_DEBUG
                LOGI("waitpid interrupted, retrying\n");
                #endif
            } else {
                break;
            }
        }
        if (gotPid != pid) {
#ifdef OPENATLAS_DEXOPT_DEBUG
            LOGE("waitpid failed: wanted %d, got %d: %s\n", (int) pid, (int) gotPid, strerror(errno));
#endif
            return 1;
        }

        if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
#ifdef OPENATLAS_DEXOPT_DEBUG
            LOGI("--- END '%s' (success) ---\n", zipName);
#endif
            return 0;
        } else {
#ifdef OPENATLAS_DEXOPT_DEBUG
            LOGI("--- END '%s' --- status=0x%04x, process failed\n",
                 zipName, status);
#endif
            return 1;
        }
    }

    /* notreached */
}

显然,对于ART虚拟机,调用的是run_dex2oat()进行优化,而对于Dalvik虚拟机,则调用run_dexopt()进行优化工作。

优化完了之后,调用loadDex()方法:

  private synchronized void loadDex(File file) throws IOException {
        if (this.dexFile == null) {
            this.dexFile = DexFile.loadDex(this.bundleFile.getAbsolutePath(),
                    file.getAbsolutePath(), 0);
        }
    }

这个其实就是将优化过的odex或oat文件中的数据加载到内存中,调用的是DexFile.loadDex()方法进行解析,由于这里涉及到较多的dex文件格式,虚拟机以及优化的问题,展开的话内容太多,会在后面专门写博客分析。这里先跳过,只要记住:在调用DexFile.loadDex()生成DexFile对象之后,就可以利用它来查找插件中定义的类了。DexFile本来也是有public Class loadClass(String,ClassLoader)方法的。

到这里,可以总结一下安装插件过程中主要做了哪些事:

  • 新建了BundleImpl对象,在新建这个对象的过程中,新建了BundleArchive和BundleArchiveRevision,BundleClassLoader对象;
  • 对一些特殊的ROM进行了兼容,方法是复制插件文件到目标位置或者建立软链接;
  • 新建了插件版本元数据文件metaFile,就是类似/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test/version.1/meta这样的文件,用于记录文件协议和插件文件位置;
  • 新建(或打开)了类似/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test/meta这样的插件元数据文件,往其中写入了包名,启动级别和启动状态;
  • 新建(或打开)了类似/data/data/cn.edu.atlasdemo/files/storage/meta这样的记录总体插件状态的元数据文件,其中记录了当前Framework中的startLevel;
  • 对于dex文件进行了优化
  • 将BundleEvent.LOADED和BundleEvent.STARTED事件通知BundleListener

2.使用时安装流程

前面说过,使用时安装类似懒加载机制,比如在需要用到插件中的某个组件时,会先check一下,如果发现有插件存在该组件,并且插件还未安装,就会先安装该插件。

使用时安装的流程如下:

Android插件化(三):OpenAtlas的插件重建以及使用时安装

从以上两图中可以看出,宿主启动时安装和使用时安装只是前面部分不同,从判断插件存档文件是否存在开始,流程就一样了。

而会引起使用时的情况有很多,如ContextImplHook.bindService(),ContextImplHook.startActivity(),ContextImplHook.startService(),

ContextImplHook.findClass(),InstrumentationHook.execStartActivityInternal(),由于其他几种在后面会专门分析,这里就只分析ContextImpl.findClass()这种情况:

     @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        ClassLoadFromBundle.checkInstallBundleIfNeed(className);
        Class<?> loadFromInstalledBundles = ClassLoadFromBundle.loadFromInstalledBundles(className);
        if (loadFromInstalledBundles != null) {
            return loadFromInstalledBundles;
        }
        throw new ClassNotFoundException("Can't find class " + className + printExceptionInfo() + " " + ClassLoadFromBundle.getClassNotFoundReason(className));
    }

ClassLoadFromBundle.checkInstallBundleIfNeed()方法如下:

 public static void checkInstallBundleIfNeed(String bundleName) {
        synchronized (bundleName) {
            if (sInternalBundles == null) {
                resolveInternalBundles();
            }
            String bundleForComponet = BundleInfoList.getInstance().getBundleNameForComponet(bundleName);
            if (TextUtils.isEmpty(bundleForComponet)) {
                Log.e(TAG, "Failed to find the bundle in BundleInfoList for component " + bundleForComponet);
                insertToReasonList(bundleName, "not found in BundleInfoList!");
            }
            if (sInternalBundles == null || sInternalBundles.contains(bundleForComponet)) {
                checkInstallBundleAndDependency(bundleForComponet);
                return;
            }
        }
    }

首次查找时sInternalBundles==null,故进入resolveInternalBundles();

public static synchronized void resolveInternalBundles() {
    synchronized (ClassLoadFromBundle.class) {
        if (sInternalBundles == null || sInternalBundles.size() == 0) {
            String str = "lib/" + AtlasConfig.PRELOAD_DIR + "/libcom_";
            String str2 = ".so";
            List<String> arrayList = new ArrayList<String>();
            try {
                sZipFile = new ZipFile(RuntimeVariables.androidApplication.getApplicationInfo().sourceDir);
                Enumeration<?> entries = sZipFile.entries();
                while (entries.hasMoreElements()) {
                    String name = ((ZipEntry) entries.nextElement()).getName();
                    if (name.startsWith(str) && name.endsWith(str2)) {
                        arrayList.add(getPackageNameFromEntryName(name));
                    }
                }

                sInternalBundles = arrayList;
            } catch (Exception e) {
                Log.e(TAG, "Exception while get bundles in assets or lib", e);
            }
        }
    }
}

利用的是寻找目录下ZipFile(其实确切地说是解压缩文件)的方法,其实效率非常低,因为打log发现sZipFile的entries有AndroidManifest.xml,META-INF/CERT.RSA,META-INF/CERT.SF,META-INF/MANIFEST.MF,classes.dex,

lib/armeabi/libcom_lizhangqu_test.so,lib/armeabi/libcom_lizhangqu_zxing.so,lib/armeabi/libdexopt.so,

lib/x86/libdexopt.so,res/anim/…,res/color/..,res/color-v11/..,res/drawable/..,res/drawable-../..,res/layout-../..所有这些文件,其实就是apk解压后的文件,如果宿主apk比较大,其中的资源和so文件较多,那么这样的查找就很费时。其实完全可以利用之前BundleParser的解析结果,然后按照约定去检查指定文件是否存在即可。

经过resolveInternalBundles()之后,sInternalBundles就包含了所有的插件名称,如{“com.lizhangqu.test”,“com.lizhangqu.zxing”},之后从解析的json文件结果中获取传入的组件对应的插件名称,如果sInternalBundles中含有该插件名称的话,就安装该插件。

其实这里的逻辑有冗余的地方,因为其实只要利用json文件解析的结果进行判断就行了,如果某个插件中含有该组件,就直接安装即可(如果之前没有安装的话),而且其实后面的checkInstallBundleAndDependency()方法中还会对插件文件是否存在进行判断.

之后进入checkInstallBundleAndDependency()中:

 //检查插件的安装以及依赖情况.location是类似"com.lizhangqu.test"这样的包名,concat是类似"libcom_lizhangqu_test.so"这样的
    public static void checkInstallBundleAndDependency(String location) {
        List<String> dependencyForBundle = BundleInfoList.getInstance().getDependencyForBundle(location);
        if (dependencyForBundle != null && dependencyForBundle.size() > 0) {
            for (int i = 0; i < dependencyForBundle.size(); i++) {
                checkInstallBundleAndDependency(dependencyForBundle.get(i));

            }
        }
        if (Atlas.getInstance().getBundle(location) == null) {
            String concat = "lib".concat(location.replace(".", "_")).concat(".so");
            File file = new File(new File(Framework.getProperty(PlatformConfigure.ATLAS_APP_DIRECTORY), "lib"), concat);
            if (file.exists()) { //如果so文件存在,如libcom_lizhangqu_test.so文件存在,就进行安装
                try {
                    if (checkAvailableDisk()) {
                        Atlas.getInstance().installBundle(location, file);
                        return;
                    }
                    log.error("disk size not enough");
                    OpenAtlasMonitor.getInstance().trace(Integer.valueOf(-1), location, "", "disk size not enough");
                } catch (Throwable e) {
                    log.error("failed to install bundle " + location, e);
                    OpenAtlasMonitor.getInstance().trace(Integer.valueOf(-1), location, "",
                            "failed to install bundle ", e);
                    throw new RuntimeException("atlas-2.3.47failed to install bundle " + location, e);
                }
            } else if (sInternalBundles == null || !sInternalBundles.contains(location)) {
                log.error(" can not find the library " + concat + " for bundle" + location);
                OpenAtlasMonitor.getInstance().trace(Integer.valueOf(-1), "" + location, "",
                        "can not find the library " + concat);
            } else {
                installFromApkZip(location, concat);
            }
        }
    }

这个方法其实很简单:

+ 先检查当前插件对其他插件的依赖情况,如果依赖其他的插件,则需要先安装其他的插件,其他的插件如果仍然有依赖,则还需要先安装依赖,所以这是一个递归方法; + 检查插件文件是否存在,如果存在而且磁盘空间充足,就安装插件;之后插件的安装过程就跟前面随宿主启动时安装的过程一样了,不再赘述。

9)安装完插件后类的加载过程

在回到DelegateClassLoader的findClass()方法中,在调用ClassLoadFromBundle.checkInstallBundleIfNeed(className);安装完插件之后,接着就需要加载类了。

这里是通过调用ClassLoadFromBundle.loadFromInstalledBundles()来进行加载:

//component是类似"com.lizhangqu.test.MainActivity"这样的,其实这里查找bundle的效率太低,如果利用HashMap和packageName来进行查找的话要快的多,其中的classLoader其实是BundleClassLoader对象
static Class<?> loadFromInstalledBundles(String componet) throws ClassNotFoundException {
    BundleImpl bundleImpl;
    int i = 0;
    Class<?> cls = null;
    List<Bundle> bundles = Framework.getBundles();
    if (!(bundles == null || bundles.isEmpty())) {
        for (Bundle bundle : bundles) {
            bundleImpl = (BundleImpl) bundle;
            PackageLite packageLite = DelegateComponent.getPackage(bundleImpl.getLocation());
            if (packageLite != null && packageLite.components.contains(componet)) {
                bundleImpl.getArchive().optDexFile();
                ClassLoader classLoader = bundleImpl.getClassLoader();  //classLoader是BundleClassLoader对象
                if (classLoader != null) {
                    try {
                        cls = classLoader.loadClass(componet);
                        if (cls != null) {
                            return cls;
                        }
                    } catch (ClassNotFoundException e) {
                        throw new ClassNotFoundException("Can't find class " + componet + " in BundleClassLoader: "
                                + bundleImpl.getLocation() + " [" + (bundles == null ? 0 : bundles.size()) + "]"
                                + "classloader is: " + (classLoader == null ? "null" : "not null")
                                + " packageversion " + getPackageVersion() + " exception:" + e.getMessage());
                    }
                }
                StringBuilder append = new StringBuilder().append("Can't find class ").append(componet)
                        .append(" in BundleClassLoader: ").append(bundleImpl.getLocation()).append(" [");
                if (bundles != null) {
                    i = bundles.size();
                }
                throw new ClassNotFoundException(append.append(i).append("]")
                        .append(classLoader == null ? "classloader is null" : "classloader not null")
                        .append(" packageversion ").append(getPackageVersion()).toString());
            }
        }
    }
    //一般在上面就会返回,走不到这个分支.如果之前加载过(通过bundleImpl.getArchive().isDexOpted()可知),并且现在要加载的类并不是4大组件之一,则可以利用之前保存在bundleImpl中的ClassLoader对象直接解析
    if (!(bundles == null || bundles.isEmpty())) {
        Class<?> cls2 = null;
        for (Bundle bundle2 : Framework.getBundles()) {
            bundleImpl = (BundleImpl) bundle2;
            if (bundleImpl.getArchive().isDexOpted()) {
                Class<?> loadClass = null;
                ClassLoader classLoader2 = bundleImpl.getClassLoader();
                if (classLoader2 != null) {
                    try {
                        loadClass = classLoader2.loadClass(componet);
                        if (loadClass != null) {
                            return loadClass;
                        }
                    } catch (ClassNotFoundException e2) {
                    }
                } else {
                    loadClass = cls2;
                }
                cls2 = loadClass;
            }
        }
        cls = cls2;
    }
    return cls;
}

这个方法其实比较简单,主要是以下部分:

  • 遍历Framework中注册的所有插件,如果该插件的组件中包含该组件名,则进入下一步;
  • 调用bundleImpl.getArchive().optDexFile();而BundleImpl.getArchive().optDexFile();在前面mOptDexProcess.processPackage(false,false)分析过,这个就是先对dex进行优化(如果之前没有优化的话),然后利用DexFile.loadDex(odexFile);加载优化后的odex文件,并生成DexFile对象,之后bundleImpl.getClassLoader()返回BundleClassLoader对象,而BundleClassLoader其实是利用装饰模式,加载插件中的类时调用的是findOwnClass(),之后的调用流程为BundleClassLoader.findOwnClass()–>BundleArchive.findClass(String,ClassLoader)–>BundleArchiveRevision.findClass(String,ClassLoader),而该方法代码如下:
  //str是类似"com.lizhangqu.test.MainActivity"或"com.lizhangqu.test.MusicService"这样,而classLoader是BundleClassLoader对象,revisionDir类似"/data/data/cn.deu.zafu.atlasdemo/files/storage/com.lizhangqu.test/version.1"
    Class<?> findClass(String str, ClassLoader classLoader)
            throws ClassNotFoundException {
        try {
            if (OpenAtlasHacks.LexFile == null
                    || OpenAtlasHacks.LexFile.getmClass() == null) {
                if (!isDexOpted()) {
                    optDexFile();
                }
                if (this.dexFile == null) {
                    loadDex(new File(this.revisionDir, BUNDLE_ODEX_FILE));
                }
                Class<?> loadClass = this.dexFile.loadClass(str, classLoader);
                this.isDexFileUsed = true;
                return loadClass;
            }
            if (this.dexClassLoader == null) {
                File file = new File(RuntimeVariables.androidApplication
                        .getFilesDir().getParentFile(), "lib");
                this.dexClassLoader = new BundleArchiveRevisionClassLoader(
                        this.bundleFile.getAbsolutePath(),
                        this.revisionDir.getAbsolutePath(),
                        file.getAbsolutePath(), classLoader);
            }
            return (Class) OpenAtlasHacks.DexClassLoader_findClass.invoke(
                    this.dexClassLoader, str);
        } catch (IllegalArgumentException e) {
            return null;
        } catch (InvocationTargetException e2) {
            return null;
        } catch (Throwable e3) {
            if (!(e3 instanceof ClassNotFoundException)) {
                if (e3 instanceof DexLoadException) {
                    throw ((DexLoadException) e3);
                }
                log.error("Exception while find class in archive revision: "
                        + this.bundleFile.getAbsolutePath(), e3);
            }
            return null;
        }
    }

显然,对应普通的ROM(非YunOS),在loadDex()之后,利用dexFile就可以加载到我们需要的类。

但是,对应YunOS(即lexFile!=null),这里先生成BundleArchiveRevisionClassLoader对象(BundleArchiveRevisionClassLoader继承自DexClassLoader),之后利用反射调用DexClassLoader的findClass()方法来加载类(其实findClass()方法是BaseDexClassLoader中的,不过DexClassLoader继承自BaseDexClassLoader).

如下是BundleArchiveRevision的内部类BundleArchiveRevisionClassLoader的定义:

class BundleArchiveRevisionClassLoader extends DexClassLoader {
    /**
     * @param dexPath the list of jar/apk files containing classes and resources, delimited by File.pathSeparator, which defaults to ":" on Android
     * @param optimizedDirectory directory where optimized dex files should be written; must not be null
     * @param libraryPath the list of directories containing native libraries, delimited by File.pathSeparator; may be null
     * @param
     * **/
    BundleArchiveRevisionClassLoader(String dexPath, String optimizedDirectory, String libraryPath,
                                     ClassLoader parent) {
        super(dexPath, optimizedDirectory, libraryPath, parent);
    }

    @Override
    public String findLibrary(String name) {
        String findLibrary = super.findLibrary(name);
        if (!TextUtils.isEmpty(findLibrary)) {
            return findLibrary;
        }
        File findSoLibrary = BundleArchiveRevision.this
                .findSoLibrary(System.mapLibraryName(name));
        if (findSoLibrary != null && findSoLibrary.exists()) {
            return findSoLibrary.getAbsolutePath();
        }
        try {
            return (String) OpenAtlasHacks.ClassLoader_findLibrary.invoke(
                    Framework.getSystemClassLoader(), name);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

所以我们可以总结出来,对于普通的ROM,插件中的类是通过dexFile加载出来的;而对应YunOS系统,则是先生成BundleArchiveRevisionClassLoader(它是DexClassLoader的子类)对象,之后通过反射调用它的findClass()方法来加载类。

 
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • [译] 提升 Android 应用性能的几个建议
    [译] 提升 Android 应用性能的几个建议
    本文译自Android开发者网站,主要介绍了提升Android应用性能表现的几个建议。阅读本文时还要务必记得“过早优化是万恶之源”,优化起码应该放在实现了应用的MVP版本之后。本文主要介绍了提升Android应用性能的一些小方法,组合使用这些方法往往能够改善我们所
  • 深入理解Android虚拟机体系结构
    深入理解Android虚拟机体系结构
    来源:LeoLiang连接:http://www.cnblogs.com/lao-liang/p/5111399.html1.什么是Dalvik虚拟机Dalvik虚拟机是Google公司自己设计用于Android平台的Java虚拟机,它是Android平台的重要组成部分,支持dex格式(Dalvik Executable)的Java应用程序的运行。dex格式
  • Android虚拟机调试器原理与实现
    Android虚拟机调试器原理与实现
    * 本文原创作者:渔村安全,本文属FreeBuf原创奖励计划,未经许可禁止转载 本文主要讲解Android虚拟机动态调试背后涉及到的技术原理,除了JDWP协议细节,还包括任意位置断点、堆栈输出、变量值获取等基础调试功能的具体实现。另外本文提供了一款新的android动
点击排行