Android高级之Dalvik初识-阿里云开发者社区

开发者社区> 开发与运维> 正文
登录阅读全文

Android高级之Dalvik初识

简介:  本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 研究安卓已多年,一直在应用层做开发,Framework层只是看过,也就是大家常说的"底层",而高级一点的功能如热加载、处理器类型配置,必须得了解再深入些才好,Library、Runtime、Kernel层等;当然了解底层的原因,不是在于去做底层开发,而是更好的做应用层,使其功能更强大和完善。


 本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处!

研究安卓已多年,一直在应用层做开发,Framework层只是看过,也就是大家常说的"底层",而高级一点的功能如热加载、处理器类型配置,必须得了解再深入些才好,Library、Runtime、Kernel层等;当然了解底层的原因,不是在于去做底层开发,而是更好的做应用层,使其功能更强大和完善。这里的底层是除应用层以外其他层。而Dalvik是什么,就是把各层串起来,处理安卓系统和服务的一个计算机。

Dalvik这个名字,是创始人的祖先生存过一个小村庄的名字,大概为纪念先人的原因,这个村庄位于冰岛。前面也有讲这个虚拟机是专门为移动设备,即内存小、计算能力低、运行标准要求高提供的嵌入式设备,正因为克服如此多的问题,以其优良的性能出现在世人面前,才受到众多厂商的追捧,前有HTC、摩托罗拉,后来阿里云、小米;越来越追求代码层的优化,更好的解决方案,更快的解释、加载速度,更好的体验和兼容性,来抵消硬件设备带来的价格冲击-这也是国内厂商,应对众多街机的一种战略手段,以数十万上百万的出厂设备*单利-有限的研发力量价格=最终厂家利润,假设每件研发投入100万,价格降低5块,则收益至少达到平衡或500万以上;个人不太认同这种恶性竞争,逼死开发的策略,商品应该优质高价,让消费者使用安全放心,带给的是心理的安全感、舒适感、品牌感知度,以及强大的功能,像IPhone一样,让生活归生活,功能归功能;总之从厂商竞争情况来说,个人觉得华为做的最好。但万变不离其宗,产品总是不完善的,需要维护和升级,以维持其对于公众的领先地位,因此理解虚拟机,有助于从全局出发,提出更好的优化或解决方案,为消费者提供更好的产品。

闲话这么多,进入正题。Dalvik来自于JVM,大多数的java程序都可以执行(为什么说大多数,因为它退出JSF标准,按说1.6以后是否全面的支持Java,这事能肯定)。前面对JVM有过讲解,它基于堆栈,处理代码指令,需要不断的从主内存复制到临时内存,然后操作完再存回去,有寄存器但仅限于虚拟机自己使用;而Dalvik基于寄存器,代码的执行本身就依赖于寄存器,且避免过多的存取,字节码指令减少47%,访问次数减少37%,因此执行速度更快。但寄存器有一个缺陷,只有65536个,每个32位,即总共256k,因此它还需要另一种文件Odex的支持。说到它,就得先讲dex文件,我们都知道java文件被编译后形成.class文件,这种文件符合JVM的执行规则;而Dalvik则把.class文件使用dx工具压缩形成dex文件,它符合寄存器的执行规则,同时又是对原数据进行过优化,共享共同数据,降低冗余,使用代码结构紧凑,因而包大小大约为同类型jar文件的50%。

Dex文件的格式与前面讲的类文件格式类似,可以对比着看:有基本数据类,如byte占8位,表示1byte的有符号数,ubyte同样,但表示无符号数;也有文件结构,也有字符串索引,字段索引,方法索引,类型索引,类定义,多的是原型数据索引,连接数据区等;同时header信息里也存着魔数,文件总长度,以上各种索引及类型的个数、地址等,索引除公用的是绝对位置,其他均为相对位置。数据的存放。Dalvik字节码总共有226条,使用时会用一个hash指令表来存储,格式如 B|A|op 12x 第一个是指令,第二个是缩写,用来说明它的操作逻辑。

Odex文件是为了提高程序执行速度而做的本地缓存,一般存在data/dalvik-cache/xxx.odex,这个文件已经由寄存器加载时帮忙做过字节码校验、空方法消除、替换优化等工作,执行时优化解释odex,没有再执行dex,把加载过的字节码缓存入odex,这就是它的工作原理。它在Dex文件前添加了头部信息,尾部拼接了依赖库、寄存器映射关系、类hash索引等辅助信息,索引再介绍一下,它包括文件的偏移地址和类的偏移地址,通过hash定位可以尽快查找到资源并加载。讲到这里,dex加载的原理已经明了,由Dalvik加载,处理器解释执行,缓存放入odex。而Odex不断增大,也是安卓手机内存变小的原因,因而对于卸载的App的odex文件可以删除,不常用的选择删除,常用的保留,不然每次加载解释后仍然会增大,而手机速度更慢。降低Android手机内存最好的方法就是找到data/dalvik-cache里不使用的odex删除。

