Android怎样动态加载代码技术

2026年09月25日 18:24
有2个网友回答
网友(1):

在开发Android App的过程当中,可能希望实现插件式软件架构,将一部分代码以另外一个APK的形式单独发布,而在主程序中加载并执行这个APK中的代码。

实现这个任务的一般方法是:

复制代码
// 加载类cls
Context pluginContext = mainContext.createPackageContext(PLUGIN_PKG, Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE);
ClassLoader loader = pluginContext.getClassLoader();
Class cls = loader.loadClass(CLASS_NAME);
// 通过反射技术,调用cls中的方法,下面是一个示例,实际代码因情况而定
Object obj = cls.newInstance();
Method method = cls.getDeclaredMethod("someMethod");
method.invoke(obj);
复制代码
但是,这个方法在Android 4.1及之后的系统中存在一些问题:对于收费应用,Google Play会将其安装在一个加密目录之下(具体就是/data/app-asec),而不是一个普通目录之下(具体就是/data/app);安装在加密目录中的应用,我们是无法使用上述方法来加载并执行代码的;而实际情况是,我们经常就是依靠插件应用来收费的。

解决上述问题的一个方案是:将插件的二进制代码拷贝到SD卡中,主程序从SD卡中加载并执行其代码。

实现这个任务的具体方法是:

