《ANDROID 多媒体编程从初学到精通》 作者:华清远见 第 2 章 Android 基础 人有不为也,而后可以有为。 -- 《孟子•离娄下》 在本章中,将会介绍一些 Android 的基础知识,如 Android 的 UI 框架、启动过程、进程间通信、上 下层之间的交互方法,以及 Android 为了获得更高的速度而在系统性能上所做的优化等,为接下来的章 节做好铺垫。 专业始于专注 卓识源于远见 ‐ 2 ‐ Hello World 按照惯例,同时也是为了更好地引导读者进入精彩的 Android 世界,我们接下来要实现一个简单的 “hello World”例子。为了编写的方便,这里以 Android SDK 2.1 为基础基于 Eclipse Galileo(Eclipse 3.5) 作为 IDE 来完成开发。 通过“File”“New”“Project”创建 Android 工程,如图 2-1 所示。设置“Project name:”为“helloWorld”, 设置“Build Target”为“Android 2.1”,设置“Application name:”为“helloWorld”,设置“Package name:” 为“com.miaozl.helloWorld”,设置“Create Activity:”为“helloWorld”。然后单击“Finish”按钮即可完成 工程的创建。 图 2-1 创建 Android 工程 需要说明的是,在 Android 的设计中,Android 1.0 的 SDK 版本为“1”、Android 1.1 的 SDK 版本为“2”、 Android 1.5 的 SDK 版本为“3”、 Android 1.5 的 SDK 版本为“3”、 Android 1.6 的 SDK 版本为“4”、 Android 2.0 的 SDK 版本为“5”、 Android 2.0.1 的 SDK 版本为“6”、 Android 2.1 的 SDK 版本为“7”,Android 2.2 的 SDK 版本为“8”,Android 2.3 的 SDK 版本为“9”,Android 3.0 的 SDK 版本为“Honeycomb”。在具体 SDK 版本的选择上,应在考虑技术实现可行性的前提下,选择尽量小的版本,这有利于扩大兼容的物理设 备。 在创建 Android 工程的过程中。由于 Eclipse 的原因,系统可能会提示“no classfiles specified,Conversion to Dalvik format failed with error 1”,如果出现该错误信息,按“F5”键刷新一次工程即可。需要说明的是, 在 Foryo 后,该问题已经很少出现。对于复制过来的工程,可能会引起工程属性方面的问题,这一问题可 以通过 Android 工程的右键菜单选择“Android Tools”“Fix Project Properties”修复系统属性解决。 另外如果是第一次创建工程, Eclipse 会提示用户“Failed to find an AVD compatible with target”。这是 由于 Android 在稍后的版本中不再提供默认的 Android 虚拟设备(AV D,Android Virtual Device),需要用 户自行创建。在 Linux 中,AV D 相关的文件包括 userdata.img 映像文件,默认情况下存于/root/.android 目 录中,AV D 描述了当前模拟器的设备配置信息。当工程创建完成后,即可在 Eclipse 的“Package Explorer” 中看到 Android 的工程布局。Eclipse 对于所辖工程的维护默认情况下位于/root/workspace/.metedata 目录中。 如果期望获得一个干净的运行环境,直接删除/root/.android 和/root/workspace/.metedata 目录即可。 如图 2-2 所示为所生成的 Android 的“helloWorld”工程布局,其中“.settings”描述了 Eclipse 和采用 的 JDT 包的若干信息;“.assets”为空目录,描述了工程的断言信息,断言并不常用,常见的用法是维护一 些工程的资源文件,但断言对于单个文件有 1MB 的大小限制。 “bin”为输出文件目录,包含了各源文件对应的 CLASS 字节码文件、“ap_”格式的资源文件、DEX 字节码文件和最终利用“aapt”工具打包的 APK 格式的 Android 安装包。需要说明的是 APK 格式的 Android 专业始于专注 卓识源于远见 ‐ 3 ‐ 安装包本质上为 ZIP 格式的压缩包,包含了资源、DEX 字节码文件和 AndroidManifest.xml 等,当系统检 测到 APK 格式的文件时,系统会把它当做一个应用看待。 “gen”为资源数据目录。包含了“aapt”工具在工程内所发现的所有资源数据。 “res”为描述资源的 XML 文件目录,通常包括“drawable”、“layout”、“values”等子目录,描述了工 程涉及的图标、字符串和布局等信息,复杂的应用还涉及“menu”、“color”、“style”等目录。其中布局资 源文件为 main.xml,字符串资源文件为 strings.xml。 “src”为工程源文件目录,包含了开发者创建的 Java 文件。 “.classpath”文件描述了工程涉及的路径信息。 “.project”文件描述了工程名、所需的编译命令等工程信息。 “AndroidManifest.xml”则描述了工程实现细节如“manifest”、“application”、“activity”、“intent-filter”、 “action”、“category”、SDK 版本信息等信息、应用程序的权限等,是 Android 工程中最重要的文件。 “default.properties”描述了 SDK 的版本信息。 图 2-2 Android 工程布局 对于基于原生代码的应用,在工程下可能还存在“jni”目录用于存放原生源代码,存在“libs”目录, 用于存放编译原生源代码生成的动态共享库等。 下面为主要文件的实现细节: 1.src\com\miaozl\helloWorld\helloWorld.java 代码 2-1 为“hello World”工程的 helloWorld.java 文件的内容,在该文件中定义了一个名为“helloWorld” 的 Activity,并通过 setContentView()函数加载该 Activity 的布局资源文件。 代码 2-1 helloWorld.java package com.miaozl.helloWorld; import android.app.Activity; import android.os.Bundle; public class helloWorld extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //创建 Activity setContentView(R.layout.main); //加载布局资源 } } 利用 setContentView ()函数还可以直接加载 View 类,View 是用来构建应用程序的 UI 控件系统的,包 括列表(Lists)、网格(Grids)、文本框(Text Boxs)、按钮(Buttons)等扩展类。图 2-3 显示了 Android 系统的 View 子系统的类图。其中 android.view.View 类为 UI 控件的基类,android.view.ViewGroup 为布局 类的基类。 专业始于专注 卓识源于远见 ‐ 4 ‐ android.view.View java.lang.Object android.view.ViewGroup android.widget.AnalogClock android.widget.ImageView android.inputmethodservice.KeyboardView android.view.SurfaceView android.widget.TextView android.view.ViewStub android.widget.LinearLayout android.widget.TableLayout android.widget.RelativeLayout android.widget.AbsoluteLayout android.webkit.WebView 图 2-3 View 子系统类图 Activity 与窗口管理器有着密切的关系。View 子系统是 Android 整个 UI 框架的基础。 2.res\layout\main.xml 代码 2-2 为“helloWorld”工程的 main.xml 文件,在该文件中定义了该 Activity 的布局属性,定义了该 Activity 的根布局为 LinearLayout,设置该 Activity 为竖向的全屏窗口,并定义了一个 TextView 控件。 代码 2-2 main.xml 在 Android 中,布局管理器决定了 Activity 中的 UI 接口的布局, UI 的布局具有两种方式,一种是基 于 XML 文件实现,另一种是基于代码实现。其中基于 XML 文件的方式主要用于针对不同屏幕旋转度、 不同显示分辨率和不同本地化语言的默认方式的布局,有利于解耦应用的行为和显示,可以帮助用户在不 修改代码的情况下改变应用的布局;基于代码的方式则侧重于实时修改布局,性能上略占优势,但不利于 应用的本地化和适应不同的硬件设备。尤其是随着平板电脑的目标环境引入,硬件环境更趋复杂。 在 XML 布局文件中,必须存在一个类型为 View 和 ViewGroup 的根元素(main.xml 文件中的根元素 为 LinearLayout)。只有在定义了根元素之后,用户才能添加随后的布局对象或者 UI 控件(如 main.xml 文 件中的 TextView 等)。 需要说明的是,对于 UI 控件而言,其“android:layout_width”和“android:layout_height”属性在较高 版本中通常有三个属性值即"fill_parent"、"match_parent"、"wrap_content"可以选择。其中"fill_parent"和 "match_parent"属性值的定义均为“-1”。其实际意义相同。"match_parent"属性值具有更明确的字面含义, 受到 Google 的推荐。 如果在不同的屏幕方向或者硬件配置、语言环境下,布局文件稍有不同,可以将同名布局文件放置在 不同的布局文件夹下,如在简体中文环境下的布局文件夹为“layout-zh-rCN”。 在构建布局文件时,很自然会涉及到像素的问题,在 Android 中,有 dp、px、dip 等几种单位,其中 专业始于专注 卓识源于远见 ‐ 5 ‐ dip 独立于物理设备,是 Android 为了适应多种不同分辨率而设计的像素单位,最常用的也是 dip。 需要注意的是 dip 的设置与分辨率无关,但与屏幕密度(density)有关,默认情况下,QVGA 的密度 为 120,系数为 0.75,HVGA 的密度为 160,系数为 1.0,WVGA 的密度为 240,系数为 1.5。需要说明的 是,目前 Android 支持的密度包括 xhdpi、hdpi、mdpi、ldpi。其中 hdpi 通常适用于高分辨率的智能终端, mdpi 则通常用于高分辨率的平板电脑。而 ldpi 则适用于入门级的智能终端。另外,在 Foryo 中,还引入了 xhdpi 的概念,其密度定义为 320,适用于拥有高分屏的高端智能终端,针对不同密度,Android 对其采用 的菜单图标、应用图标等的大小做了明确的定义。针对 hdpi 的密度,其菜单图标和应用图标的大小为 72*72, 针对 mdpi 的密度,其菜单图标和应用图标的大小为 48*48,针 对 ldpi 的密度,其菜单图标和应用图标的大 小为 36*36。 另外还有一个 nodpi 的概念需要注意,这个概念是为避免像素伸缩的图片资源而设计的。 px 与 dip 的关系如下: px = (int) (dip*density+0.5f) //一个 dip 在 hdpi 下,相当于 1.5 个物理像素 在实际实现中,屏幕密度称为 scale,px 和 dip 的转换过程如下: public void scale(float scale) { x = (int) (x * scale + 0.5f); y = (int) (y * scale + 0.5f); if (width > 0) { width = (int) (width * scale + 0.5f); } if (height > 0) { height = (int) (height * scale + 0.5f); } } 同时,在描述字体的大小时,涉及的单位为 sp。在实际开发中,很多初学者容易胡乱的使用这些单位。 这会引起许多潜在的问题。 在设置控件的填充时,需要注意到 Android 提供了两种填充类型:内填充和外填充,对于内填充即填 充占用的空间属于该控件的一部分,对于外填充即填充占用的空间属于该控件的父控件的一部分。 其中内填充的属性设置方式为: //控件顶部内填充 5dip 外填充的属性设置方式为: //控件顶部外填充 5dip 布局文件在实际的开发者有很多的技巧需要揣摩,如何使做的设计更加灵活,能够适应多种设备,不 是一言能蔽之的。希望读者多加体会。 3.res\values\strings.xml 代码 2-3 为“ helloWorld”工程的 strings.xml 文件,在该文件中,定义了 Activity 所涉及的字符串资源。 为了进行软件的本地化,Android 采用了 ISO 命名规范来设置不同语言的资源目录名,对于简体中文,资 源目录为“values-zh-rCN”;对于繁体中文,资源目录为“values-zh-rTW”。 代码 2-3 strings.xml Hello World, helloWorld! //“hello”字符串 helloWorld //“app_name”字符串 专业始于专注 卓识源于远见 ‐ 6 ‐ 在更复杂的情况下,可能会遇到字符串数组的情况,其定义方式如下: Home Work Other Custom 其他类型的数组还包括颜色数组、图片数组、整数数组等,其定义方式可以参考 frameworks/base/core/res/res/values/arrays.xml 文件。 4..\ AndroidManifest.xml 代码 2-4 为“helloWorld”工程的 AndroidManifest.xml 文件。对于每个应用而言,该文件唯一。 AndroidManifest.xml 文件定义了应用的 activity、intent、uses-sdk、uses-permission、service、uses-library、 Content provider、Broadcast Receiver 等信息。 代码 2-4 AndroidManifest.xml //版本名 //定义 intent 过滤器 //定义采用的 Android 版本信息 在“uses-sdk”元素中,开发者可以根据应用的情况指定 3 种 Android 的版本信息。android:minSdkVersion 属性描述了该应用正常运行要求的最低 Android 版本信息,默认值为“1”;android:maxSdkVersion 属性描 述了该应用正常运行要求的最高 Android 版本信息,如果该属性没有声明,则系统假定其默认值为无限大; android:targetSdkVersion 属性描述了该应用正常运行的最佳 Android 版本信息,声明 android:targetSdkVersion 属性,可使应用调用特定平台的行为,而不是局限于最低版本的平台支持能力。 除了 Activity 外,在实际开发中,构建的服务(service)、接收器(receiver)、提供器(provider)、用 到的库(uses-library)等都必须在 AndroidManifest.xml 中声明,否则会引发异常。其中服务和提供器可以 被其他应用调用。 “android:versionCode”属性定义了该应用的当前版本号,配合数字签名证书可以用于应用的升级。 “android:versionName”属性通常指定显示给最终用户的版本信息。 在 Foryo 中,Android 增加了“android:installLocation”属性,可以使开发者指定应用安装的位置,其 值包括:"auto"、"internalOnly"、"preferExternal"。这有利于入门级 Android 终端的市场拓展。 应用框架 在传统的功能手机(Feature Phone)中,开发者需要将大量的工作用于系统层面的配置,消耗了大量 的时间,而且这些配置由于种种原因往往成了系统不能稳定的主要根源。在智能手机(Smart Phone)中, 专业始于专注 卓识源于远见 ‐ 7 ‐ 平台通常提供了完善的应用框架,其扩展性和灵活性足以应对大多数变化,使得开发者可以将主要精力集 中在应用层面。对 Android 而言,同其他智能手机平台一样,提供了完善的应用框架,甚至更加卓越,使 得开发者只需要关注应用本身,而无须在系统层面上消耗太多的时间和精力。 2.2.1 应用组件 随着移动终端的不断发展,尤其是对消费者类型产品而言,用户已经不再满足于基本的功能需求,绚 丽的 UI 和良好的用户体验逐渐成为用户关注的重点。Android 在 UI 方面做了大量的工作,整个应用架构 显得非常灵活且易扩展,在提供了丰富组件的基础上,为开发者进行差异化方面的工作提供了强大的支持。 对 Android 应用程序而言,应用组件主要由 Activity、Service、Broadcast Receivers、Intent、Content Providers、AndroidManifest 等构成。Activity 是与用户直接交互 UI 组件; Service 是运行在后台、用户不 可见的服务组件;Broadcast Receivers 是进行系统消息广播的广播组件;Intent 是应用组件间、进程间进行 通信的通信组件;Content Providers 是不同应用间传递、分享数据的内容组件;Android Manifest 为应用程 序的管理组件。其他组件还有 App Widgets、Graphics、Audio and Video、Data Storage、Resources 等。 1.Activity 作为和用户之间直接交互的 UI 组件,Activity 组件无疑在 Android 的 UI 组件中占据着重要的位置。 在通常情况下,Activity 作为一个全屏的窗口出现,也可以作为浮动窗口或者其他 Activity 的子 Activity 出 现。 作为应用生命周期中最重要的组件之一,Activity的发布和管理构成了平台应用模型的基础。在Android 中,Activity 的管理是通过 Activity 栈的方式来进行的,在 Activity 的生命周期中,存在 4 种状态:激活 (active)、运行(running)、停止(stopped)、暂停(paused)。 当 Activity 处于屏幕的前端时,Activity 处于“active”或“running”状态,当 Activity 失去焦点但可 见(新建了一个非全屏或者透明的 Activity,该 Activity 处于 Activity 栈的顶部)时,Activity 处于“paused” 状态。处于“paused”状态的 Activity 依然维护着其所有的状态和成员信息,并继续处于窗口管理器的管 理之下,在内存极端不足情况下,处于“paused”状态的 Activity 有可能被杀死。 当 Activity 不再可见时,该 Activity 处于“stopped”状态,处于“stopped”状态的 Activity 会继续维 护其所有的状态和成员信息,但当系统内存不足需要清理释放时,处于“stopped”状态的 Activity 便成为 被杀死的考虑对象。 在 Activity 变得再次可见时,如果需要做界面上的刷新,相关的操作可以在 Activity:: onResume()方法 中处理。在 Activity 构建完成后会获得焦点,相应的焦点变化可以在 Activity:: onWindowFocusChanged() 方法中监听。 Activity 和 Activity 间的切换通常需要借助于 Intent 组件来进行。一个最简单的调用方式如下: Intent in = new Intent(context, BluetoothOppBtErrorActivity.class); //设置调用的 Activity in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //设置标志位 in.putExtra("title", context.getString(R.string.unknown_file)); //设置标题 in.putExtra("content", context.getString(R.string.unknown_file_desc));//设置显示内容 context.startActivity(in); //启动 Activity,不需返回数据 在被调 Activty 中,处理数据的方法如下: protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); String mErrorTitle = intent.getStringExtra("title"); mErrorContent = intent.getStringExtra("content"); …… } 在泛化 Activity 类时,必须实现 Activity:: onCreate()和 Activity:: onPause ()两个方法。另外通过一个 Activity 调用另一个 Activity 时,如果不需要返回数据,可以通过 Context.startActivity()方法来进行。如果 需要返回数据,则通过 Context.startActivityForResult()方法来处理,常见的方式如下: 专业始于专注 卓识源于远见 ‐ 8 ‐ Intent intent = new Intent(); intent.setClass(this, CreatePlaylist.class); startActivityForResult(intent, NEW_PLAYLIST); // NEW_PLAYLIST 为“requestCode” 在被调 Activity 中,处理完请求后,可以通过如下方法向调用 Activity 返回数据: setResult(RESULT_OK, (new Intent()).setData(uri)); 对于返回的数据,调用 Activity 将在其 onActivityResult()方法中处理。处理过程如下: protected void onActivityResult(int requestCode, int resultCode, Intent intent) { switch (requestCode) { case NEW_PLAYLIST: if (resultCode == RESULT_OK) { Uri uri = intent.getData(); …… } } } 主要的 Activity 属性包括:taskAffinity 、launchMode 、allowTaskReparenting 、clearTaskOnLaunch 、 alwaysRetainTaskState finishOnTaskLaunce 等。 另外,对于某些特殊的 Activity,可能希望提供对标题栏的定制,在 Android 中,对标题栏支持如下特 征:FEATURE_NO_TITLE、FEATURE_PROGRESS、FEATURE_LEFT_ICON、FEATURE_RIGHT_ICON、 FEATURE_CUSTOM_TITLE 等。 为了隐藏标题栏,可以在 Java 代码中实现,也可以在 AndroidManifest.xml 文件中实现,由于框架上 的因素,如果是通过 Java 代码实现,标题栏在 Activity 初始化时仍有短暂的可见性,在 AndroidManifest.xml 文件中实现,则不存在类似问题。其 Java 实现方式如下: public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); //Java 代码方式 setContentView(R.layout.main); //必须放置在 requestWindowFeature()后面 …… } 其 AndroidManifest.xml 文件实现方式如下: //禁止界面旋转 如果希望自定义标题栏。方法为: public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); setContentView(R.layout.main); getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title); …… } 如果希望同时隐藏标题栏和状态栏,方法如下: 专业始于专注 卓识源于远见 ‐ 9 ‐ 2.Services 服务(Services)组件通常运行在后台,对用户而言不具有可视性,守护进程和硬件服务、原生服务等 多是服务组件,这在 1.4 节体系架构和 2.3 节启动过程中有详细描述。与其他组件一样,服务组件通常运 行在应用的主线程中,如果服务组件对时间片等资源消耗较大,通常会将服务组件放置在一个单独的线程 中运行,以防阻塞 UI 线程即主线程,影响用户体验。 通过 Context.startService()方法可以启动服务,通过 Context.bindService()方法可以绑定服务。通过 Context.stopService()方法可以停止服务。另外服务本身也可以通过 Context.stopSelf()方法自行终止。在实际 实现中,为了绑定服务,必须指定一个服务连接(ServiceConnection),具体如下: public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); …… getApplicationContext().bindService(new Intent(this, PowerManagerService.class), mConnection, BIND_AUTO_CREATE); ……. } 其中 mConnection 即是 ServiceConnection 的一个实例,BIND_AUTO_CREATE 指明了连接的类型,表 明如果服务没有创建,自动创建服务。默认情况下,可以通过调用组件的上下文绑定或者解绑服务,但是 在同一个应用中如果需多次绑定服务时,需要整体通过应用上下文(getApplicationContext())的方式实现。 在调用组件被销毁时,服务必须解绑,否则会造成引用计数上的错误,造成服务泄漏,解绑服务的实 现如下: protected void onDestroy() { getApplicationContext().unbindService(mConnection); super.onDestroy(); } 服务连接的实现如下: private ServiceConnection mConnection = new ServiceConnection(){ public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub Log.d(TAG, "===onServiceConnected=="); mPowerManager = ((PowerManagerService.LocalBinder)service).getService(); initLayout(); } public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub mPowerManager = null; } }; 需要说明的是,由于服务的连接是异构的,必须保证服务的实例在服务绑定成功后使用。如果在 bindService()执行后立即使用,会引发 NullPointerException 异常。 通过在 AndroidManifest.xml 文件中声明“service”元素,应用程序可以全权接入相应的服务。当该服 务允许被外部应用调用时,需要将服务的“android:exported”属性设为“true”。其默认值为“false”。如果 希望在设置应用中查看服务时,多看到的服务图标与定义该服务的应用图标有所区别,则应设置服务的 “android:icon”属性。否则看到的服务图标即定义该服务的应用图标。服务的声明如下: //运行在不同的进程中 为了接入对于被权限保护的服务,通过在 AndroidManifest.xml 文件中声明相应服务的权限,才可以启 动、终止、绑定相应的服务。如为了改变网络状态,需要声明权限: 系统级的权限在 frameworks/base/core/res/AndroidManifest.xml 中定义。 专业始于专注 卓识源于远见 ‐ 10 ‐ 最后,在服务的创建、启动、销毁阶段,服务驻留的进程会处于前台进程状态,以免服务出现不可预 料的异常。需要说明的是,当系统内存严重紧张时,系统可能会重启甚至销毁服务。 服务根据驻留的对象不同可以分为本地服务(Local Services)和远程服务(Romate Services)。就笔者 而言,一般将远程服务分为应用级远程服务和系统级远程服务两种。系统级的服务实现颇为复杂。除了定 义服务本身外,还需要定义被上层应用调用的接口。一般通过 AIDL 调用。而应用级远程服务除了 AIDL 外,Android 还提供了较为简单的 Message 方式的调用。但如果实现的服务希望被其他应用调用,则必须 通过 AIDL 进行。 在具体的实现中,为了调用驻留在其他应用下的远程服务,需要将该远程服务的 I*.java 接口拷贝到调 用的工程中,如对于 com.miaozl.test.TestService 服务,需要将 com. miaozl.test.ITestService 拷贝到调用远程 服务的其他工程的“src”目录下,绑定的方法为: Intent serviceIntent = new Intent(); serviceIntent.setComponent(new ComponentName("com.lingpan.test","com.lingpan.test.TestService")); bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE); mConnection 的实现如下: private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub ITestService test = ITestService.Stub.asInterface(service); try { test.setTest(); //调用远程服务的方法 } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub } }; 至于本地服务和应用级的远程服务的具体实现,android 的帮助文档和示例代码中已经有明确的说明, 在本节中,笔者就不再详述。 至于系统级的远程服务,对于应用开发者而言,大可不必理会,只有在担任整个系统的架构任务时才 会涉及,服务的实现可以参考 UiModeManagerService.java 来进行,服务对应用层的接口的实现可以参考 UiModeManager.java 来进行,为了通过 getSystemService()方法调用自定义的方法,需要在 ContextImpl.java 中构建系统服务对应用层的接口的单子模式实例。 3.Broadcast Receivers 广播接收器(Broadcast Receivers)是用来接收或者响应广播、通告的一个应用组件,它与通知管理器 密切相关。当时区发生改变、电量不足、工作语言发生改变等事件发生时,注册相应广播接收器的应用将 会收到这些信息。 所有的广播接收器都需要基于 android.content.BroadcastReceiver 基类。通过 Context.registerReceiver() 方法,开发者可以动态地注册广播接收器。 定义接收的广播的方式如下: IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_DATE_CHANGED); 专业始于专注 卓识源于远见 ‐ 11 ‐ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); registerReceiver(mIntentReceiver, filter); 广播接收器的实现如下: private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(Intent.ACTION_TIME_CHANGED) || action.equals(Intent.ACTION_DATE_CHANGED) || action.equals(Intent.ACTION_TIMEZONE_CHANGED)) { …… } } }; 在调用组件被销毁时,必须销毁广播接收器,其方法如下: protected void onPause() { super.onPause(); unregisterReceiver(mIntentReceiver); } 当然在 AndroidManifest.xml 文件中,也可以定义相应的广播接收器。其方法如下: 在目前的实现中,广播分为两种类型:标准广播(Normal broadcasts)、顺序广播(Ordered broadcasts)。 标准广播指广播是完全异步的,所有的接收器处于无序的运行状态。这类广播通过 Context.sendBroadcast()方法发送。 顺序广播则按照一定的优先级进行广播,高优先级的接收器向低优先级的接收器转播广播。如果需要, 高优先级的接收器也可以抛弃广播,从而切断低优先级的接收器收到广播的可能,在同一优先级的接收器, 收到广播的顺序则是随机的。顺序广播通过 Context.sendOrderedBroadcast()方法发送。 4.Intent 在 Android 中,应用组件也是通过 Intent 来激活的,其中内容提供器是通过 ContentResolver 发出请求 的方式来激活的,而 Activity、服务和广播接收器则是通过所谓的 Intent 异步消息的方式来激活的。 利用 Intent 激活组件的操作方法有:startActivity(Intent)、startService(Intent)、bindService(Intent, ServiceConnection, int)、sendBroadcast(Intent)等,其中 startActivity()方法用于发起 Activity,startService() 方法用于发起服务,bindService()方法用于绑定服务,而 sendBroadcast()方法则用来向所有关联的广播接 收器发送广播。 为了能收到 Intent 消息,AndroidManifest.xml 的“intent-filter”元素必须包含至少一个“action”元素, 否则 Intent 过滤器会过滤掉所有的 Intent 消息。 在通信过程中,Intent 负责对通信消息进行描述,Android 则根据 Intent 的描述,找到匹配的组件,将 Intent 传递给匹配的组件,并完成组件的调用。如在文件管理器中,为了打开一个文件,相应 Intent 的实 现如下: Intent activityIntent = new Intent(Intent.ACTION_VIEW); activityIntent.setDataAndType(path, mimetype); activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(activityIntent); 如果实在希望了解是否有匹配的组件存在,可以做如下的判断: public static boolean isRecognizedFileType(Context context, Uri fileUri, String mimetype) { boolean ret = true; if (D) Log.d(TAG, "RecognizedFileType() fileUri: " + fileUri + " mimetype: " + mimetype); Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW); 专业始于专注 卓识源于远见 ‐ 12 ‐ mimetypeIntent.setDataAndType(fileUri, mimetype); List list = context.getPackageManager().queryIntentActivities (mimetypeIntent, PackageManager.MATCH_DEFAULT_ONLY); if (list.size() == 0) { if (D) Log.d(TAG, "NO application to handle MIME type " + mimetype); ret = false; } return ret; } 因此,Intent 在通信过程中起着媒介的作用,专门传递组件互相调用的相关信息,实现了调用者与被 调用者之间的解耦。 在 Android 中,Intent 携带的信息主要有两种属性:行为(Action)和数据(Data)。其他属性还有类 别(Category)、数据类型(Type)、组件(Component)、附加信息(extras)等。 “Action”属性是对所执行消息主旨的描述,对常见的 Action,如查看、编辑、拨号等,Android 在系 统层面进行了定义,如 DIAL_ACTION、MAIN_ACTION、 VIEW_ACTION、EDIT_ACTION。 对特殊的行为,允许开发者自定义。 “ Data ”属性即所执行消息涉及的数据,在 Android 中采用指向数据的 URI 来表示。如 content://contacts/people/1,意思为电话簿中标识符为“1”的条目信息。 “Category”属性即所执行消息的附加信息。例如,LAUNCHER_CATEGORY 表示 Intent 的目标 Activity 应该作为应用主界面出现;而 ALTERNATIVE_CATEGORY 表示当前的 Intent 是一系列的可选行为 中的一个,这些行为可以在同一块数据上执行。 “Type”属性显式指定 Intent 的数据类型(MIME)。一般 Intent 的数据类型能够根据数据本身进行判定, 但是通过设置“Type”属性,可以显示指定数据类型。 “Component”属性指定 Intent 的目标组件的类名。通常 Android 会根据 Intent 中包含的其他属性的信 息,如 Action、Sata、Type、Category 等属性进行解析,最终找到一个与之匹配的目标组件。但 是如果指定“Component”属性,将直接使用其指定的组件,而不再执行上述解析过程。指定该 属性后,Intent 的其他所有属性均变为可选属性。该属性在 Activity 的相互调用中经常使用。 “extras”属性是其他所有附加信息的集合。使用“extras”属性可以为组件提供扩展信息,比如如果要 执行“发送电子邮件”,可以将电子邮件的标题、正文等保存在“extras”属性里,传给电子邮件 发送组件。这给程序开发带来了较强的灵活性。该属性在传递数据时经常使用。 在 Android 中,根据是否声明“Component”属性的不同,Intent 可以分为显式 Intent 和隐式 Intent 两 种。 其中显式 Intent 即指定了“Component”属性的 Intent(代码实现为通过调用 setComponent()方法或 setClass()方法)。通过指定具体的组件类,通知应用启动对应的组件。 隐式 Intent 即没有指定“Component”属性的 Intent。这类 Intent 需要包含足够的信息,这样系统才能 根据这些信息,在所有的可用组件中,解析出匹配此 Intent 的组件。 对于显式 Intent,Android 不需要去做解析,因为目标组件已经很明确,Android 需要解析的是那些隐 式 Intent,通过解析,将 Intent 传递给匹配此 Intent 的 Activity、IntentReceiver 或服务。实际的 Activity 的 解析是通过 resolveActivity()进行的。 Intent 解析机制主要是通过查找已注册在 AndroidManifest.xml 中的所有 Intent 过滤器及其中定义的 Intent,最终找到匹配的组件。在解析过程中,Android 依赖于 Intent 的 Action、Type、Category 等属性, 解析策略如下: 如果 Intent 声明“Action”属性,则目标组件的 Intent 过滤器的 Action 列表中就必须包含该 Action, 否则不能匹配。 如果 Intent 没有声明“Type”属性,系统将从“Data”属性中得到数据的类型。和 Action 一样,目标组 件的数据类型列表中必须包含 Intent 的数据类型,否则不能匹配。 如果 Intent 中的“Data”属性不是“content:”类型的 URI,且没有声明“Type”属性,系统将根据 Intent 专业始于专注 卓识源于远见 ‐ 13 ‐ 中数据的协议类型(scheme,如 http:、mailto:)进行匹配。同上,Intent 的协议类型必须出现在 目标组件的协议类型列表中。 如果 Intent 为“Category”属性赋予了多个类别值,这些类别必须全部出现在组件的类别列表中。假 如 Intent 中包含了两个类别:LAUNCHER_CATEGORY 和 ALTERNATIVE_CATEGORY,解析 得到的目标组件必须至少包含这两个类别。 当希望能够对一个外部的事件(来电、电量发生变化等)做出响应时,开发者需要注册一个 Intent 过 滤器。Intent 接收器在关联的事件发生时,会通知应用收到的事件,各种应用还可以通过使用 Context.broadcastIntent()方法将 Intent 消息广播给其他应用程序。 Intent 过滤器可以在 AndroidManifest.xml 中注册,如为了接收打开相应类型的音频文件的请求,相应 的 Intent 过滤器如下: 部分开发者可能搞不明白 MIME 类型的含义,有时可能自我想象的确定 MIME 类型,殊不知 MIME 类型是有国际规范的,对于每一种文件类型,其标识符唯一。 Intent 的标志位主要有 FLAG_ACTIVITY_NEW_TASK 、 FLAG_ACTIVITY_ CLEAR_TOP 、 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 、FLAG_ACTIVITY_SINGLE_ TOP 等。这些标志位的详 细含义可以参考 Android 的帮助文档,考虑到篇幅的限制,在本书中不再详述。 5.Content Providers 内容提供器(Content Providers)是 Android 提供的一个在不同应用间传递、分享数据的机制,这些数 据可以存储在文件系统、SQLite 数据库等中。 自定义的内容提供器必须扩展自 android.content.ContentProvider 基类。 如果期望应用将拥有的数据变为公共数据,开发者可以通过自定义应用自己的内容提供器,或者将数 据添加到已有的内容提供器中的方式来进行。下面是一个内容提供器的定义: 6.Android Manifest 在 Android 启动应用之前,它必须了解应用组件的情况,在 Android 中,应用运行涉及的多数应用组 件都必须在 AndroidManifest.xml 中声明。 在 AndroidManifest.xml 中,注册应用组件的属性称为元素。元素和元素的属性构成了 AndroidManifest.xml 的管理体系。需要注意的是,部分元素也可以构成其他元素的属性。 对于 Activity,对应的元素为“activity”;对于服务,对应的元素为“service”;对于广播接收器,对应 的元素为“receiver”;对于内容提供器,对应的元素为“provider”。 对于 Activity、服务和内容提供器等类型的应用组件而言,如果该组件没有在 AndroidManifest.xml 中 声明,该组件将无法在系统中运行。但对于广播接收器而言,除了在 AndroidManifest.xml 中声明外,还可 以在代码中动态声明并通过 Context.registerReceiver()方法进行注册。 专业始于专注 卓识源于远见 ‐ 14 ‐ 目前,Android 的 AndroidManifest.xml 支持的元素有“action”、“activity”、“activity-alias”、“application”、 “category”、“data”、“grant-uri-permission”、“instrumentation”、“intent-filter”、“manifest”、“meta-data”、 “permission”、“permission-group”、“permission-tree”、“provider”、“receiver”、“service”、“uses-configuration”、 “uses-library”、“uses-permission”、“uses-sdk”、“uses-feature”、“supports-screens”等。 其中“Action”元素的语法为:。 对于系统定义的 Action ,默认的前缀为 android.intent.action ,常用的 Action 有 android.intent.action.MAIN、android.intent.action.WEB_SEARCH。 对于开发者自定义的 Action ,建议以所在包的包名作为前缀以保证命名的唯一性,如 com.miaozl.helloworld.TRANSMOGRIFY。 “activity”元素的语法为: 其中比较重要的元素的属性有“configChanges”、“launchMode”、“noHistory”、“permission”、 “screenOrientation”、“theme”等。 为了防止在配置变化时,Activity 的重启,可以配置“android:configChanges”属性,一般情况下配置 为“android:configChanges="orientation|keyboardHidden">”,这样在屏幕旋转或者物理键盘隐藏时,Activity 将不会重启,相应的配置变化将会通过 onConfigurationChanged() 传递给 Activity ,下面是 onConfigurationChanged 的一个实现过程: public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (LOCAL_LOGV) { Log.v(TAG, "onConfigurationChanged: " + newConfig); } mIsKeyboardOpen = newConfig.keyboardHidden == KEYBOARDHIDDEN_NO; boolean isLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE; if (mIsLandscape != isLandscape) { 专业始于专注 卓识源于远见 ‐ 15 ‐ mIsLandscape = isLandscape; mAttachmentEditor.update(mWorkingMessage); } onKeyboardStateChanged(mIsKeyboardOpen); } 另一个比较重要的是“android:theme”属性,在 Android 中,其可以选择的值包括"Theme"、 "Theme.NoTitleBar" 、 "Theme.NoTitleBar.Fullscreen" 、 "Theme.Black" 、 "Theme.Black.NoTitleBar" 、 "Theme.Black.NoTitleBar.Fullscreen" 、 "Theme.Dialog" 、 "Theme.Light" 、 "Theme.Light.NoTitleBar" 、 "Theme.Light.NoTitleBar.Fullscreen" 、 "Theme.Translucent" 、 "Theme.Translucent.NoTitleBar" 、 "Theme.Translucent.NoTitleBar.Fullscreen"等。 “activity-alias”元素的语法为: “activity-alias”元素用来声明应用中 Activity 的别名,但与通常意义上的别名不同,“activity-alias”元 素声明的 Activity 是作为独立的实体存在的,可以具有自己的 Intent 过滤器。 “application”元素的语法为: …. 利用“application”元素可以声明应用中组件的公共属性,其中如果设置“icon”、“label”、“permission”、 “process”、“taskAffinity”、“allowTaskReparenting”等属性,属性值将成为组件元素的默认属性,而 “debuggable”、“enabled”、“description”、“allowClearUserData”等属性,属性值将作为应用的整体属性且 不能被应用组件所覆盖。“backupAgent”属性在 Foryo 中引入。 “category”元素的语法为:。 利用“category”元素可以向 Intent 过滤器中添加“category”属性。最常用的系统“category”属性值 有"android.intent.category.LAUNCHER"。在进行压力测试时,可以将 Activity 的“category”属性设置为 "android.intent.category. MONKEY",以确保所有的 Activity 都能够纳入压力测试的范畴。 “data”元素的语法为: 利用“data”元素可以在 Intent 过滤器中设置应用涉及的数据的“host”、“mimeType”、“path”、 “pathPattern”、“pathPrefix”、“port”、“scheme”等属性。需要说明的是,与 RFC 中定义不同,在 Android 中,“scheme”属性是大小写敏感的,如“http”和“Http”表示的含义不同,因此在描述“scheme”属性 时,必须一律小写。需要注意的是,如果 Intent 过滤器包含了本地数据,相应的“scheme”属性值为“content” 或“file”。下面是日历的一个 Intent 过滤器定义: “grant-uri-permission”元素的语法为: 利用“grant-uri-permission”元素可以为内容提供器中的数据子集添加 URI 许可(grant-uri-permission)。 当 provider ::grant-uri-permission 属性为“true”时,URI 许可对内容提供器中的所有数据有效;当 provider ::grant-uri-permission 属性为“false”时,URI 许可仅对该元素指定的数据子集有效。利用 “grant-uri-permission”元素可以监控应用和系统的交互情况。 “instrumentation”元素的语法为: “instrumentation”元素主要用于单元测试中。为单元测试编写大量代码,在业界巨头中才比较常见, 在开发周期大幅缩短的今天,中小企业是无法承受这样的人力成本的,所以普通开发者一般不会用到。 “intent-filter”元素的语法为: 利用“intent-filter”元素可以声明 Intent 过滤器,帮父组件声明可以处理何种信息,在 Activty、服务、 内容提供器、广播接收器中比较常用。 “manifest”元素的语法为: “manifest”元素为 AndroidManifest.xml 的根元素,在声明“manifest”元素时,必须包含“application” 元素并声明“application”元素和 xlmns:android、manifest.package 等属性。其中 xlmns:android 属性定义了 专业始于专注 卓识源于远见 ‐ 17 ‐ Android 的名字空间,通常设置为“http://schemas.android.com/apk/res/android”。manifest.package 属性定义 了应用的 Java 包名,如“com.miaozl.helloWorld”。 “meta-data”元素的语法为: 利用“meta-data”元素可以为 Activity、服务、广播接收器等组件声明对,这 在开发 UI 相关的程序时非常有用,例子如下: “permission”元素的语法为: “permission”元素描述了接入特定组件、应用本身或者其他应用功能的安全权限。通常保护等级分为 “normal”、“dangerous”、“signature”、“signatureOrSystem”等。其中“normal”为默认权限。下面具体说 明各等级的含义: “normal”权限为默认权限,允许请求服务的应用能够接入较独立的应用级功能。“normal”权限不需 要用户显式声明,对其他应用、系统、用户具有最小的限制。 “dangerous”权限允许请求服务的应用能够访问私有的用户数据或控制,通常这类操作被视为对用户 构成潜在的风险,需要提醒用户。 “signature”权限要求请求服务的应用和声明权限具有相同的数字签名证书。 “signatureOrSystem”权限对应用限制最严格,要求请求服务的应用和提供服务的应用均包含在系统映 像中或者与系统映像具有相同的数字签名证书。在通常情况下,应避免使用“signatureOrSystem” 权限。“signatureOrSystem”权限主要应用于多个运营商均有应用包含在系统映像中并期望分享特 定功能的情况。或者用于保护私密性的信息。 下面是通过权限进行操作限制的方法: public boolean isEnabled() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return isEnabledInternal(); } 如果希望了解调用进程的 UID 和 PID,方法如下: Binder.getCallingPid() Binder.getCallingUid() “permission-group”元素的语法为: “permission-group”元素可以声明逻辑意义上的权限组,通过 permission::permissionGroup 属性可以将 权限加入到相应的权限组中。下面是系统的一个权限定义: “permission-tree”元素的语法为: 通过“permission-tree”元素可以声明权限树的基本信息,通过 PackageManager. addPermission()方法可 以将权限加入到权限树中。 “provider”元素的语法为: 通过“provider”元素可以声明内容提供器,内容提供器为接入应用管理的数据提供了可能。下面是 BT 的内容提供器的声明: “receiver”元素的语法为: 通过“receiver”元素可以声明广播接收器,广播接收器允许应用接收系统或者其他应用发送过来的消 息。下面是 BT 的一个广播接收器的声明: “service”元素的语法为: 专业始于专注 卓识源于远见 ‐ 19 ‐ 通过“service”元素可以声明服务,服务通常作为后台程序运行。 下面是 BT 中一个服务的声明: “uses-configuration”元素的语法为: 通过“uses-configuration”元素可以声明应用要求的软件、硬件特征,如五向键、软键盘、键盘类型 (qwerty、twelvekey 等)、导航键(dpad、trackball、wheel 等)、触摸屏(notouch、stylus、finger)等。 “uses-library”元素的语法为:。 “uses-library”元素声明了应用需要链接的共享库。 “uses-permission”元素的语法为:。 “uses-permission”元素声明了为保证应用正常运行所需的权限,如 android.permission.CAMERA 、 android.permission.READ_CONTACTS 等。 “uses-sdk”元素的语法为: 。 “uses-sdk”元素声明了应用与 Android 平台版本的兼容性,“minSdkVersion”属性的默认值为 1。 “targetSdkVersion”属性和“maxSdkVersion”属性在 Donut 中引入。Android 平台版本的更多信息请参考 2.1 节 Hello World。 “uses-feature”元素的语法为: “uses-feature”元素定义了应用要求的硬件环境,如 openGL ES 的版本情况。“android:glEsVersion”属 性以整数表示,前 16 位表示主版本号,后 16 位表示辅版本号,对于 OpenGL ES 1.2,其“android:glEsVersion” 属性的值为 0x00010002。 对于 NFC 应用,“uses-feature”元素配置如下: 对于 SIP 应用,“uses-feature”元素配置如下: “uses-feature”元素对于那些依赖特定硬件设备的应用而言,必须关注。 补充一句,在 Android 2.3 中,还增加了几种新的传感器如陀螺仪(gyroscope)、向量旋转(rotation vector)、 直线加速度(linear acceleration)、重力(gravity)、气压计(barometer)等的支持。 “supports-screens”元素的语法为: 在 samples\ApiDemos\ AndroidManifest.xml 中,通过 receiver::Resource 属性设置数据源: 代码 2-6 AndroidManifest.xml //数据源 专业始于专注 卓识源于远见 ‐ 21 ‐ 2)重载 App Widget 接口 重载 App Widget 的基本接口,如 AppWidgetProvider:: onUpdate()、AppWidgetProvider:: onDeleted ()、 AppWidgetProvider:: onEnabled () 、 AppWidgetProvider:: onDisabled () 等,实现可参考 ExampleAppWidgetProvider.java。 3)定义 App Widget 的布局 定义 App Widget 的初始布局,如 samples\ApiDemos\res\layout\ appwidget_configure.xml 、 samples\ApiDemos\res\layout\appwidget_provider.xml 等。 另外,为了显示 App Widget,需要创建一个 Activity,在实际的开发中,App Widget 通常位于 Home Screen 中。需要注意的是,由于 App Widget 和数据源通常位于不同的进程中,为了更新数据,需要用到 RemoteViews 类。 代码 2-7 updateAppWidget static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId, String titlePrefix) { Log.d(TAG, "updateAppWidget appWidgetId="+appWidgetId+"titlePrefix="+ titlePrefix); //通过 getString()有利于字符串的本地化,可以通过 java.util.Formatter 提供格式字符串 CharSequence text=context.getString(R.string.appwidget_text_format, ExampleAppWidgetConfigure.loadTitlePref(context, appWidgetId), "0x"+Long.toHexString(SystemClock.elapsedRealtime())); RemoteViews views=new RemoteViews(context.getPackageName(), R.layout.appwidget_ provider); //创建 RemoteViews views.setTextViewText(R.id.appwidget_text, text); appWidgetManager.updateAppWidget(appWidgetId, views); //通知 Widget 管理器 } App Widgets 涉及的类包括 android.appwidget.AppWidgetHost、android.appwidget. AppWidget HostView、 android.appwidget.AppWidgetManager 、 android.appwidget.AppWidget Provider 、 android.appwidget.AppWidgetProviderInfo 等。 8.Data Storage 对于现代的操作系统而言,如何访问数据、访问文件系统都是不可或缺的一部分,在 Android 中,上 层应用访问数据和文件系统通常是基于内容提供器的方式实现的。 Android 在内容提供器之外还提供了 4 种机制,如参数选择(Preferences)、文件(Files)、数据库 (Databases)、网络(Network)等来针对不同的数据类型执行操作。 其中参数选择机制主要用于配置值,应用于应用程序内部配置运行参数信息等场景,如开 机启动时的问候语、个性化设置等。在低端的操作系统中,此类数据一般存储在 NVRAM 中,在高端的操 作系统中,则通常保存在文件系统中。 利用 Context.getSharedPreferences()方法可以依据参数名以不同的模式提取出配置信息,提取模式包括 MODE_PRIVATE 、MODE_WORLD_READABLE、MODE_WORLD_ WRITEABLE 等。对于 Activity 私 有的参数,则可以通过 Activity.getPreferences()方法来提取参数。当需要在不同的应用间分享信息时,则需 要借助内容提供器来进行。下面是一个实例: 代码 2-8 SharedPreferences import android.app.Activity; import android.content.SharedPreferences; public class Calc extends Activity { public static final String PREFS_NAME="MyPrefsFile"; ... @Override protected void onCreate(Bundle state){ super.onCreate(state); . . . 专业始于专注 卓识源于远见 ‐ 22 ‐ // 获取 参数 SharedPreferences settings=getSharedPreferences(PREFS_NAME, 0); boolean silent=settings.getBoolean("silentMode", false); setSilent(silent); } @Override protected void onStop(){ super.onStop(); //保存参数 SharedPreferences settings=getSharedPreferences(PREFS_NAME, 0); SharedPreferences.Editor editor=settings.edit(); editor.putBoolean("silentMode", mSilentMode); //提交变化 editor.commit(); } } 由于复杂的数据,如 GPS 数据、Theme 等,需要利用文件的方式进行存储。在 Android 中,对文件同 样提供了强大的支持,通过 Context.openFileInput()方法可以读文件,此方法在打开文件后会返回一个 java.io.FileInputStream 流对象供系统读取信息,通过 Context.openFileOutput()方法可以写文件,此方法在打 开文件后会返回一个 java.io.FileOutputStream 流对象供系统写入信息。如果操作的文件是在编译期生成并 需要被放置在 APK 包中,则此类文件通常应放置在 res/raw/myDataFile 处,当需要读取时,可以通过 Resources.openRawResource() 方法进行读取,Resources.openRawResource() 方法会返回一个 java.io.InputStream 供用户操作。 对于更复杂的数据,如电话簿、短消息、号码归属地等,通常会利用 SQLite 数据库的方式来提供更便 利的创建、检索、存储支持。另外,Android 还为 SQLite 数据库的一个封装即内容提供器,通过内容提供 器可以更简洁的操作数据库,当然在性能上会略有下降。 在 Android 中,SQLite 应放置在/data/data/package_name/databases_name 目录下。如果是在线游戏或者 云计算等,可以通过 Android API(如 java.net.*、android.net.*)在服务器端存储数据。 在 Linux 中,对于 SQLite 数据库文件即*.db,可以通过 sqliteman 图形界面工具来查看 db 文件。当然 通过 Sqlite3 也可以在 adb 下通过命令行方式查看数据库数据。 在 Android 的帮助文档和示例中,对 ContentProvider 及其 SQLiteDatabase 都有比较丰富的描述,限于 篇幅,在本书中就不再详述了。 9.Resources 在 Android 中,大量的数据和配置信息以资源的方式存在,理解资源对进行 Android 应用开发具有十 分重要的意义。 对于字符串资源,通常应放置在 res/values/目录下,以 XML 文件保存。 对于音频、视频资源,通常应放置在 res/raw 目录下。 对于应用布局信息,通常应放置在 res/layout 目录下,以 XML 文件保存。 对于图像资源,通常应放置在 res/drawable 目录下。如果需要对图像进行配置,则可以利用 XML 文件 进行配置。 对于菜单资源,通常应放置在 res/menu 目录下。下面是一个包含了子菜单的菜单的资源文件实现: 专业始于专注 卓识源于远见 ‐ 23 ‐ 下面是一个选项菜单加载的过程: public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.browser, menu); } 执行菜单选择的过程如下: public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id. happy: …… return true; case R.id. neutral: …… return true; Case R.id. sad: …… return true; default: return false; } } 如果是动态菜单,在菜单显示前,可以在 onPrepareOptionsMenu(Menu menu)预先做一判断。当然也可 以直接通过代码实现菜单,方法如下: public boolean onCreateOptionsMenu(Menu menu) { menu.add(0, DISPLAY_MODE_LAUNCH, 0, R.string.quick_launch_display_mode_applications) .setIcon(com.android.internal.R.drawable.ic_menu_archive); menu.add(0, DISPLAY_MODE_SHORTCUT, 0, R.string.quick_launch_display_mode_shortcuts) .setIcon(com.android.internal.R.drawable.ic_menu_goto); return true; } 对于动态效果资源,通常应放置在 res/anim 目录下。下面是一个动态效果的实现: 动画效果常用的场景不多,在 Activity 加载或者退出时,会用到动画效果,另一个比较常用的场景是 ViewSwitcher\ ViewFlipper。下面是 ViewSwitcher 设置动画效果的方法: mViewSwitcher.setInAnimation(AnimationUtils.loadAnimation(this,R.anim.push_up_in)); mViewSwitcher.setOutAnimation(AnimationUtils.loadAnimation(this,R.anim.push_up_out)); Android 在示例中提供了数十个动态效果的示例,开发者可以直接复制即可在自己的代码中引用。 对于数据和 app widget 提供器等资源,通常放置在 res/xml 目录下。另一个比较常见的放置在 res/xml 目录下的资源文件是配置文件。下面是 Android 示例中的一个配置文件: 专业始于专注 卓识源于远见 ‐ 24 ‐ 在代码中加载配置文件的方法如下: public void onCreate() { PreferenceManager.setDefaultValues(this, R.xml.default_values, false); } Android 的资源文件内容丰富,尤其是布局文件,往往需要丰富的经验才能写出考虑周详的代码。 2.2.2 Activity 和 Tasks 作为和用户之间直接交互的 UI 组件,Activity 执行的是一个窗口的功能,Tasks 则是 Activity 的堆栈。 下面简单介绍 Activity 的部分细节。 1)启动 Activity 启动 Activity,需要调用 android.content.Context::startActivity()方法。 2)释放 Activity 当用户离开一个应用较长时间时,系统会将该应用除根 Activity 以外的其他 Activity 释放。 如果开发者期望维持应用的最后状态,可将 Activity 的 android::alwaysRetainTaskState 属性设为“true”, 该属性的默认值为“false”。 与 Activity 的 android::alwaysRetainTaskState 属性相反,当 Activity 的 android::clearTask OnLaunch 属性 的值为“true”时,在应用转入后台运行时,应用中除根 Activity 外的其他 Activity 都将立刻被清除。 与以上两个 Activity 的属性作用于 Tasks 不同,Activity 的 android::finishOnTaskLaunch 属性仅作用于 当前的 Activity。当 Activity 的 android::finishOnTaskLaunch 属性的值为“true”时,一旦当前 Tasks 转到后 台,该 Activity 即刻被清除。 2.2.3 进程和线程 在默认情况下,每个应用均运行在其独自拥有的 Linux 进程中,而每个进程拥有一个唯一的 Java 虚拟 机(Dalvik)和一个唯一的 Linux 用户 ID。在 Android 中,进程还引入了严格的权限机制,在默认情况下, 应用的文件仅对应用本身可视。 如果设置两个应用拥有共同的 Linux 用户 ID,应用间将具有互视性,同时会共享 Linux 进程和 Dalvik 虚拟机,这在系统资源紧张时,是个不错的节省系统资源的方式。关于进程的更多信息,可以参考 2.2.4 节组件生命周期。 与其他智能终端平台不同,在 Android 中,为了取得更好的用户体验,大量引入线程进行开发,当某 一任务对资源的销毁较大或者持续时间较长时,应考虑新建一个线程来执行该任务。在多媒体框架中,线 程的使用比比皆是。Java 线程的更多信息可以参考文献[16]。 专业始于专注 卓识源于远见 ‐ 25 ‐ 2.2.4 组件生命周期 在应用组件中,所有组件都是具有生命周期的,当组件被创建时,开始其生命周期,当组件被销毁时, 结束其生命周期。本节将着重介绍 Activity、服务、广播接收器、进程的生命周期。 1.Activity 生命周期 在系统中,Activity 由 Activity 栈即 Tasks 来管理,Activity 在其整个生命周期中,有 4 个状态:运行 (running)、暂停(paused)、停止(stopped)、销毁(Destroyed)。当一个 Activity 被启动时,该 Activity 被 放置在 Tasks 的顶部,其状态处于“running”状态。 当一个 Activity 被置于前台时,该 Activity 处于“running”状态。 当一个 Activity 失去焦点但仍然可视时,该 Activity 处于“paused”状态。在“paused”状态下,如果 系统可用内存非常低,该 Activity 则可能被销毁。 当一个 Activity 完全不可视时,该 Activity 处于“stopped”状态。当系统需要更多内存时,该 Activity 可能被销毁。 当一个 Activity 因为某种原因被销毁时,该 Activity 将处于“Destroyed”状态。 图 2-4 显示了 Activity 的状态迁移过程。 2.服务生命周期 服务通常作为后台进程运行,通过 Context.startService() 方法可以启动服务,如果需要销毁一个服务,则可以调用 Context.stopService() 、 Service.stopSelf() 、 Service.stopSelfResult()等方法。 当客户端需要和服务进行绑定来利用服务时,需要调用 Context.bindService() 方法,结束绑定需要调用 Context.unbindService()方法。 图 2-5 显示了服务的状态迁移过程。 图 2-5 服务生命周期 3.广播接收器生命周期 广播接收器必须定义的一个回调方法为: void onReceive(Context curContext, Intent broadcastMsg) 图 2-4 Activity 生命周期 专业始于专注 卓识源于远见 ‐ 26 ‐ 当一个广播消息到达时,Android 会调用 BroadcastReceiver ::onReceive()方法来处理消息,在处理过程 中,广播接收器处于激活状态,当处理结束时,广播接收器处于去活状态。 注册一个广播接收器的方法为 Context::registerReceiver(BroadcastReceiver, IntentFilter)。 4.进程生命周期 在 Android 中,根据进程中运行的组件,以及组件的状态将进程分为 5 类:前台进程(foreground process)、可视进程(visible process)、服务进程(service process)、后台进程(background process)、空进 程(empty process)。在系统运行过程中,如果系统内存不足,Android 将会按照进程当前的状态决定哪个 进程被杀死。 1)前台进程 当一个进程出现如下场景时,该进程被称为前台进程。场景包括: 正在运行一个与用户交互的 Activity。 驻留了一个与用户交互的 Activity 相绑定的服务。 拥有一个正在执行生命周期调用(onCreate(),onStart()、onDestroy())的服务对象。 拥有一个正在执行 onReceive()方法的广播接收器对象。 2)可视进程 当一个进程没有包含任何前台组件,但对用户视觉有影响时,该进程被称为可视进程。这类进程的典 型场景包括弹出窗口隐藏的全屏窗口等。另外驻留了与处于“paused”状态的 Activity 绑定的服务进程也 属于这类进程。 3)服务进程 当进程中驻留了一个服务组件时,该进程称为服务进程,除非系统已经没有足够的内存去运行前台进 程和可视进程,否则服务进程将一直在系统中保持运行。这类进程的典型场景如 Mp3 的后台播放。 4)后台进程 当进程的所有 Activity 对用户而言均不可见时,对用户体验来说没有直接的影响,这些进程称为后台 进程。为了满足更重要的进程的资源需求,后台进程可能会被系统杀死。 5)空进程 当一个进程没有驻留任何激活的应用组件时,该进程称为空进程,这类进程仍存在于系统中的原因是, 作为缓冲可以提高进程的下次启动时间。但为了保证系统的正常运行,当系统资源濒临不足时,Android 常会选择杀死这类进程。 启动过程 和大多数 Linux 发行版一样,在执行开机后,Android 也是通过引导加载器(bootloader)首先将 Linux 内核装载到内存中,然后启动初始化进程。初始化进程在完成一系列初始化工作后,会启动相关的守护进 程、Zygote 虚拟机和相关的实时进程,进而完成整个启动过程。图 2-6 显示了 Android 在启动过程中各进 程的启动顺序。 专业始于专注 卓识源于远见 ‐ 27 ‐ 图 2-6 Android 的启动过程 2.3.1 init.rc 初始化脚本 在启动 system\core\rootdir\init.rc 脚本后,会首先创建系统运行所需的各种环境变量,接着创建各种文 件系统(如系统文件系统、用户文件系统、SD Card、缓存(Cache)等)的挂载点,并将这些文件系统挂 载到 MTD(Memory Technology Device)上。 然后会为接下来的守护进程和实时进程的创建做些准备,如设置进程的内存门槛、系统服务和守护进 程的权限等,并为各种网络(如 WIFI、UMTS、EDGE、GPRS、CDMA(Android 1.6 后支持)等)设置 所需的 TCP 缓存,然后即创建各种守护进程,具体如下: 代码 2-9 初始化过程 on init sysclktz 0 //设置系统时区 loglevel 3 //日志级别 # 设置全局环境变量 export PATH /sbin:/system/sbin:/system/bin:/system/xbin //设置环境变量 export LD_LIBRARY_PATH /system/lib //共享库路径 export ANDROID_BOOTLOGO 1 export ANDROID_ROOT /system //根路径 export ANDROID_ASSETS /system/app export ANDROID_DATA /data export EXTERNAL_STORAGE /sdcard //SD 卡 export BOOTCLASSPATH /system/framework/core.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/fra mework/android.policy.jar:/system/framework/services.jar //核心库 # 后向兼容 symlink /system/etc /etc #创建挂载点并将 tmpfs 挂载到 sqlite_stmt_journals 上 mkdir /sdcard 0000 system system mkdir /system mkdir /data 0771 system system mkdir /cache 0770 system cache mkdir /sqlite_stmt_journals 01777 root root mount tmpfs tmpfs /sqlite_stmt_journals size=4m mount rootfs rootfs / ro remount 专业始于专注 卓识源于远见 ‐ 28 ‐ write /proc/sys/kernel/panic_on_oops 1 write /proc/sys/kernel/hung_task_timeout_secs 0 write /proc/cpu/alignment 4 write /proc/sys/kernel/sched_latency_ns 10000000 write /proc/sys/kernel/sched_wakeup_granularity_ns 2000000 # 挂载 MTD 分区 # 首先挂载 System 分区以保存文件系统检查点 mount yaffs2 mtd@system /system //挂载 yaffs2 文件系统 mount yaffs2 mtd@system /system ro remount # 挂载 Data 分区,设置权限 mount yaffs2 mtd@userdata /data nosuid nodev chown system system /data chmod 0771 /data # 挂载 Cache 分区,并设置权限 mount yaffs2 mtd@cache /cache nosuid nodev chown system cache /cache chmod 0770 /cache # 设置修复路径权限 chown system system /cache/recovery chmod 0770 /cache/recovery # 创建基本的文件系统结构 mkdir /data/misc 01771 system misc mkdir /data/misc/hcid 0770 bluetooth bluetooth mkdir /data/local 0771 shell shell mkdir /data/local/tmp 0771 shell shell mkdir /data/data 0771 system system mkdir /data/app-private 0771 system system mkdir /data/app 0771 system system mkdir /data/property 0700 root root # 配置 dalvik-cache 权限 mkdir /data/dalvik-cache 0771 system system chown system system /data/dalvik-cache chmod 0771 /data/dalvik-cache # 创建 lost+found 目录 mkdir /data/lost+found 0770 mkdir /cache/lost+found 0770 # 配置 lost+found 权限 chown root root /data/lost+found chmod 0770 /data/lost+found chown root root /cache/lost+found chmod 0770 /cache/lost+found on boot # 基本网络初始化 ifup lo hostname localhost domainname localdomain # 设置 RLIMIT_NICE,使优先级范围为 19~-20 setrlimit 13 40 40 # 设置 rmnet 超时值 write /sys/devices/virtual/net/rmnet0/timeout_suspend 5000000 //定义 ActivityManagerService 用到的系统属性 setprop ro.FOREGROUND_APP_ADJ 0 setprop ro.VISIBLE_APP_ADJ 1 setprop ro.SECONDARY_SERVER_ADJ 2 setprop ro.HOME_APP_ADJ 4 setprop ro.HIDDEN_APP_MIN_ADJ 7 setprop ro.CONTENT_PROVIDER_ADJ 14 setprop ro.EMPTY_APP_ADJ 15 //定义垃圾回收用到的内存门槛 setprop ro.FOREGROUND_APP_MEM 1536 setprop ro.VISIBLE_APP_MEM 2048 专业始于专注 卓识源于远见 ‐ 29 ‐ setprop ro.SECONDARY_SERVER_MEM 4096 setprop ro.HOME_APP_MEM 4096 setprop ro.HIDDEN_APP_MEM 5120 setprop ro.CONTENT_PROVIDER_MEM 5632 setprop ro.EMPTY_APP_MEM 6144 write /sys/module/lowmemorykiller/parameters/adj 0,1,2,7,14,15 write /proc/sys/vm/overcommit_memory 1 write /proc/sys/vm/min_free_order_shift 4 write /sys/module/lowmemorykiller/parameters/minfree 1536,2048,4096,5120,5632,6144 #设置 init 进程能够创建的子进程的数量 write /proc/1/oom_adj -16 #设置系统服务和守护进程的权限 chown radio system /sys/android_power/state chown radio system /sys/android_power/request_state chown radio system /sys/android_power/acquire_full_wake_lock chown radio system /sys/android_power/acquire_partial_wake_lock chown radio system /sys/android_power/release_wake_lock chown radio system /sys/power/state chown radio system /sys/power/wake_lock chown radio system /sys/power/wake_unlock chmod 0660 /sys/power/state chmod 0660 /sys/power/wake_lock chmod 0660 /sys/power/wake_unlock chown system system /sys/class/timed_output/vibrator/enable chown system system /sys/class/leds/keyboard-backlight/brightness chown system system /sys/class/leds/lcd-backlight/brightness chown system system /sys/class/leds/button-backlight/brightness chown system system /sys/class/leds/jogball-backlight/brightness chown system system /sys/class/leds/red/brightness chown system system /sys/class/leds/green/brightness chown system system /sys/class/leds/blue/brightness chown system system /sys/class/leds/red/device/grpfreq chown system system /sys/class/leds/red/device/grppwm chown system system /sys/class/leds/red/device/blink chown system system /sys/class/leds/red/brightness chown system system /sys/class/leds/green/brightness chown system system /sys/class/leds/blue/brightness chown system system /sys/class/leds/red/device/grpfreq chown system system /sys/class/leds/red/device/grppwm chown system system /sys/class/leds/red/device/blink chown system system /sys/class/timed_output/vibrator/enable chown system system /sys/module/sco/parameters/disable_esco chown system system /sys/kernel/ipv4/tcp_wmem_min chown system system /sys/kernel/ipv4/tcp_wmem_def chown system system /sys/kernel/ipv4/tcp_wmem_max chown system system /sys/kernel/ipv4/tcp_rmem_min chown system system /sys/kernel/ipv4/tcp_rmem_def chown system system /sys/kernel/ipv4/tcp_rmem_max chown root radio /proc/cmdline #定义各种网络情况下的 TCP 缓冲 # ReadMin, ReadInitial, ReadMax, WriteMin, WriteInitial, WriteMax, setprop net.tcp.buffersize.default 4096,87380,110208,4096,16384,110208 setprop net.tcp.buffersize.wifi 4095,87380,110208,4096,16384,110208 setprop net.tcp.buffersize.umts 4094,87380,110208,4096,16384,110208 setprop net.tcp.buffersize.edge 4093,26280,35040,4096,16384,35040 setprop net.tcp.buffersize.gprs 4092,8760,11680,4096,8760,11680 class_start default # 初始化守护进程 service console /system/bin/sh //启动 Shell console 专业始于专注 卓识源于远见 ‐ 30 ‐ # adbd 由 persist.service.adb.enable 系统属性控制 service adbd /sbin/adbd //启动 adb 守护进程 disabled on property:ro.kernel.qemu=1 start adbd on property:persist.service.adb.enable=1 start adbd on property:persist.service.adb.enable=0 stop adbd service servicemanager /system/bin/servicemanager //启动 IBinder 通信 user system critical onrestart restart zygote onrestart restart media service vold /system/bin/vold socket vold stream 0660 root mount service debuggerd /system/bin/debuggerd //启动调试系统 service ril-daemon /system/bin/rild //启动 RIL(Radio Interface Layer)守护进程 socket rild stream 660 root radio socket rild-debug stream 660 radio system user root //root 用户权限 group radio cache inet misc service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server socket zygote stream 666 onrestart write /sys/android_power/request_state wake onrestart write /sys/power/state on service media /system/bin/mediaserver //启动音频管理器等媒体服务 user media group system audio camera graphics inet net_bt net_bt_admin service bootsound /system/bin/playmp3 //启动开机音乐 user media group audio oneshot service dbus /system/bin/dbus-daemon --system –nofork //启动 Dbus 守护进程 socket dbus stream 660 bluetooth bluetooth user bluetooth group bluetooth net_bt_admin service hcid /system/bin/hcid -s -n -f /etc/bluez/hcid.conf //重定向 hcid socket bluetooth stream 660 bluetooth bluetooth socket dbus_bluetooth stream 660 bluetooth bluetooth group bluetooth net_bt_admin misc disabled service hfag /system/bin/sdptool add --channel=10 HFAG //启动蓝牙免提音频网关 user bluetooth group bluetooth net_bt_admin disabled oneshot service hsag /system/bin/sdptool add --channel=11 HSAG //启动蓝牙耳机音频网关 user bluetooth group bluetooth net_bt_admin disabled oneshot service installd /system/bin/installd //启动安装 APK 包守护进程 socket installd stream 600 system system service flash_recovery /system/bin/flash_image recovery /system/recovery.img oneshot 为了便于读者理解 init.rc 脚本,在此对 init.rc 脚本的编写做简要的说明,在 init.rc 脚本中,有 4 种类 型的命令方式,分别为“Actions”、“Commands”、“Services”和“Options”。 “Actions”的命令行参数如下: on 专业始于专注 卓识源于远见 ‐ 31 ‐ “Commands”的方法包括: exec [ ]* //创建进程 export //设置环境变量 ifup //激活网络接口 import //导入配置文件 hostname //设置主机名 chmod //改变文件权限 chown //改变文件归属 class_start //启动特定服务 class_stop //停止特定服务 domainname //设置守护进程名 insmod //安装共享库 mkdir [mode] [owner] [group] //创建路径 mount [ ]* //挂载文件系统 setkey //按键映射 setprop //设置系统属性 setrlimit //设置资源限制 start //启动服务 stop //停止服务 symlink //创建符号链接 sysclktz //设置系统时区 trigger //触发事件 write [ ]* //打开文件并写入内容 “Services”的命令行参数如下: service [ ]*
还剩50页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 5 金币 [ 分享pdf获得金币 ] 1 人已下载

下载pdf

pdf贡献者

肉肉花花

贡献于2014-06-14

下载需要 5 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf