android动态加载jar

最近客户需求通过后台下发代码的方式来实现新增功能,权衡了热修复和动态加载最终选择的动态加载jar的方式实现该功能。首先客户端编码,以jar的方式导出,将jar放到服务器供客户端下载并进行动态加载。

DexClassLoader :可以加载文件系统上的jar、dex、apk
PathClassLoader :可以加载/data/app目录下的apk,这也意味着,它只能加载已经安装的apk
URLClassLoader :可以加载Java中的jar,但是由于dalvik不能直接识别jar,所以此方法在Android中无法使用,尽管还有这个类

一、android studio下导出jar

新建一个library工程,不知道怎么创建library工程?本人是这样子创建的:
1、新建一个android工程
2、修改module的build.gradle文件,将 apply plugin: ‘com.android.application’ 改为 apply plugin: ‘com.android.library’,去掉defaultConfig下的applicationId(library工程不需要applicationId)

在library中定义接口:

package fota.adups.myapplication;

/**
 * Created by wilson on 2017/5/7.
 */

public interface ILoader {
    String sayHi();
}

定义实现类:

package fota.adups.myapplication;

/**
 * Created by wilson on 2017/5/7.
 */

public class JarLoader implements ILoader{

    public JarLoader() {
    }

    @Override
    public String sayHi() {
        return "I am jar loader.xxxxxxx";
    }

}

这里要注意了,生成jar时要去掉接口文件,否则宿主会挂掉,android studio下生成jar的详细教程可以点击这里

生成jar后里面的java文件在java中是可以直接用的,但是android虚拟机不能直接识别此.class格式文件,需要进过dx工具处理转化为dex格式文件。在android sdk的build-tools目录(如下图)执行dx --dex --output=test.jar classes.jar命令
这里写图片描述

在回头看下转化后的jar里有什么:
这里写图片描述
正是android可以识别的dex格式

二、在另外一个工程中动态加载jar

动态加载是通过java反射进行的,直接上代码吧

private void loadJar() {


        final File optimizedDexOutputPath = new File(getStoragePath(this,false).toString()
                + File.separator + "loader_dex.jar");
        Log.d(TAG,"loadJar,"+optimizedDexOutputPath.getAbsolutePath()+",,"+optimizedDexOutputPath.exists());



        //jar的包名和主工程的包名一致时可以用下面的代码   特点:方便简单  缺点:具有包名一致的限制
        /*BaseDexClassLoader cl = new BaseDexClassLoader(optimizedDexOutputPath.getAbsolutePath(),
                this.getFilesDir(),null, this.getClass().getClassLoader());
        Class libProviderClazz = null;
        try {
            // 载入JarLoader类, 并且通过反射构建JarLoader对象, 然后调用sayHi方法
            libProviderClazz = cl.loadClass("fota.adups.myapplication.JarLoader");
            ILoader loader = (ILoader) libProviderClazz.newInstance();
            Toast.makeText(MainActivity.this, loader.sayHi(), Toast.LENGTH_SHORT).show();
        } catch (Exception exception) {
            // Handle exception gracefully here.
            exception.printStackTrace();
        }*/


        //jar的包名和主工程的可以不一致,通用性强,可以适用于动态加载apk
        // 4.1以后不能够将optimizedDirectory设置到sd卡目录, 否则抛出异常.
        DexClassLoader classLoader = new DexClassLoader(optimizedDexOutputPath.getAbsolutePath(), getFilesDir().getAbsolutePath(),
                null, getClassLoader());

        try {
            // 通过反射机制调用, 包名为com.example.loaduninstallapkdemo, 类名为UninstallApkActivity
            Class mLoadClass = classLoader.loadClass("fota.adups.myapplication.JarLoader");
            Constructor constructor = mLoadClass.getConstructor(new Class[] {});
            Object testActivity = constructor.newInstance(new Object[] {});

            // 获取sayHello方法
            Method helloMethod = mLoadClass.getMethod("sayHi", new  Class[]{});
            helloMethod.setAccessible(true);
            Object content = helloMethod.invoke(testActivity, new Object[] {});
            Toast.makeText(MainActivity.this, content.toString(), Toast.LENGTH_LONG).show();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

上面的方法中展示了两种方式加载jar,一种是jar和主工程的包名一致时,可以直接在主工程中也定义出jar中的接口,然后从jar中获取接口的具体实现就行了;第二种是jar和主工程的包名不一致,此时利用Java类的祖先Object来获取jar中的接口实现。

值得一提的时,在参考某一篇文档调用BaseDexClassLoader方法时,路径次序填错了,搞得老是报找不到class文件的错误,坑死了。

扫描二维码关注公众号,回复: 1009668 查看本文章

源码地址

参考文章:Android动态加载jar/dex
Android动态加载jar、apk的实现
Android插件化探索(四)免安装运行Activity(下)

猜你喜欢

转载自blog.csdn.net/u013795543/article/details/71333016