前言
环境
- 语言–Kotlin
- JDK11
- SDK33
- AndroidStudio版本
Android Studio Dolphin | 2021.3.1 Patch 1
Build #AI-213.7172.25.2113.9123335, built on September 30, 2022
概述
- libaray项目打包成jar
- jar通过dx/d8命令行工具转为dex.jar
- dex.jar放到assets目录下
- App启动读取assets中的dex.jar复制到App可访问的文件夹中(建议内部存储的沙盒中,不受权限限制)
- 实例化DexClassLoader加载dex获取ClassLoader对象
- 通过ClassLoader.loadClass方法,获取想要执行的类
- 可以通过接口实现or反射实现,获取类的内部属性或者执行类的内部方法
ClassLoader
与JVM不同,Dalvik的虚拟机不能用ClassCload直接加载.dex,Android从ClassLoader派生出了两个类:DexClassLoader和PathClassLoader;
而这两个类就是我们加载dex文件的关键,这两者的区别是:
1.DexClassLoader:可以加载jar/apk/dex,可以从SD卡中加载未安装的apk;
2.PathClassLoader:要传入系统中apk的存放Path,所以只能加载已经安装的apk文件。
因此我们动态加载dex的核心类是DexClassLoader
实践
打包jar
1.创建一个library项目,命名为dexlib
2.添加如下代码:
引入Glide是为了实验第三方库依赖的可行性。
//Glide4.x
implementation 'com.github.bumptech.glide:glide:4.13.1'
implementation 'com.github.bumptech.glide:okhttp3-integration:4.13.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.1'
class DexWork {
fun showNavToast(context: Context, text: String) {
Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
}
fun loadImage(imageView: ImageView, url: String) {
Glide.with(imageView.context).load(url).into(imageView)
}
fun getClassName(): String {
return this.javaClass.canonicalName
}
}
3.build.gradle中添加打包jar代码,并sync now
//根据Library名称生存jar包到build目录下
//可根据自己需求更改
task againMakeJar(type: Copy) {
//Library名称
def name = project.name
//删除之前的旧jar包
delete 'libs/' + name + '.jar'
//从这个目录下取出默认jar包,不同版本目录均不一样,根据自己项目在build中找classes.jar所在目录
from('build/intermediates/compile_library_classes_jar/release/')
into('libs/') //将jar包输出到指定目录下
include('classes.jar')
rename('classes.jar', name + '.jar') //自定义jar包的名字
}
againMakeJar.dependsOn(build)
4.gradle中双击againMakeJar
,在dexlib/libs中生成dexlib.jar
生成dex.jar
1.在dexlib/libs下打开命令行
2.参考Android 使用dx/d8将jar转换为dex,运行对应的命令行
3.等几秒生成dexlib_dex.jar
4.dexlib_dex.jar是一个包含dex等jar
app项目
1.dexlib_dex.jar
放到app项目等assets目录下,并在build.gradle中添加Glide依赖。
2.启动执行如下代码,将dexlib_dex.jar
复制到内部存储到沙盒中去。
private val dexName = "dexlib_dex.jar"
Utils.copyDex(this, dexName)
/**
* 复制dex到沙盒中
*/
fun copyDex(context: Context, dexName: String) {
val cacheFile = File(context.filesDir, "dex")
if (!cacheFile.exists()) {
cacheFile.mkdirs()
}
val internalPath = cacheFile.absolutePath + File.separator + dexName
val desFile = File(internalPath)
if (!desFile.exists()) {
desFile.createNewFile()
}
var `in`: InputStream? = null
var out: OutputStream? = null
try {
`in` = context.applicationContext.assets.open(dexName)
out = FileOutputStream(desFile.absolutePath)
val bytes = ByteArray(1024)
var len = 0
while (`in`.read(bytes).also { len = it } != -1) out.write(bytes, 0, len)
out.flush()
} catch (e: IOException) {
e.printStackTrace()
} finally {
try {
`in`?.close()
out?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
3.执行如下方法,加载dex,获取到DexClassLoader对象。
Utils.loader = Utils.loadDexClass(this, dexName)
/**
* 加载dex
*/
fun loadDexClass(context: Context, dexName: String): DexClassLoader? {
try {
//下面开始加载dex class
//1.待加载的dex文件路径,如果是外存路径,一定要加上读外存文件的权限,
//2.解压后的dex存放位置,此位置一定要是可读写且仅该应用可读写
//3.指向包含本地库(so)的文件夹路径,可以设为null
//4.父级类加载器,一般可以通过Context.getClassLoader获取到,也可以通过ClassLoader.getSystemClassLoader()取到。
val cacheFile = File(context.filesDir, "dex")
val internalPath = cacheFile.absolutePath + File.separator + dexName
return DexClassLoader(internalPath, cacheFile.absolutePath, null, context.classLoader)
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
4.根据完整类名获取DexWork类,反射代码执行DexWork类中的方法,完整Activity代码如下:
Tips:接口实现的方式相较于反射实现起来更简单,可参考:https://blog.csdn.net/wy353208214/article/details/50859422
class NormalActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_show)
/**
* 非静态类反射,kt代码跟java代码反射调用完全一致
* invoke 第一个参数传入类实例
*/
val cla = Utils.loader?.loadClass("com.demon.dexlib.DexWork")
cla?.run {
val className = getMethod("getClassName").invoke(newInstance()) as String
findViewById<TextView>(R.id.text).text = className
findViewById<Button>(R.id.btn1).setOnClickListener {
getMethod("showNavToast", Context::class.java, String::class.java).invoke(newInstance(), this@NormalActivity, className)
}
val img = findViewById<ImageView>(R.id.iv)
findViewById<Button>(R.id.btn2).setOnClickListener {
getMethod("loadImage", ImageView::class.java, String::class.java).invoke(
newInstance(), img,
"https://idemon.oss-cn-guangzhou.aliyuncs.com/D.png"
)
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="原生toast"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Load Image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn1" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Hello World!"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn2" />
<ImageView
android:id="@+id/iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text" />
</androidx.constraintlayout.widget.ConstraintLayout>
5.运行app项目,发行可以正常DexWork中的代码,图片也可以正常加载(注意网络权限)。
6.dex动态加载实践完成。也可知:app项目同时引入library中的第三库依赖,可以解决jar无法打包第三方库代码的问题。
效果截图
源码
https://github.com/DeMonDemoSpace/DexDynamicLoad文章来源:https://www.toymoban.com/news/detail-411382.html
参考
https://blog.csdn.net/wy353208214/article/details/50859422文章来源地址https://www.toymoban.com/news/detail-411382.html
到了这里,关于Android dex动态加载(Kotlin版)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!