第4章 Android技术核心框架分析
学习编程不能打无把握之仗,学习Android传感器和外部设备开发也是如此。要想真正精通开发Android传感器和外部设备应用程序开发技术的精髓,不仅需要学习底层和Android框架方面的知识,还需要掌握Android顶层应用程序开发的基本知识。本章将详细讲解Android系统的体系结构,为读者步入本书后面高级知识的学习打下基础。
4.1 分析Android的系统架构
为了更加深入地理解Android系统的精髓,初学者们很有必要了解Android系统的整体架构,了解它的具体组成。只有这样才能知道Android究竟能干什么,人们所要学的是什么。
4.1.1 Android体系结构介绍
Android是一个移动设备的开发平台,其软件层次结构包括操作系统(OS)、中间件(MiddleWare)和应用程序(Application)。根据Android的软件框图,其软件层次结构自下而上分为以下4层:
(1)操作系统层(OS);
(2)各种库(Libraries)和Android运行环境(RunTime);
(3)应用程序框架(Application Framework);
(4)应用程序(Application)。
上述各个层的具体结构如图4-1所示。
▲图4-1 Android操作系统的组件结构图
1.操作系统层(OS)——最底层
因为Android源于Linux,使用了Linux内核,所以Android使用Linux 2.6作为操作系统。Linux 2.6是一种标准的技术,Linux也是一个开放的操作系统。Android对操作系统的使用包括核心和驱动程序两部分,Android的Linux核心为标准的Linux 2.6内核,Android更多的是需要一些与移动设备相关的驱动程序。主要的驱动如下所示。
· 显示驱动(Display Driver):常用基于Linux的帧缓冲(Frame Buffer)驱动;
· Flash内存驱动(Flash Memory Driver):是基于MTD的Flash驱动程序;
· 照相机驱动(Camera Driver):常用基于Linux的v4l(Video for)驱动;
· 音频驱动(Audio Driver):常用基于ALSA(Advanced Linux Sound Architecture,高级Linux声音体系)驱动;
· Wi-Fi驱动(Camera Driver):基于IEEE 802.11标准的驱动程序;
· 键盘驱动(KeyBoard Driver):作为输入设备的键盘驱动;
· 蓝牙驱动(Bluetooth Driver):基于IEEE 802.15.1标准的无线传输技术;
· Binder IPC驱动:Android一个特殊的驱动程序,具有单独的设备节点,提供进程间通信的功能;
· Power Management(能源管理):管理电池电量等信息。
2.各种库(Libraries)和Android运行环境(RunTime)——中间层
本层次对应一般嵌入式系统,相当于中间件层次。Android的本层次分成两个部分,一个是各种库,另一个是Android运行环境。本层的内容大多是使用C实现的。其中包含的各种库如下所示。
· C库:C语言的标准库,也是系统中一个最为底层的库,C库是通过Linux的系统调用来实现;
· 多媒体框架(MediaFrameword):这部分内容是Android多媒体的核心部分,基于PacketVideo(即PV)的OpenCORE,从功能上本库一共分为两大部分,一个部分是音频、视频的回放(PlayBack),另一部分是则是音视频的记录(Recorder);
· SGL:2D图像引擎;
· SSL:即Secure Socket Layer位于TCP/IP协议与各种应用层协议之间,为数据通信提供安全支持;
· OpenGL ES 1.0:提供了对3D的支持;
· 界面管理工具(Surface Management):提供了对管理显示子系统等功能;
· SQLite:一个通用的嵌入式数据库;
· WebKit:网络浏览器的核心;
· FreeType:位图和矢量字体的功能;
Android的各种库一般是以系统中间件的形式提供的,它们均有的一个显著特点就是与移动设备的平台的应用密切相关。
Android运行环境主要是指的虚拟机技术——Dalvik。Dalvik虚拟机和一般Java虚拟机(Java VM)不同,它执行的不是Java标准的字节码(Bytecode),而是Dalvik可执行格式(.dex)中执行文件。在执行的过程中,每一个应用程序即一个进程(Linux的一个Process)。二者最大的区别在于Java VM是基于栈(Stack-based)的虚拟机,而Dalvik是基于寄存器(Register-based)的虚拟机。显然,后者最大的好处在于可以根据硬件实现更大的优化,这更适合移动设备的特点。
3.应用程序(Application)
Android的应用程序主要是用户界面(User Interface)方面的,通常用Java语言编写,其中还可以包含各种资源文件(放置在res目录中)。Java程序和相关资源在经过编译后,会生成一个APK包。Android本身提供了主屏幕(Home)、联系人(Contact)、电话(Phone)和浏览器(Browers)等众多的核心应用。同时应用程序的开发者还可以使用应用程序框架层的API实现自己的程序。这也是Android开源的巨大潜力的体现。
4.应用程序框架(Application Framework)
Android的应用程序框架为应用程序层的开发者提供APIs,它实际上是一个应用程序的框架。由于上层的应用程序是以Java构建的,因此本层次提供的首先包含了UI程序中所需要的各种控件,例如:Views(视图组件),其中又包括了List(列表)、Grid(栅格)、Text Box(文本框)和Button(按钮)等。甚至一个嵌入式的Web浏览器。
作为一个基本的Android应用程序,可以利用应用程序框架中的以下5个部分来构建:
· Activity(活动);
· Broadcast Intent Receiver(广播意图接收者);
· Service(服务);
· Content Provider(内容提供者);
· Intent and Intent Filter(意图和意图过滤器)。
4.1.2 Android应用工程文件组成
讲解完Android的整体结构之后,接下来开始讲解Android工程文件的组成。在Eclipse中,一个基本的Android项目的目录结构如图4-2所示。
▲图4-2 Android应用工程文件组成
1.src目录
在里面保存了开发人员编写的程序文件。和一般的Java项目一样,“src”目录下保存的是项目的所有包及源文件(.java),“res”目录下包含了项目中的所有资源。例如,程序图标(drawable)、布局文件(layout)和常量(values)等。不同的是,在Java项目中没有“gen”目录,也没有每个Android项目都必须有的AndroidManfest.xml文件。
“.java”格式文件是在建立项目时自动生成的,这个文件是只读模式,不能更改。R.java文件是定义该项目所有资源的索引文件。例如下面是某项目中R.java文件的代码。
package com.yarin.Android.HelloAndroid; public final class R { public static final class attr { } public static final class drawable { public static final int icon=0x7f020000; } public static final class layout { public static final int main=0x7f030000; } public static final class string { public static final int app_name=0x7f040001; public static final int hello=0x7f040000; } }
在上述代码中定义了很多常量,并且这些常量的名字都与res文件夹中的文件名相同,这再次证明.java文件中所存储的是该项目所有资源的索引。有了这个文件,在程序中使用资源将变得更加方便,可以很快地找到要使用的资源,由于这个文件不能被手动编辑,所以在项目中加入了新的资源时,只需要刷新一下该项目,.java文件便自动生成了所有资源的索引。
2.设置文件AndroidManfest.xml
文件AndroidManfest.xml是一个控制文件,在里面包含了该项目中所使用的Activity、Service和Receiver。例如下面是某项目中文件AndroidManfest.xml的代码。
<? xml version="1.0" encoding="utf-8"? > <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.yarin.Android.HelloAndroid" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".HelloAndroid" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-sdk android:minSdkVersion="9" /> </manifest>
在上述代码中,intent-filters描述了Activity启动的位置和时间。每当一个Activity(或者操作系统)要执行一个操作时,它将创建出一个Intent的对象,这个Intent对象可以描述想做什么、想处理什么数据、数据的类型以及一些其他信息。Android会和每个Application所出现的intent-filter的数据进行比较,找到最合适Activity来处理调用者所指定的数据和操作。下面来仔细分析AndroidManfest.xml文件,如表4-1所示。
表4-1 AndroidManfest.xml分析
3.常量定义文件
下面介绍一在资源文件中对常量的定义,例如文件String.xml的代码如下所示。
<? xml version="1.0" encoding="utf-8"? > <resources> <string name="hello">Hello World, HelloAndroid! </string> <string name="app_name">HelloAndroid</string> </resources>
上述常量定义文件的代码非常简单,只定义了两个字符串资源,请不要小看上面的几行代码。它们的内容很“露脸”,里面的字符直接显示在手机屏幕中,就像动态网站中的HTML一样。
4.布局文件
布局(layout)文件一般位于“res\layout\main.xml”目录,通过其代码能够生成一个显示界面。例如下面的代码。
<? xml version="1.0" encoding="utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> </LinearLayout>
在上述代码中,有以下几个布局和参数。
· <LinearLayout></LinearLayout>:在这个标签中,所有元件都是按由上到下的队列排成的。
· android:orientation:表示这个介质的版面配置方式是从上到下垂直地排列其内部的视图。
· android:layout_width:定义当前视图在屏幕上所占的宽度,fill_parent即填充整个屏幕。
· android:layout_height:定义当前视图在屏幕上所占的高度,fill_parent即填充整个屏幕。
· wrap_content:随着文字栏位的不同而改变这个视图的宽度或高度。
在上述布局代码中,使用了一个TextView来配置文本标签Widget(构件),其中设置的属性android:layout_width为整个屏幕的宽度,android:layout_height可以根据文字来改变高度,而android:text则设置了这个TextView要显示的文字内容,这里引用了@string中的hello字符串,即String.xml文件中的hello所代表的字符串资源。hello字符串的内容Hello World, HelloAndroid!这就是人们在HelloAndroid项目运行时看到的字符串。
注意
上面介绍的文件只是主要文件,在项目中需要自行编写。在项目中还有很多其他的文件,那些文件很少需要自己编写,所以在此就不进行讲解了。
4.2 Android的五大组件
一个典型的Android应用程序通常由5个组件组成,这5个组件构成了Android的核心功能。本节将一一讲解这五大组件的基本知识,为读者步入本书后面知识的学习打下基础。
4.2.1 Activity界面组件
Activitiy是这5个组件中最常用的一个组件。程序中Activity通常的表现形式是一个单独的界面(screen)。每个Activitiy都是一个单独的类,它扩展实现了Activity基础类。这个类显示为一个由Views组成的用户界面,并响应事件。大多数程序有多个Activity。例如,一个文本信息程序有以下几个界面:显示联系人列表界面、写信息界面、查看信息界面或者设置界面等。每个界面都是一个Activity。切换到另一个界面就是载入一个新的Activity。在某些情况下,一个Activity可能会给前一个Activity返回值,例如,一个让用户选择相片的Activity会把选择到的相片返回给其调用者。
打开一个新界面后,前一个界面就被暂停,并放入历史栈中(界面切换历史栈)。使用者可以回溯前面已经打开的存储在历史栈中的界面。也可以从历史栈中删除没有界面价值的界面。Android在历史栈中保留程序运行产生的所有界面:从第一个界面,到最后一个。
4.2.2 Intent切换组件
Android通过一个专门的Intent类来进行界面的切换。Intent描述了程序想做什么(Intent翻译为意图、目的、意向)。Intent类还有一个相关类IntentFilter。Intent是一个请求来做什么事情,IntentFilter则描述了一个Activity(或下文的IntentReceiver)能处理什么意图。显示某人联系信息的Activity使用了一个IntentFilter,就是说它知道如何处理应用到此人数据的VIEW操作。Activities在文件AndroidManifest.xml中使用IntentFilters。
通过解析Intents可以实现Activity的切换,人们可以使用startActivity(myIntent)启用新的Activity。系统会考察所有安装程序的IntentFilters,然后找到与myIntent匹配最好的IntentFilters所对应的Activity。这个新Activity能够接收Intent传来的消息,并因此被启用。解析Intents的过程发生在startActivity被实时调用的时候,这样做有如下两个好处。
(1)Activity仅发出一个Intent请求,便能重用其他组件的功能。
(2)Activity可以随时被替换为有等价IntentFilter的新Activity。
4.2.3 Service服务组件
Service是一个没有UI且长驻系统的代码,最常见的例子是媒体播放器从播放列表中播放歌曲。在媒体播放器程序中,可能有一个或多个Activities让用户选择播放的歌曲。然而在后台播放歌曲时无须Activity干涉,因为用户希望在音乐播放的同时能够切换到其他界面。既然这样,媒体播放器Activity需要通过Context.startService()启动一个Service,这个Service在后台运行以保持继续播放音乐。在媒体播放器被关闭之前,系统会保持音乐后台播放Service的正常运行。可以用Context.bindService()方法连接到一个Service上(如果Service未运行的话,连接后还会启动它),连接后就可以通过一个Service提供的接口与Service进行通话。对音乐Service来说,提供了暂停和重放等功能。
1.如何使用服务
在Android系统中有如下两种使用服务的方法。
(1)通过调用Context.startServece()启动服务,调用Context.stopService()结束服务,startService()可以传递参数给Service。
(2)通过调用Context.bindService()启动,调用Context.unbindService()结束,还可以通过ServiceConnection访问Service。二者可以混合使用,例如可以先调用startService()再调用unbindService()。
2.Service的生命周期
在startService()后,即使调用startService()的进程结束了,Service还仍然存在,一直到有进程调用stoptService()或者Service自己结束(stopSelf())为止。
在bindService()后,Service就和调用bindService()的进程同生共死了,也就是说当调用bindService()的进程结束了,那么它绑定的Service也要跟着被结束,当然期间也可以调用unbind Service()让Service结束。
当混合使用上述两种方式时,例如你启动startService(),我启动bindService(),那么只有你调用stoptService()而且我也调用unbindService(),这个Service才会被结束。
3.进程生命周期
Android系统将会尝试保留那些启动的或者绑定的服务进程,具体说明如下所示。
(1)如果该服务正在进程的onCreate()、onStart()或者onDestroy()这些方法中执行时,那么主进程将会成为一个前台进程,以确保此代码不会被停止。
(2)如果服务已经开始,那么它的主进程的重要性会低于所有的可见进程,但是会高于不可见进程。由于只有少数几个进程是用户可见的,所以只要不是内存特别低,该服务就不会停止。
(3)如果有多个客户端绑定了服务,只要客户端中的一个对于用户是可见的,就可以认为该服务是可见的。
4.2.4 用Broadcast/Receiver广播机制组件
当要执行一些与外部事件相关的代码时,例如来电响铃时或者半夜时就可能用到IntentReceiver。尽管IntentReceiver使用NotificationManager来通知用户一些好玩的事情发生,如果没有UI.IntentReceivers可以在文件AndroidManifest.xml中声明,也可以使用Context.register Receiver()来声明。当一个IntentReceiver被触发时,如果需要系统自然会自动启动程序。程序也可以通过Context.broadcastIntent()来发送自己的Intent广播给其他程序。
4.2.5 ContentProvider存储组件
应用程序把数据存储一个SQLite数据库格式文件里,或者存储在其他有效设备里。如果想让其他程序能够使用程序中的数据,此时Content Provider就很有用了。Content Provider是一个实现了一系列标准方法的类,这个类使得其他程序能存储、读取某种Content Provider可处理的数据。
4.3 进程和线程
进程和线程很容易理解,计算机中有一个进程管理器,当打开后,会显示当前运行的所有程序。同样在Android中也有进程,当某个组件第一次运行的时候,Android会启动一个进程。在默认情况下,所有的组件和程序运行在这个进程和线程中,也可以安排组件在其他的进程或者线程中运行。
4.3.1 什么是进程
组件运行的进程是由manifest file控制的。组件的节点一般都包含一个process属性,例如<activity>、<service>、<receiver>和<provider>节点。属性process可以设置组件运行的进程,可以配置组件在一个独立进程中运行,或者多个组件在同一个进程中运行,甚至可以多个程序在一个进程中运行,当然前提是这些程序共享一个User ID并给定同样的权限。另外<application>节点也包含了process属性,用来设置程序中所有组件的默认进程。
当更加常用的进程无法获取足够内存时,Android会智能地关闭不常用的进程。当下次启动程序的时候会重新启动这些进程。当决定哪个进程需要被关闭的时候,Android会考虑哪个对用户更加有用。例如Android会倾向于关闭一个长期不显示在界面的进程来支持一个经常显示在界面的进程。是否关闭一个进程决定于组件在进程中的状态。
4.3.2 什么是线程
当用户界面需要很快对用户进行响应,就需要将一些费时的操作,如网络连接、下载或者非常占用服务器时间的操作等放到其他线程。也就是说,即使为组件分配了不同的进程,有时候也需要再分配线程。
线程是通过Java的标准对象Thread来创建的,在Android中提供了如下方便地管理线程的方法:
(1)Looper在线程中运行一个消息循环;
(2)Handler传递一个消息;
(3)HandlerThread创建一个带有消息循环的线程;
(4)Android让一个应用程序在单独的线程中,指导它创建自己的线程;
(5)应用程序组件(Activity、Service、Broadcast receiver)所有都在理想的主线程中实例化;
(6)当被系统调用时,没有一个组件应该执行长时间或是阻塞操作(例如网络呼叫或是计算循环),这将中断所有在该进程的其他组件;
(7)可以创建一个新的线程来执行长期操作。
4.3.3 应用程序的生命周期
自然界的事物都有自己的生命周期,例如人的生、老、病、死。作为一个Android应用程序也如同自然界的生物一样,也有自己的生命周期。人们开发一个程序的目的是为了完成一个功能,例如银行计算加息的软件,每当一个用户去柜台办理取款业务时,银行工作人员便启动了这个程序的生命,当用这个软件完成利息计算时,这个软件当前的任务就完成了,此时就需要结束自己的使命。肯定有人提出疑问:生生死死多么麻烦,就让这个程序一直是“活着”的状态,一个用户办理完取款业务后,继续等着下一个用户办理取款业务,这样这个程序就“长生不老”了。其实谁都想自己的程序“长生不老”,但是很不幸,不能这样做。原因是计算机的处理性能是一定的,一个人、两个人、三个人计算机可以处理这个任务。但是一个安装这个软件的机器一天会处理成千上万个取款业务,如果它们都一直“活着”,一台有限配置的计算机能承受得了吗?
由此可见,应用程序的生命周期就是一个程序的存活时间,即在什么时间内有效。Android是一个构建在Linux之上的开源移动开发平台,在Android中,多数情况下每个程序都是在各自独立的Linux进程中运行的。当一个程序或其某些部分被请求时,它的进程就“出生”了;当这个程序没有必要再运行下去且系统需要回收这个进程的内存用于其他程序时,这个进程就“死亡”了。可以看出,Android程序的生命周期是由系统控制的而非程序自身直接控制的。这和编写桌面应用程序时的思维有一些不同,一个桌面应用程序的进程也是在其他进程或用户请求时被创建的,但是往往是在程序自身收到关闭请求后执行一个特定的动作(例如从main函数中返回)而导致进程结束的。要想做好某种类型的程序或者某种平台下的程序的开发,最关键的就是要弄清楚这种类型的程序或整个平台下的程序的一般工作模式并熟记在心。在Android中,程序的生命周期控制就是属于这个范畴。
开发者必须理解不同的应用程序组件,尤其是Activity、Service和Intent Receiver,需要了解这些组件是如何影响应用程序的生命周期的。如果不能正确地使用这些组件,可能会导致系统终止正在执行重要任务的应用程序进程。
一个常见的进程生命周期漏洞的例子是Intent Receiver(意图接收器),当Intent Receiver在onReceive方法中接收到一个Intent(意图)时,它会启动一个线程,然后返回。一旦返回,系统将认为Intent Receiver不再处于活动状态,因而Intent Receiver所在的进程也就不再有用了(除非该进程中还有其他的组件处于活动状态)。因此,系统可能会在任意时刻终止该进程以回收占有的内存。这样进程中创建出的那个线程也将被终止。解决这个问题的方法是从Intent Receiver中启动一个服务,让系统知道进程中还有处于活动状态的工作。为了使系统能够正确决定在内存不足时应该终止哪个进程,Android根据每个进程中运行的组件及组件的状态把进程放入一个“Importance Hierarchy(重要性分级)”中。
进程的类型多种多样,按照重要的程度主要包括如下几类进程。
1.前台进程(Foreground)
前台进程是看得见的,与用户当前正在做的事情密切相关,不同的应用程序组件能够通过不同的方法将它的宿主进程移到前台。在下面的任何一个条件下系统都会把进程移动到前台。
· 进程正在屏幕的最前端运行一个与用户交互的活动(Activity),它的onResume方法被调用;
· 进程有一正在运行的Intent Receiver(它的IntentReceiver.onReceive方法正在执行);
· 进程有一个服务(Service),并且在服务的某个回调函数(Service.onCreate、Service.onStart或Service.onDestroy)内有正在执行的代码。
2.可见进程(Visible)
可见进程也是可见的,它有一个可以被用户从屏幕上看到的活动,但不在前台(它的onPause方法被调用)。假如前台的活动是一个对话框,以前的活动隐藏在对话框之后就会现这种进程了。可见进程非常重要,一般不允许被终止,除非是了保证前台进程的运行而不得不终止它。
3.服务进程(Service)
服务进程是无法看见的,拥有一个已经用startService()方法启动的服务。虽然用户无法直接看到这些进程,但它们做的事情却是用户所关心的(如后台MP3回放或后台网络数据的上传、下载)。所以系统将一直运行这些进程,除非内存不足以维持所有的前台进程和可见进程。
4.后台进程(Background)
后台进程也是看不见的,只有打开之后才能看见。例如迅雷下载,可以将其最小化,虽然在桌面上看不见了,但是它一直在进行下载的工作。拥有一个当前用户看不到的活动(它的onStop()方法被调用)。这些进程对用户体验没有直接的影响。如果它们正确执行了活动生命周期,系统可以在任意时刻终止该进程以回收内存,并提供给前面3种类型的进程使用。系统中通常有很多这样的进程在运行,因此要将这些进程保存在LRU列表中,以确保当内存不足时用户最近看到的进程最后一个被终止。
5.空进程(Empty)
空进程是指不拥有任何活动的应用程序组件的进程。保留这种进程的唯一原因是在下次应用程序的某个组件需要运行时,不需要重新创建进程,这样可以提高启动速度。系统将以进程中当前处于活动状态组件的重要程度为基础对进程进行分类。进程的优先级可能也会根据该进程与其他进程的依赖关系而增长。假如进程A通过在进程B中设置Context.BIND_AUTO_CREATE标记或使用ContentProvider绑定到一个服务(Service),那么进程B在分类时至少要被看成与进程A同等重要。
例如Activity的状态转换图如图4-3所示。
▲图4-3 Activity状态转换图
图4-3所示的状态的变化是由Android内存管理器决定的,Android会首先关闭那些包含Inactive Activity的应用程序,然后关闭Stopped状态的程序。只有在极端情况下才会移除Paused状态的程序。
4.4 Android和Linux的关系
在了解Linux和Android的关系之前,首先需要明确如下3点。
(1)Android采用Linux作为内核。
(2)Android对Linux内核做了修改,以适应其在移动设备上的应用。
(3)Andorid开始是作为Linux的一个分支,后来由于无法并入Linux的主开发树,曾经被Linux内核组从开发树中删除。2012年5月18日,Linux kernel 3.3发布后来又被加入。
4.4.1 Android继承于Linux
Android是在Linux的内核基础之上运行的,提供的核心系统服务包括安全、内存管理、进程管理、网络组和驱动模型等内容。内核部分还相当于一个介于硬件层和系统中其他软件组之间的一个抽象层次。但是严格来说它不算是Linux操作系统。
因为Android内核是由标准的Linux内核修改而来的,所以继承了Linux内核的诸多优点,保留了Linux内核的主题架构。同时Android按照移动设备的需求,在文件系统、内存管理、进程间通信机制和电源管理方面进行了修改,添加了相关的驱动程序和必要的新功能。但是和其他精简的Linux系统相比(例如uClinux), Android很大程度地保留了Linux的基本架构,因此Android的应用性和扩展性更强。当前Android版本对应的Linux内核版本如下所示。
· Android 1.5:Linux-2.6.27。
· Android 1.6:Linux-2.6.29。
· Android 2.0,2.1:Linux-2.6.29。
· Android 2.2:Linux-2.6.32.9。
· Android 4.3:Linux-3.4。
4.4.2 Android和Linux内核的区别
Android系统的系统层面的底层是Linux,中间加上了一个叫作Dalvik的Java虚拟机,表面层上面是Android运行库。每个Android应用都运行在自己的进程上,享有Dalvik虚拟机为它分配的专有实例。为了支持多个虚拟机在同一个设备上高效运行,Dalvik被改写过。
Dalvik虚拟机执行的是Dalvik格式的可执行文件(.dex),该格式经过优化,以降低内存耗用到最低。Java编译器将Java源文件转为class文件,class文件又被内置的dx工具转化为dex格式文件,这种文件在Dalvik虚拟机上注册并运行。
Android系统的应用软件都是运行在Dalvik之上的Java软件,而Dalvik是运行在Linux中的,在一些底层功能,例如线程和低内存管理方面,Dalvik虚拟机是依赖Linux内核的。由此可见,可以说Android是运行在Linux之上的操作系统,但是它本身不能算是Linux的某个版本。
Android内核和Linux内核的差别主要体现在11个方面,接下来将一一简要介绍。
1.Android Binder
其源代码位于:
drivers/staging/android/binder.c
Android Binder是基于OpenBinder框架的一个驱动,用于提供Android平台的进程间通信(Inter-Process Communication, IPC)。原来的Linux系统上层应用的进程间通信主要是D-bus(Desktop bus),采用消息总线的方式来进行IPC。
2.Android电源管理(PM)
Android电源管理是一个基于标准Linux电源管理系统的轻量级的Android电源管理驱动,针对嵌入式设备做了很多优化。利用锁和定时器来切换系统状态,控制设备在不同状态下的功耗,以达到节能的目的。
Android电源管理的源代码分别位于:
kernel/power/earlysuspend.c kernel/power/consoleearlysuspend.c kernel/power/fbearlysuspend.c kernel/power/wakelock.c kernel/power/userwakelock.c
3.低内存管理器(Low Memory Killer)
Android中的低内存管理器和Linux标准的OOM(Out Of Memory)相比,其机制更加灵活,它可以根据需要杀死进程来释放需要的内存。Low Memory Killer的代码很简单,关键的一个函数是Lowmem_shrinker。作为一个模块在初始化时调用register_shrinke注册了个lowmem_shrinker,它会被VM在内存紧张的情况下调用。Lowmem_shrinker完成具体操作。简单说就是寻找一个最合适的进程将其杀死,从而释放它占用的内存。
低内存管理器的源代码位于drivers/staging/android/lowmemorykiller.c。
4.匿名共享内存(Ashmem)
匿名共享内存为进程间提供大块共享内存,同时为内核提供回收和管理这个内存的机制。如果一个程序尝试访问Kernel释放的一个共享内存块,它将会收到一个错误提示,然后重新分配内存并重载数据。
匿名共享内存的源代码位于mm/ashmem.c。
5.Android PMEM(Physical)
PMEM用于向用户空间提供连续的物理内存区域,DSP和某些设备只能工作在连续的物理内存上。驱动中提供了mmap、open、release和ioctl等接口。
Android PMEM的源代码位于drivers/misc/pmem.c。
6.Android Logger
Android Logger是一个轻量级的日志设备,用于抓取Android系统的各种日志,是Linux所没有的。
Android Logger的源代码位于drivers/staging/android/logger.c。
7.Android Alarm
Android Alarm提供了一个定时器用于把设备从睡眠状态唤醒,同时它也提供了一个即使在设备睡眠时也会运行的时钟基准。
Android Alarm的源代码位于:
· drivers/rtc/alarm.c;
· drivers/rtc/alarm-dev.c。
8.USB Gadget驱动
USB Gadget驱动是一个基于标准Linux USB gadget驱动框架的设备驱动,Android的USB驱动是基于gadget框架的。
USB Gadget驱动的源代码位于:
· drivers/usb/gadget/android.c;
· drivers/usb/gadget/f_adb.c;
· drivers/usb/gadget/f_mass_storage.c。
9.Android Ram Console
为了提供调试功能,Android允许将调试日志信息写入一个被称为RAM Console的设备里,它是一个基于RAM的Buffer。
Android Ram Console的源代码位于drivers/staging/android/ram_console.c。
10.Android timed device
Android timed device提供了对设备进行定时控制功能,目前仅仅支持vibrator和LED设备。
Android timed device的源代码位于drivers/staging/android/timed_output.c(timed_gpio.c)。
11.YAFFS2文件系统
在Android系统中,采用YAFFS2作为MTD nand flash文件系统。YAFFS2是一个快速稳定的应用于NAND和NOR Flash的跨平台的嵌入式设备文件系统,同其他Flash文件系统相比,YAFFS2使用更小的内存来保存它的运行状态,因此它占用内存小;YAFFS2的垃圾回收非常简单而且快速,因此能达到更好的性能;YAFFS2在大容量的NAND Flash上性能表现尤为明显,非常适合大容量的Flash存储。
YAFFS2文件系统源代码位于fs/yaffs2/目录下。
Android是在Linux的内核基础之上运行的,提供的核心系统服务包括安全、内存管理、进程管理、网络组和驱动模型等内容。内核部分还相当于一个介于硬件层和系统中其他软件组之间的一个抽象层次。但是严格来说它不算是Linux操作系统。
4.5 第一段Android程序
本实例的功能是在手机屏幕中显示问候语“你好我的朋友!”,在具体开始之前先做一个简单的流程规划,如图4-4所示。
▲图4-4 规划流程图
在本节的内容中,将详细讲解本实例的具体实现流程。
4.5.1 新建Android工程
(1)在Eclipse中依次单击“File”|“New”|“Project”新建一个工程文件。如图4-5所示。
▲图4-5 新建工程文件
(2)选择“Android Project”选项,单击“Next”按钮。
(3)在弹出的“New Android Project”对话框中,设置工程信息。如图4-6所示。
▲图4-6 设置工程
在图4-6所示的界面中依次设置工程名称、包名称、Activity名称和应用名称。
现在已经创建了一个名为“first”的工程文件,现在打开文件first.java,会显示自动生成的如下代码。
package first.a; import android.app.Activity; import android.os.Bundle; public class fistMM extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } }
如果此时运行程序,将不会显示任何内容。此时可以对上述代码进行适当的修改,让程序输出“HelloWorld”。具体代码如下所示。
package first.a; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class fistMM extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); TextView tv = new TextView(this); tv.setText("你好我的朋友!"); setContentView(tv); } }
经过上述代码改写后,应该可以在屏幕中输出“你好我的朋友!”,完全符合预期的要求。
4.5.2 调试程序
Android调试一般分为3个步骤,分别是设置断点、Debug调试和断点调试。
1.设置断点
此处的设置断点和Java中的方法是一样的,可以通过双击代码左侧的区域进行断点设置。如图4-7所示。
▲图4-7 设置断点
为了调试方便,可以设置显示代码的行数。只需要在代码左侧的空白部分单击右键,在弹出命令中选择“Show Line Numbers”。如图4-8所示。
▲图4-8 显示行数
2.Debug调试
Debug Android调试项目的方法和普通Debug Java调试项目的方法类似,唯一的不同是在选择调试项目时选择“Android Application”命令。具体方法是右键单击项目名,在弹出命令中依次选择“Debug As”|“Android Application”命令。如图4-9所示。
▲图4-9 Debug项目
3.断点调试
可以进行单步调试,具体调试方法和调试普通Java程序的方法类似,调试界面如图4-10所示。
▲图4-10 调试界面
4.5.3 运行程序
将上述代码保存后就可运行这段程序了,具体过程如下所示。
(1)右键单击项目名,在弹出命令中依次选择“Run As”|“Android Application”。如图4-11所示。
▲图4-11 开始调试
(2)此时工程开始运行,运行完成后在屏幕中输出“你好我的朋友!”这段文字。如图4-12所示。
▲图4-12 运行结果