复制代码
Class cls = null;
try {
// 尝试第一种方法
cls = loadClass1(mainContext, pkg, entryCls);
} catch (Exception e) {
// 尝试第二种方法
cls = loadClass2(mainContext, pkg, entryCls);
}
// 示例代码
Object obj = cls.newInstance();
Method method = cls.getDeclaredMethod("someMethod");
method.invoke(obj);
// 第一种加载方法
private Class loadClass1(Context mainContext, String pkg, String entryCls) throws Exception {
Context pluginContext = mainContext.createPackageContext(pkg, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
ClassLoader loader = pluginContext.getClassLoader();
return loader.loadClass(entryCls);
}

// 第二种加载方法
private Class loadClass2(Context mainContext, String pkg, String entryCls) throws Exception {
Context pluginContext = mainContext.createPackageContext(pkg, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
String path = generatePluginDexPath(mainContext, pkg);
ensureFileExist(pluginContext, pkg, path);
// cacheDir必须是主程序的私有目录,否则DexClassLoader可能会拒绝加载
String cacheDir = mainContext.getApplicationInfo().dataDir;
ClassLoader parentLoader = pluginContext.getClassLoader();
DexClassLoader loader = new DexClassLoader(path, cacheDir, null, parentLoader);
return loader.loadClass(entryCls);
}

// 获取程序版本号
private int getVersionCode(Context context, String pkg) {
PackageInfo info = null;
int versionCode = 0;
try {
info = context.getPackageManager().getPackageInfo(pkg, PackageManager.GET_ACTIVITIES);
versionCode = info.versionCode;
} catch (Exception e) {}
return versionCode;
}

// 获取插件二进制代码的存储位置,注意做好版本控制;路径必须是以.dex结束,否则加载会出问题
private String generatePluginDexPath(Context context, String pkg) {
int version = getVersionCode(context, pkg);
String path = getMyAppPath() + ".classes/" + pkg + version + ".dex";
return path;
}

// 主程序在SD卡上的数据目录
private String getMyAppPath() {
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/MyApp/";
}
// 拷贝插件的二进制代码到SD卡
private void ensureFileExist(Context pluginContext, String pkg, String path) throws Exception {
File file = new File(path);
if(file.exists()) return;
file.getParentFile().mkdirs();
Resources res = pluginContext.getResources();
int id = res.getIdentifier("classes", "raw", pkg);
InputStream in = res.openRawResource(id);
FileOutputStream out = new FileOutputStream(file);
try {
byte[] buffer = new byte[1024 * 1024];
int n = 0;
while((n = in.read(buffer)) > 0) {
out.write(buffer, 0, n);
} out.flush();
} catch (IOException e) {
in.close();
out.close();
}
}
复制代码

插件工程这边也需要做相应的修改:

1.编译插件工程;

2.将bin目录之下的classes.dex拷贝到/res/raw目录之下;

3.重新编译插件工程;

4.发布插件APK。

网友(2):

在开发Android App的过程当中,可能希望实现插件式软件架构,将一部分代码以另外一个apk的形式单独发布,而在主程序中加载并执行这个apk中的代码。

实现这个任务的一般方法是:

复制代码
// 加载类cls
Context pluginContext = mainContext.createPackageContext(PLUGIN_PKG, Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE);
classLoader loader = pluginContext.getclassLoader();
class cls = loader.loadclass(class_NAME);
// 通过反射技术,调用cls中的方法,下面是一个示例,实际代码因情况而定
Object obj = cls.newInstance();
Method method = cls.getDeclaredMethod("someMethod");
method.invoke(obj);
复制代码
但是,这个方法在Android 4.1及之后的系统中存在一些问题:对于收费应用,Google Play会将其安装在一个加密目录之下(具体就是/data/app-asec),而不是一个普通目录之下(具体就是/data/app);安装在加密目录中的应用,我们是无法使用上述方法来加载并执行代码的;而实际情况是,我们经常就是依靠插件应用来收费的。

解决上述问题的一个方案是:将插件的二进制代码拷贝到SD卡中,主程序从SD卡中加载并执行其代码。

实现这个任务的具体方法是:

复制代码
class cls = null;
try {
// 尝试第一种方法
cls = loadclass1(mainContext, pkg, entrycls);
} catch (Exception e) {
// 尝试第二种方法
cls = loadclass2(mainContext, pkg, entrycls);
}
// 示例代码
Object obj = cls.newInstance();
Method method = cls.getDeclaredMethod("someMethod");
method.invoke(obj);
// 第一种加载方法
private class loadclass1(Context mainContext, String pkg, String entrycls) throws Exception {
Context pluginContext = mainContext.createPackageContext(pkg, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
classLoader loader = pluginContext.getclassLoader();
return loader.loadclass(entrycls);
}

// 第二种加载方法
private class loadclass2(Context mainContext, String pkg, String entrycls) throws Exception {
Context pluginContext = mainContext.createPackageContext(pkg, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
String path = generatePluginDexPath(mainContext, pkg);
ensureFileExist(pluginContext, pkg, path);
// cacheDir必须是主程序的私有目录,否则DexclassLoader可能会拒绝加载
String cacheDir = mainContext.getApplicationInfo().dataDir;
classLoader parentLoader = pluginContext.getclassLoader();
DexclassLoader loader = new DexclassLoader(path, cacheDir, null, parentLoader);
return loader.loadclass(entrycls);
}

// 获取程序版本号
private int getVersionCode(Context context, String pkg) {
PackageInfo info = null;
int versionCode = 0;
try {
info = context.getPackageManager().getPackageInfo(pkg, PackageManager.GET_ACTIVITIES);
versionCode = info.versionCode;
} catch (Exception e) {}
return versionCode;
}

// 获取插件二进制代码的存储位置,注意做好版本控制;路径必须是以.dex结束,否则加载会出问题
private String generatePluginDexPath(Context context, String pkg) {
int version = getVersionCode(context, pkg);
String path = getMyAppPath() + ".classes/" + pkg + version + ".dex";
return path;
}

// 主程序在SD卡上的数据目录
private String getMyAppPath() {
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/MyApp/";
}
// 拷贝插件的二进制代码到SD卡
private void ensureFileExist(Context pluginContext, String pkg, String path) throws Exception {
File file = new File(path);
if(file.exists()) return;
file.getParentFile().mkdirs();
Resources res = pluginContext.getResources();
int id = res.getIdentifier("classes", "raw", pkg);
InputStream in = res.openRawResource(id);
FileOutputStream out = new FileOutputStream(file);
try {
byte[] buffer = new byte[1024 * 1024];
int n = 0;
while((n = in.read(buffer)) > 0) {
out.write(buffer, 0, n);
} out.flush();
} catch (IOException e) {
in.close();
out.close();
}
}
复制代码

插件工程这边也需要做相应的修改:

1.编译插件工程;

2.将bin目录之下的classes.dex拷贝到/res/raw目录之下;

3.重新编译插件工程;

4.发布插件APK。