【Android学习】DVM——apk生成、安装、启动、反汇编

0,参考资料

《Android框架揭秘》 [韩]金泰延 宋亨周 朴知勋 李白 林起永 著 武传海 译

1,Dalvik

1)概念

Dalvik是Google公司自己设计用于Android平台的Java虚拟机。
Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一,它可以支持已转换为.dex(即Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik应用设计的一种压缩格式,适合内存和处理器速度有限的系统。

2)与Linux进程关系

Dalvik经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik应用作为独立的Linux进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。

每个 Android 应用程序都运行在单独的 Dalvik 虚拟机内(即每个 Android 应用程序对用一条 Dalvik VM实例、对应一个Linux进程), Dalvik 专门针对同时高效地运行多个虚拟机进行优化,因此 Android 系统以方便的实现对应用程序进行隔离。

Android 运行时由两部分组成:

①Android 核心库集

提供了 Java 语言核心库所能使用的绝大部分功能。

②Dalvik 虚拟机

DVM负责运行 Android 应用程序。

2)Dalvik字节码

Android平台不用JVM来执行代码,而是把应用编译成Dalvik字节码,使用Dalvik虚拟机来执行。最终应用只包含Dalvik字节码。

过程:
Java代码 –(编译成)–> Java字节码 –(dex编译器:dx,SDK工具 编译成)Dalvik字节码(dex字节码)。

最后获得结果:classes.dex。
通过反编译classes.dex即可得到java源代码。运用安卓反编译工具dex2jar可将dex文件反编译成.jar文件,然后运用jd-gui工具即可查看反编译后得到的源代码。

3)ART(Android runtime)

①概念

谷歌Android 4.4系统新增的一种应用运行模式,与传统的Dalvik模式不同,ART模式可以实现更为流畅的安卓系统体验,对于大家来说,只要明白ART模式可让系统体验更加流畅,不过只有在安卓4.4以上系统中采用此功能。

Art上应用启动快,运行快,但是耗费更多存储空间,安装时间长,总的来说ART的功效就是”空间换时间“。

②与DVM区别

i> 处理应用程序执行的方式不同

Dalvik是依靠一个Just-In-Time(JIT)编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不同硬件和架构上运行。
ART则完全改变了这套做法,在应用安装的时候就预编译字节码到机器语言,这一机制叫Ahead-Of-Time(AOT)编译。在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。

③ART优点

系统性能的显著提升
应用启动更快、运行更快、体验更流畅、触感反馈更及时
更长的电池续航能力
支持更低的硬件

④ART缺点

更大的存储空间占用,可能会增加10%-20%
更长的应用安装时间

4)JIT(实时编译器)

Android2.2引入JIT,Dalvik JIT编译器把Dalvik字节码编译成本地代码,这可以显著提高性能。

原因:
①本地代码直接由CPU执行,不需要虚拟机解释执行;
②本地代码可以被特定架构优化。

2,Android代码执行过程

1)app启动过程

点击应用图标后会去启动应用的LauncherActivity,如果LancerActivity所在的进程没有创建,还会创建新进程,整体的流程就是一个Activity的启动流程。
app启动过程

①主要角色

①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的启动、切换、调度等各种操作都在这个类里完成。

②相关进程

i> 调用者进程

如果是在桌面启动应用就是Launcher应用进程。

ii> ActivityManagerService等所在的System Server进程

该进程主要运行着系统服务组件。

iii> Zygote进程

该进程主要用来fork新进程。

iv> 新启动的应用进程

该进程就是用来承载应用运行的进程了,它也是应用的主线程(新创建的进程就是主线程),处理组件生命周期、界面绘制等相关事情。

③整个流程

①点击桌面应用图标,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主要源码组成

Kernel

Android Linux 内核2.6

bionic

Android 标准C运行支持库

bootloader

Android内核加载器参考

build

Android的Build系统

cts

Android兼容性测试源

dalvik

Dalvik虚拟机

external

Android使用的开放源

frameworks

Android框架

hardware

Android HAL(Hanrdware Abstraction Layer,硬件抽象层)库源

packages

包含Android 基本应用,Content Provider等

system

Android初始化进程、蓝牙工具集等

2)Android启动过程

Androi启动过程

Linux内核

