zoukankan      html  css  js  c++  java
  • Android插件化(三):OpenAtlas的插件重建以及使用时安装

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

    转 https://www.300168.com/yidong/show-2778.html

      
    核心提示:在上一篇博客 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
    ", 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
    ", odexName, strerror(errno));
    #endif
            close(zipFd);
            return 1;
        }
    #ifdef OPENATLAS_DEXOPT_DEBUG
        LOGI("--- BEGIN '%s' (bootstrap=%d) ---
    ", 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
    ",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
    ", (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
    ");
                    #endif
                } else {
                    break;
                }
            }
            if (gotPid != pid) {
    #ifdef OPENATLAS_DEXOPT_DEBUG
                LOGE("waitpid failed: wanted %d, got %d: %s
    ", (int) pid, (int) gotPid, strerror(errno));
    #endif
                return 1;
            }
    
            if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
    #ifdef OPENATLAS_DEXOPT_DEBUG
                LOGI("--- END '%s' (success) ---
    ", zipName);
    #endif
                return 0;
            } else {
    #ifdef OPENATLAS_DEXOPT_DEBUG
                LOGI("--- END '%s' --- status=0x%04x, process failed
    ",
                     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()方法来加载类。

  • 相关阅读:
    MongoDB存储
    python 查看文件名和文件路径
    Python遍历文件个文件夹
    Python图片缩放
    python opencv
    Python3 关于UnicodeDecodeError/UnicodeEncodeError: ‘gbk’ codec can’t decode/encode bytes类似的文本编码问题
    jmter使用
    HttpRunnerManager使用
    PostMan使用
    工作中的思想
  • 原文地址:https://www.cnblogs.com/it-tsz/p/11509863.html
Copyright © 2011-2022 走看看