Android
中常用的两种类加载器:PathClassLoader
和DexClassLoader
,它们都继承于BaseDexClassLoader
。
PathClassLoader
可以加载已经安装到Android
系统中的apk
文件,在ART
虚拟机上可以加载未安装的apk
的dex
,在Dalvik
则不行;DexClassLoader
可以加载jar
、dex
和未安装的apk
。
考虑到需要兼容4.0
版本的Android
系统,以下选用DexClassLoader
实现动态加载。
动态加载jar
一、新建项目PluginDemo
此时会默认生成Project
的主module
app
gradle
版本选择使用3.1.4
二、新建module
PluginJarDemo
新建类PluginJarDemo
,以作演示。
package com.don.pluginjardemo;
import android.util.Log;
/**
* Created by don on 2019-09-09
*/
public class PluginJarDemo {
private final String TAG = "PluginJarDemo";
private String content;
public void writeTest(String content) {
Log.i(TAG, "writeTest " + content + " " + this);
this.content = content;
}
public String readTest() {
Log.i(TAG, "readTest " + this);
return content;
}
}
接下来通过DexClassLoader
动态加载PluginJarDemo
1、编译module
PluginJarDemo
,生成jar
包
task makeJar(type: Jar, dependsOn: [':PluginJarDemo:assembleRelease' ]) {
archiveName = "pluginDemo.jar"
destinationDir = file('build/libs')
def where = "build/intermediates/classes/release"
from(where)
from fileTree(dir: 'src/main', includes: ['assets/**'])
exclude('**/R.class')
exclude('**/R\$*.class')
include "**/*.*"
}
在命令行中执行
gradlew :PluginJarDemo:makeJar
在PluginJarDemo
的build/libs/
目录下会生成pluginDemo.jar
文件。
这种直接生成的jar
包是不能被DexClassLoader
加载的,因为其中没有dex
文件。
2、生成包含dex
文件的新jar
包
task makeDex(type: Exec, description: 'for PluginJarDemo dex') {
doFirst {
println("convert jar to dex start")
// 拼接的dex转换工具路径
Properties properties = new Properties()
//local.properites也放在posdevice目录下
File propertyFile = new File(rootDir.getAbsolutePath() + "/local.properties")
properties.load(propertyFile.newDataInputStream())
def dexToolPath = properties.getProperty('sdk.dir') + "/build-tools/27.0.3"
// dex转换工具参数
def dexToolArgs = "--dex --output="
def releaseJar = "build/libs/pluginDemo.jar"
def dexJar = "build/libs/pluginDex.jar"
def finalCmd
def dxToolName
if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
// Windows
dxToolName = "dx.bat"
finalCmd = dexToolPath + "/$dxToolName " + dexToolArgs + dexJar + " " + releaseJar
finalCmd = finalCmd.replaceAll("/", "\\\\")
commandLine "cmd", "/c", finalCmd
} else {
// linux
dxToolName = "dx"
finalCmd = dexToolPath + "/$dxToolName " + dexToolArgs + dexJar + " " + releaseJar
finalCmd = finalCmd.replaceAll("\\\\", "/")
commandLine "/bin/sh", "-c", finalCmd
}
println("convert jar to dex end, command: " + finalCmd)
}
}
在命令行中运行
gradlew :PluginJarDemo:makeDex
在PluginJarDemo
的build/libs/
目录下会生成pluginDex.jar
文件,这个文件包含class.dex
文件,可被DexClassLoader
动态加载。
三、动态加载pluginDex.jar
在主module
app
中新建类JarLoad
用于动态加载pluginDex.jar
package com.don.plugindemo;
import android.content.Context;
import android.util.Log;
import java.io.File;
import java.lang.reflect.Method;
import dalvik.system.DexClassLoader;
/**
* Created by don on 2019-09-09
*/
public class JarLoader {
private final static String TAG = "JarLoader";
private final String DEMO_CLASS = "com.don.pluginjardemo.PluginJarDemo";
private Class mDemoClass;
private Object mDemoInstance;
public void load(Context context, String pluginPath) {
File file = new File(pluginPath);
if (!file.exists()) {
Log.i(TAG, "load ignore, invalid file: " + pluginPath);
return;
}
String dataFile = context.getFilesDir().getAbsolutePath();
try {
DexClassLoader dexClassLoader = new DexClassLoader(pluginPath, dataFile, null, getClass().getClassLoader());
mDemoClass = dexClassLoader.loadClass(DEMO_CLASS);
mDemoInstance = mDemoClass.newInstance();
} catch (Exception e) {
Log.w(TAG, e);
}
}
public void writeTest(String content) {
try {
Method method = mDemoClass.getDeclaredMethod("writeTest", String.class);
method.invoke(mDemoInstance, content);
} catch (Exception e) {
Log.w(TAG, e);
}
}
public String readTest() {
try {
Method method = mDemoClass.getDeclaredMethod("readTest");
return method.invoke(mDemoInstance).toString();
} catch (Exception e) {
Log.w(TAG, e);
}
return null;
}
}
DexClassLoader
的创建需要填4个参数:
- 第一个是jar包的地址
- 第二个是jar包中dex文件被加载之后的路径,需要可执行,这里使用了app的安* 装根目录
- 第三个是so文件目录,如果该jar包需要加载so,则需要传入so文件所在目录,同样的,此目录需要有可执行权限
- 第四个是父类构造器,使用当前app的构造器即可
jar
包动态加载成功之后可以通过反射机制创建jar
包中的PluginJarDemo
对象并操作。
在主界面添加一个按钮,点击之后读取PluginJarDemo
实例中的content
变量。
package com.don.plugindemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private JarLoader mJarLoader;
private Button mJarBtn;
private TextView mResultTxt;
private View.OnClickListener mClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.jarBtn:
mJarLoader.writeTest("jar load test");
mResultTxt.setText(mJarLoader.readTest());
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
mJarLoader = new JarLoader();
mJarLoader.load(this,"sdcard/pluginDex.jar");
}
private void initView() {
mJarBtn = (Button) findViewById(R.id.jarBtn);
mJarBtn.setOnClickListener(mClickListener);
mResultTxt = (TextView) findViewById(R.id.resultTxt);
}
}
运行,点击按钮 JAR
。
如下图,可以从pluginDex.jar
中成功读取content
变量
动态加载dex
与动态加载jar
类似,唯一的区别是需要将打包的jar
转换为dex
文件,以上述实现为例,通过下面的命令可以得到一个可用于动态加载的dex
文件
dx --dex --output=pluginDex.dex pluginDemo.jar
接下来通过加载pluginDex.dex
的实现,与动态加载pluginDex.jar
文件一样。
网友评论