生成android 动态加载的jar包
方式1 脚本文件生成
复制代码
- 用途:Android 运行时直接加载、插件化、热修复、Android 插件
- 特点:内部包含 classes.dex,手机能直接执行
- 在根目录下创建脚本
- 设置文件名字 set filename=xxx.jar
- 设置源文件目录 set srcDir=全路径
- 设置dx路径 set dx=全路径\dx.bat
- 删除输出目录文件 del out\%filename%
- 执行 dx 打包 call %dx% --dex --output=out\%filename% %srcDir%
- 等待退出 pause
方式2 Gradle标准打包
复制代码
- 用途:纯 Java 代码库、给其他模块依赖、做 SDK 基础包
- 特点:只有 .class 文件,没有 dex,不能直接在 Android 运行
- 在模块下的build.gradle 添加
task makeJar(type: Copy){
//删除已存在的 jar
delete 'build/libs/test.jar'
//设置拷贝文件
from('build/intermediates/aar_main_jar/debug/syncDebugLibJars/')
//打进jar包的文件目录
into('build/libs/')
include('classes.jar')
//重命名
rename('classes.jar', 'test.jar')
}
然后再根目录下执行 gradlew makeJar
- 如遇到版本不兼容问题
在根目录下gradle.properties中添加
org.gradle.java.home=安装路径下/jbr
java 加载jar包
内部加载jar
复制代码
- 需要再main目录先新建assets目录
- 把需要的jar放到目录中
- 不需要权限,最稳定
- 使用方式 复制到内部--> 动态加载 + 反射
- 加载流程
- InputStream is = getAssets().open("xxxx.jar"); //获取assets目录下的jar包
- File destFile = new File(getFilesDir(), "xxxx.jar"); //获取应用私有内部存储目录,?默认路径?:/data/user/0/<包名>/files/
- Files.copy(is, destFile.toPath(), StandardCopyOption.REPLACE_EXISTING); //用 Files 或 IO 工具类复制
外部加载jar
复制代码
- 放在/sdcard/Download/目录下
- 需要权限(Android 6.0 以后 必须满足两个条件才生效:静态申请+动态申请)
- 静态申请权限(在清单文件写入)
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- 动态权限申请
- ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 100);
- 支持热更新,替换jar就生效
- 使用方式 直接路径--> 动态加载 + 反射
通用的加载流程
复制代码
- 如果内部加载 需要先拷贝,外部加载直接指定绝对路径即可,然后都是统一下面的流程
- private File optimizedDir;
- optimizedDir = getDir("自定义名字", MODE_PRIVATE);
- DexClassLoader loader = new DexClassLoader(dexPath, optimizedDir.getAbsolutePath(), null, getClassLoader());
- Class<?> clazz = loader.loadClass("a.b.c.mylibrary.Test"); //加载类 报名+类名
- Object ins_Test = clazz.newInstance(); //实例化类
- Method meth_showFunc = clazz.getMethod("showFunc", int.class); //获取方法, 第一个方法名,后面是方法参数...
- meth_showFunc.invoke(null); //static方法参数传null, 常规方法参数传实例化类属性(如: ins_Test)
NDK JNI 加载jar包
通用的加载流程
复制代码
- 拿到当前的加载器
- jclass cls = env->GetObjectClass(thiz); //拿到当前的Activity的类
- jmethodID mid = env->GetMethodID(cls, "getClassLoader", "()Ljava/lang/ClassLoader;") //找到getClassLoader() 方法
- jobject parentLoader = env->CallObjectMethod(thiz, mid); //调用,获取父加载器
- 创建DexClassLoader 去加载jar文件
- jstring jarPath = env->NewStringUTF("jar的全路径"); //jar路径(必须是真实文件,不能是assets)
- jstring optDir = env->NewStringUTF("/data/data/包名/app_dex"); //优化输出目录
- jclass loaderCls = env->findClass("dalvik/system/DexClassLoader"); //找到DexClassLoader类
- jmethodID ctor = env->GetMethodID(loaderCls, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V"); //找到构造方法
- jobjcet dexLoader = env->NewObject(loaderCls, ctor, jarPath, optDir, NULL, parentLoader) //创建加载类
- 加载jar里面的类
- jmethodID mid_loadClass = env->GetMethodID(loaderCls, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); //找到loadClass方法
- jstring cls_name = env->NewStringUTF("包名.类名") //类名
- jobject tmpClass = (jclass)env->CallObjectMethod(dexLoader, mid_loadClass, cls_name); //加载类
- 创建对象
- jobject ins_obj = env->AllocObject(tmpClass); //这是类的实例
- 方法操作
- jmethodID meth1 = env->GetMethodID(tmpClass, "方法名", "签名"); //找到常规方法
- env->CallVoidMethod(ins_obj, meth1); //调用方法(有多中类型 根据真实方法返回值类型选择), 有参数在后面填写
- jmethodID meth2 = env->GetStaticMethodID(tmpClass, "方法名", "签名"); //找到静态方法
- env->CallStaticVoidMethod(tmpClass, meth2); //调用方法(有多中类型 根据真实方法返回值类型选择), 有参数在后面填写
- 属性操作
- jfieldID fid_age = env->GetFieldID(tmpClass, "age", "I"); //获取属性ID
- env->SetIntField(ins_obj, fid_age, 11); //直接设置属性
- jint age = env->GetIntField(ins_obj, fid_age); //直接读取属性
- 属性不同类型对照
- java属性 --> JNI函数 --> 签名
- int --> GetIntField --> I
- boolean --> GetBooleanField --> Z
- String --> GetObjectField --> Ljava/lang/String;
- float --> GetFloatField --> F
- 对象 --> GetObjectField --> L 全类名;
JNI 接口函数
复制代码
- JNI函数的2个默认参数(所有函数都有)
- JNIEnv *env //JNI环境(所有操作都靠它)
- jobject thiz //Java 调用者(this)
- 最核心5大类接口(能写任何NDK代码)
- 查找类(FindClass)
- 作用: 通过类名找到Java类
- 格式:jclass env->FindClass("类路径用/分隔");
- 获取方法(GetMethodID)
- 作用:获取Java方法的ID(必须反射)
- 格式:jmethodID env->GetMethodID(jclass clazz, const char *name, const char *sig);// 参数1: 类 参数2: 方法名 参数3: 方法签名
- 例子:jmethodID mid = env->GetMethodID(cls, "setAge", "(I)V"); //普通方法例子
jmethodID env->GetStaticMethodID(cls, "setAge", "(I)V"); //静态方法例子
jmethodID ctor = env->GetMethodID(cls, "<init>", "签名"); //构造方法例子
- 创建对象(AllocObject/NewObject)
- 格式:jobject ins_obj = AllocObject(jclass clazz);
- 格式:jobject ins_obj = NewObject(jclass, jmethodID, 参数...);
- 特点:AllocObject不调用构造,适合构造方法为空或者里面逻辑不重要,只需要一个实例,用来调用普通方法或者成员属性;
NewObject调用构造,适合类的构造方法必须执行才能正常使用,需要先获取对应的构造方法MethodID;
- 调用方法(CallXXXMethod)
- 作用:调用Java方法
- 格式:(返回值类型) env->CallVoidMethod(对象, 方法ID, 参数...); //常规方法调用 返回值类型(Jint, jboolean等)
- 格式:(返回值类型) env->CallStaticVoidMethod(类, 方法ID, 参数...); //静态方法调用 返回值类型(Jint, jboolean等)
- 属性操作(Get/SetField)
- 作用:读写Java属性
- 格式:jfieldID env->GetFieldID(类, 属性名, 签名); //获取属性ID
- 格式:(返回值类型) env->GetXXXField(对象, 属性ID); //读取属性值
- 格式:env->SetXXXField(对象, 属性ID, 值);
- 字符串操作(必用)
- 把C语言字符串 --> 转换成java的String对象
- 格式:jstring NewStringUTF(const char *bytes);
- 把Java的String --> 转换成C语言字符串
- 格式:const char *GetStringUTFChars(jstring string, jboolean* isCopy);
- 释放字符串(必须与GetStringUTFChars成对使用, 否则内存泄漏)
- 格式:void ReleaseStringUTFChars(jstring string, const char *chars);
- 获取Java字符串长度
- 格式:jsize GetStringLength(jstring string);