资讯

精准传达 • 有效沟通

从品牌网站建设到网络营销策划,从策略到执行的一站式服务

android加载,android加载3d模型

Android So加载的路径选择

我们在Android应用程序会常常的加载一些So文件来完成我们的目标,那么我们的APK加载So是有哪些平时我们没有注意到的事情呢?

创新互联-专业网站定制、快速模板网站建设、高性价比刚察网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式刚察网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖刚察地区。费用合理售后完善,十余年实体公司更值得信赖。

1. 首先我们一般开发会遇见两种APK(其实一般大部分只会遇到一种),一种为系统级APK,另外一种为普通APK。那么这个两种APK跟So加载有什么关系呢?别急,让我们先聊聊我们那些操作会产生这些类型的APK。

普通级AKP: 

pm install + 包名将会把APK安装到 /data/app 目录下,同时会把So映射到/data/app-lib/包命/ 目录下。这个就是普通的APK(pm Install -r 会替换原有的APK,当然必须是一样的签名)。

系统级APK:

push  + 绝对路径 + 包名 /system/app 目录下(必须把原有的包名删除哦!),这时APK就会在System/app下面了,这时你需要把你的APK的So 同时push到system/lib里面。因为apk里面的So并不会自动映射到system/lib下面。

一般我们在使用加载So的方法时候,会使用到System.load(pathName)和 System.loadLibrary(libName)这两种方法。这篇文章主要讲讲System.load(pathName)这个绝对路径加载的注意点。

我们通常会直接使用

context.getApplicationInfo().nativeLibraryDir +/具体名字.so  来让系统帮我寻找加载So所需要的路径。那么这里问题就来了。

如果是系统级APK

context.getApplicationInfo().nativeLibraryDir = /system/lib/

如果是普通级APK

context.getApplicationInfo().nativeLibraryDir  =/data/data-lib/PackageName/ 对!就是那个映射的So系统会根据这个去data/app/包名下面寻找真正的So文件。

这个需要注意的细节,主要用于在中间件,系统预置程序的研发人员与测试上面。我们在拿到芯片厂商给予调试模式的开发硬件上进行Demo和So的更换测试的时候,需要自己和测试都需要知道,自己安装的APK是什么类型,会加载什么路径,以免我们的底层老司机在帮忙测试问题的时候造成不必要的麻烦。

Android类加载机制

Android手写热修复(一)--ClassLoader

我们平时编写的 .java 文件不是可执行文件,需要先编译成 .class 文件才可以被虚拟机执行。所谓类加载是指通过 类加载器 把class文件加载到虚拟机的内存空间,具体来说是方法区。类通常是按需加载,即第一次使用该类时才加载。

首先,Java与Android都是把类加载到虚拟机内存中,然后由虚拟机转换成设备识别的机器码。但是由于二者使用的虚拟机不同,所以在类加载方面也是有所区别的。Java的虚拟机是JVM,Android的虚拟机是dalvik/art(5.0以后虚拟机是art,是对dalvik的一种升级)。 Java虚拟机运行的是class文件,而Android 虚拟机运行的是dex文件。 dex其实是class文件的集合,是对class文件优化的产物,是为了避免出现重复的class。

从上面的讲解中,我们已经知道我们平时写的类是被 类加载器 加载尽虚拟机内存才能运行。下面就通过Framework源码来为大家讲解Android中最主要的5个类加载器。

在Activity做个简单验证:

结果:

可以看出系统类由BootClassLoader加载,apk中的类由PathClassLoader加载,PathClassLoader的父类加载器是BootClassLoader。如果暂时不能理解父类加载器是什么,没关系,后面讲双亲委托机制的时候会理解的。

下面的源码解析基于 Android SDK API28 ,这几个类加载器(除了ClassLoader)没办法直接在AS上查看源码,AS搜索到的是反编译的class的内容,是不可信的,为大家推荐一个在线工具查看, 在线查看Android Framework源码 。

用来加载本地文件系统上的文件或目录,通常是用来加载apk中我们自己写的类,而像 Activity.class 这种系统的类不是由它加载。注意:这里,并不像很多网上文章说的那样只能加载apk,本地的其他目录的文件也是可以的,这一点我会在后面验证说明。

也是被用来加载 jar 、apk、dex,通常用来加载未安装到应用中的文件。注意,它需要一个应用私有的可写的目录来存放优化后的dex文件。千万不要选择外部存储路径,因为这样可能会导致你的应用遭到注入攻击。

