Android dex动态加载(Kotlin版)

news2025/1/6 21:55:12

前言

环境

  1. 语言–Kotlin
  2. JDK11
  3. SDK33
  4. AndroidStudio版本
Android Studio Dolphin | 2021.3.1 Patch 1
Build #AI-213.7172.25.2113.9123335, built on September 30, 2022

概述

  1. libaray项目打包成jar
  2. jar通过dx命令行工具转为dex.jar
  3. dex.jar放到assets目录下
  4. App启动读取assets中的dex.jar复制到App可访问的文件夹中(建议内部存储的沙盒中,不受权限限制)
  5. 实例化DexClassLoader加载dex获取ClassLoader对象
  6. 通过ClassLoader.loadClass方法,获取想要执行的类
  7. 可以通过接口实现or反射实现,获取类的内部属性或者执行类的内部方法
dx命令行工具

dx位于AndroidSDK build-tools中,我的mac中有4个版本:

1. 28.0.3(有dx)
2. 29.0.2(有dx)
3. 30.0.3(有dx)
4. 33.0.0(无dx,但是将dx&lib/dx.jar复制过来可正常使用)
  1. windows电脑可以直接在目录下执行dx,mac需要./dx来执行。
  2. 建议将AndroidSDK/build-tools/版本号配置到环境变量中。

在这里插入图片描述

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.执行: dx --dex --output=dexlib_dex.jar dexlib.jar
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://blog.csdn.net/wy353208214/article/details/50859422

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/75616.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

外贸业务12年,我想和大家分享这几点感受

如今再回看这段经历&#xff0c;很庆幸我的三观一直都很正确&#xff0c;那就是买家第一。 不管是什么原因&#xff0c;只要你想退&#xff0c;我都可以接受退&#xff0c;我不能退回上级供应商的那我就自己留着&#xff0c;只为了不想因为这一次拒绝而失去这个买家&#xff1…

springboot集成security(鉴权)

本文承接上一章节内容&#xff1a;springboot集成security&#xff08;认证&#xff09; 上一章节&#xff1a; https://blog.csdn.net/m0_54355172/article/details/128239128 1. 授予静态资源访问权限 因为我的演示案例涉及到多个页面&#xff0c;所以先说一下如何给静态资源…

数据挖掘——关联规则(Association Rule)Apriori算法和python代码实现

关联规则&#xff08;Association Rule&#xff09;什么是关联规则一些基本概念任务是什么Apriori 算法核心思想步骤与流程图如何找到候选集python代码实现什么是关联规则 关联规则(Association Rules)是反映一个事物与其他事物之间的相互依存性和关联性&#xff0c;是数据挖掘…

线程2的深度剖析

加锁 synchronized 1.修饰方法&#xff08;普通方法&#xff0c;静态方法&#xff09;普通方法实际上加到了this上&#xff0c;静态方法加到了类对象上。 2.修饰代码块 手动指定加到那个对象上 明确锁对象针对那个对象加锁&#xff0c;如果两个线程针对同一个对象加锁&am…

(1)AWD入门攻略大纲

1.比赛介绍 (1)比赛环境 (2)常见服务器信息介绍 比赛名称 白名单&#xff1a;一般用于防止外部恶意攻击&#xff0c;如果赛方发现名单以外IP可能会进行封禁处理。 服务器账号密码 Token和虚拟IP&#xff1a;token为提交答案的凭证&#xff0c;绑定了队伍&#xff1b;虚拟IP为…

SpringCloud中Feign注解@FeignClient参数一览表

写在前面 Feign是微服务中服务间调用的优选组件&#xff0c;后来的OpenFeign也是基于此来开展的。 为什么要梳理一下Feign注解FeignClient中的各个参数&#xff1f; 踩坑太多面试总问 参数一栏表 FeignClient的源码示例图如下&#xff1a; 今天我们接着来说最后的几个参数。…

Java面试题(六)美团JVM夺命7连问(灵魂拷问)

0.来看一道美团的面试题 这题直接把人给问懵逼了&#xff0c;你能全部答出来吗&#xff1f; Object o new Object();请解释对象的创建过程&#xff1f;DCL要不要加volatile问题&#xff1f;对象在内存中的存储布局&#xff1f;什么是指针压缩&#xff1f;对象头具体包含哪些…

生成树问题汇总

生成树问题汇总注1、最小(大)生成树思路代码例子&#xff1a;1、最小生成树结果是2、最大生成树结果2、在最小生成树中再加一条边&#xff0c;求新的最小生成树思路代码核心代码全部代码例子3、次小生成树思路:在上一个功能基础上进一步扩充代码核心代码全部代码例子4、判断最小…

