android动态加载jar

xiaoxiao2021-02-28  84

最近客户需求通过后台下发代码的方式来实现新增功能,权衡了热修复和动态加载最终选择的动态加载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文件的错误,坑死了。

源码地址

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

转载请注明原文地址: https://www.6miu.com/read-77509.html

最新回复(0)