关于dex文件优化,可能很多人还是不理解,水平有限,我简单解释一下,

构造器参数解释:

关于optimizedDirectory:

1、这是dex优化后的路径,它必须是一个应用私有的可写的目录否则会存在注入攻击的风险;

2、这个参数在API 26(8.0)之前是有值的,之后的话,这个参数已经没有影响了,因为在调用父构造器的时候这个参数始终为null,也就是说Android 8.0 以后DexClassLoader和PathClassLoader基本一样的来;

3、在加载app的时候,apk内部的dex已经执行过优化了,优化之后放在系统目录/data/dalvik-cache下。

这个构造器的关键是初始化了一个DexPathList对象,这个是后面加载class的关键类。

这个构造方法等关键是通过 makeDexElements() 方法来获取Element数组,这个Element数组非常关键,后面查找class就会用到它,也是热修复的关键点之一。

splitDexPath(dexPath) 方法是把dexPath目录下的所有文件转换成一个File集合,如果是多个文件的话,会用 : 作为分隔符。

makeDexElements()

小结一下,这个方法就是把指定目录下的文件apk/jar/zip/dex按不同的方式封装成Element对象,然后按顺序添加到Element[]数组中。

DexPathList#loadDexFile()

可以看到 DexFile 最终是调用了openDexFile、native方法openDexFileNative去打开Dex文件的,如果outputName为空,则自动生成一个缓存目录,具体来说是 /data/dalvik-cache/xxx@classes.dex 。openDexFileNative这个native方法就不具体分析了,主要是对dex文件进行了优化操作,将优化后得odex文件通过mmap映射到内存中。感兴趣的同学可以参考:

《DexClassLoader和PathClassLoader加载Dex流程》

现在在回头看看DexClassLoader与PathClassLoader的区别。DexClassLoader可以指定odex的路径,而PathClassLoader则采用系统默认的缓存路径,在8.0以后没有区别。

ClassLoader是一个抽象类,有3个构造方法,最终调用的还是第一个构造方法,主要功能是保存实现类传入的parent参数,也就是父类加载器。ClassLoader的实现类主要有2个,一个是前面讲过的BaseDexClassLoader,另一个是BootClassLoader。

BootClassLoader是ClassLoader的内部类,而且继承了ClassLoader。

这是加载一个类的入口,流程如下:

1、 先检查这个类是否已经被加载,有的话直接返回Class对象;

2、如果没有加载过,通过父类加载器去加载,可以看出parent是通过递归的方式去加载class的;

3、如果所有的父类加载器都没有加载过,就由当前的类加载器去加载。

通常我们自己写的类是通过当前类加载器调用 findClass 方法去加载的,但是在 ClassLoader 中这是个空方法,具体的实现在它的子类 BaseDexClassLoader 中。

BaseDexClassLoader # findClass

可以看到是通过pathList去查找class的,这个对象其实之前讲过,它是在BaseDexClassLoader 的构造方法中初始化的,它实际上是一个 DexPathList 对象。

DexPathList # findClass()

对Element数组遍历,再通过Element对象的 findClass 方法去查找class,有的话就直接返回这个class,找不到则返回null。 这里可以看出获取Class是通过DexFile来实现的,而各种类加载器操作的是Dex。Android虚拟机加载的dex文件,而不是class文件。

1、加载一个类是通过双亲委托机制来实现的。

2、如果是第一次加载class,那是通过 BaseDexClassLoader 中的findClass方法实现的;接着进入 DexPathList 中的findClass方法,内部通过遍历Element数组,从Element对象中去查找类;Element实际上是对Dex文件的包装,最终还是从dexfile去查找的class。

3、一般app运行主要用到2个类加载器,一个是PathClassLoader:主要用于加载自己写的类;另一个是BootClassLoader:用于加载Framework中的类;

4、热修复和插件化一般是利用DexClassLoader来实现。

5、PathClassLoader和DexClassLoader其实都可以加载apk/jar/dex,区别是 DexClassLoader 可以指定 optimizedDirectory ,也就是 dex2oat 的产物 .odex 存放的位置,而 PathClassLoader 只能使用系统默认位置。但是在8.0 以后二者是没有区别的,只能使用系统默认的位置了。

这张图来源于:

Android虚拟机框架:类加载机制

