zoukankan      html  css  js  c++  java
  • Android插件化(4):OpenAtlasの插件的卸载与更新

    Android插件化(4):OpenAtlasの插件的卸载与更新

    核心提示:如果看过我的前两篇博客Android插件化(2):OpenAtlas插件安装过程分析和Android插件化(3):OpenAtlas的插件重建以及使用时安装,就知道在插件的安装过程中OpenAtlas做了哪些事,那么插件的卸载就只需要把持久化和内存中的内容移除即可。1.插件的卸载插件卸载的

    如果看过我的前两篇博客 Android插件化(2):OpenAtlas插件安装过程分析 和 Android插件化(3):OpenAtlas的插件重建以及使用时安装 ,就知道在插件的安装过程中OpenAtlas做了哪些事,那么插件的卸载就只需要把持久化和内存中的内容移除即可。

    1.插件的卸载

    插件卸载的对外接口为Atlas的uninstallBundle()方法:

         public void uninstallBundle(String pkgName) throws BundleException {
            Bundle bundle = Framework.getBundle(pkgName);
            if (bundle != null) {
                BundleImpl bundleImpl = (BundleImpl) bundle;
                try {
                    File archiveFile = bundleImpl.getArchive().getArchiveFile();
                    if (archiveFile.canWrite()) {
                        archiveFile.delete();
                    }
                    bundleImpl.getArchive().purge();
                    File revisionDir = bundleImpl.getArchive().getCurrentRevision()
                            .getRevisionDir();
                    bundle.uninstall();
                    if (revisionDir != null) {
                        Framework.deleteDirectory(revisionDir);
                        return;
                    }
                    return;
                } catch (Exception e) {
                    log.error("uninstall bundle error: " + pkgName + e.getMessage());
                    return;
                }
            }
            throw new BundleException("Could not uninstall bundle " + pkgName + ", because could not find it");
        }
    
    • 先根据包名来获取插件对象(BundleImpl对象),如果不为空,则获取该插件对象源文件并删除,其中bundleImpl.getArchive().getArchiveFile()的值类似/data/data/cn.edu.zafu.atlasdemo/lib/libcom_lizhangqu_test.so这样的文件(如果可写的话).
    • 之后调用BundleArchive的purge()进行清理工作,代码如下:
         @Override
        public void purge() throws Exception {
            if (this.revisions.size() > 1) {
                long revisionNum = this.currentRevision.getRevisionNum();
                for (Long longValue : this.revisions.keySet()) {
                    long longValue2 = longValue.longValue();
                    if (longValue2 != revisionNum) {
                        File file = new File(this.bundleDir, "version."
                                + String.valueOf(longValue2));
                        if (file.exists()) {
                            Framework.deleteDirectory(file);
                        }
                    }
                }
                this.revisions.clear();
                this.revisions.put(Long.valueOf(revisionNum), this.currentRevision);
            }
        }
    

    代码比较简单,先是遍历删除该插件各个版本(当前版本除外)的目录(类似"/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test/version.1"),之后清除revisions中的所有元素,再将当前版本的插件信息(currentRevision)插入到revisions中.

    之后调用bundle.uninstall(),该方法的代码如下:

         @Override
        public synchronized void uninstall() throws BundleException {
            if (this.state == BundleEvent.INSTALLED) {
                throw new IllegalStateException("Bundle " + toString() + " is already uninstalled.");
            }
            if (this.state == BundleEvent.RESOLVED) {
                try {
                    stopBundle();
                } catch (Throwable th) {
                    Framework.notifyFrameworkListeners(BundleEvent.STARTED, this, th);
                }
            }
            //为什么this.state=BundleEvent.INSTALLED而不是this.state=BundleEvent.UNINSTALLED?
            this.state = BundleEvent.INSTALLED;
            new File(this.bundleDir, "meta").delete();
            if (this.classloader.originalExporter != null) {
                this.classloader.originalExporter.cleanup(true);
                this.classloader.originalExporter = null;
            }
            this.classloader.cleanup(true);
            this.classloader = null;
            Framework.bundles.remove(this);
            Framework.notifyBundleListeners(BundleEvent.UNINSTALLED, this);
            this.context.isValid = false;
            this.context.bundle = null;
    
        }
    

    这个方法主要做了以下事情:

    + 如果当前处于BundleEvent.RESOLVED状态,则需要调用stopBundle()使当前的状态置为BundleEvent.STOPPED并通知监听者; + 之后将状态置为BundleEvent.INSTALLED,然后删除类似/data/data/cn.edu.zafu.atlasdemo/files/storage/com.lizhangqu.test/meta这样的插件元数据文件; + 清除BundleClassLoader中的originalExporter,这个表示BundleClassLoader中对外输出的BundleClassLoader对象; + 调用BundleClassLoader的cleanup()方法以清除包的依赖关系,但是实际上OpenAtlas并未实现包的依赖这个功能,所以到了ACDD这个方法中就只是将BundleImpl在BundleClassLoader中的引用去掉了; + 之后将BundleClassLoader对象置为null,然后将当前插件重Framework.bundles中移除 + 通知监听者,当前插件已经卸载

    再回到BundleArchive的purge()方法中,会将插件版本目录删除,这样就话把这个插件版本下的所有数据都清除,包括安装插件时新建的插件版本元数据文件.

    到这里为止,插件的卸载就完成了,整个过程非常简单。

    2.插件的更新

    插件的更新接口为Atlas.updateBundle()方法:

    public void updateBundle(String pkgName, File mBundleFile) throws BundleException {
        if (!mBundleFile.exists()) {
            throw new BundleException("file not  found" + mBundleFile.getAbsolutePath());
        }
        Bundle bundle = Framework.getBundle(pkgName);
        if (bundle != null) {
            bundle.update(mBundleFile);
            return;
        }
        throw new BundleException("Could not update bundle " + pkgName
                + ", because could not find it");
    }
    

    显然,这里只是先判断插件是否存在,如果存在的话,则调用BundleImpl的update()方法:

       @Override
        public synchronized void update(File bundleFile) throws BundleException {
            //这里应该是写错了,应该修改成BundleEvent.UNINSTALLED或者this.state!=BundleEvent.UNINSTALLED
            //额,确实没有错,因为在uninstall之后有this.state=BundleEvent.INSTALLED;只是这种逻辑很奇怪
            if (this.state == BundleEvent.INSTALLED) {
                throw new IllegalStateException("Cannot update uninstalled bundle "
                        + toString());
            }
            try {
                this.archive.newRevision(this.location, this.bundleDir, bundleFile);
            } catch (Throwable e) {
                throw new BundleException("Could not update bundle " + toString(),
                        e);
            }
        }
    

    既然是更新,那就表示是新版本,所以调用BundleArchve的newRevision()方法:

       /**
         *
         * @param packageName
         * @param bundleDir
         * @param archiveFile 类似"/data/data/cn.edu.zafu.altasdemo/lib/libcom_lizhangqu_test.so"这样的文件
         * @return
         * @throws IOException
         */
        @Override
        public BundleArchiveRevision newRevision(String packageName, File bundleDir, File archiveFile)
                throws IOException {
            long revision = 1 + this.revisions.lastKey().longValue();
            BundleArchiveRevision bundleArchiveRevision = new BundleArchiveRevision(
                    packageName, revision, new File(bundleDir, "version."
                    + String.valueOf(revision)), archiveFile);
            this.revisions.put(Long.valueOf(revision), bundleArchiveRevision);
            return bundleArchiveRevision;
        }
    

    显然,这里先是获取当前最新版本的版本号,然后在次基础上加1,之后新建一个基于当前插件文件archiveFile的BundleArchiveRevision对象,最后把这个对象放到revisions集合中,这样在下次查找插件版本的时候,找到的最新版本就是这里创建的插件版本。

    不过我觉得逻辑上不太好的一点就是在新建了插件版本对象后,并没有立即切换,结合插件对象的重建过程可知,这意味着版本更新需要在下次起作用。显然,后面的一个改进方向是做到热更新。

  • 相关阅读:
    TCP源码—连接建立
    TCP系列02—连接管理—1、三次握手与四次挥手
    TCP系列01—概述及协议头格式
    ubuntu软件管理apt与dpkg
    318. Maximum Product of Word Lengths
    317. Shortest Distance from All Buildings
    316. Remove Duplicate Letters
    315. Count of Smaller Numbers After Self
    314. Binary Tree Vertical Order Traversal
    313. Super Ugly Number
  • 原文地址:https://www.cnblogs.com/it-tsz/p/11509866.html
Copyright © 2011-2022 走看看