dex是重点,而apk的组成还有主配置文件,资源文件,通过aapt工具打包而成,用jarsigner签名。而dex的加载跟class文件加载非常类似,把Dex(class)与一个DexFile(ClassFile)文件关联,建立起数据结构,再把各个类依次加载生成对象,把指针交给解释器引用执行,而4.0.4解释器又有两种,C语言和汇编,移动和快速型;但虚拟机除了支持java语言以外,还支持c语言编写的程序,按照java接口进行调用,执行效率更高;当然使用JIT补丁的方法,会更便捷,加载更快,移植性较强,后面我们将介绍热加载的原理和常用的几种框架。

Apk内部的文件包含以下几种:

resources.arsc:二进制文件,资源id和路径映射组成

res:二进制,项目中的res目录

class.dex:class编译成的可执行文件,反编译后可以看到源码结构

AndroidManifest.xml:二进制文件,反编译后可看到部分内容

讲完dex文件,接下来可以讲讲安卓系统的启动过程,之前有过介绍,但本次希望以更通俗的语法来说明。系统按键触动驱动,触发init.rc的init进程启动,这是系统启动后的首个进程,再创建各种守护进程,然后启动Zygote进程,也就是用户进程,其他应用都是由它创建-fork子进程共享内存和资源,启动系统服务的SystemServer进程(相当于服务端)监听socket指令操作。x86初始化,执行dalvik/dalvikvm/Main.c中的main方法,执行JNI_CreateJavaVM函数创建虚拟机;而Arm平台初始化是从Zygote进程开始的,执行/jni/AndroidRuntime.cpp的startVm方法,通过JNI_CreateJavaVM函数创建虚拟机。然后调用startReg注册JNI函数,用来调用java类;调用ZygoteInit类的main方法:1、创建socket接口(相当于客户端),接收应用请求 2、调用预加载类和资源 3、启动SystemServer并分裂出sytem_server,启动各项系统服务,最终将线程加入Bind通信系统,此进程退出则Zygote退出重启4、调用runSelectLoopMode监听程序请求 注:子进程同样可以再fork出一个Zygote进程,所以在实际操作时,要区分一下。在子进程中fork函数返回0,在父进程返回负值 或此进程的pid,可以用来区别运行的是否是子进程或者创建成功与否。

虚拟机的功能可以分为以下几部分

1、进程管理:每个虚拟机都有一个进程,依赖于Zygote机制实现

2、Zygote进程管理:会fork出来一个子Zygote进程,一个SystemServer进程,一个非Zygote进程的Speciallize进程

3、类加载:先加载所有类库,然后加载字节码,放入类数据结构,供类解释器执行,如果有关联的超类、接口等也一并加载

4、内存管理:使用新老生代结合的方式,使用复制+标记-清除的算法

4、本地接口:JNI

5、反射机制:通过类结构的加载,查看、调用、修改类中的方法和属性

6、解释器:负责执行Dex字节码

7、即时编译:JIT


Dalvik编译的原理在于:开发者编译项目成dex,在安装时转成odex,减少了JIT时再进行字节码校验等工作;

ART在Android4.4版本出现,相比较Dalvik的优势在于,直接将dex编译成可执行的机器码(而不再JIT将字节码转换为机器码),这样加速App的运行,减少电量的消耗,用户体验更加流畅;当然劣势也是有的,App安装时比较慢,缓存文件变大;较之IOS一次开发者编译,即在用户手机运行有先天的弊端,优势在于Android热修复和动态加载方便,而IOS相对则比较古板。


Dex较之class文件,添加新操作码支持,进行文件优化和压缩,加快解析速度


签名好处:1、顺利升级2、热修复 3、进程级数据共享
Meta-Inf里的文件Manifest.MF是对Apk包中非Meta-Inf的所有文件进行数据校验,而Cert.Sf对Manifest.MF和其他文件进行校验,Cert.Rsa中存储签名信息


Dalvik运行在Arm的CPU上,那么有必要了解一下Arm

Arm:一家1990年成立的英国公司,主要进行Cpu授权,是一种知识产权公司,占据移动95%的市场,主要靠设备抽成盈利。