在类加载流程分析中,我们已经知道,查找class是通过DexPathList来完成的,实际上DexPathList最终还是遍历其Element数组,获取DexFile对象来加载Class文件。 由于数组是有序的,如果2个dex文件中存在相同类名的class,那么类加载器就只会加载数组前面的dex中的class。如果apk中出现了有bug的class,那只要把修复的class打包成dex文件并且放在 DexPathList 中Element数组`的前面,就可以实现bug修复了 。下一篇为大家带来的手写热修复。

Android类加载机制的细枝末节

从JVM到Dalivk再到ART(class,dex,odex,vdex,ELF)

类加载机制系列2——深入理解Android中的类加载器

Android 热修复核心原理,ClassLoader类加载

Android-类加载

双亲委托机制

类在进行类加载的时候,把加载任务托管给父类加载器,如能加载成功,则返回,否则依次向子类加载器递归尝试类加载。

意义:

①避免类的重复加载,父类加载已加载该类时,子ClassLoader就没有必要加载一次了。

②安全性,防止核心API被随意篡改。

ClassLoader

ClassLoader本身是一个抽象方法。它的主要实现类有BootClassLoader、PathClassLoader、DexClassLoader.

BootClassLoader:用于加载Android Framwork层(SDK)的class文件

PathClassLoader:用于Android应用程序加载器,可以加载指定的dex和jar、zip、apk中的classes.dex(系统使用)

DexClassLoader:用于加载指定的dex和jar、zip、apk中的classes.dex。(供开发者使用)

拓展:

在API26之前。

optimizedDirectory 参数就是dexopt的产出目录(odex)。那 PathClassLoader 创建时,这个目录为null,就

意味着不进行dexopt?并不是, optimizedDirectory 为null时的默认路径为:/data/dalvik-cache。

在API26之后DexClassLoader也取消了optimizedDirectory

热修复相关

LoadClass:

findClass:PathClassLoader和DexClassLoader的父类BaseDexClassLoader中实现findClass。

BaseDexClassLoader中

PathClassLoader加载过后,pathlist 中存在一个Element数组,Element类中存在一个dexFile成员表示dex文件,即:APK中有X个dex,则Element数组就有X个元素。

总结:

可能看到这里我们比较乱了,理一下。一个类的加载经历了哪些。我们以PathClassLoader为例。

①加载一个类的时候,首先通过Class缓存寻找是否已经加载过该类。参考抽象类的loadClass方法。

②若在缓存中未找到该类,则交由父加载器加载该类。参考抽象类的loadClass方法。

③调用父加载器PathClassLoader的父类BaseDexClassLoader实现的findClass方法加载该类。

④PathClassLoader在初始化的时候调用父构造方法实例化DexPathList属性,DexPathList属性初始化时构造方法内通过makePathElements(或makeDexElements 不同API可能不同)加载APK内的dex文件生成Element数组。

⑤BaseDexClassLoader实现的findClass方法中顺序循环已存在的Element数组,通过Element中的DexFile加载类。。

⑥未找到,抛出类未找到异常。

热修复(multide 形式(thinker、qfix))

热修复的原理。我们只需在应用启动的时候,一般是在application方法中(因为class加载首先从缓存中加载),在应用启动后,经过PathClassLoader加载过后所有的类都在 pathList的Element 数组,把生成的Elment数组插入到PathList的Element数组的最前方。在加载类的时候就只会加载到我们需要更新的类了,因为是顺序寻找,找到就返回。(先从我们补丁的dex文件生成的element寻找,找不到再从APK的dex生成的element种寻找)。

热修复基本思路总结:

①获取到当前引用的PathClassLoader

②反射获取其中DexPathList属性:DexPathList pathList.

③获取到补丁包path.dex文件的Element[]数组 pElements。参考PathClassLoader怎么把dex文件转换为Element数组的。于是我们反射执行DexPathList 中的makePathElements方法(视API而定)传入dex路径得到补丁包的element数组。

④获取pathList的dexElements数组。

⑤把补丁包的pElements数组合并到pathList的dexElements数组的前方,即newElements=pElements+dexElements

⑥反射赋值把newElements替换掉pathList的dexElements

热修复没这么简单,还需考虑混淆,API版本不同导致的使用makePathElements方法或makeDexElements方法等因素。

热修复(InstantRun 形式(Robust))待了解。


分享文章:android加载,android加载3d模型
URL地址:http://www.cdkjz.cn/article/dsceeig.html
多年建站经验

多一份参考,总有益处

联系快上网,免费获得专属《策划方案》及报价

咨询相关问题或预约面谈,可以通过以下方式与我们联系

大客户专线   成都:13518219792   座机:028-86922220