第 章 应用程序 学习目标 本章主要介绍Android 项目中的目录结构、 AndroidManifest.xml文件等项目构成文件,同时,对 Android 应用程序组件Activity、Service、Intent和 IntentFilter、BroadcastReceiver、ContentProvider进行介 绍,并就Android程序生命周期和组件生命周期详细讲解。 通过本章的内容,读者能够学习以下知识要点。 (1)Android项目文件及应用程序。 (2)Android系统的进程优先级的变化方式。 (3)Android系统的基本组件。 (4)Android程序生命周期及Activity的生命周期中 各状态的变化关系。 (5)Activity事件回调函数的作用和调用顺序。 (6)Android应用程序的调试方法和工具。 在Android的应用过程中,对于初学者而言,通常会混 淆Android项目和Android应用程序的概念。它们之间既 有区别又有联系,要创建Android应用,必须先创建 Android项目,然后才能在项目中创建Android应用程序, 下面就Android项目的构成和Android应用程序的组成进 行介绍。 3.nrid项目的构成 1 Ado 1.目录结构 3.1 在建立新项目的过程中,ADT 会自动建立一些目录和 文件,这些目录和文件有其固定的作用,有的允许修改,有 的不能修改。AndroidStudio环境下和Eclipse环境下创 建Android项目的目录结构不同,本书针对Android 第 3 章 Android应用程序 71 Studio环境下新建项目Hello 工程进行介绍,项目工程结构包含.gradle、.idea、app、 gradle、.gitignore、build.gradle、gradle.properties、gradlew、gradlew.bat、local.properties、 Hello.iml、setting.gradle,如图3-1所示,下面逐一介绍。 图3-1 Android Studio 环境下Android 工程目录结构 .gradle目录和.idea目录:分别为AndroidStudio集成环境下自动化构建工具gradle (基于JVM 的构建工具)产生的文件和开发工具idea(其全称是IntelliJIDEA,开发集成工 具)产生的文件,存放项目的配置信息,包括历史记录,版本控制信息等。 iml是intellijidea自动创建的模块文件,存储模块开发相关的信息、依赖信息等。 app目录:包含build目录、libs目录、src目录、.gitignore文件、build.gradle文件、app. iml文件、proguard-rules.pro文件。 . build目录包含了编译时自动生成的文件。 .libs目录存放项目需要的第三方JAR包。 .src目录是源代码目录,所有允许用户修改的Java文件和用户自己添加的Java文 件都保存在这个目录中的main目录下。 . .gitignore是git源码管理文件ignore,作用是将app模块内指定的目录或文件排除 在代码版本控制之外。 . build.gradle是app模块中的gradle的配置文件,另外一个build.gradle文件位于 工程根目录下。 .app.iml是idea创建的android模块文件,保存模块路径、依赖关系及其他配置 信息。 . proguard-rules.pro是代码混淆配置模板。 gradle.properties:运行环境配置文件,在它里面可以定义全局的属性,然后在各个模 块的build.gradle中直接引用。 gradlew:是Linux下的shell脚本,Linux用户可以通过它来执行gradle任务。 gradlew.bat:是Windows下的可执行脚本,用户可以通过它执行gradle 任务,与 gradle文件夹配合使用。 local.properties:是项目工程的本地配置。该文件是在工程编译时自动生成的,用于 描述开发者本机的环境配置,如SDK的本地路径、NDK的本地路径等。 setting.gradle:配置同时编译的模块。初始内容为include':app',表示只编译app Android应用开发案例教程(第2版) 72 模块。 Hello.iml:工程的配置文件。 3.1.2 AndroidManifest.xml文件简介 AndroidManifest.xml是XML格式的Android程序声明文件,是全局描述文件,包含 了Android系统运行Android程序前所必须掌握的重要信息,这些信息包含应用程序名 称、图标、包名称、模块组成、授权和SDK最低版本等。创建的每个Android项目应用程序 必须在根目录下包含一个AndroidManifest.xml工程文件。 AndroidManifest.xml文件的代码: 1. <? xml version="1.0" encoding="utf-8"? > 2. <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3. package="com.hisoft" 4. > 5. <application 6. android:allowBackup="true" 7. android:icon="@mipmap/ic_launcher" 8. android:label="@string/app_name" 9. android:roundIcon="@mipmap/ic_launcher_round" 10. android:supportsRtl="true" 11. android:theme="@style/AppTheme"> 12. <activity android:name=".HelloWorldActivity"> 13. <intent-filter> 14. <action android:name="android.intent.action.MAIN" /> 15. <category android:name="android.intent.category.LAUNCHER" /> 16. </intent-filter> 17. </activity> 18. </application> 19. </manifest> AndroidManifest.xml文件的根元素是manifest,包含了xmlns:android、package、 android:versionCode和android:versionName共4个属性。 xmlns:android定义了Android的命名空间,值为http://schemas.android.com/apk/ res/android。 package定义了应用程序的包名称。 manifest元素仅能包含一个application元素,application元素中能够声明Android程 序中最重要的4个组成部分,包括Activity、Service、BroadcastReceiver和ContentProvider,所定 义的属性将影响所有组成部分。 第6行属性android:allowBackup定义是否允许备份应用的数据,默认是true,表示 用户可通过adbbackup和adbrestore来对应用数据进行备份和恢复。实际中为安全起 见,建议开发者将allowBackup标志值设置为false来关闭应用程序的备份和恢复功能,以 免造成信息泄露。 第 3章 Android应用程序 第7行属性android:icon定义了Android应用程序的图标,其中@mipmap/ic_ launcher是一种资源引用方式,表示资源类型是图像,资源名称为ic_launcher,对应的资源 文件为res/mipmap目录下的ic_launcher.n。 pg 第8行属性android:label则定义了Android应用程序的标签名称。@string/app_ name同样属于资源引用,表示资源类型是字符串,资源名称为app_name,资源保存在res/ vaus目录下的srnsxml文件中。 letig. 第9行属性android:roundIcon与第7行属性类似,不同的是其引用了圆形图片。 第10行属性android:supportsRtl表示application是否支持从右到左(RTL,right 的布局。如果设置为tn设置应为17(And2以后)或更 to-left) rue,targetSdkVersioroid4. 高,各种RTL的API将被激活,应用程序可以显示RTL布局。如果taretSdkVersion设 置为16或更低,RTL的设置将被忽略。 g 第11行属性android:theme定义主题风格,其中的@style/AppTheme是引用的res/ le/y.l中的主题样式。 vausstlesxm 第12行属性android:name定义了实现Activity类的名称,可以是完整的类名称,也 可以是简化后的类名称。 aciiy元素是对Actvty子类的声明,必须在AndodMaietxml文件中声明的 tvtiirinfs. Activity才能在用户界面中显示。 inetfler中声明了两个子元素aton和ctgritn-itr使HeloAndod程 tn-itciaeoy,netfleri 序在启动时,将.HeloWorldActivity这个Activity作为默认启动模块。 3.1.3 build目录 在上文的目录结构中已讲述,在app目录下build/generated/not_namespaced_r_clas sources/debug/procesDebugResources/r/包名的目录下只存放一个由ADT自动生成, 并(_) 不需要人工修改的R.aa文件。 jvR.aa文件包含对drale、aot和vaus目录内的资源的引用指针,Andrid程 jvawblyuleo 序能够直接通过R类引用目录中的资源。 Android系统中资源引用有两种方式:一种是在代码中引用资源;另一种是在资源中 引用资源。 代码中引用资源需要使用资源的ID,可以通过[R.eore_yp.eore_ame]或 rsuctersucn[adod.rsuc_ypersuc_ame] nriR.eoret.eoren获取。 resource_type代表资源类型,也就是R类中的内部类名称。 resource_name代表资源名称,对应资源的文件名或在XML文件中定义的资源名称 属性。 ackagtype:n资源中引用资源,引用格式:@[pe:]ame 。 .@表示对资源的引用。 . jpackage是包名称,如果在相同的包,package则可以省略。 R.ava文件不能手工修改,如果向资源目录中增加或删除资源文件,则需要在工程名 称上右击,选择Rh来更新R.a文件中的代码。 efresjavR类包含的几个内部类,分别与资源类型相对应,资源ID便保存在这些内部类中,例 Android应用开发案例教程(第2版) 74 如子类drawable表示图像资源,内部的静态变量icon 表示资源名称,其资源ID 为 0x7f020000。一般情况下,资源名称与资源文件名相同。 3.1.4 res目录 在Androidstudio环境下,res目录位于app/src/main/下,res目录中包含了9个子目 录,介绍如下。 drawable目录:存放背景文件ic_launcher_background.xml文件,使用SVG 格式绘 制出带纹理的底图。 drawable-v24目录:存放前景文件ic_launcher_foreground.xml文件,使用SVG 格式 绘制出一个带有投影效果的Android机器人Logo。 layout目录:用来保存与用户界面相关的布局文件,这些布局文件都是XML文件,默 认存放的是activity_main.xml文件。 mipmap-anydpi-v26目录:该文件夹用来存放自适应图标,用ic_launcher.xml和ic_ launcher_round.xml两个文件表示,分别资源引用drawable目录中的。 mipmap-hdpi目录:里面主要放高分辨率的图片,如WVGA(480×800)、FWVGA (480×854),默认存放的是ic_launcher.png图片。 mipmap-mdpi目录:里面主要放中等分辨率的图片,如HVGA(320×480)。 mipmap-xhdpi目录:里面主要放超高分辨率的图片,如QVGA(720×1280)。 mipmap-xxhdpi目录:里面主要放超超高分辨率的图片,如QVGA(1080×1920)。 mipmap-xxxhdpi目录:里面主要放超高分辨率的图片,如2K屏幕。 上述mipmap各个文件夹代表像素密度(dpi),mipmap仅用于应用启动图标,可以根 据不同分辨率进行优化。其他的图标资源,要放到drawable文件夹中。系统会根据机器 的分辨率来分别到这几个文件夹里面去找对应的图片。 valuse目录:保存文件颜色、风格、主题和字符串等,默认存放的是strings.xml文件。 上述activity_main.xml文件,是界面布局文件,利用XML语言描述的用户界面界面 布局的相关内容将在后续章节用户界面设计中进行详细介绍。 (1)activity_main.xml文件代码: 1. <? xml version="1.0" encoding="utf-8"? > 2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3. android:orientation="vertical" 4. android:layout_width="fill_parent" 5. android:layout_height="fill_parent" 6. > 7. <TextView 8. android:layout_width="fill_parent" 9. android:layout_height="wrap_content" 10. android:text="@string/hello" 11. /> 12. </LinearLayout> 第 3 章 Android应用程序 75 第7行的代码说明在界面中使用TextView 控件,TextView 控件主要用来显示字符 串文本。 第10行代码说明TextView控件需要显示的字符串,非常明显,@string/hello是对资 源的引用。 (2)strings.xml文件代码: <? xml version="1.0" encoding="utf-8"? > <resources> <string name="hello">Hello World, HelloWorldActivity!</string> <string name="app_name">HelloWorld</string> </resources> 通过strings.xml文件的第3行代码分析,在TextView 控件中显示的字符串应是 HelloWorld,HelloAndroidActivity! 如果修改strings.xml文件的第3行代码的内容,重新编译、运行后,模拟器中显示的 结果也应该随之更改。 3.2 Android应用程序组成 3.2.1 Android应用程序概述 Android应用程序是在Android应用框架之上,由一些系统自带和用户创建的应用程 图3-2 Android 应用程序组件 序组成。组件是可以调用的基本功能模块,Android应用程 序就是由组件组成的。一个Android的应用程序通常包含 4个核心组件和一个Intent,4 个核心组件分别是: Activity、Service、BroadcaseReceiver 和ContentProvider。 Intent是组件之间进行通信的载体,它不仅可以在同一个应 用中起传递信息的作用,还可以在不同的应用间传递信息, 如图3-2所示。 3.2.2 Activity组件 Activity是Android程序的呈现层,显示可视化的用户界面,并接收与用户交互所产生 的界面事件。一个Android应用程序可以包含一个或多个Activity,其中一个作为main Activity用于启动显示,一般在程序启动后会呈现一个Activity,用于提示用户程序已经正 常启动。 Activity通过View管理用户界面UI。View 绘制用户界面UI与处理用户界面事件 (UIevent),View可通过XML描述定义,也可在代码中生成。一般情况下,Android建议 将UI设计和逻辑分离,AndroidUI设计类似swing,通过布局(layout)组织UI组件。 在应用程序中,每一个Activity都是一个单独的类,继承实现了Activity基础父类,这 个类通过它的方法设置并显示由Views组成的用户界面UI,并接受、响应与用户交互产生 的界面事件。Activity通过startActivity或startActivityForResult启动另外的Activity。 Android应用开发案例教程(第2版) 76 在应用程序中一个Activity在界面上的表现形式通常有:全屏窗体、非全屏悬浮窗 体、对话框等。 3.2.3 Service组件 Service常用于没有用户界面,但需要长时间在后台运行的应用。与应用程序的其他 模块(例如activity)一同运行于主线程中。一般通过startService或bindService方法创建 Service,通过stopService或stopSelf方法终止Service。通常情况下,都在Activity中启动 和终止Service。 在Android应用中,Service的典型应用是音乐播放器。在一个媒体播放器程序中,大 概要有一个或多个活动(Activity)来供用户选择歌曲并播放它。然而,音乐的回放就不能 使用活动(Activity)了,因为用户希望能够切换到其他界面时音乐继续播放。这种情况下, 媒体播放器活动(Activity)要用Context.startService()启动一个服务来在后台运行保持音 乐的播放。系统将保持这个音乐回放服务的运行直到它结束。需要注意,你要用Context. bindService()方法连接服务(如果它没有运行,要先启动它)。当连接到服务后,你可以通 过服务暴露的一个接口和它通信。对于音乐服务,它支持暂停、倒带、重放等功能。 3.2.4 Intent和IntentFilter组件 1.Intent Android中提供了Intent机制来协助应用间的交互与通信,Intent负责对应用中一次 操作的动作、动作涉及数据、附加数据进行描述。Android则根据此Intent的描述,负责找 到对应的组件,将Intent传递给调用的组件,并完成组件的调用。Intent不仅可用于应用 程序之间,也可用于应用程序内部的Activity/Service之间的交互。因此,Intent在这里起着一 个媒体中介的作用,类似于消息、事件通知,它充当Activity、Service、broadcastReceiver之间联 系的桥梁,专门提供组件互相调用的相关信息,实现调用者与被调用者之间的解耦。具体详 述见8.1.2节。 通常Intent分为显式和隐式两类。显式的Intent,就是指定了组件名字,由程序指定 具体的目标组件来处理。即在构造Intent对象时就指定接收者,指定了一个明确的组件 (setComponent或setClass)来使用处理Intent。 Intent intent = new Intent( getApplicationContext(), Test.class ); startActivity(intent); 特别注意:被启动的Activity需要在AndroidManifest.xml中进行定义。 隐式的Intent就是没有指定Intent的组件名字,没有指定明确的组件来处理该 Intent。使用这种方式时,需要让Intent与应用中的IntentFilter描述表相匹配。需要 Android根据Intent中的Action、data、Category等来解析匹配。由系统接收调用并决定 如何处理,即Intent的发送者在构造Intent对象时,并不知道也不关心接收者是谁,有利于 第 3 章 Android应用程序 77 降低发送者和接收者之间的耦合。如:startActivity(newIntent(Intent.ACTION_ DIAL));。 Intent intent = new Intent(); intent.setAction("test.intent.IntentTest"); startActivity(intent); 目标组件(Activity、Service、BroadcastReceiver)是通过设置它们的IntentFilter来界 定其处理的Intent。如果一个组件没有定义IntentFilter,那么它只能接受处理显式的 Intent,只有定义了IntentFilter的组件才能同时处理隐式和显式的Intent。 一个Intent对象包含了很多数据的信息,由6部分组成: . Action———要执行的动作。 . Data———执行动作要操作的数据。 . Category———被执行动作的附加信息。 . Extras———其他所有附加信息的集合。 . Type———显式指定Intent的数据类型(MIME)。 . Component———指定Intent的目标组件的类名称比如要执行的动作、类别、数据、 附加信息等。 下面就一个Intent中包含的信息进行简要介绍。 1)Action 一个Intent的Action在很大程度上说明这个Intent要做什么,例如查看(View)、删除 (Delete)、编辑(Edit)等。Action 是一个字符串命名的动作,Android中预定义了很多 Action,可以参考Intent类查看,表3-1是Android文档中的几个动作。 表3-1 Android动作 Constant(全局常量) Targetcomponent (目标组件) Action(动作) ACTION_CALL Activity 调用拨打电话界面 ACTION_EDIT Activity 提供显式的可使用户编辑访问给定的 数据 ACTION_MAIN Activity 发起一个任务或程序的起始Activity,无 数据输入和返回信息 ACTION_SYNC Activity 用于移动设备上带有数据的服务的数据 同步 ACTION_BATTERY_LOW broadcastReceiver 调用电池电量低的系统警告对话框框 ACTION_HEADSET_PLUG broadcastReceiver 无线耳机插入和拔出状态检测 ACTION_SCREEN_ON broadcastReceiver 屏幕亮屏监听 ACTION_TIMEZONE_CHANGED broadcastReceiver 时间时区设置更改 此外,用户也可以自定义Action,比如ACTION_CALL_BUTTON:拨打按钮,调用 拨号程序。定义的Action最好能表明其所表示的意义,要做什么,这样Intent中的数据才 Android应用开发案例教程(第2版) 好填充。Intent对象的getAction()可以获取动作,使用setAction()可以设置动作。 2)Data 其实就是一个URI,用于执行一个Action时所用到的数据的URI和MIME 。不同的 Action有不同的数据规格,比如ACTION_EDIT动作,数据就可以包含一个用于编辑文档 的URI 。如果是一个ACTION_CALL动作,那么数据就是一个包含了tel:6546541的数 据字段,所以上面提到的自定义Action时要规范命名。数据的URI和类型对于Intent的 匹配是很重要的,Android往往根据数据的URI和MIME找到能处理该Intent的最佳目 标组件。 3)component(组件) 指定Intent的目标组件的类名称。通常Android会根据Intent中包含的其他属性的 信息,比如Action、Data/Type、Category进行查找,最终找到一个与之匹配的目标组件。 如果设置了Intent目标组件的名字,那么这个Intent就会被传递给特定的组件,而不 再执行上述查找过程,指定了这个属性以后,Intent的其他所有属性都是可选的。也就是 我们说的显式Intent。如果不设置,则是隐式的Intent,Android系统将根据IntentFilter 中的信息进行匹配。 4)Category Category指定了用于处理Intent的组件的类型信息,一个Intent可以添加多个 Category,使用addCategory()方法即可,使用removeCategory()删除一个已经添加的类 别。Android的Intent类里定义了很多常用的类别,可以参考使用。 5)Extras 有些用于处理Intent的目标组件需要一些额外的信息,那么就可以通过Intent的 put. ()方法把额外的信息塞入到Intent对象中,用于目标组件的使用,一个附件信息就是 一个key-value的键值对。Intent有一系列的put和get方法用于处理附加信息的塞入和 取出。 2.IntentFilter 应用程序的组件为了告诉Android自己能响应、处理哪些隐式Intent请求,可以声明 一个甚至多个IntentFilter。每个IntentFilter描述该组件所能响应Intent请求的能 力———组件希望接收什么类型的请求行为,什么类型的请求数据。比如在请求网页浏览器 这个例子中,网页浏览器程序的IntentFilter就应该声明它所希望接收的IntentAction是 WEB_SEARCH_ACTION,以及与之相关的请求数据是网页地址URI格式。如何为组件 声明自己的InetFitr? rinfs.l文件中用属性<Itn tnle常见的方法是在AndodMaietxmnetFilter>描述组件的IntentFilter。 Intent解析机制主要是通过查找已注册在AndroidManifestxml中的所有 IntentFilter及其中定义的Intent,最终找到匹配的Intent。在这个解析过程(.) 中,Android是 通过Intent的Action、Type、Category这3个属性来进行判断的,判断方法如下。 .如果Intent指明定了Action,则目标组件的IntentFilter的Action列表中就必须包 含有这个Action,否则不能匹配。 .如果Intent没有提供Type,系统将从Data中得到数据类型。和Action一样,目标 第 3 章 Android应用程序 79 组件的数据类型列表中必须包含Intent的数据类型,否则不能匹配。 . 如果Intent中的数据不是content类型的URI,而且Intent也没有明确指定它的 type,将根据Intent中数据的scheme(比如http:或者mailto:)进行匹配。同上, Intent的scheme必须出现在目标组件的scheme列表中。 . 如果Intent指定了一个或多个category,这些类别必须全部出现在组建的类别列表 中。比如Intent 中包含了两个类别: LAUNCHER _ CATEGORY 和 ALTERNATIVE_CATEGORY,解析得到的目标组件必须至少包含这两个类别。 一个Intent对象只能指定一个action,而一个IntentFilter可以指定多个action。 action的列表不能为空,否则它将组织所有的intent。 一个Intent对象的Action必须和IntentFilter中的某一个Action匹配,才能通过测 试。如果IntentFilter的Action列表为空,则不通过。如果Intent对象不指定Action,并 且IntentFilter的Action列表不为空,则通过测试。 下面针对Intent和IntentFilter中包含的子元素Action(动作)、Data(数据)以及 Category(类别)进行比较并对具体规则进行详细介绍。 1)动作测试 <intent-filter>元素中可以包括子元素<action>,比如: <intent-filter> <action android:name="com.example.project.SHOW_CURRENT" /> <action android:name="com.example.project.SHOW_RECENT" /> <action android:name="com.example.project.SHOW_PENDING" /> </intent-filter> 一条<intent-filter>元素至少应该包含一个<action>,否则任何Intent请求都不能 和该<intent-filter> 匹配。如果Intent请求的Action 和<intent-filter> 中某一条< action>匹配,那么该Intent就通过了这条<intent-filter>的动作测试。如果Intent请求 或<intent-filter>中没有说明具体的Action类型,那么会出现下面两种情况。 如果<intent-filter>中没有包含任何Action类型,那么无论什么Intent请求都无法 和这条<intent-filter>匹配; 反之,如果Intent请求中没有设定Action类型,那么只要<intent-filter>中包含有 Action类型,这个Intent请求就将顺利地通过<intent-filter>的行为测试。 2)类别测试 <intent-filter>元素可以包含<category>子元素,比如: <intent-filter ...> <category android:name="android.Intent.Category.DEFAULT" /> <category android:name="android.Intent.Category.BROWSABLE" /> </intent-filter> 只有当Intent请求中所有的Category与组件中某一个IntentFilter的<category>完 全匹配时,才会让该Intent请求通过测试,IntentFilter中多余的<category>声明并不会 导致匹配失败。一个没有指定任何类别测试的IntentFilter仅仅只会匹配没有设置类别的 Intent请求。 Android应用开发案例教程(第2版) 80 3)数据测试 数据在<intent-filter>中的描述如下: <intent-filter ... > <data android:type="video/mpeg" android:scheme="http" ... /> <data android:type="audio/mpeg" android:scheme="http" ... /> </intent-filter> <data>元素指定了希望接收的Intent请求的数据URI和数据类型,URI被分成3 部分来进行匹配:scheme、authority和path。其中,用setData()设定的Intent请求的URI 数据类型和scheme必须与IntentFilter中所指定的一致。若IntentFilter中还指定了 authority或path,它们也需要相匹配才会通过测试。 3.2.5 BroadcastReceiver组件 在Android中,Broadcast是一种广泛运用在应用程序之间传输信息的组件。而 BroadcastReceiver是接收并响应广播消息的组件,对发送出来的Broadcast进行过滤接收 并响应,它不包含任何用户界面,可以通过启动Activity或者Notification通知用户接收到 重要信息,在Notification中有多种方法提示用户,如闪动背景灯、振动设备、发出声音或在 状态栏上放置一个持久的图标。 BroadcastReceiver过滤接收的过程如下。 在需要发送信息时,把要发送的信息和用于过滤的信息(如Action、Category)装入一 个Intent 对象,然后通过调用Context.sendBroadcast()、sendOrderBroadcast()或 sendStickyBroadcast()方法,把Intent对象以广播的方式发送出去。 当Intent发送后,所有已经注册的BroadcastReceiver会检查注册时的IntentFilter是 否与发送的Intent相匹配,若匹配则调用BroadcastReceiver的onReceive()方法。因此在 我们定义一个BroadcastReceiver时,通常都需要实现onReceive()方法。 BroadcastReceiver注册有两种方式: 一种方式是静态地在AndroidManifest.xml中用<receiver>标签声明注册,并在标签 内用<intent-filter>标签设置过滤器; 另一种方式是动态地在代码中先定义并设置好一个IntentFilter对象,然后在需要注册的 地方调用Context.registerReceiver()方法,如果取消时就调用Context.unregisterReceiver() 方法。 不管是用XML注册的还是用代码注册的,在程序退出时,一般都需要注销,否则下次 启动程序可能会有多个BroadcastReceiver。另外,若在使用sendBroadcast()的方法时指 定了接收权限,则只有在AndroidManifest.xml中用<uses-permission>标签声明了拥有 此权限的BroascastReceiver才会有可能接收到发送来的Broadcast。 同样,若在注册BroadcastReceiver时指定了可接收的Broadcast的权限,则只有在包 内的AndroidManifest.xml中用<uses-permission>标签声明了拥有此权限的Context对 象所发送的Broadcast才能被这个BroadcastReceiver所接收。 3.2.6 ContentProvider组件 ContentProvider是Android系统提供的一种标准的共享数据的机制,在Android中 第 3 章Android应用程序 每一个应用程序的资源都为私有,应用程序可以通过ContentProvider组件访问其他应用 程序的私有数据(私有数据可以是存储在文件系统中的文件,或者是存放在SQLite中的数 据), 如图3-3所示。 图3-3 应用程序、ContentResolver 与ContentProvider 对ContentProvider的使用,有两种方式: .ContentResolver访问。 .Cnetgtotneovr()。 otx.eCnetRsleAndroid系统内部也提供一些内置的ContentProvider,能够为应用程序提供重要的数 据信息。使用ContentProvider对外共享数据的好处是统一了数据的访问方式。 3.nrid生命周期 3 Ado 3.程序生命周期 3.1 程序的生命周期是指在Android系统中进程从启动到终止的所有阶段,也就是 Android程序启动到停止的全过程。程序的生命周期是由 Android系统进行调度和控制的。 Android系统中的进程分为:前台进程、可见进程、服务进 程、后台进程、空进程。 Android系统中的进程优先级由高到低,如图3-4所示。 1. 前台进程 前台进程是Android系统中最重要的进程,是指与用户正在 交互的进程,包含以下4种情况: .进程中的Activity正在与用户进行交互。 .进程服务被Activity调用,而且这个Activity正在与用 户 进行交互 。 .进程服务正在执行声明周期中的回调方法,如onCreate() 、 onStart() 或onDestroy() 。 .进程的BroadcastReceiver正在执行onReceive() 方法。 Android系统有多个前台进程同时运行时,可能会出现资源不足的情况,此时会清除 部分前台进程,保证主要的用户界面能够及时响应。 图3-4 Android系统的 进程及优先级 Android应用开发案例教程(第2版) 2. 可见进程 可见进程指部分程序界面能够被用户看见,但不在前台与用户交互,不响应界面事件 的进程。如果一个进程包含服务,且这个服务正在被用户可见的Activity调用,此进程同 样被视为可见进程。 Android系统一般存在少量的可见进程,只有在特殊的情况下,Android系统才会为保 证前台进程的资源而清除可见进程。 3. 服务进程 服务进程是指包含已启动服务的进程,通常特点: .没有用户界面。 .在后台长期运行。 Android系统在不能保证前台进程或可视进程所必要的资源时,才会强行清除服务 进程。 4. 后台进程 后台进程是指不包含任何已经启动的服务,而且没有任何用户可见的Activity的 进程。 Android系统中一般存在数量较多的后台进程,在系统资源紧张时,系统将优先清除 用户较长时间没有见到的后台进程。 5. 空进程 空进程是指不包含任何活跃组件的进程,空进程在系统资源紧张时会被首先清除。但 为了提高Android系统应用程序的启动速度,Android系统会将空进程保存在系统内存 中,在用户重新启动该程序时,空进程会被重新使用。 除了以上的优先级外,以下两方面也决定它们的优先级: .进程的优先级取决于所有组件中的优先级最高的部分。 .进程的优先级会根据与其他进程的依赖关系而变化。 3.组件生命周期 3.2 所有Android组件都具有自己的生命周期,是指从组件的建立到组件的销毁整个过 程。在生命周期中,组件会在可见、不可见、活动、非活动等状态中不断变化。下面就各个 组件的生命周期逐一进行讲述。 1.Service生命周期 Service组件通常没有用户界面UI,其启动后一直运行于后台;它与应用程序的其他模 块(如activity)一同运行于程序的主线程中。 一个Service的生命周期通常包含:创建、启动、销毁这几个过程。 Service只继承了onCreate()、onStart()、onDestroy()3个方法,当第一次启动Service 时,先后调用了onCreate()、onStart() 这两个方法;当停止Service时,则执行onDestroy() 方法。需要注意的是,如果Service已经启动了,当再次启动Service时,不会再执行 第 3 章 Android应用程序 83 onCreate()方法,而是直接执行onStart()方法。 创建Service 的方式有两种,一种是通过startService 创建,另外一种是通过 bindService创建Service。两种创建方式的区别在于,startService是创建并启动Service, 而bindService只是创建了一个Service实例并取得了一个与该Service关联的binder对 象,但没有启动它,如图3-5所示。 图3-5 Service 的生命周期 如果没有程序停止它或者它没有自己停止,Service将一直运行。在这种模式下, Service开始于调用Context.startService(),停止于Context.stopService()。Service可以 通过调用stopService()或Service.stopSelfResult()停止自己。不管调用多少次 startService(),只需要调用一次stopService()就可以停止Service。一般在Activity中启 动和终止Service。它可以通过接口被外部程序调用。外部程序建立到Service的连接,通 过连接来操作Service。建立连接开始于Context.bindService(),结束于Context. unbindService()。多个客户端可以绑定到同一个Service,如果Service 没有启动, bindService()可以选择启动它。 上述两种方式不是完全分离的。通过startService()启动的服务。如一个Intent想要 播放音乐,通过startService()方法启动后台播放音乐的Service。然后,用户想要操作播 放器或者获取当前正在播放的乐曲的信息,一个Activity就会通过bindService()建立一个 到这个Service的连接。这种情况下stopService()在全部的连接关闭后才会真正停止 Service。 像Activity一样,Service也有可以通过监视状态实现的生命周期。但是比Activity要 少,通常只有3种方法,并且是public而不是protected的属性的,具体如下: 1. void onCreate() 2. void onStart(Intent intent) 3. void onDestroy() 通过实现上述3种方法,可以监视Service生命周期的两个嵌套循环: 整个生命周期从onCreate()开始,到onDestroy()结束,像Activity一样,一个Android Service的生命周期在onCreate()中执行初始化操作,在onDestroy()中释放所有用到的资源。 如:后台播放音乐的Service可能在onCreate()创建一个播放音乐的线程,在onDestroy()中销 毁这个线程。 活动生命周期开始于onStart()。这个方法处理传入到startService()方法的Intent。音乐 服务会打开Intent查看要播放哪首歌曲,并开始播放。当服务停止的时候,没有方法检测到 (没有onStop()方法)、onCreate()和onDestroy()用于所有通过Context.startService()or Android应用开发案例教程(第2版) 84 Context.bindService()启动的Service。onStart()只用于通过startService()开始的Service。 如果一个AndroidService生命周期是可以从外部绑定的,它就可以触发以下的方法: 1. IBinder onBind(Intent intent) 2. boolean onUnbind(Intent intent) 3. void onRebind(Intent intent) onBind()回调被传递给调用bindService的Intent、onUnbind()被unbindService()中 的intent处理。如果服务允许被绑定,那么onBind()方法返回客户端和Service的沟通通 道。如果一个新的客户端连接到服务,onUnbind()会触发onRebind()调用。 后续案例将会讲解说明Service的回调方法。将通过startService和bindService()启 动的Service分开了,但是要注意不管它们是怎么启动的,都有可能被客户端连接,因此都 有可能触发到onBind()和onUnbind()方法。 2.Service生命周期应用案例 具体实现步骤如下。 (1)在AndroidStudio中选择File→New→NewProject,创建一个新的Android工 程,项目名为ServiceTestDemo,目标API选择28(即Android9.0版本),应用程序名为 ServiceTestDemo,包名为com.hisoft.activity,创建的Activity的名字为ServiceTestDemoActivity, 最小SDK版本根据选择的目标API会自动添加为15。 (2)修改res目录下layout文件夹中main.xml代码,添加4个Button控件,代码 如下: 1. <? xml version="1.0" encoding="utf-8"? > 2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3. android:orientation="vertical" 4. android:layout_width="fill_parent" 5. android:layout_height="fill_parent" 6. > 7. <TextView 8. android:id="@+id/text" 9. android:layout_width="fill_parent" 10. android:layout_height="wrap_content" 11. android:text="@string/hello" 12. /> 13. <Button 14. android:id="@+id/startservice" 15. android:layout_width="fill_parent" 16. android:layout_height="wrap_content" 17. android:text="启动Service" 18. /> 19. <Button 第 3 章 Android应用程序 85 20. android:id="@+id/stopservice" 21. android:layout_width="fill_parent" 22. android:layout_height="wrap_content" 23. android:text="停止Service" 24. /> 25. <Button 26. android:id="@+id/bindservice" 27. android:layout_width="fill_parent" 28. android:layout_height="wrap_content" 29. android:text="绑定Service" 30. /> 31. <Button 32. android:id="@+id/unbindservice" 33. android:layout_width="fill_parent" 34. android:layout_height="wrap_content" 35. android:text="解除Service" 36. /> 37. </LinearLayout> (3)在上述包下,新建一个Service,命名为MyService.java,代码如下: 1. package com.hisoft.service; 2. import android.app.Service; 3. import android.content.Intent; 4. import android.os.Binder; 5. import android.os.IBinder; 6. import android.text.format.Time; 7. import android.util.Log; 8. public class MyService extends Service { 9. //定义一个Tag 标签 10. private static final String TAG ="TestService"; 11. //这里定义把一个Binder 类用在onBind()方法里,这样Activity 可以获取到 12. private MyBinder mBinder =new MyBinder(); 13. @Override 14. public IBinder onBind(Intent intent) { 15. Log.e(TAG, " -----start IBinder-----~~~"); 16. return mBinder; 17. } 18. @Override 19. public void onCreate() { 20. Log.e(TAG, "-----start onCreate-----"); 21. super.onCreate(); 22. } 23. 24. @Override Android应用开发案例教程(第2版) 86 25. public void onStart(Intent intent, int startId) { 26. Log.e(TAG, "-----start onStart-----"); 27. super.onStart(intent, startId); 28. } 29. 30. @Override 31. public void onDestroy() { 32. Log.e(TAG, "-----start onDestroy-----"); 33. super.onDestroy(); 34. } 35. 36. 37. @Override 38. public boolean onUnbind(Intent intent) { 39. Log.e(TAG, "-----start onUnbind-----"); 40. return super.onUnbind(intent); 41. } 42. 43. //这里是一个获取当前时间的函数,不过没有格式化 44. public String getSystemTime(){ 45. 46. Time t =new Time(); 47. t.setToNow(); 48. return t.toString(); 49. } 50. 51. public class MyBinder extends Binder{ 52. MyService getService() 53. { 54. return MyService.this; 55. } 56. } 57. } (4)修改com.hisoft.activity包下的ServiceTestDemoActivity.java文件,代码如下: 1. package com.hisoft.activity; 2. import android.app.Activity; 3. import android.content.ComponentName; 4. import android.content.Context; 5. import android.content.Intent; 6. import android.content.ServiceConnection; 7. import android.os.Bundle; 8. import android.os.IBinder; 9. import android.view.View; 第 3 章 Android应用程序 87 10. import android.view.View.OnClickListener; 11. import android.widget.Button; 12. import android.widget.TextView; 13. public class ServiceTestDemoActivity extends Activity implements OnClickListener{ 14. private MyService mMyService; 15. private TextView mTextView; 16. private Button startServiceButton; 17. private Button stopServiceButton; 18. private Button bindServiceButton; 19. private Button unbindServiceButton; 20. private Context mContext; 21. //这里需要用到ServiceConnection,在Context.bindService()和Context. //unBindService()里用到 22. private ServiceConnection mServiceConnection = new ServiceConnection() { 23. //当单击bindService 按钮时,让TextView 显示MyService 里getSystemTime() //方法的返回值 24. public void onServiceConnected(ComponentName name, IBinder service) { 25. //TODO Auto-generated method stub 26. //mMyService = ((MyService.MyBinder)service).getService(); 27. } 28. 29. public void onServiceDisconnected(ComponentName name) { 30. //TODO Auto-generated method stub 31. 32. } 33. }; 34. public void onCreate(Bundle savedInstanceState) { 35. super.onCreate(savedInstanceState); 36. setContentView(R.layout.main); 37. setupViews(); 38. } 39. 40. public void setupViews(){ 41. 42. mContext = ServiceDemo.this; 43. mTextView = (TextView)findViewById(R.id.text); 44. 45. startServiceButton = (Button)findViewById(R.id.startservice); 46. stopServiceButton = (Button)findViewById(R.id.stopservice); 47. bindServiceButton = (Button)findViewById(R.id.bindservice); 48. unbindServiceButton = (Button)findViewById(R.id.unbindservice); 49. Android应用开发案例教程(第2版) 88 50. startServiceButton.setOnClickListener(this); 51. stopServiceButton.setOnClickListener(this); 52. bindServiceButton.setOnClickListener(this); 53. unbindServiceButton.setOnClickListener(this); 54. } 55. 56. public void onClick(View v) { 57. //TODO Auto-generated method stub 58. if(v == startServiceButton){ 59. Intent i = new Intent(); 60. i.setClass(ServiceTestDemoActivity.this, MyService.class); 61. mContext.startService(i); 62. }else if(v == stopServiceButton){ 63. Intent i = new Intent(); 64. i.setClass(ServiceTestDemoActivity.this, MyService.class); 65. mContext.stopService(i); 66. }else if(v == bindServiceButton){ 67. Intent i = new Intent(); 68. i.setClass(ServiceTestDemoActivity.this, MyService.class); 69. mContext.bindService(i, mServiceConnection, BIND_AUTO_CREATE); 70. }else{ 71. mContext.unbindService(mServiceConnection); 72. } 73. } 74. 75. } (5)修改AndroidManifest.xml代码,在<application>标签下的根目录下,添加注册 新创建的MyService,如下代码第14行: 1. <? xml version="1.0" encoding="utf-8"? > 2. <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3. package="com.hisoft.activity " 4. android:versionCode="1" 5. android:versionName="1.0"> 6. <application android:icon="@drawable/icon" android:label= "@string/app_name"> 7. <activity android:name=". ServiceTestDemoActivity" 8. android:label="@string/app_name"> 9. <intent-filter> 10. <action android:name="android.intent.action.MAIN" /> 11. <category android:name="android.intent.category.LAUNCHER" /> 12. </intent-filter> 13. </activity> 第 3 章 Android应用程序 89 14. <service android:name=".MyService" android:exported="true"></service> 15. </application> 16. </manifest> (6)部署工程,并执行上述工程,运行结果如图3-6所示。 图3-6 Service 生命周期运行界面 ① 单击“启动SERVICE”按钮时,程序先后执行了Service中onCreate()、onStart()这 两个方法,打开日志界面Logcat视窗,显示如图3-7所示内容。 图3-7 启动Service 调用顺序 ② 然后单击Home键进入Settings(设置)→Applications(应用)→RunningServices (正在运行的服务)查看刚才新启动的服务,如图3-8所示。 图3-8 新启动的Service 服务 ③ 单击“停止SERVICE”按钮时,Service则执行了onDestroy()方法,如图3-9所示。 ④ 再次单击“启动SERVICE”按钮,然后再单击bindService按钮(通常bindService都