Android启动时,首先通过bootloader(系统加载器),加载Linux内核。
在Linux加载启动时,与普通Linux启动过程相同,先初始化内核,然后调用init进程。

init进程

init进程对各种设备进行初始化,运行Android framework 所需用的各种Daemon、Context Manager、Media Server、Zygote等。

在Linux中,init进程是Android启动后,由内核启动的第一个用户级进程,也是所有进程的父进程。

四大功能:
分析及允许init.rc文件

分析init.rc启动脚本文件,病根据相关文件中包含的内容,执行相应的功能。

生成设备驱动节点

应用程序访问设备驱动时,生成设备节点文件。

处理子进程终止
属性服务

保存系统允许所需的环境变量。

init执行Daemon进程

USB Daemon(usbd):管理USB连接
Android Debug Bridge Daemon(adbd):Android Debug Bridge连接管理。
Debugger Daemon(debuggerd):启动Debugger系统。
Radio Interface Layer Daemon(rild):管理无线通信连接。

Context Manager进程

Context Manager是一个管理Android系统服务的重要进程。系统服务是组成Android Framework的重要组件,提供从相机、音频、视频处理到各种应用程序直至需要的重要API。
Context Manager提供运行与Android内的各种系统服务信息。应用程序或Framework内部模块在调用系统服务时,需要先向服务管理器申请,而后通过Binder IPC(Interprocess conmmunication)调用系统服务。
在系统系统时,Android所有系统服务都要把各自的handle信息注册到Context Manager,此时,Binder IPC用来进行进程间通信。

Media Server

用于运行基于C/C++的本地系统服务,如Audio Flinger(负责音频输出)、Camera等。

Zygote进程

用于缩短Android应用程序加载时间。

每当执行Java应用程序时,Zygote进程会派生出一个子进程来执行应用程序,该子进程是用来执行Java应用程序的虚拟机,初始化JVM,并启动它。

在应用程序运行前,Zygote进程通过共享已运行的JVM代码与内存信息,缩短应用程序运行所耗费的时间。并且,它会事先将应用程序要使用的Android Framework中的类与资源加载到内存中,并组织形成所用资源的链接信息。新运行的App在使用所需资源时不必每次形成资源的链接ixnx,这会节省大量时间,提高程序运行速度。

除了Zygote,Maemo平台的Launcher、Qt extended的quick Launcher等都是基于相同的目的而被使用的。

意义:一方面提高设备资源利用率,一方面尽可能的加长设备的使用时间。

System Server进程

是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应用,启动过程继续进行。

3,APK

1)APK打包

①概念

Android的包文件APK分为两个部分:代码和资源,所以打包方面也分为资源打包和代码打包。

②打包流程

打包流程
1)通过AAPT工具进行资源文件(包括AndroidManifest.xml、布局文件、各种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文件的速度会更快。

2)APK的安装流程

apk的安装流程
1)复制APK到/data/app目录下,解压并扫描安装包。
2)资源管理器解析APK里的资源文件。
3)解析AndroidManifest文件,并在/data/data/目录下创建对应的应用数据目录。
4)然后对dex文件进行优化,并保存在dalvik-cache目录下。
5)将AndroidManifest文件解析出的四大组件信息注册到PackageManagerService中。
6)安装完成后,发送广播。

3)64k方法

①问题

随着功能的增加,方法数增多,就会出现所谓的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错误。

③解决方案–MultiDex的使用

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设备上运行。

⑤实现

配置build.gradle

android {
      compileSdkVersion 21
      buildToolsVersion "21.1.0"//必须使用21或之后的版本
      defaultConfig {
        ...
        minSdkVersion 14
        targetSdkVersion 21
        ...


        // Enabling multidex support.
        multiDexEnabled true
    }
    ...
  }
  dependencies {
  compile 'com.android.support:multidex:1.0.0'
  }

配置Application
如果用户没有重写Application,只需修改Manifest文件中的内容:

<application
        ...
        android:name="android.support.multidex.MultiDexApplication">
        ...
    </application>

如果用户继承变重写了Application,可以将继承的Application换成MultiDexApplication。
或者重写attachBaseContext() 方法,

@Override
 protected void attachBaseContext(Context base) {
     super.attachBaseContext(base);
     MultiDex.install(this);
}

特别注意,如果没有实现这部分代码,运行时会出现NoClassDefFoundError的错误,尤其是在依赖三方函数库时。
这个问题之前出现过很多次,当时是由于配置了build.gradle文件但是没有使用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.** {
    *;
}

然后在build.gradle中进行配置:

android {
    defaultConfig {
        multiDexEnabled true
        multiDexKeepProguard file('multiDexKeep.pro') 
        multiDexKeepFile file('multiDexKeep.txt') 
    }
}
dependencies {
    compile'com.android.support:multidex:1.0.1'
}

⑥Multidex优化

multidex会加长构建应用的时间,这个必要的过程可能会拖慢你的开发进度。 为加速构建过程,我们可以在Gradle中配置productFlavors。
开发时将minSdkVersion改为21使用ART运行时机制,这样能加快构建速度。release时改为合适的minSdkVersion,这样仅在release时费时较长。
build.gradle配置如下:

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设备上。

4)apk解压

apk文件是压缩文件,可以用压缩工具(如WinZip、7-Zip)来查看apk文件的内容。
解压后如图所示:
apk

i>assets
ii>lib
iii>META-INF

这个文件夹存储的是关于签名的一些信息。

iv>res
v>AndroidManifest.xml
vi>classes.dex

包含应用的字节码。
Android的工具包里有工具dexdump,可以把classes.dex的二进制代码转化位使人易读的格式。我们可以使用dexdump查看该文件。

vii>resources.arsc

4,Android反汇编

①下载 dex2jar工具。

点击下载dex2jar

②解压后,把classes.dex放入目录

目录示例

③CMD运行

如图,打开安装目录,运行dex2jar.bat classes.dex(因版本不同,本次举例使用 d2j-dex2jar.bat classes.dex命令)
cmd命令

运行命令后会生成文件:classes-dex2jar.jar(如图所示)
classes-dex2jar.jar

④下载jd-gui.exe

点击下载jd-gui.exe

⑤安装jd-gui,打开jar文件即可查看

jd-gui查看文件

这个apk是直接生成,没有进行代码混淆,所以代码很完整。这也说明了反逆向工程的重要性。

5,模块化编程

1)动态加载

①概念

动态加载技术就是使用类加载器加载相应的apk、dex、jar(必须含有dex文件),再通过反射获得该apk、dex、jar内部的资源(class、图片、color等等)进而供宿主app使用。

②类加载器

Android支持动态加载的两种方式是:DexClassLoader和PathClassLoader。

PathClassLoader

只能加载已经安装到Android系统的APK文件,即/data/app目录,Android默认的类加载器。

DexClassLoader

可以加载任意目录下的dex、jar、apk、zip文件,且支持从SD卡加载。

实现

2)插件化

①概念

插件化是体现在功能拆分方面的,它将某个功能独立提取出来,独立开发,独立测试,再插入到主应用中。依次来较少主应用的规模。

②应用

  1. 开发者将插件代码封装成Jar或者APK
  2. 宿主下载或者从本地加载Jar或者APK到宿主中
  3. 将宿主调用插件中的算法或者Android特定的Class(如Activity)

③原因

一个应用程序dex文件的方法数最大不能超过65536个;
可以让应用程序实现插件化、插拔式结构,对后期维护有益。

④解决问题

代码加载

类的加载可以使用Java的ClassLoader机制,还需要组件生命周期管理。

资源加载

用AssetManager的隐藏方法addAssetPath。

⑤原理——Hook机制之动态代理

使用代理机制进行API Hook进而达到方法增强。

静态代理
动态代理

可以简单理解为JVM可以在运行时帮我们动态生成一系列的代理类。

代理Hook

如果我们自己创建代理对象,然后把原始对象替换为我们的代理对象,就可以在这个代理对象中为所欲为了;修改参数,替换返回值,称之为Hook。

整个Hook过程简要总结如下:

  1. 寻找Hook点,原则是静态变量或者单例对象,尽量Hook public的对象和方法,非public不保证每个版本都一样,需要适配。
  2. 选择合适的代理方式,如果是接口可以用动态代理;如果是类可以手动写代理也可以使用cglib。
  3. 偷梁换柱-用代理对象替换原始对象

2)热修复

①概念

热修复是体现在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)。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值