一个轻量级的分布式日志标记追踪神器,十分钟接入,非常好用!

TLog简介 1、TLog通过对日志打标签完成企业级微服务的日志追踪。它不收集日志&#xff0c;使用简单&#xff0c; 产生全局唯一的追踪码。除了追踪码以外&#xff0c;TLog还支持SpanId和上下游服务信息 标签的追加。 2、为用户使用方便而设计&#xff0c;提供完全零侵入式接入…

es入门(上)

笔记来源于学习 b站中的【IT李老师】的elasticsearch课程 自己在实习做的es模块中的理解。 后续会有 中&#xff0c;下篇笔记更新&#xff0c;目前这一篇是上篇。 目录 Elastic Stack简介 1.1简介 1.2特色 1.3组件介绍 2.Elasticsearch的接收与核心概念 2.1搜索是什么…

【Keras+计算机视觉+Tensorflow】OCR文字识别实战(附源码和数据集 超详细必看)

需要源码和数据集请点赞关注收藏后评论区留言私信~~~ 一、OCR文字识别简介 利用计算机自动识别字符的技术&#xff0c;是模式识别应用的一个重要领域。人们在生产和生活中&#xff0c;要处理大量的文字、报表和文本。为了减轻人们的劳动&#xff0c;提高处理效率&#xff0c;从…

[python]初步练习脚本

之前练习的python&#xff0c;编写的脚本&#xff0c;现在作为记录&#xff0c;方便查看~ python 初步练习脚本基础部分的练习脚本脚本代码1、helloworld.py&#xff0c;有for循环语句2、main.py3、range—test.py&#xff0c;范围4、RE.py&#xff0c;花式输出内容5、turtle练…

Jekins安装和部署

1.官网下载 注意jekins各版本不同支持jdk的版本也不同 https://www.jenkins.io/download/ 如图进去后可看见最新版&#xff0c;而past releases是历史版本 查看自己各版本的支持 我下载的是2.346.1版本&#xff0c;是war包形式 2.启动jekins 直接在war包路径 java命令启动…

lspci命令整理

1. 作用&#xff1a; 显示当前主机的所有PCI总线信息 2. 常用指令&#xff1a; lspci -nn 第一列的数字&#xff1a;总线编号(Bus Number)&#xff1a;设备编号&#xff08;Device Number&#xff09;&#xff1a;功能编号&#xff08;Function Number&#xff09; 第一个中括…

全国青少年软件编程等级考试C语言标准解读(1_10级)

考试性质 全国青少年软件编程等级考试标准&#xff08;C/C&#xff09;由中国电子学会科普培训与应用推广中心指定。由全国青少年电子信息科普创新联盟标准工作组开发&#xff0c;由中国电子学会普及工作委员会审核通过&#xff0c;适用于由中国电子学会主办的青少年软件编程等…

vue中的process.env的理解

创建项目的时候突然发现好多前端有好多地方用到了这个process.env.xxxx但是发现其实我的新项目并没有定义这个内容&#xff0c;于是就对这个变量产生了好奇&#xff0c;这里总结一下 上图是我在node命令行下执行的查看了一下变量&#xff0c;看这情况直接是把系统的环境变量给…

少走弯路,关于在线客服系统的二三事

日常生活中&#xff0c;我们购买一个服务或一个商品时&#xff0c;时常会遇到以下场景&#xff1a; 售前咨询&#xff1a;向商家咨询服务的信息咨询、商品的规格产品咨询、以及商场活动、优惠咨询等 售后服务&#xff1a;商品使用问题、商品不满意退/换货等 在移动通信没有普…

Camera Surface 从应用到cameraserver的流转

一、Android相机应用与Surface Camera应用的预览一般通过SurfaceView去显示&#xff0c;SurfaceView作为显示的载体&#xff0c; Surface surface mSurfaceView.getSurfaceHolder().getSurface(); 获取的surface会通过Camera API1/API2的接口下发到framework层&#xff1b;…

基于模型的设计(MBD)在汽车ECU软件开发中的实践

基于模型的设计&#xff08;Model-based Design&#xff0c;以下简称MBD&#xff09;是一种围绕模型展开的项目开发方法&#xff0c;指对开发对象或者项目产品进行精确建模&#xff0c;项目的需求分析、功能设计、系统框架、代码生成、测试验证等开发环节都在模型的基础上展开。…

如何用策略模式,优化你代码里的的if-else?

最近有一个学妹在跟我沟通如何有效的去避免代码中一长串的if else判断或者switch条件判断&#xff1f;针对更多的回答就是合理的去使用设计来规避这个问题。 在设计模式中&#xff0c;可以使用工厂模式或者策略模式来处理这类问题&#xff0c;之前已经分享了工厂模式&#xff…