美文网首页
Anroid插件化一:jar包动态加载

Anroid插件化一:jar包动态加载

作者: DON_1007 | 来源:发表于2019-09-28 14:53 被阅读0次

    Android中常用的两种类加载器:PathClassLoaderDexClassLoader,它们都继承于BaseDexClassLoader
    PathClassLoader 可以加载已经安装到Android系统中的apk文件,在ART虚拟机上可以加载未安装的apkdex,在Dalvik则不行;DexClassLoader可以加载jardex和未安装的apk

    考虑到需要兼容4.0版本的Android系统,以下选用DexClassLoader实现动态加载。

    动态加载jar

    一、新建项目PluginDemo

    此时会默认生成Project的主module app
    gradle版本选择使用3.1.4
    二、新建module PluginJarDemo
    新建类PluginJarDemo,以作演示。

    package com.don.pluginjardemo;
    
    import android.util.Log;
    
    /**
     * Created by don on 2019-09-09
     */
    public class PluginJarDemo {
        private final String TAG = "PluginJarDemo";
        private String content;
    
        public void writeTest(String content) {
            Log.i(TAG, "writeTest " + content + "  " + this);
            this.content = content;
        }
    
        public String readTest() {
            Log.i(TAG, "readTest " + this);
            return content;
        }
    
    }
    

    接下来通过DexClassLoader动态加载PluginJarDemo

    1、编译module PluginJarDemo,生成jar

    task makeJar(type: Jar, dependsOn: [':PluginJarDemo:assembleRelease' ]) {
    
        archiveName = "pluginDemo.jar"
        destinationDir = file('build/libs')
    
        def where = "build/intermediates/classes/release"
        from(where)
        from fileTree(dir: 'src/main', includes: ['assets/**'])
    
        exclude('**/R.class')
        exclude('**/R\$*.class')
        include "**/*.*"
    }
    

    在命令行中执行
    gradlew :PluginJarDemo:makeJar
    PluginJarDemobuild/libs/目录下会生成pluginDemo.jar文件。
    这种直接生成的jar包是不能被DexClassLoader加载的,因为其中没有dex文件。

    2、生成包含dex文件的新jar

    task makeDex(type: Exec, description: 'for PluginJarDemo dex') {
        doFirst {
            println("convert jar to dex start")
    
            // 拼接的dex转换工具路径
            Properties properties = new Properties()
            //local.properites也放在posdevice目录下
            File propertyFile = new File(rootDir.getAbsolutePath() + "/local.properties")
            properties.load(propertyFile.newDataInputStream())
            def dexToolPath = properties.getProperty('sdk.dir') + "/build-tools/27.0.3"
            // dex转换工具参数
            def dexToolArgs = "--dex --output="
            def releaseJar = "build/libs/pluginDemo.jar"
            def dexJar = "build/libs/pluginDex.jar"
    
            def finalCmd
            def dxToolName
            if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
                // Windows
                dxToolName = "dx.bat"
                finalCmd = dexToolPath + "/$dxToolName " + dexToolArgs + dexJar + " " + releaseJar
                finalCmd = finalCmd.replaceAll("/", "\\\\")
                commandLine "cmd", "/c", finalCmd
            } else {
                // linux
                dxToolName = "dx"
                finalCmd = dexToolPath + "/$dxToolName " + dexToolArgs + dexJar + " " + releaseJar
                finalCmd = finalCmd.replaceAll("\\\\", "/")
                commandLine "/bin/sh", "-c", finalCmd
            }
            println("convert jar to dex end, command: " + finalCmd)
        }
    }
    

    在命令行中运行
    gradlew :PluginJarDemo:makeDex
    PluginJarDemobuild/libs/目录下会生成pluginDex.jar文件,这个文件包含class.dex文件,可被DexClassLoader动态加载。

    三、动态加载pluginDex.jar

    在主module app中新建类JarLoad用于动态加载pluginDex.jar

    package com.don.plugindemo;
    
    import android.content.Context;
    import android.util.Log;
    
    import java.io.File;
    import java.lang.reflect.Method;
    
    import dalvik.system.DexClassLoader;
    
    /**
     * Created by don on 2019-09-09
     */
    public class JarLoader {
        private final static String TAG = "JarLoader";
        private final String DEMO_CLASS = "com.don.pluginjardemo.PluginJarDemo";
        private Class mDemoClass;
        private Object mDemoInstance;
    
        public void load(Context context, String pluginPath) {
            File file = new File(pluginPath);
            if (!file.exists()) {
                Log.i(TAG, "load ignore, invalid file: " + pluginPath);
                return;
            }
            String dataFile = context.getFilesDir().getAbsolutePath();
            try {
                DexClassLoader dexClassLoader = new DexClassLoader(pluginPath, dataFile, null, getClass().getClassLoader());
                mDemoClass = dexClassLoader.loadClass(DEMO_CLASS);
                mDemoInstance = mDemoClass.newInstance();
            } catch (Exception e) {
                Log.w(TAG, e);
            }
        }
    
        public void writeTest(String content) {
            try {
                Method method = mDemoClass.getDeclaredMethod("writeTest", String.class);
                method.invoke(mDemoInstance, content);
            } catch (Exception e) {
                Log.w(TAG, e);
            }
        }
    
        public String readTest() {
            try {
                Method method = mDemoClass.getDeclaredMethod("readTest");
                return method.invoke(mDemoInstance).toString();
            } catch (Exception e) {
                Log.w(TAG, e);
            }
            return null;
        }
    
    }
    

    DexClassLoader的创建需要填4个参数:

    • 第一个是jar包的地址
    • 第二个是jar包中dex文件被加载之后的路径,需要可执行,这里使用了app的安* 装根目录
    • 第三个是so文件目录,如果该jar包需要加载so,则需要传入so文件所在目录,同样的,此目录需要有可执行权限
    • 第四个是父类构造器,使用当前app的构造器即可

    jar包动态加载成功之后可以通过反射机制创建jar包中的PluginJarDemo对象并操作。

    在主界面添加一个按钮,点击之后读取PluginJarDemo实例中的content变量。

    package com.don.plugindemo;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    
    public class MainActivity extends AppCompatActivity {
    
        private JarLoader mJarLoader;
        private Button mJarBtn;
        private TextView mResultTxt;
        private View.OnClickListener mClickListener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                switch (v.getId()) {
                    case R.id.jarBtn:
                        mJarLoader.writeTest("jar load test");
                        mResultTxt.setText(mJarLoader.readTest());
                        break;
                }
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initView();
            mJarLoader = new JarLoader();
            mJarLoader.load(this,"sdcard/pluginDex.jar");
        }
    
        private void initView() {
            mJarBtn = (Button) findViewById(R.id.jarBtn);
            mJarBtn.setOnClickListener(mClickListener);
            mResultTxt = (TextView) findViewById(R.id.resultTxt);
        }
    }
    

    运行,点击按钮 JAR
    如下图,可以从pluginDex.jar中成功读取content变量

    image.png

    动态加载dex与动态加载jar类似,唯一的区别是需要将打包的jar转换为dex文件,以上述实现为例,通过下面的命令可以得到一个可用于动态加载的dex文件
    dx --dex --output=pluginDex.dex pluginDemo.jar
    接下来通过加载pluginDex.dex的实现,与动态加载pluginDex.jar文件一样。

    相关文章

      网友评论

          本文标题:Anroid插件化一:jar包动态加载

          本文链接:https://www.haomeiwen.com/subject/emqmyctx.html