关于安卓类加载机制分析可以参考:https://blog.youkuaiyun.com/u013394527/article/details/80980340
类加载机制 是 研究插件化 和 热修复 的基础。 加载外部 dex文件中的类,我们在这部分主要做的流程有:
- 1.编写基本的Java文件并编译为.class文件。
- 2.将.class文件转为.dex文件。
- 3.将转好的dex文件放入创建好的Android工程内并在启动时将其写入本地。
- 4.加载解压后的.dex文件中的类,并调用其方法进行测试。
前提提交:系统环境里,配置jdk,Android sdk,然后开始以下操作:
1、创建一个独立的java 类 (TestUtil.java)
public class TestUtil {
public void testFunc() {
System.out.println("jack test func");
}
public String getStr(int no) {
return "jack number:"+no;
}
}
2, 用jdk里的javac 编译生成class文件,以下是编译:
3.用Android sdk里的dx工具,把class文件生成dex: (Android\Sdk\build-tools\30.0.3\dx.bat ),build-tools的每个版本下都有dx,我用的是build-tools\30.0.3下dx.bat
dx的基本用法如下:
dx --dex [--output=<file>] [<file>.class | <file>.{zip,jar,apk} | <directory>] ...
参数1:--dex 表示要执行打包 dex动作;
参数2: --output=myA.dex 表示设置生成的文件的名称;
参数3:需要转换的class文件
同理:也可以把jar打包成dex
dx --dex --output=mytarget.dex myorigin.jar
4,在Android工程中加载外部dex文件
实现思路:把外部 独立的 dex文件 拷贝到 安卓项目的 assets 目录下, app运行时 将 asstes 目录下的文件 通过 流式读取 复制到 应用的私有目录下。
为什么要复制出来?因为assets目录下的文件并不能直接通过new File(xxx_path)的形式来访问, 它只能通过 Context.getAssets().open() 来获取一个访问流。
通过 DexClassLoader 读取 复制到app私有目录 下的 dex,调用 dexClassLoader.loadClass("Test") 获取 对应的 Class<?>, 并通过构造函数创建找对象,再通过反射访问其内部的方法
源码如下:
DexClassLoader dexClassLoader_a;
public void OperateDexA(View view) {
if (null == dexClassLoader_a) {
dexClassLoader_a = loadDex("myA.dex", false);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (null == dexClassLoader_a) {
return;
}
try {
Class<?> clazz = dexClassLoader_a.loadClass("TestUtil");
System.out.println("loaded clas: " + clazz);
System.out.println("class loader: " + clazz.getClassLoader());
System.out.println("class loader parent: " + clazz.getClassLoader().getParent());
Constructor constructor = clazz.getConstructor();
constructor.setAccessible(true);
Object obj = constructor.newInstance();
Method getStr = clazz.getDeclaredMethod("getStr", int.class);
// getStr.setAccessible(true);
Object result = getStr.invoke(obj, 666);
System.out.println("result:>>>>>>>> " + result.toString());
Method testFunc = clazz.getDeclaredMethod("testFunc");
testFunc.setAccessible(true);
testFunc.invoke(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
//dex文件里要加载so库,演示如何载入带有so库的dex
private DexClassLoader loadDex(String dexName, boolean hasLibs) {
File originDex = null;
try {
InputStream open = getAssets().open(dexName);
File dexOutputDir = getCacheDir();
System.out.println("getCacheDir---189: " + dexOutputDir.getAbsolutePath());
originDex = new File(dexOutputDir, dexName);
FileOutputStream fileOutputStream = new FileOutputStream(originDex);
byte[] bytes = new byte[1024];
int length = 0;
while ((length = open.read(bytes)) != -1) {
fileOutputStream.write(bytes, 0, length);
}
fileOutputStream.flush();
fileOutputStream.close();
open.close();
} catch (IOException e) {
e.printStackTrace();
}
// 2.创建DexClassLoader加载dex文件中的类
if (originDex != null) {
File dexOptimizeDir = getDir("dex", Context.MODE_PRIVATE);
String dexOutputPath = dexOptimizeDir.getAbsolutePath();
//把外部dex要System.loadLibrary("native-lib")的so文件放在jniLibs目录打包进当前应用里,
// 演示如何让外部的dex在当前应用里设置搜索libpath
String librarySearchPath = null;
if (hasLibs) {
librarySearchPath = getApplicationInfo().nativeLibraryDir;
System.out.println("librarySearchPath: " + librarySearchPath);
}
return new DexClassLoader(originDex.getAbsolutePath(), dexOutputPath, librarySearchPath,
getClassLoader());
} else {
return null;
}
}
执行运行后输出如下:
二、如何把不同包下的class文件打包成一个dex。(放在包下的类,要有相应的文件路径,如:com\jack\Category.class)
调用代码如下:
DexClassLoader dexClassLoader_b;
public void OperateDexB(View view) {
if (null == dexClassLoader_b) {
dexClassLoader_b = loadDex("myB.dex", false);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (null == dexClassLoader_b) {
return;
}
try {
Class<?> categoryclazz = dexClassLoader_b.loadClass("com.jack.Category");
System.out.println("loaded clas: " + categoryclazz);
System.out.println("class loader: " + categoryclazz.getClassLoader());
System.out.println("class loader parent: " + categoryclazz.getClassLoader().getParent());
Constructor categoryconstructor = categoryclazz.getConstructor();
categoryconstructor.setAccessible(true);
Object category = categoryconstructor.newInstance();
Field channel = categoryclazz.getDeclaredField("channel");
// channel.setAccessible(true);
channel.set(category, "cmakeDemo_channel");
Method cateMethod = categoryclazz.getDeclaredMethod("toString");
System.out.println("toString:>>>>>> " + cateMethod.invoke(category));
Method cateSum = categoryclazz.getDeclaredMethod("sum", int.class, int.class);
cateSum.invoke(category, 10, 22);
System.out.println(">>>>>>>>>>>>>>>>>>>Employee:>>>>>>>> ");
Class<?> clazz = dexClassLoader_b.loadClass("Employee");
Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class, int.class);
// constructor.setAccessible(true);
Object obj = constructor.newInstance("Peter", 1, 8000);
Method raiseSalary = clazz.getDeclaredMethod("raiseSalary", int.class);
raiseSalary.setAccessible(true);
raiseSalary.invoke(obj, 5000);
Method printEmployee = clazz.getDeclaredMethod("printEmployee");
printEmployee.setAccessible(true);
printEmployee.invoke(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
//dex文件里要加载so库,演示如何载入带有so库的dex
private DexClassLoader loadDex(String dexName, boolean hasLibs) {
File originDex = null;
try {
InputStream open = getAssets().open(dexName);
File dexOutputDir = getCacheDir();
System.out.println("getCacheDir---189: " + dexOutputDir.getAbsolutePath());
originDex = new File(dexOutputDir, dexName);
FileOutputStream fileOutputStream = new FileOutputStream(originDex);
byte[] bytes = new byte[1024];
int length = 0;
while ((length = open.read(bytes)) != -1) {
fileOutputStream.write(bytes, 0, length);
}
fileOutputStream.flush();
fileOutputStream.close();
open.close();
} catch (IOException e) {
e.printStackTrace();
}
// 2.创建DexClassLoader加载dex文件中的类
if (originDex != null) {
File dexOptimizeDir = getDir("dex", Context.MODE_PRIVATE);
String dexOutputPath = dexOptimizeDir.getAbsolutePath();
//把外部dex要System.loadLibrary("native-lib")的so文件放在jniLibs目录打包进当前应用里,
// 演示如何让外部的dex在当前应用里设置搜索libpath
String librarySearchPath = null;
if (hasLibs) {
librarySearchPath = getApplicationInfo().nativeLibraryDir;
System.out.println("librarySearchPath: " + librarySearchPath);
}
return new DexClassLoader(originDex.getAbsolutePath(), dexOutputPath, librarySearchPath,
getClassLoader());
} else {
return null;
}
}
运行的结果如下:
三、加载的dex需要载入动态库 so文件的
可以用AS 生成一个带有so的apk
生成dex有两个方法:
①、编写gradle的task 把需要打包的class 打包生成jar,然后在利用以上的dx命令打包生成dex文件
def curVersion = '1.0.3.12'
def jarName = "Player-" + getDate() + "-v" + curVersion + "-release"
def javaFile = file('build/libs/java.jar')
//package jar
task buildJavaJar(type: Jar) {
archiveName = "java.jar"
def srcClassDir = [project.buildDir.absolutePath + "/intermediates/javac/debug/classes"]
from srcClassDir
exclude "com/mymusic/player/BuildConfig.class"
exclude "com/mymusic/player/MainActivity.class"
include "com/mymusic/beans/Node.class"
include "com/mymusic/utils/JNITools.class"
include "com/mymusic/utils/MyNetWork.class"
}
task buildAssetsJar(type: Jar, dependsOn: buildJavaJar) {
from zipTree(javaFile)
from fileTree(dir: 'src/main', includes: ['assets/**'])
baseName = jarName
}
②就是把生成的apk里dex取出来
然后把生成的so文件,拷贝到jniLibs文件下
拷贝到以下
DexClassLoader的 librarySearchPath 改为当前应用的lib:(librarySearchPath = getApplicationInfo().nativeLibraryDir;)
源码如下:
//dex文件里要加载so库,演示如何载入带有so库的dex
private DexClassLoader loadDex(String dexName, boolean hasLibs) {
File originDex = null;
try {
InputStream open = getAssets().open(dexName);
File dexOutputDir = getCacheDir();
System.out.println("getCacheDir---189: " + dexOutputDir.getAbsolutePath());
originDex = new File(dexOutputDir, dexName);
FileOutputStream fileOutputStream = new FileOutputStream(originDex);
byte[] bytes = new byte[1024];
int length = 0;
while ((length = open.read(bytes)) != -1) {
fileOutputStream.write(bytes, 0, length);
}
fileOutputStream.flush();
fileOutputStream.close();
open.close();
} catch (IOException e) {
e.printStackTrace();
}
// 2.创建DexClassLoader加载dex文件中的类
if (originDex != null) {
File dexOptimizeDir = getDir("dex", Context.MODE_PRIVATE);
String dexOutputPath = dexOptimizeDir.getAbsolutePath();
//把外部dex要System.loadLibrary("native-lib")的so文件放在jniLibs目录打包进当前应用里,
// 演示如何让外部的dex在当前应用里设置搜索libpath
String librarySearchPath = null;
if (hasLibs) {
librarySearchPath = getApplicationInfo().nativeLibraryDir;
System.out.println("librarySearchPath: " + librarySearchPath);
}
return new DexClassLoader(originDex.getAbsolutePath(), dexOutputPath, librarySearchPath,
getClassLoader());
} else {
return null;
}
}
DexClassLoader dexClassLoader;
public void operate_another_dex(View view) {
if (null == dexClassLoader) {
dexClassLoader = loadDex("myclasses.dex", true);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (null == dexClassLoader) {
return;
}
try {
Class<?> jnitoolclazz = dexClassLoader.loadClass("com.mymusic.utils.JNITools");
System.out.println("loaded clas: " + jnitoolclazz);
System.out.println("class loader: " + jnitoolclazz.getClassLoader());
System.out.println("class loader parent: " + jnitoolclazz.getClassLoader().getParent());
Constructor jnitoolconstructor = jnitoolclazz.getConstructor();
jnitoolconstructor.setAccessible(true);
Object jnitoolobj = jnitoolconstructor.newInstance();
Method jnitoolMethod = jnitoolclazz.getDeclaredMethod("testNodeFunc", int.class, String.class);
System.out.println("testNodeFunc:>>>>>> " + jnitoolMethod.invoke(jnitoolobj, 100, "my-name is Peter"));
Method jnitoolstringFromJNI = jnitoolclazz.getDeclaredMethod("stringFromJNI");
System.out.println("stringFromJNI:" + jnitoolstringFromJNI.invoke(jnitoolobj));
Method jnitoolsum = jnitoolclazz.getDeclaredMethod("jniSum", int.class, int.class);
System.out.println("jnitoolsum>>>>>:" + jnitoolsum.invoke(jnitoolobj, 101, 202));
} catch (Exception e) {
e.printStackTrace();
}
try {
Class<?> jninetclazz = dexClassLoader.loadClass("com.mymusic.utils.MyNetWork");
System.out.println("loaded clas: " + jninetclazz);
System.out.println("class loader: " + jninetclazz.getClassLoader());
System.out.println("class loader parent: " + jninetclazz.getClassLoader().getParent());
Constructor jninetconstructor = jninetclazz.getConstructor();
jninetconstructor.setAccessible(true);
Object jninetobj = jninetconstructor.newInstance();
Method jninetMethod = jninetclazz.getDeclaredMethod("syncGet", String.class);
System.out.println("syncGet:>>>>>> " + jninetMethod.invoke(jninetobj, "https://www.baidu.com"));
} catch (Exception e) {
e.printStackTrace();
}
}
输出如下:
调用的外部代码关键部分也贴出来,如下:
package com.mymusic.utils;
import android.util.Log;
import com.mymusic.beans.Node;
public class JNITools {
String TAG = "jniTag";
static {
System.loadLibrary("good-native-lib");
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native int jniSum(int a, int b);
public String testNodeFunc(int no, String name) {
Node node = new Node(no, name);
Log.d(TAG, "init data>>>>>: " + node);
node.updateData(888, stringFromJNI());
Log.d(TAG, "three updateDate data>>>>>: " + node);
return "JNITools:" + node;
}
}
#include <jni.h>
#include <string>
#include "android/log.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifndef LOG_TAG
#define LOG_TAG "AndroidJNI"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#endif
JNIEXPORT jstring JNICALL
Java_com_mymusic_utils_JNITools_stringFromJNI(JNIEnv *env, jobject thiz) {
std::string hello = "hello from C++";
return env->NewStringUTF(hello.c_str());
}
#ifdef __cplusplus
};
#endif
extern "C"
JNIEXPORT jint JNICALL
Java_com_mymusic_utils_JNITools_jniSum(JNIEnv *env, jobject thiz, jint a, jint b) {
jint result = a + b;
// __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "%d + %d = %d", a, b, result);
LOGE("%d + %d = %d", a, b, result);
return result;
}
cmake_minimum_required(VERSION 3.10.2)
project("player")
add_library( # Sets the name of the library.
good-native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
good-native-lib.cpp)
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
target_link_libraries( # Specifies the target library.
good-native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib})
总结一下,以此勉励,如有什么问题欢迎指正。
参考以下文章:
https://blog.youkuaiyun.com/u013394527/article/details/81384468