《Android框架揭秘》 [韩]金泰延 宋亨周 朴知勋 李白 林起永 著 武传海 译
Dalvik是Google公司自己设计用于Android平台的Java虚拟机。
Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一,它可以支持已转换为.dex(即Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik应用设计的一种压缩格式,适合内存和处理器速度有限的系统。
Dalvik经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik应用作为独立的Linux进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。
每个 Android 应用程序都运行在单独的 Dalvik 虚拟机内(即每个 Android 应用程序对用一条 Dalvik VM实例、对应一个Linux进程), Dalvik 专门针对同时高效地运行多个虚拟机进行优化,因此 Android 系统以方便的实现对应用程序进行隔离。
Android 运行时由两部分组成:
提供了 Java 语言核心库所能使用的绝大部分功能。
DVM负责运行 Android 应用程序。
Android平台不用JVM来执行代码,而是把应用编译成Dalvik字节码,使用Dalvik虚拟机来执行。最终应用只包含Dalvik字节码。
过程:
Java代码 –(编译成)–> Java字节码 –(dex编译器:dx,SDK工具 编译成)Dalvik字节码(dex字节码)。
最后获得结果:classes.dex。
通过反编译classes.dex即可得到java源代码。运用安卓反编译工具dex2jar可将dex文件反编译成.jar文件,然后运用jd-gui工具即可查看反编译后得到的源代码。
谷歌Android 4.4系统新增的一种应用运行模式,与传统的Dalvik模式不同,ART模式可以实现更为流畅的安卓系统体验,对于大家来说,只要明白ART模式可让系统体验更加流畅,不过只有在安卓4.4以上系统中采用此功能。
Art上应用启动快,运行快,但是耗费更多存储空间,安装时间长,总的来说ART的功效就是”空间换时间“。
Dalvik是依靠一个Just-In-Time(JIT)编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不同硬件和架构上运行。
ART则完全改变了这套做法,在应用安装的时候就预编译字节码到机器语言,这一机制叫Ahead-Of-Time(AOT)编译。在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。
系统性能的显著提升
应用启动更快、运行更快、体验更流畅、触感反馈更及时
更长的电池续航能力
支持更低的硬件
更大的存储空间占用,可能会增加10%-20%
更长的应用安装时间
Android2.2引入JIT,Dalvik JIT编译器把Dalvik字节码编译成本地代码,这可以显著提高性能。
原因:
①本地代码直接由CPU执行,不需要虚拟机解释执行;
②本地代码可以被特定架构优化。
点击应用图标后会去启动应用的LauncherActivity,如果LancerActivity所在的进程没有创建,还会创建新进程,整体的流程就是一个Activity的启动流程。
①Instrumentation
监控应用与系统相关的交互行为。
②AMS
组件管理调度中心,什么都不干,但是什么都管。
③ActivityStarter
Activity启动的控制器,处理Intent与Flag对Activity启动的影响,具体说来有:
1 寻找符合启动条件的Activity,如果有多个,让用户选择;
2 校验启动参数的合法性;
3 返回int参数,代表Activity是否启动成功。
④ActivityStackSupervisior
这个类的作用你从它的名字就可以看出来,它用来管理任务栈。
这是高版本才有的类,它用来管理多个ActivityStack,早期的版本只有一个ActivityStack对应着手机屏幕,后来高版本支持多屏以后,就有了多个ActivityStack,于是就引入了ActivityStackSupervisior用来管理多个ActivityStack。
⑤ActivityStack
用来管理任务栈里的Activity。
⑥ActivityThread
最终干活的人,是ActivityThread的内部类,Activity、Service、BroadcastReceiver的启动、切换、调度等各种操作都在这个类里完成。
如果是在桌面启动应用就是Launcher应用进程。
该进程主要运行着系统服务组件。
该进程主要用来fork新进程。
该进程就是用来承载应用运行的进程了,它也是应用的主线程(新创建的进程就是主线程),处理组件生命周期、界面绘制等相关事情。
①点击桌面应用图标,Launcher进程将启动Activity(MainActivity)的请求以Binder的方式发送给了AMS。
②AMS接收到启动请求后,交付ActivityStarter处理Intent和Flag等信息,然后再交给ActivityStackSupervisior/ActivityStack
③处理Activity进栈相关流程。同时以Socket方式请求Zygote进程fork新进程。
Zygote接收到新进程创建请求后fork出新进程。
④在新进程里创建ActivityThread对象,新创建的进程就是应用的主线程,在主线程里开启Looper消息循环,开始处理创建Activity。
⑤ActivityThread利用ClassLoader去加载Activity、创建Activity实例,并回调Activity的onCreate()方法。这样便完成了Activity的启动。
Android Linux 内核2.6
Android 标准C运行支持库
Android内核加载器参考
Android的Build系统
Android兼容性测试源
Dalvik虚拟机
Android使用的开放源
Android框架
Android HAL(Hanrdware Abstraction Layer,硬件抽象层)库源
包含Android 基本应用,Content Provider等
Android初始化进程、蓝牙工具集等
Android启动时,首先通过bootloader(系统加载器),加载Linux内核。
在Linux加载启动时,与普通Linux启动过程相同,先初始化内核,然后调用init进程。
init进程对各种设备进行初始化,运行Android framework 所需用的各种Daemon、Context Manager、Media Server、Zygote等。
在Linux中,init进程是Android启动后,由内核启动的第一个用户级进程,也是所有进程的父进程。
分析启动脚本文件,病根据相关文件中包含的内容,执行相应的功能。
应用程序访问设备驱动时,生成设备节点文件。
保存系统允许所需的环境变量。
USB Daemon(usbd):管理USB连接
Android Debug Bridge Daemon(adbd):Android Debug Bridge连接管理。
Debugger Daemon(debuggerd):启动Debugger系统。
Radio Interface Layer Daemon(rild):管理无线通信连接。
Context Manager是一个管理Android系统服务的重要进程。系统服务是组成Android Framework的重要组件,提供从相机、音频、视频处理到各种应用程序直至需要的重要API。
Context Manager提供运行与Android内的各种系统服务信息。应用程序或Framework内部模块在调用系统服务时,需要先向服务管理器申请,而后通过Binder IPC(Interprocess conmmunication)调用系统服务。
在系统系统时,Android所有系统服务都要把各自的handle信息注册到Context Manager,此时,Binder IPC用来进行进程间通信。
用于运行基于C/C++的本地系统服务,如Audio Flinger(负责音频输出)、Camera等。
用于缩短Android应用程序加载时间。
每当执行Java应用程序时,Zygote进程会派生出一个子进程来执行应用程序,该子进程是用来执行Java应用程序的虚拟机,初始化JVM,并启动它。
在应用程序运行前,Zygote进程通过共享已运行的JVM代码与内存信息,缩短应用程序运行所耗费的时间。并且,它会事先将应用程序要使用的Android Framework中的类与资源加载到内存中,并组织形成所用资源的链接信息。新运行的App在使用所需资源时不必每次形成资源的链接ixnx,这会节省大量时间,提高程序运行速度。
除了Zygote,Maemo平台的Launcher、Qt extended的quick Launcher等都是基于相同的目的而被使用的。
意义:一方面提高设备资源利用率,一方面尽可能的加长设备的使用时间。
是Android系统的核心进程,由Zygote进程创建。
在System Server进程中可以看到它建立的Android大部分服务,如Activity Manager Service(管理应用程序的生命周期)、Location Manager Service(提供终端的地理位置信息)。
为了将运行在System Server进程中的Java系统服务提供给Android应用程序或Framework内部模块调用,需要在Context Manager中注册。
在 通过Binder IPC将java系统服务注册到基于C语言的服务管理器时,需要使用JNI(Java Native Interface)本地编程接口。JNI允许java代码与其它编程语言(如C、C++、汇编语言)编写的应用程序和库进行交互操作。
当所有Java系统服务加载完毕后,Activity Manager Service会允许HOME应用,启动过程继续进行。
Android的包文件APK分为两个部分:代码和资源,所以打包方面也分为资源打包和代码打包。
1)通过AAPT工具进行资源文件(包括l、布局文件、各种xml资源等)的打包,生成R.java文件。
2)通过AIDL工具处理AIDL文件,生成相应的Java文件。
3)通过Javac工具编译项目源码,生成Class文件。
4)通过DEX工具将所有的Class文件转换成DEX文件,该过程主要完成Java字节码转换成Dalvik字节码,压缩常量池以及清除冗余信息等工作。
5)通过ApkBuilder工具将资源文件、DEX文件打包生成APK文件。
6)利用KeyStore对生成的APK文件进行签名。
7)如果是正式版的APK,还会利用ZipAlign工具进行对齐处理,对齐的过程就是将APK文件中所有的资源文件举例文件的起始距离都偏移4字节的整数倍,这样通过内存映射访问APK文件的速度会更快。
1)复制APK到/data/app目录下,解压并扫描安装包。
2)资源管理器解析APK里的资源文件。
3)解析AndroidManifest文件,并在/data/data/目录下创建对应的应用数据目录。
4)然后对dex文件进行优化,并保存在dalvik-cache目录下。
5)将AndroidManifest文件解析出的四大组件信息注册到PackageManagerService中。
6)安装完成后,发送广播。
随着功能的增加,方法数增多,就会出现所谓的64k方法数问题。
Android APK文件本质上是一个压缩文件,它包含的classes.dex文件是Dalvik字节码文件,这个dex文件中存放的就是编译后的Java代码。Dalvik可执行文件规范限制了单个.dex文件最多引用的方法数是65536个。
在DEX文件中,method、field、class等的个数使用short类型来做索引,即两个字节(65535),method、field、class等均有此限制。
APK在安装过程中会调用dexopt将DEX文件优化成ODEX文件,dexopt使用LinearAlloc来存储应用信息,关于LinearAlloc缓冲区大小,不同的版本经历了4M/8M/16M的限制,超出缓冲区时就会抛出INSTALL_FAILED_DEXOPT错误。
google为了规避上述问题,推出了MultiDex解决方案解决方法数超限问题,可以配置方法数超过 64K 的应用。
①Android5.0之前
Dalvik在5.0之前,为每一个APK只生成一个classes.dex,所以会有上述所说的方法数超限的问题,如果我们可以将一个dex文件分成多个,在应用启动时,加载第一个(主dex)dex文件,当启动以后,再依次加载其他dex文件。这样就可以规避上述问题了。MultiDex即是实现了这样的功能
②Android5.0之后
Android5.0及更高版本使用支持从apk中加载多个dex文件的ART运行时机制,在应用安装时,加载classed(…N).dex文件并编译成一个.oat文件以支持在Android设备上运行。
配置adle
android {compileSdkVersion 21buildToolsVersion "21.1.0"//必须使用21或之后的版本defaultConfig {...minSdkVersion // Enabling multidex support.multiDexEnabled true}...}dependencies {compile 'com.android.support:multidex:1.0.0'}
配置Application
如果用户没有重写Application,只需修改Manifest文件中的内容:
&android:name="android.support.multidex.MultiDexApplication">...</application>
如果用户继承变重写了Application,可以将继承的Application换成MultiDexApplication。
或者重写attachBaseContext() 方法,
@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);MultiDex.install(this);
}
特别注意,如果没有实现这部分代码,运行时会出现NoClassDefFoundError的错误,尤其是在依赖三方函数库时。
这个问题之前出现过很多次,当时是由于配置了adle文件但是没有使用MultiDexApplication造成的。所以开发者一定要记得使用MultiDexApplication或者MultiDex.install(this),具体参考上面提到过的配置Application。
还有一种情况,上面提到过的,使用MultiDex机制,必然存在主dex文件和从dex文件,应用启动时所需要的类,都应放入主dex中,否则也会出现NoClassDefFoundError。这种情况可以手动添加一些类到主dex中:
multiDexKeepFile
手动加入要放到Main.dex中的类。
android/support/multidex/MultiDex.class
multiDexKeepProguard
以Proguard的方式手动加入要放到Main.dex中的类。
-keep class android.support.multidex.** {
*;
}
然后在adle中进行配置:
android {defaultConfig {multiDexEnabled truemultiDexKeepProguard file('multiDexKeep.pro') multiDexKeepFile file(') }
}
dependencies {compile'com.android.support:multidex:1.0.1'
}
multidex会加长构建应用的时间,这个必要的过程可能会拖慢你的开发进度。 为加速构建过程,我们可以在Gradle中配置productFlavors。
开发时将minSdkVersion改为21使用ART运行时机制,这样能加快构建速度。release时改为合适的minSdkVersion,这样仅在release时费时较长。
adle配置如下:
android {productFlavors {dev {minSdkVersion 21}prod {minSdkVersion 14}}...buildTypes {release {...}}}dependencies {compile 'com.android.support:multidex:1.0.0'}
完成上述配置后,你可以使用结合了dev productFlavor和buildType属性的devDebug变体app。
这个变体app包含如下特性:
关闭了混淆(proguard)
支持multidex
minSdkVersion 设置为 Android API level 21.
值得注意的是:上述配置后的devDebug变种app仅能运行在Android 5.0设备上。
apk文件是压缩文件,可以用压缩工具(如WinZip、7-Zip)来查看apk文件的内容。
解压后如图所示:
这个文件夹存储的是关于签名的一些信息。
包含应用的字节码。
Android的工具包里有工具dexdump,可以把classes.dex的二进制代码转化位使人易读的格式。我们可以使用dexdump查看该文件。
点击下载dex2jar
如图,打开安装目录,运行dex2jar.bat classes.dex(因版本不同,本次举例使用 d2j-dex2jar.bat classes.dex命令)
运行命令后会生成文件:classes-dex2jar.jar(如图所示)
点击下载
这个apk是直接生成,没有进行代码混淆,所以代码很完整。这也说明了反逆向工程的重要性。
动态加载技术就是使用类加载器加载相应的apk、dex、jar(必须含有dex文件),再通过反射获得该apk、dex、jar内部的资源(class、图片、color等等)进而供宿主app使用。
Android支持动态加载的两种方式是:DexClassLoader和PathClassLoader。
只能加载已经安装到Android系统的APK文件,即/data/app目录,Android默认的类加载器。
可以加载任意目录下的dex、jar、apk、zip文件,且支持从SD卡加载。
插件化是体现在功能拆分方面的,它将某个功能独立提取出来,独立开发,独立测试,再插入到主应用中。依次来较少主应用的规模。
一个应用程序dex文件的方法数最大不能超过65536个;
可以让应用程序实现插件化、插拔式结构,对后期维护有益。
类的加载可以使用Java的ClassLoader机制,还需要组件生命周期管理。
用AssetManager的隐藏方法addAssetPath。
使用代理机制进行API Hook进而达到方法增强。
可以简单理解为JVM可以在运行时帮我们动态生成一系列的代理类。
如果我们自己创建代理对象,然后把原始对象替换为我们的代理对象,就可以在这个代理对象中为所欲为了;修改参数,替换返回值,称之为Hook。
整个Hook过程简要总结如下:
热修复是体现在bug修复方面的,它实现的是不需要重新发版和重新安装,就可以去修复已知的bug。即:线上修复。
利用PathClassLoader和DexClassLoader去加载与bug类同名的类,替换掉bug类,进而达到修复bug的目的,原理是在app打包的时候阻止类打上CLASS_ISPREVERIFIED标志,然后在热修复的时候动态改变BaseDexClassLoader对象间接引用的dexElements,替换掉旧的类。
基于Xposed中的思想,通过修改c层的Method实例描述,来实现更改与之对应的java方法的行为,从而达到修复的目的。
Xposed
诞生于XDA论坛,类似一个应用平台,不同的是其提供诸多系统级的应用。可实现许多神奇的功能。Xposed需要以越狱为前提,像是iOS中的cydia。
Xposed可以修改任何程序的任何java方法(需root),github上提供了XposedInstaller,是一个android app。提供很多framework层,应用层级的程序。开发者可以为其开发一些系统或应用方面的插件,自定义android系统,它甚至可以做动态权限管理(XposedMods)。
本文发布于:2024-02-02 03:54:19,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170681759341188.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |