Android平台架构和特性
Android系统的底层建立在Linux系统之上,该平台由操作系统、中间件、用户界面和应用软件4层组成,它采用一种被称为软件叠层(Software Stack)的方式进行构建。这种软件叠层结构使得层与层之间相互分离,明确各层的分工。这种分工保证了层与层之间的低耦合,当下层的层内或层下发生改变时,上层应用程序无须任何改变。
从图1.1可看出, Android系统主要由5部分组成,下面分别对这5部分进行简单介绍:
1. 应用程序层
Android系统将会包含系列的核心应用程序,包括电子邮件客户端、SMS程序、日历、地图、浏览器。联系人等。这些应用程序都是用Java编写的。
2. 应用程序架构
当我们开发Android应用程序时,就是面向底层的应用程序框架进行的。从这个意义上来看,Android系统上的应用程序时完全平等的,不管是Android系统提供的程序,还是普通开发者提供的程序,都可以访问Android提供的API框架。
应用程序框架除可作为应用程序开发的基础之外,也是软件复用的重要手段,任何一个应用程序都可发布它的功能模块——只要发布时遵守了框架的约定,那么其他应用程序也可以使用这个功能模块。
3. 函数库
Android包含一套被不同组件所使用的C/C++库的集合。一般来说,Android应用开发者不能直接调用这套C/C++库集,但可以通过它上面的应用程序框架来调用这些库。
下面列出一些核心库:
- 系统C库:一个从BSD系统派生出来的标准C系统库(libc),并且专门为嵌入式Linux设备调整过。
- 媒体库:基于PacketVideo的OpenCORE,这套媒体库支持播放和录制许多流行的音频和视频格式,以及查看静态图片。主要包括MPEG4、H.264、MP3、AAC、AMR、JPG、PNG等多媒体格式。
- Surface Manager:管理对显示子系统的访问,并可以对多个应用程序的2D和3D图层机提供无缝整合。
- LibWebCore:一个全新的Web浏览器引擎,该引擎为Android浏览器提供支持,也为WebView提供支持,WebView完全可以嵌入开发者自己的应用程序中。
- SGL:底层的2D图形引擎。
- 3D libraries:基于OpenGL ES 1.0 API实现的3D系统,这套3D库既可使用硬件3D加速(如果硬件系统支持), 也可使用高度优化的软件3D加速。
- FreeType:位图和向量字体显示。
- SQLite:供所有应用程序使用的、功能强大的轻量级关系数据库。
4. Android运行时
Android运行时由两部分组成:Android核心库集和Dalvik虚拟机。其中核心库集提供了Java语言核心库所能使用的绝大部分功能,而虚拟机则负责运行Android应用程序。
每个Android应用程序都运行在单独的Dalvik虚拟机内(即每个Android应用程序对应一条Dalvik进程),Dalvik专门针对同时高效地运行多个虚拟机进行了优化,因此Android系统以方便地实现对应用程序进行隔离。
由于Android应用程序的编程语言是Java,因此有些人会把Dalvik虚拟机和JVM搞混,但实际上二者存在区别:Dalvik并未完全遵守JVM规范,两者也不兼容。实际上,JVM虚拟机运行的是Java字节码(通常就是.class文件),但Dalvik运行的是其专有的dex(Dalvik Executable)文件。JVM直接从.class文件或JAR包中加载字节码然后运行;而Dalvik则无法直接从.class文件或JAR包中加载字节码,它需要通过DX工具将应用程序的所有.class文件编译成.dex文件,Dalvik则运行该.dev文件。
Dalvik虚拟机非常适合在移动终端上使用,相对于在PC或服务器上运行的虚拟机而言,Dalvik虚拟机不需要很快的CPU计算速度和大量的内存空间,它主要有如下两个特点:
- 运行专有的.dev文件。专有的.dev文件减少了.class文件中的冗余信息,而且会把所有.class文件整合到一个文件中,从而提高运行性能;而且DX工具还会对.dex文件进行一些性能的优化。
- 基于寄存器实现。大多数虚拟机(包括JVM)都是基于栈的,而Davlik虚拟机则是基于寄存器的。一般来说,基于寄存器的虚拟机具有更好的性能表现,但在硬件通用性上略差。
Dalvik虚拟机依赖于Linux内核提供的核心功能,如线程和底层内存管理。
5. Linux内核
Android系统建立在Linux 2.6之上。Linux内核提供了安全性、内存管理、进程管理、网络协议栈和驱动模型等核心系统服务。此外,Linux内核也是系统硬件和软件叠层之间的抽象层。
Android常用的开发工具
Android开发编译器现在最流行的是Android Studio,现在用Eclipse的开发者已经很少了。高效地开发者要善于利用工具,而且是好的高效地工具。笔者喜欢JetBrains公司的系列产品,现在流行的Java IDE就是该公司开发的IntelliJ IDEA, 而Android Studio是基于IntelliJ IDEA的,是Google和JetBrains公司合作的项目。开发J2EE服务器端项目我用的就是IntelliJ IDEA。Web前端开发我喜欢用Webstorm,也是JetBrains公司的产品。
# Android模拟器
最理想的开发环境当然是用手机真机,但一般个人甚至单位都不会提供现在市面上所有品牌、所有尺寸、所有版本的手机来开发测试,这不现实。所以Android模拟器emulator对开发的适配工作就非常有帮助,能够在电脑上直接模拟各种手机机型的运行环境。
# 使用DDMS进行调试
Android提供了一个DDMS(Dalvik Debug Monitor Service)调试环境。不管是Eclipse还是Android Studio,一般都有这些调试面板:设备面板、信息输出面板、线程跟踪面板、Heap内存跟踪面板、模拟器控制面板、文件管理对话框。
关于DDMS的使用,多说无益,读者必须在实践中不断积累。
# 使用ADB
Android Debug Bridge(ADB)
是一个功能非常强大的工具,它位于Android SDK
安装目录的platform-tools
子目录下。ADB
工具即可完成模拟器文件与电脑文件的相互复制,也可安装APK应用,甚至可以直接切换到Android系统中执行Linux命令。
# 使用DX编译Android应用
前面已经提到,Android平台有一个重要的概念:Android运行时。Android运行时使用的虚拟机并没有遵循JVM规范,Android所使用的虚拟机是Dalvik虚拟机。
Dalvik虚拟机并不直接运行Java二进制文件,而是运行它特有的*.dex
文件,因此我们需要通过DX工具将Android应用的.class文件转换为.dex文件。
DX工具的常见命令格式如下:
dx --dex [--dump-to=] [--core-library] [.class | .{zip,jar,apk} | ]
上面的命令中[--dump-to=
指定生成的*.dex
文件的文件名;而--core-library
指定需要转换的*.class
、*.zip
、*.jar
文件或者目录。
例如以下命令:
dx -dex --dump-to=g:\a.dex --core-library d:\helloworld\bin
将d:\helloworld\bin
路径下所有二进制文件转换为g:\
根目录下的a.dex
文件。
# 使用AAPT打包资源
开发Android应用时,该应用中可能会包含许多资源文件,包括各种图片、音频文件等。Android Asset Packaging Tool(AAPT)
就是用来打包这些资源文件的。其实开发中,IDE已经为我们做好这些工作,打包的命令,以及上一小节的DX编译命令,实际上并不需要我们在命令行执行,在此仅做了解。
# 使用mksdcard管理虚拟SD卡
我们可以在创建AVD设备时创建一个虚拟SD卡。实际上还可以使用mksdcard命令来单独创建一个虚拟存储卡。
mksdcard命令的语法格式如下:
mksdcard [-1 label]
上面的命令格式中
例如以下命令:
mksdcard 64M D:\avds\.android\avd\henry.avd\sdcard.img
创建一个大小为64MB的虚拟SD卡,该SD卡对应的镜像文件为D:\avds\.android\avd\henry.avd\sdcard.img
。
实际上,Android Studio提供丰富的用户界面给用户设置虚拟SD卡,这里的命令只有做加深理解的意义。
Android应用结构分析
用Android Studio创建的项目,应用目录结构如图:
关于每一个目录和文件的具体意义和用法,我另写了单独的一节Android应用结构解析
Android基本组件
Android应用通常由一个或多个基本组件组成,android应用中最常用的组件就是Activity。Android应用还可能包括Service、BroacastReceiver、ContentProvider等组件:
-
Activity: 实际上Activity是Window的容器,Activity包含一个getWindow()方法,该方法返回该Activity所包含的窗口。对于Activity而言,开发者一般不需要关心Window对象。如果应用程序不调用Activity的setContentView()来设置该窗口显示的内容,那么该程序将显示一个空窗口。
View组件是所有UI控件、容器控件的基类,View组件就是Android应用中用户实实在在看到的部分。但View组件需要放到容器组件中,或者使用Activity将它显示出来。如果需要通过某个Activity把指定View显示出来,调用Activity的setContentView()方法即可。
-
Service: Service与Activity的地位是并列的,它也代表一个单独的Android组件。Service与Activity的区别在于:Service通常位于后台运行,它一般不需要与用户交互,因此Service组件没有图形用户界面。
一个Service组件被运行起来之后,它将拥有自己独立的生命周期,Service组件通常用于为其他组件提供后台服务或监控其他组件的运行状态。
BroadcastReceiver: BroadcastReceiver即广播消息接收器,BroadcastReceiver非常类似于事件编程中的监听器。与普通事件监听器不同的是:普通事件监听器的事件源是程序中的对象;而BroadcastReceiver监听的事件源是Android应用中的其他组件。
当其他组件通过sendBroadcast()、sendStickyBroadcast()或sendOrderedBroadcast()方法发送广播消息时,
如果该BroadcastReceiver也对该消息“感兴趣”(通过IntentFilter配置),
BroadcastReceiver的onReceive(Context context, Intent intent)方法将会被触发。
有2种方式注册这个系统级的“事件监听器”:
- 在Java代码中通过Context.registReceiver()方法注册BroadcastReceiver。
- 在AndroidManifest.xml文件中使用
元素注册。
-
ContentProvider: 对于Android应用程序,它们必须相互独立,各自运行在自己的Dalvik虚拟机实例中,如果这些Android应用程序之间需要实现实时的数据交换。
Android系统为这种跨应用的数据交换提供了一个标准: ContentProvider。
当用户实现自己的ContentProvider时,需要实现如下抽象方法:insert(Uri, ContentValues): 向ContentProvider插入数据
delete(Uri, ContentValues): 删除ContentProvider中指定数据
update(Uri, ContentValues, String, String[]): 更新ContentProvider中指定数据
query(Uri, String[], String, String[], String): 从ContentProvider查询数据。通常与ContentProvider结合使用的是ContentResolver,一个应用程序使用ContentProvider暴露自己的数据,而另一个应用程序则通过ContentResolver来访问数据。
-
Intent和IntentFilter: Intent不是Android应用的组件,但它对于Android应用的作用非常大——它是Android应用内不同组件之间通信的载体。
当Android运行时需要连接不同的组件时,通常就需要借助于Intent来实现。Intent封装了当前组件需要启动或触发的目标组件的信息。
Intent可以启动应用中另一个Activity,也可以启动一个Service组件,还可以发送一条广播消息来触发系统中的BroadcastReceiver。
也就是说,Activity、Service、BroadcastReceiver三种组件之间的通信都以Intent作为载体,只是不同组件使用Intent的机制略有区别而已。- 启动Activity,Context.startActivity(Intent intent)或Context.startActivityForResult(Intent intent, int requestCode)
- 启动Service,Context.startService(Intent intent)或Context.bindService(Intent service, ServiceConnection conn, int flags)
- 启动BroadcastReceiver. Context.sendBroadcast(Intent intent)、Context.sendStickyBroadcast(Intent intent)或Context.sendOrderedBroadcast(Intent intent, String receiverPermission)
Intent分为2类:
显示Intent: 明确指定需要启动或者触发的组件的类名。
隐式Intent: 指定需要启动或者触发的组件应满足怎样的条件。对显示Intent, Android系统无须对该Intent做任何解析,系统直接找到指定的目标组件,启动或触发它。
对隐式Intent,Android系统需要对该Intent进行解析,解析出它的条件,然后再去系统中查找与之匹配的目标组件。如果找到符合条件的组件,就启动或触发它们。
IntentFilter用来声明满足的条件,从而判断被调用组件是否符合隐式Intent。
Android应用签名
Android项目以它的报名作为唯一标识。如果在同一台手机上安装两个包名相同的应用,后面安装的应用就可以覆盖前面安装的应用。为了避免这种情况发生,Android要求对作为产品发布的应用进行签名。
签名主要有如下两个作用:
确定发布者的身份。由于应用开发者可以通过使用相同包名来替换已经安装的程序,因此使用签名可以避免发生这种情况。
确保应用的完整性。签名会对应用包中的每个文件进行处理,从而确保程序包中的文件不会被替换。
Android应用签名的作用类似于现实生活中的签名。当开发者对Android应用签名时,相当于告诉外界:该应用程序是由”我“开发的,”我“会对该应用负责——因为有签名(签名有密钥),别人无法冒名顶替”我“;与此同时,”我“也无法冒名顶替别人。
注:本节内容整理自《疯狂android讲义》