产权领域:指令集架构、微处理器、GPU、互连架构等

特点:低功耗、低成本,使用RISC(Reduced Instruction Set Computer,通常仅执行1或2个指令即可完成意图),

和big.LITTLE架构(适配高性能-64位的内核和低性能-32位的内核切换),方便其他品牌贴标生产

授权方式:架构、内核、使用三种形式,分别针对大、中、小型公司

合作伙伴:高通、联发科等蚂蚁联盟

合作形式:实行OEM(Original Equipment Manufacture)

对手劣势:1968年成立的Intel使用CISC(Complex Instruction Set Computer,通常仅执行3或4个指令即可完成意图)架构的x86功耗高、掉电快

适配执行:从上面介绍可以看出,Arm占据绝对多数市场,因此mips和x86基础不需要考虑适配,而且它们会主动解析Arm指令成自己能使用的指令(使得自身效率更低,不得不赞叹规模效应的强大),big.LITTLE的高低性能内核切换,可以进行有效省电(计算量小用低性能、大用高性能,性能越高越耗电)。而64位寄存器的使用,减少内存存取的次数,提高CPU处理效率,用于RISC架构高性能的处理器。

Mips:1998年成立,同样依据RISC架构,中科院设计的“龙芯”与它95%类似,涉及侵权,而准备收购其估值1亿的20%股份。

在Android Studio下,则可以通过以下的构建方式指定需要类型的SO库。

productFlavors {
    flavor1 {
        ndk {
            abiFilters "armeabi-v7a"
            abiFilters "x86"
            abiFilters "armeabi"
        }
    }
    flavor2 {
        ndk {
            abiFilters "armeabi-v7a"
            abiFilters "x86"
            abiFilters "armeabi"
            abiFilters "arm64-v8a"
            abiFilters "x86_64"
        }
    }
}

更多:http://blog.csdn.net/reboot123/article/details/17918447

案例:

  • 如果你使用的是 1.7.4 版 (或更高版本) Agora Native SDK:

    Agora Native SDK 目前支持 64 位的 ARM 架构,只需将 arm64-v8a 里面的文件(位于SDK包内)拷贝至项目对应的 arm64-v8a 路径下。目前不支持 x86_64 架构直接运行,不过可以以 x86 方式兼容运行。具体做法就是在64位的 x86 设备上保证 APK 里面没有 x86_64 目录。

  • 如果你使用的是 1.7.4 版之前的 Agora Native SDK:

    Agora Native SDK 目前只提供 32 位 native 库(armeabi-v7a),在 64 位设备上,Android 支持以 32 位进程模式启动 APK,因此 Agora Native SDK 也可以在 64 位 Android 设备上使用,但必须保证 APK 的 arm64 目录为空。否则 Android 系统将以 64 位进程模式加载 APK,由于 Agora Native SDK 没有提供 64 位库,APK 启动会失败。 由于目前 Android 的主流还是 32 位设备,一般各厂商都会提供 32 位库,因此一般来说,在 64 位 Android 设备以 32 位进程模式启动一般不会有问题。但是如果在某些 64 位平台上出现了这样的错误: java.lang.UnsatisfiedLinkError: dlopen failed: ”libHDACEngine.so”

但是如果 64 位平台上出现以下报错:

java.lang.UnsatisfiedLinkError: dlopen failed: "libHDACEngine.so"

可能的原因:

在安装 APK 的时候,系统会按照 Build.SUPPORTED_ABIS 去查找 APK 的 lib 目录下的 native 库的目录(现有的 ABI: armeabi, armeabi-v7a,arm64-v8a, x86, x86_64, mips64, mips)。

如果在 app 中有兼容 64-bit 的目录但是又缺少库文件的话,并不会使用其他 ABI 目录下的库文件替换所缺少的库文件进行安装,这些库不混合使用,也就是说需要为每个架构提供对应的库文件。

Android 在加载 native 库的时候有回退(fallback)机制,在64位系统上如果 app 并不存在 arm64-v8a 的目录,则会尝试寻找armeabi-v7a下面的库进行加载,一般来说是向下兼容的。

解决方案:

  • 方法 1: 在构建应用程序的时候,在工程 (project) 里删除所有 arm64-v8a 下面的库以及该目录;在生成 app 后,确认 apk 的包内 lib 下没有 arm64-v8a 的目录。
  • 方法 2: 在 gradle 构建文件中设置 abiFilters,只打包 32 位架构的库:
 android {
  ...
  defaultConfig {
    ...
    ndk {
      abiFilters "armeabi-v7a","x86"
    }
  }
}

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章
最新文章
相关文章