Android-Application Fundamental-应用程序基础-中文翻译校正版

 

 

 

 

 

应用程序基础 | Android Developers

 

原文标题:Application Fundamentals

原文链接:http://developer.android.com/guide/topics/fundamentals.html

原文版本:Android 2.2 r1 - 05 Aug 2010 8:29

中文翻译:Jack Yu yjmjack@gmail.com

此中文翻译基于译言网(www.yeeyan.org)用户biAji的翻译校订而成。原翻译链接:(http://article.yeeyan.org/view/37503/34036

 

 

仅供非盈利使用,转载请保留出处及译者信息


 

Android应用程序使用Java编程语言开发。aapt工具把编译后的Java代码连同应用程序所需的其他数据和资源文件一起打包到一个Android文件中,这个文件使用.apk作为扩展名。此文件是分发并安装应用程序到移动设备的载体;是用户下载到他们的设备的文件。单一.apk文件中的所有代码被认为是一个应用程序

从多个角度来看,每个Android应用程序都存在于它自己的世界之中:

  • 默认情况下,每个应用程序均运行于它自己的Linux进程中。当应用程序中的任何代码需要被执行时,Android启动此进程,而当不再需要此进程并且其它应用程序又请求系统资源时,则关闭这个进程。
  • 每个进程都有其独有的虚拟机(VM),所以应用程序代码与所有其它应用程序代码是隔离运行的。
  • 默认情况下,每个应用程序均被赋予一个唯一的Linux用户ID,并加以权限设置,使得应用程序的文件仅对此用户及此应用程序可见——尽管也有其它的方法使得这些文件同样能为其他应用程序所访问。

使两个应用程序共享同一个用户ID是可能的,这种情况下他们可以看到彼此的文件。从系统资源维护的角度来看,拥有同一个ID的应用程序也将被安排在同一个Linux进程中运行,共享同一个VM

应用程序组件

Android的一个核心特性就是一个应用程序可以使用其它应用程序的元素(如果那个应用程序允许的话)。例如,如果你的应用程序需要显示一个图片卷动列表,而另一个应用程序已经开发了一个合用的而又允许别的应用程序使用的话,你可以直接调用那个卷动列表来完成工作,而不用自己再开发一个。你的应用程序并没有吸纳或链接其它应用程序的代码。它只是在有需求的时候启动了其它应用程序的那个功能部分。

为达到这个目的,系统必须能够在一个应用程序的任何一部分被需要时启动一个此应用程序的进程,并将那个部分的Java对象实例化。因此,不像其它大多数系统上的应用程序,Android应用程序并没有为应用程序提供一个单独的入口点(比如说,没有main()函数),而是为系统提供了可以实例化和运行所需的必备组件。一共有四种组件类型:

Activity

activity是为用户操作而展示的可视化用户界面。例如,一个activity可以展示一个菜单项列表供用户选择,或者显示一些包含说明文字的照片。一个短消息应用程序可以包括一个用于显示要发送消息到的联系人列表的activity,一个给选定的联系人写短信的activity以及翻阅以前的短信或改变设置的其他activity。尽管它们一起组成了一个内聚的用户界面,但其中每个activity都与其它的保持独立。每一个都实现为以Activity类为基类的子类。

一个应用程序可以只有一个activity,或者,如刚才提到的短信应用程序那样,包含很多个。每个activity的作用,以及有多少个activity,当然是取决于应用程序及其设计的。一般情况下,总有一个应用程序被标记为用户在应用程序启动的时候第一个看到的。从一个activity转向另一个靠的是用当前的activity启动下一个。

每个activity都被给予一个默认的窗口以进行绘制。一般情况下,这个窗口是满屏的,但它也可以是一个小的位于其它窗口之上的浮动窗口。一个activity也可以使用附加窗口——例如,一个在activity运行过程中弹出的供用户响应的对话框,或是一个当用户选择了屏幕上特定项目后显示的必要信息的窗口。

窗口显示的可视内容是由一系列层次化view构成的,这些view均继承自 View 基类。每个view均控制着窗口中一块特定的矩形区域。父级view包含并组织其子view的布局。叶节点view(位于层次结构最底端)在它们控制的矩形区域中进行绘制,并对用户直达其区域的操作做出响应。因此,viewactivity与用户进行交互的界面。例如,view可以显示一个小图片,并在用户指点它的时候产生动作。Android有一些预置的view供开发者使用——包括按钮、文本域、滚动条、菜单项、复选框等等。

view层次结构是由Activity.setContentView() 方法放入activity的窗口之中的。content view是位于层次结构根位置的View对象。(参见独立的用户界面文档以获取关于view及层次结构的更多信息。)

Service

service没有可视化的用户界面,而是在一段时间内在后台运行。例如,一个service可以在用户做其它事情的时候在后台播放背景音乐、从网络上获取数据或者计算一些东西并提供给需要这个运算结果的activity使用。每个service都继承自Service基类。

一个媒体播放器播放播放列表中的曲目是一个不错的例子。播放器应用程序可能有一个或多个activity来给用户选择歌曲并进行播放。然而,音乐播放这个任务本身不应该由任何activity来处理,因为用户期望即使在他们离开播放器应用程序而开始做别的事情时,音乐仍在继续播放。为达到这个目的,媒体播放器activity可以启动一个运行于后台的service。系统将在这个activity不再显示于屏幕之后,仍维持音乐播放service的运行。

连接至(绑定到)一个正在运行的service(如果service没有运行,则启动之)是可能的。连接之后,你可以通过那个service暴露出来的接口与service进行通讯。对于音乐service来说,这个接口可以允许用户暂停、回退、停止以及重新开始播放。

如同activity和其它组件一样,service运行于应用程序进程的主线程内。所以它不会对其它组件或用户界面有任何妨碍,它们一般会派生一个新线程来执行一些时间消耗型任务(比如音乐回放)。参见稍后的进程和线程

Broadcast receiver

broadcast receiver是一个专注于接收广播通知信息,并做出相应处理的组件。许多广播是由系统代码产生的——例如,通知时区改变、电池电量低、拍摄了一张照片或者用户改变了语言选项。应用程序也可以发起广播——例如,通知其它应用程序一些数据已经下载到设备上并处于可用状态。

一个应用程序可以拥有任意数量的broadcast receiver,以对所有它认为重要的通知信息予以响应。所有的receiver均继承自BroadcastReceiver基类。

broadcast receiver没有用户界面。然而,它们可以启动一个activity来响应它们收到的信息,或者也可以使用NotificationManager来通知用户。通知可以用多种方式来吸引用户的注意力──闪动背光灯、震动设备、播放声音等等。通知一般是在状态栏上放一个持久的图标,用户可以打开它并获取消息。

Content provider

content provider将一些特定的应用程序数据供给其它应用程序使用。数据可以存储于文件系统、SQLite数据库或其它有意义的方式。content provider继承于ContentProvider 基类,实现了一套使得其他应用程序能够检索和存储它所管理的类型数据的标准方法。然而,应用程序并不直接调用这些方法,而是使用一个 ContentResolver 对象,调用它的方法作为替代。ContentResolver可以与任何content provider进行会话;与其合作对任何相关的进程间通讯进行管理。

参阅独立的Content Providers文档以获得更多关于使用content provider的信息。

每当出现一个需要被特定组件处理的请求时,Android会确保那个组件的应用程序进程处于运行状态,必要时会启动它,并确保那个组件的一个合适的实例可用,必要时会创建那个实例。

激活组件:intent

当接收到ContentResolver发出的请求后,content provider被激活。而其它三种组件——activityservicebroadcast receiver,被一种叫做intent的异步消息所激活。intent是一个保存着消息内容的Intent对象。对于activityservice来说,它指明了所请求的操作名称,并指定了用来操作的数据的URI和其它一些信息。例如,它可以承载一个对一个activity的请求,让它为用户显示一张图片,或者让用户编辑一些文本。而对于broadcast receiver来说,Intent对象指明了所通报的操作。例如,它可以对所有感兴趣的对象通报照相按钮被按下。

对于每种组件来说,激活的方法是不同的:

一个activity经常启动另一个activity。如果它期望它所启动的那个activity返回一个结果,它会调用startActivityForResult()而不是startActivity()。例如,如果它启动了另外一个activity以使用户挑选一张照片,它也许想知道哪张照片被选中了。其结果将会被封装在一个Intent对象中,并传递给发出调用的activityonActivityResult() 方法。

  • 通过传递一个Intent对象至Context.startService()以启动一个service(或向正在运行的service给出一个新的指令)。Android调用此serviceonStart()方法并将Intent对象传递给它。

与此类似,一个intent可以被传递给 Context.bindService()以建立一个处于调用组件和目标service之间的活动连接。此service会通过onBind() 方法的调用来获取此Intent对象(如果此service尚未运行,bindService()会先启动它)。例如,一个activity可以建立一个与前述的音乐回放service的连接,这样它就可以提供给用户一些途径(用户界面)来控制回放。这个activity可以调用 bindService() 来建立此连接,然后调用service中定义的方法来控制回放。
稍后的
远程方法调用一节有关于如何绑定至一个service的更多细节。

欲了解更多关于intent消息的信息,请参阅独立文章 Intent和Intent过滤器

关闭组件

content provider仅在响应来自ContentResolver的请求时处于活动状态。而broadcast receiver仅在响应一条广播信息的时候处于活动状态。所以没有必要去显式地关闭这些组件。

activity则不同,它提供了用户界面。只要会话依然持续,无论会话过程有无空闲,activity同用户进行长时间会话且可能一直处于活动状态。与此相似,service也会在很长一段时间内保持运行。所以Android为关闭activityservice提供了一系列有序的方法。

  • activity可以通过调用自身的finish()方法来关闭。一个activity可以通过调用finishActivity()方法来关闭另外一个activity(它用startActivityForResult() 启动的)。
  • service可以通过调用自身的stopSelf()方法,或调用 Context.stopService()来停止。

系统也会在组件不再被使用的时候或者当Android必须为更多的活动组件回收内存时关闭它。稍后的组件的生命周期一节,将对这种可能性及结果进行更详细的讨论。

manifest文件

Android启动一个应用程序组件之前,它必须知道那个组件是存在的。因此,应用程序会在一个被打包到Android包中的manifest文件中声明它的组件,.apk文件还将涵括应用程序的代码、文件以及其它资源。

manifest文件是一个结构化的XML文件,而且对于所有应用程序,文件名总是AndroidManifest.xml。除了声明此应用程序各个组件,它会做很多其他工作,比如指明应用程序所需链接到的库的名称(除了默认的Android库之外)以及标出应用程序期望获得的各种权限。

manifest文件最重要的任务是向Android报告此应用程序的各个组件。举例说明,一个activity可能声明如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest . . . >
   
<application . . . >
       
<activity android:name="com.example.project.FreneticActivity"
                 
android:icon="@drawable/small_pic.png"
                 
android:label="@string/freneticLabel"
                  . . .  
>
       
</activity>
        . . .
   
</application>
</manifest>

<activity>元素的name属性指定了实现此activityActivity子类。iconlabel属性指向包含展示给用户的此activity的图标和标签的资源文件。

其它组件也以类似的方法声明——<service> 元素用于声明service<receiver> 元素用于声明broadcast receiver,而 <provider> 元素用于声明content provider。未在manifest文件中进行声明的activityservice以及content provider将不为系统所见,从而也就永不会被运行。然而,broadcast receiver既可以在manifest文件中声明,也可以在代码中动态创建(为BroadcastReceiver 对象),并以调用Context.registerReceiver()的方式注册至系统。

欲了解更多关于如何为你的应用程序构建manifest文件的信息,请参阅AndroidManifest.xml文件

Intent过滤器

一个Intent对象可以显式地指定一个目标组件。如果进行了这种指定,Android会找到这个组件(基于manifest文件中的声明)并激活它。但如果intent没有显式地指定一个目标,Android就必须找到最合适的组件来响应此intent。这个过程是通过比较Intent对象和所有潜在目标的intent过滤器完成的。组件的intent过滤器会通知Android它所能处理的intent类型。如同组件的其它必要信息一样,这些intent过滤器是在manifest文件中进行声明的。这里有一个对先前例子的扩展,其中加入了针对activity的两个intent过滤器:

<?xml version="1.0" encoding="utf-8"?>
<manifest . . . >
   
<application . . . >
       
<activity android:name="com.example.project.FreneticActivity"
                 
android:icon="@drawable/small_pic.png"
                 
android:label="@string/freneticLabel"
                  . . .  
>
           
<intent-filter . . . >
               
<action android:name="android.intent.action.MAIN" />
               
<category android:name="android.intent.category.LAUNCHER" />
           
</intent-filter>
           
<intent-filter . . . >
               
<action android:name="com.example.project.BOUNCE" />
               
<data android:mimeType="image/jpeg" />
               
<category android:name="android.intent.category.DEFAULT" />
           
</intent-filter>
       
</activity>
        . . .
   
</application>
</manifest>

示例中的第一个过滤器——action“android.intent.action.MAINcategory“android.intent.category.LAUNCHER的组合——是常见的一个。它标明了此activity应该在应用程序启动器中显示,就是用户在屏幕上看到的此设备上可供启动的应用程序的列表。换句话说,这个activity是应用程序的入口点,是用户在启动器中选择运行这个应用程序后所见到的第一个activity

第二个过滤器声明了此activity在一种特定类型的数据上可以执行的操作。

一个组件可以拥有任意数量的intent过滤器,每个都声明了一套不同的功能。如果组件没有包含任何过滤器,它只能被显式地指明作为目标组件的intent激活。

对于在代码中创建并注册的broadcast receiver来说,intent过滤器将被直接实例化IntentFilter为对象。其它所有的过滤器都在manifest文件中设置。

欲获得更多关于intent过滤器的信息,请参阅独立文档: Intent和Intent过滤器

Activity和任务

如前所述,一个activity可以启动另一个activity,包括与它处于不同应用程序中的activity。例如,假设你想让用户看到某个地方的街道地图。而已经存在一个具有此功能的activity了,那么你的activity所需要做的就是把所请求的信息放到一个Intent对象里面,并把它传递给startActivity()。于是地图浏览器就会显示那个地图。而当用户按下BACK键的时候,你的activity又会再一次的显示在屏幕上。

对于用户来说,这看起来就像是地图浏览器是你activity所在的应用程序中的一个组成部分,其实它是在另外一个应用程序中定义,并运行在那个应用程序的进程之中的。Android将这两个activity放在同一个任务中来维持一个完整的用户体验。简单的说,任务就是用户所体验到的应用程序。它是安排在一个堆栈中的一组相关的activity。堆栈中的根activity是启动了此任务的那个——一般情况下,它就是用户在应用程序启动器中所选择的activity。而堆栈顶部的activity则是当前运行的——用户直接对其进行操作的。当一个activity启动另一个activity时,新的activity就被压入堆栈,并成为当前运行的activity。而前一个activity仍保留在堆栈之中。当用户按下BACK键的时候,当前activity从堆栈中弹出,而前一个恢复为当前运行的activity

堆栈中保存的其实是对象,所以如果发生了诸如需要多个地图浏览器的情况,就会使得一个任务中出现多个同一Activity子类的实例同时存在,堆栈会为每个实例开辟一个单独入口。堆栈中的Activity永远不会重排,只会压入或弹出。

任务其实就是activity的堆栈,而不是manifest文件中的一个类或者元素。所以你无法撇开activity而为一个任务设置值。而事实上整个任务使用的值是在根activity中设置的。例如,下一节我们会谈及任务的affinity”,会发现值是从为任务的根activity设置的affinity中读取的。

任务中的所有activity是作为一个整体进行移动的。整个任务(即整个activity堆栈)可以移到前台,或退至后台。举个例子说,假设当前任务在堆栈中存有四个activity——三个在当前activity之下。当用户按下HOME键的时候,回到了应用程序启动器,然后选择了一个新的应用程序(实际上就是一个新任务)。则当前任务遁入后台,而新任务的根activity显示出来。然后,过了一小会儿,用户再次回到了应用程序启动器而又选择了前一个应用程序(实际上就是前一个任务)。于是那个任务,带着它堆栈中所有的四个activity,再一次回到前台。当用户按下BACK键的时候,屏幕不会显示出用户刚才离开的activity(上一个任务的根activity)。相反,当前任务的堆栈中最上面的activity被弹出,而同一任务中的前一个activity显示了出来。

上述的种种即是activity和任务的默认行为。但是有一些方法可以改变几乎所有这一切。activity和任务间的联系、任务中activity的行为方式,都被启动那个activityIntent对象中设置的一系列标记和manifest文件中那个activity中的<activity>元素的属性之间的相互影响所控制。无论是请求发出者和响应者在这里都拥有话语权。

我们刚才所说的这些关键Intent标记如下:

FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP

FLAG_ACTIVITY_RESET_TASK_IF_NEEDED

FLAG_ACTIVITY_SINGLE_TOP

而关键的<activity>属性是:

taskAffinity
launchMode

allowTaskReparenting

clearTaskOnLaunch

alwaysRetainTaskState

finishOnTaskLaunch

接下来的几节会描述这些标记以及属性的作用,它们是如何相互影响的,以及控制它们的使用时应该考虑到的因素。

Affinity和新任务

默认情况下,一个应用程序中的activity相互之间会有一种affinity──也就是说,它们首选都归属于一个任务。然而,可以在<activity>元素中把每个activitytaskAffinity属性设置为一个独立的affinity。于是在不同的应用程序中定义的activity可以享有同一个affinity,或者在同一个应用程序中定义的activity有着不同的affinityaffinity在两种情况下生效:当启动activityIntent对象包含了FLAG_ACTIVITY_NEW_TASK 标记,或者当activityallowTaskReparenting属性设置为true

FLAG_ACTIVITY_NEW_TASK标记

如前所述,在默认情况下,一个新activity被另外一个调用了startActivity()方法的activity载入了任务之中。并压入了调用者所在的堆栈。然而,如果传递给startActivity()Intent对象包含了FLAG_ACTIVITY_NEW_TASK标记,系统会为此新activity寻找一个不同的任务来驻留。一般情况下,如同标记所暗示的那样,这会是一个新任务。然而,这并不是必然的。如果已经存在了一个与新activity有着同样affinity的任务,则activity会载入那个任务之中。如果没有,则启用新任务。

allowTaskReparenting属性

如果一个activityallowTaskReparenting属性设置为true。它就可以从初始的任务中转移到拥有一个affinity并转向前台的任务之中。比如说,一个旅行应用程序中包含的预报所选城市的天气情况的activity。它与这个应用程序中其它的activity拥有相同的affinity(默认的affinity)而且允许父级重定向。你的一个activity启动了天气预报,于是它就会与这你个activity共处与同一任务之中。然而,当那个旅行应用程序再次回到前台的时候,这个天气预报activity就会被再次安排到原先的任务之中并显示出来。

在用户的角度看来,如果一个.apk文件中包含了多于一个的应用程序,你可能会想要为它们所辖的activity安排不一样的affinity

启动模式

<activity>元素的launchMode属性可以设置四种不同的加载模式:

"standard" (默认模式)
"
singleTop
"
"
singleTask
"
"
singleInstance"

这些模式之间的差异主要体现在四个方面:

  • 哪个任务会持有对intent做出响应的activitystandardsingleTop模式而言,是任务产生了intent(并调用 startActivity())——除非Intent对象包含FLAG_ACTIVITY_NEW_TASK标记。在这种情况下会选择一个不同的、如同上面Affinitie和新任务一节所述的任务。

相比之下,标记为singleTasksingleInstance模式的activity总是位于任务的根部。正是它们定义了一个任务,所以它们绝不会被载入到其它任务之中。

activity是否可以存在多个实例。一个standardsingleTop模式的activity可以被多次实例化。它们可以归属于多个任务,而一个给定的任务也可以拥有同一activity的多个实例。

相比之下,标记为singleTasksingleInstanceactivity被限定为只能拥有一个实例。因为这些activity都是位于任务的根部,这种限制意味着在一个设备中同一时间只允许存在一个任务的实例。

在实例所在的任务中是否可以有其他的activity一个singleInstance模式的activity将会是它所在的任务中唯一的activity。如果它启动了另一个activity,那个activity将会加载到一个不同的任务中去,而不管其启动模式如何——效果如同在intent中设置了FLAG_ACTIVITY_NEW_TASK 标记一样。在其它方面,singleInstance模式的效果与singleTask是等同的。

剩下的三种模式允许一个任务中出现多个activitysingleTask模式的activity将是任务的根activity,但它可以启动别的activity且这些新的activity与其属于同一任务。standardsingleTop模式的activity的实例则可以在堆栈的任意位置出现。

  • 是否要载入新的类实例来处理新的intent对默认的"standard"模式来说,对于每个新intent都会创建一个新的实例以进行响应,每个实例只处理一个intent。对于singleTop模式,如果目前现存的该类的实例位于目标任务堆栈的顶部,则重用此activity来处理新的intent。如果它不是在堆栈顶部,则不会被重用。而是创建一个新实例来处理新的intent并将其推入堆栈。

举例来说,假设一个任务的堆栈由根activity Aactivity BC和位于堆栈顶部的D组成,即堆栈A-B-C-D。一个针对D类型activityintent抵达的时候,如果D是默认的standard启动模式,则创建并加载一个新的该类的实例,于是堆栈变为A-B-C-D-D。 然而,如果D的载入模式为singleTop,则现有的实例将会对新intent进行处理(因为它位于堆栈顶部)而堆栈保持A-B-C-D的形态。

另一方面,如果新抵达的intent是针对B类型的activity,则无论B的模式是standard还是singleTop,都会加载一个新的B的实例(因为B不位于堆栈的顶部),而堆栈的顺序变为A-B-C-D-B

如前所述,singleTasksingleInstance模式的activity永远不会存在多于一个的实例,所以这样的实例将处理所有新的intent。一个singleInstance模式的activity总是保持在堆栈的顶部(因为它是那个堆栈中唯一的一个activity),所以它一直坚守在处理intent的岗位上。然而,对一个singleTask模式的activity来说,它上面可能有,也可能没有别的activity和它处于同一堆栈。如果有,它就不在能够处理intent的位置上,那个intent也将被舍弃。(即便在intent被舍弃的情况下,它的抵达仍将使这个任务切换至前台,并一直保留)

当一个现存的activity被要求处理一个新的intent的时候,会通过调用activityonNewIntent()方法来将intent对象传递至activity。(用来启动activity的原始intent对象可以通过调用getIntent()方法获得。)

请注意,当一个activity的新实例被创建以处理新的intent的时候,用户总可以按下BACK键来回到前面的状态(回到前一个activity)。但当一个activity的现存实例处理新的intent的时候,用户是不能靠按下BACK键回到这个新intent抵达之前的状态的。

欲获得更多关于启动模式的内容,请参阅 <activity> 元素的描述。

清理堆栈

如果用户离开一个任务很长一段时间,系统会清理该任务中除了根activity之外的所有activity。当用户再次回到这个任务的时候,除了最初的activity尚存之外,其余activity就像用户离开它的时候一样。这样做的原因是:在一段时间之后,用户再次回到一个任务的时候,他们更期望放弃他们之前的所作所为,而做些新的事情。

这是默认行为。另外,也存在一些activity的属性用以控制和改变这些行为:

alwaysRetainTaskState属性

如果一个任务的根activity的此属性设置为true,则上述默认行为不会发生。任务将在很长的一段时间后仍保留它堆栈内的所有activity

clearTaskOnLaunch属性

如果一个任务的根activity的此属性设置为true,则每当用户离开这个任务然后再返回它时,堆栈都会被清空至只剩根activity。换句话说,这是alwaysRetainTaskState的相反极端。哪怕仅是过了一小会儿,用户回到任务时,也总是回到它的初始状态。

finishOnTaskLaunch属性

这个属性与clearTaskOnLaunch属性相似,但它仅作用于单个的activity,而不是整个的任务。而且它可以清理任何activity,甚至根activity也不例外。当它设置为true的时候,此activity仅作为任务的一部分存在于当前回话中,一旦用户离开并再次回到这个任务,此activity将不复存在。

此外,还有另一种方式从堆栈中移除一个activity。如果一个Intent对象包含FLAG_ACTIVITY_CLEAR_TOP标记,而且目标任务的堆栈中已经存在一个能够响应此intentactivity类型的实例,则这个实例之上的所有activity都将被清理以使它位于堆栈的顶部来对那个intent做出响应。如果此时指定的activity的启动模式为standard,则它本身也会从堆栈中移除,并启动一个新的实例来处理到来的intent。这是因为启动模式为standardactivity总会创建一个新实例来处理新的intent

FLAG_ACTIVITY_CLEAR_TOPFLAG_ACTIVITY_NEW_TASK经常合并使用。这时,这些标记提供了一种定位其它任务中现存的activity并将它们置于可以对intent做出响应的位置的方法。

启动任务

当一个activity被指定一个以“android.intent.action.MAIN”做为action,以“android.intent.category.LAUNCHER”做为categoryintent过滤器之后(在前述Intent过滤器一节中已经有一个此类型过滤器的例子),它就被设置为一个任务的入口点。这种过滤器会在应用程序启动器中为此activity显示一个图标和标签,以供用户启动任务和在启动之后在任意时间回到这个任务。

第二个能力相当重要:用户必须可以离开一个任务,并在一段时间后返回它。出于这个考虑,能标记activity为总是初始化一个新任务的两种启动模式——singleTasksingleInstance,应该在仅当此activity有一个MAINLAUNCHER过滤器的情况之下才使用。举个例子,想想一下如果没有指定过滤器会发生什么:一个intent启动了一个singleTaskactivity,初始化了一个新任务,用户在这个任务中花费了一些时间来完成工作。然后用户按下了HOME键。于是任务被要求转至后台并被主屏幕所遮盖。因为它不存在于应用程序启动器中,这将导致用户无法再返回它。

类似的困境也可由FLAG_ACTIVITY_NEW_TASK标记所引起。如果此标记使一个activity启动了一个新任务,然后用户按下了HOME键离开了它,则用户必须要有办法再次回到这个任务。一些实体(诸如notification manager)总是在另外的任务中启动新activity,而不是作为它们自己的一部分,所以它们总是将FLAG_ACTIVITY_NEW_TASK标记包含在传递给startActivity()intent里面。如果你有一个可以被有可能使用了此标记的外部实体调用的activity,你必须注意要给用户留一个能返回到这个被外部实体启动的任务的独立途径。

一些情况下,你不想让用户再次返回到一个activity,可以将 <activity> 元素的 finishOnTaskLaunch设置为true。参见前述的清理堆栈.

进程和线程

当一个应用程序开始运行它的第一个组件时,Android会为它启动一个只有一个线程在执行的Linux进程。默认情况下,应用程序所有的组件均在这个进程和这个线程中运行。

然而,你也可以安排组件在其他进程中运行,而且可以为任意进程衍生出附加线程。

进程

组件运行所在的进程由manifest文件所控制。组件元素——<activity><service><receiver><provider>——每个都有一个 process 属性来指定此组件应当运行于哪个进程中。这些属性可以设置为每个组件运行于它自己的进程中,或设置为一些组件共享一个进程而其余的组件不这么做。它们也可以设置为不同应用程序的组件在同一个进程中运行——假如这些应用程序共享同一个Linux用户ID并被赋以相同的权限。<application>元素也有一个process属性,用以设定应用于所有组件的默认值。

所有的组件都在特定进程的主线程内实例化,而对这些组件的系统调用也将由那个线程负责分发。不会为每个实例创建单独的线程。因此,与那些调用相对应的方法——诸如View.onKeyDown()这样报告用户动作以及后面组件生命周期一节所要讨论的生命周期通知——总是运行在进程的主线程中。这意味着组件在被系统调用时,不应执行耗时或可能引起阻塞的操作(例如网络相关操作或是循环计算),因为这将阻塞同样位于这个进程中的其它组件的运行。你应该如同下面线程一节所叙述的那样,为这些耗时操作衍生出一个单独的线程进行处理。

在可用内存不足而又有一个正在为用户进行服务的进程需要更多内存的时候,Android可能会决定关闭一个进程。而在这个进程中运行着的应用程序组件也因此被销毁。当再次出现需要它们进行处理的工作时,会为这些组件重新创建进程。

在决定结束哪个进程的时候,Android会衡量它们对于用户的相对重要性。例如,相对于一个仍有用户可见的activity的进程,它更有可能去关闭一个其activity已经不为用户所见的进程。因此,决定是否关闭一个进程,依赖于在那个进程中运行的组件的状态。这些状态是后续的一节组件生命周期的讨论主题。

线程

尽管你可以把你的应用程序限制在一个单独的进程中,有时你仍然需要衍生出一个线程以处理一些后台工作。因为用户界面必须总是能及时对用户操作做出响应,所以,管控activity的线程不应该用来处理一些诸如网络下载之类的时间消耗型操作。所有不能在瞬间完成的工作都应安排到不同的线程中去。

线程在代码中是用标准Java Thread对象创建的。Android提供了一些便于管理线程的类: Looper用于在一个线程中运行一个消息循环, Handler用于处理消息,HandlerThread 用于启用一个具有消息循环的线程。

远程过程调用

Android有一个轻量级的远程过程调用(RPC)机制——即在本地调用一个方法,但在远程(在另一个进程中)执行,然后将结果返回调用者。这将方法调用及其附属数据以一个操作系统可以理解的层次进行分离,并将其从本地进程和本地地址空间传送至远程过程和远程地址空间,并在那里重新装配并对调用做出反应。返回值将以相反的方向进行传递。Android提供了完成这些工作所需的所有的代码,使你可以集中精力来定义和实现RPC接口本身。

RPC接口可以只包括方法。默认地,即便没有返回值,所有方法仍以同步的方式执行(本地方法阻塞直至远程方法结束)。

简而言之,这套机制是这样工作的:一开始,你用简单的IDL(接口定义语言)声明一个你想要实现的RPC接口。然后aidl工具会为这个声明生成一个Java接口定义,这个定义必须对本地和远程进程都可用。它包含两个内部类,如下图所示:

 

内部类中有管理你用IDL声明的接口的远程过程调用所需要的所有代码。两个内部类均实现了 IBinder接口。一个被系统用于在本地内部使用;你编写的代码可以不用去管它。另一个,称为Stub,扩展了Binder类。除了实现了IPC调用的内部代码之外,它还包括了你声明的RPC接口中的方法的声明。你应该如上图所示的那样写一个Stub的子类来实现这些方法。

一般情况下,远程过程是被一个service所管理的(因为service可以通知系统关于进程以及它连接到别的进程的信息)。它包含着aidl工具产生的接口文件和实现了RPC方法的Stub的子类。而service的客户端只需要包括aidl工具产生的接口文件。

下面将说明service与其客户端之间的连接是如何建立的:

  • service的客户端(位于本地)应该实现 onServiceConnected() onServiceDisconnected() 方法。这样,当至远程service的连接成功建立或者断开的时候,它们会收到通知。这样它们就可以调用 bindService() 来设置连接。
  • service应该实现 onBind() 方法以接受或拒绝连接。这取决于它收到的intent(即传递给bindService()intent)。如果接受了连接,它会返回Stub子类的一个实例。
  • 如果service接受了连接,Android将会调用客户端的onServiceConnected() 方法,并传递给它一个IBinder对象,它是由此service所管理的Stub子类的一个代理。通过这个代理,客户端可以对远程service进行调用。

以上简要描述省略了一些RPC机制的细节。欲获得更多信息,请参见使用AIDL设计一个远程接口IBinder类的描述。

线程安全方法

在一些上下文中,你所实现的方法有可能会被多于一个的线程所调用,因此它们必须被写成线程安全的。

对于我们上一节所讨论的RPC机制中的可以被远程调用的方法来说,这是必须首先考虑的。如果针对一个IBinder对象中实现的方法的调用源自这个IBinder对象所在的同一个进程时,这个方法将会在调用者的线程中执行。然而,如果这个调用源自其它的进程,则这个方法将会在一个线程池中选出的线程中运行,这个线程池由Android加以管理,并与IBinder存在于同一进程内;这个方法不会在进程的主线程内执行。举例来说,一个服务的 onBind()方法应为服务进程的主线程所调用,而在由onBind()返回的对象中实现的方法(例如,一个实现了RPC方法的Stub的子类)将为池中的线程所调用。因为服务可以拥有多于一个的客户端,而同一时间,可以有多个池中的线程调用同一个IBinder方法。因此IBinder方法必须实现为线程安全的。

类似地,一个content provider能接受源自其它进程的数据请求。尽管ContentResolverContentProvider类隐藏了交互沟通过程的管理细节,响应这些请求的ContentProvider方法——query()insert()delete()update()getType()——是从那个content provider的进程中的线程池中调用的,而不是从该进程的主线程中调用。因为这些方法有可能在同一时间被任意数量的线程所调用,所以它们也必须被实现为线程安全的。

组件生命周期

应用程序组件有其生命周期——由Android初始化它们来响应intent,直到这个实例被摧毁。在此之间,它们有时是活动的有时则是非活动的,或者,如果它是一个activity,则可为用户所见或不可见。这一节讨论了activityservice以及broadcast receiver的生命周期——包括它们在生命周期中的各种状态,在状态之间转换时通知你的方法,以及这些状态对它们所在的进程被终止、实例被销毁所产生的影响。

Activity生命周期

一个activity主要有三个状态:

  • 当位于屏幕前台时(位于当前任务堆栈的顶部),它是活动运行的状态。它就是响应用户操作的activity
  • 当它失去焦点但仍然对用户可见时,它处于暂停状态。即是:在它之上有另外一个activity。这个activity也许是透明的,或者未能完全遮蔽全屏,所以被暂停的activity仍对用户可见。暂停的activity仍然是存活的(它保留着所有的状态和成员信息并保持着到window manager的连接),但当系统处于极低内存的情况下,仍然可以杀死这个activity
  • 如果它完全被另一个activity覆盖是,它处于停止状态。它仍然保留所有的状态和成员信息。然而,它不在为用户可见,所以它的窗口将被隐藏,如果其它地方需要内存,则系统常常会杀死这个activity

如果一个activity处于暂停或停止状态,系统可以通过要求它结束(调用它的 finish() 方法)或直接杀死它的进程的方法来将它驱出内存。当它再次为用户可见的时候,它必须被完全重新启动并恢复至先前的状态。

当一个activity从一个状态转换到另一个状态时,它通过调用下列protected方法获知这种改变:

void onCreate(Bundle savedInstanceState)
void onStart()

void onRestart()

void onResume()

void onPause()

void onStop()

void onDestroy()

你可以重写所有这些方法以在状态改变时进行合适的工作。所有的activity都必须实现 onCreate(),用来当对象第一次实例化时进行初始化设置。很多activity会实现 onPause()以提交数据变化或准备停止与用户的交互。

调用父类

所有activity生命周期方法的实现都必须先调用其父类的版本。比如说:

protected void onPause() {
    super.onPause();
    . . .
}

总得来说,这七个方法定义了一个activity完整的生命周期。实现这些方法可以帮助你监察三个嵌套的生命周期循环:

  • 一个activity完整生命周期自第一次调用onCreate()开始,直至调用onDestroy()为止。activityonCreate()中完成所有全局状态初始化,而在onDestroy()中释放所有系统资源。例如,如果activity有一个线程在后台运行以从网络上下载数据,它可以在onCreate()中创建那个线程,而在onDestroy()中销毁那个线程。
  • 一个activity可视生命周期onStart()调用开始直到相对应的onStop()调用结束。在此期间,用户可以在屏幕上看到此activity,尽管它也许并不是位于前台或者正在与用户做交互。在这两个方法中,你可以维护用来向用户显示这个activity的资源。例如,你可以在onStart()中注册一个BroadcastReceiver来监控那些会影响到你UI的改变,而在当用户无法再看到你程序所显示的内容时通过onStop()来取消注册。onStart() onStop()方法可以随着应用程序是否为用户可见而被多次调用。
  • 一个activity前台生命周期onResume()调用起直到相对应的 onPause()调用为止。在此期间,activity位于前台最前端并与用户进行交互。activity可能会经常在暂停和恢复之间进行状态转换——例如,当设备转入休眠状态或有新的activity启动时,将调用onPause()方法;当activity获得结果或者接收到新的intent时会调用onResume()方法。因此,这两个方法中的代码应当是非常轻量级的。

下图展示了上述循环过程以及activity状态改变所经历的过程。着色的椭圆是activity可以经历的主要状态。矩形框代表了当activity在状态间发生转换时,你为执行操作而实现的回调方法。

 

下表详细描述了这些方法,并在activity的整个生命周期中定位了它们的位置:

 

方法

描述

可被杀死

下一个

onCreate()

activity第一次被创建的时候调用。这里是你做所有普通静态设置的地方——创建视图、绑定数据至列表等。如果曾经有状态记录(参阅后述保存Activity状态。),则调用此方法时会传入一个包含着此activity以前状态的Bundle对象作为参数。

总继之以onStart()

onStart()

    

onRestart()

activity停止后,在再次启动之前被调用。

总继之以onStart()

onStart()

onStart()

activity正要变得为用户所见时被调用。

activity将转向前台时继以onResume(),在activity将变为隐藏时继以onStop()

onResume()

onStop()

    

onResume()

activity即将开始与用户进行交互之前被调用。此时activity位于堆栈顶部,并接受用户输入。

总是继之以onPause()

onPause()

onPause()

当系统将要启动或继续另一个activity时调用。此方法主要用来将未保存的更改提交到持久性数据、停止动画或其他耗费CPU的东西。这一切动作应该在短时间内完成,因为下一个activity必须等到此方法返回后才会继续。

如果activity重新回到前台,继以onResume(),如果activity变为用户不可见时,继以onStop()

onResume()

onStop()

onStop()

activity不再为用户可见时调用此方法。这可能发生在它被销毁或者另一个activity(可能是现存的或者是新的)回到运行状态并覆盖了它。

如果activity回到前台,继以onRestart(),如果activity准备关闭,继以onDestroy()

onRestart()

onDestroy()

onDestroy()

activity被销毁前调用。这是activity能接收的最后一个调用。这可能发生在activity结束(调用了它的finish()方法)或者因为系统需要空间所以临时销毁了此activity的实例时。你可以用isFinishing() 方法来区分这两种情况。

请注意上表中可被杀死一列。它标示了在方法返回后,还没执行activity的其余代码的任意时间里,系统是否可以杀死此activity的进程。三个方法(onPause()onStop()onDestroy())被标记为。因为onPause()是三个中的第一个,也是唯一一个在进程被杀死之前必然会调用的方法——onStop()onDestroy()有可能不被执行。因此,你应该用onPause()来将所有持久性数据(比如用户的编辑结果)写入存储器中。

可被杀死一列中标记为的方法在它们被调用时将保护activity所在的进程不会被杀死。所以只有在onPause()方法返回后到onResume()方法被调用时这一段时间中,一个activity才处于可被杀死的状态。在onPause()再次被调用并返回之前,它又将是不可被杀死的。

如后面一节进程和生命周期所述,即使是在这里技术上没有被定义为可杀死activity仍然有可能被系统杀死──但这仅会发生在实在没有其它方法的极端情况之下。

保存activity状态

当系统而不是用户自己出于回收内存的考虑,关闭了一个activity之后。用户可能会期望当他再次回到那个activity的时候,它仍保持着上次离开时的样子。

为了获取activity被杀死前的状态,你应该为activity实现onSaveInstanceState() 方法。Androidactivity有可能被销毁之前(即onPause()调用之前)会调用此方法。它会将一个以名称-值配对的方式记录了此activity动态状态的Bundle对象传递给该方法。当此activity再次启动时,这个Bundle会传递给onCreate()方法以及紧跟onStart()方法之后调用的onRestoreInstanceState(),所以它们两个都可以恢复先前捕获的状态。

onPause()或先前讨论的其它方法不同,onSaveInstanceState()onRestoreInstanceState()并不是生命周期方法。它们并不是总会被调用。例如,Android会在activity可能被系统销毁之前调用 onSaveInstanceState(),但因用户动作(比如按下了BACK键)而导致的销毁则不会调用。在这种情况下,用户没打算再次回到这个activity,所以没有保存状态的必要。

因为onSaveInstanceState()不是总会被调用,所以你应该只用它来为activity保存一些临时的状态,而不应用来保存持久性数据。而是应该用onPause()来达到这个目的。

协调activity

当一个activity启动了另一个activity时,它们都会经历生命周期转变。一个会暂停乃至停止,而另一个则启动。这种情况下,你可能需要协调好这两个activity

生命周期回调的顺序是良定义的,尤其是在两个activity在同一个进程内的情况下:

  1. 当前activityonPause()方法被调用。
  2. 接下来,顺序调用新启动activityonCreate()onStart()onResume()方法。
  3. 然后,如果新启动的activity不再于屏幕上可见,则它的onStop()方法被调用。

Service生命周期

service的使用方式有两种:

  • 它可以启动并运行,直至有人停止了它或它自己停止。在这种模式下,它以调用Context.startService()而被启动,以调用Context.stopService()而被结束。它可以通过Service.stopSelf() Service.stopSelfResult()的调用来停止自身。不论调用了多少次startService()方法,你只需要调用一次stopService()来停止服务。
  • 程序上,它可以通过自己定义并暴露出来的接口进行操作。客户端建立一个到Service对象的连接,并通过那个连接来调用service。连接以调用Context.bindService()方法建立,以调用 Context.unbindService()方法关闭。多个客户端可以绑定至同一个服务。如果服务此时还没有启动,bindService()会先启动它。

这两种模式并不是完全独立的。你可以绑定至一个用startService()启动的service。例如,一个后台音乐播放service可以通过调用startService()并传递给它一个包含要播放的音乐的Intent对象来启动。不久之后,当用户想要对播放器进行控制或者获取当前播放曲目的信息时,一个activity会通过调用bindService()建立一个到此service的连接来完成操作。在这种情况下,直到最后一个绑定连接被关闭,stopService()才会真正停止一个服务。

activity一样,service也有一系列你可以实现以用于监控其状态变化的生命周期方法。但相对于activity要少一些,只有三个,而且,它们是public属性,并非protected

void onCreate()
void onStart(Intent intent)

void onDestroy()

通过实现这些方法,你监控service的两个嵌套的生命周期循环:

  • service完整生命周期始于调用onCreate()而终于onDestroy()方法返回。如同activity一样,serviceonCreate()里面进行它自己的初始设置,而在onDestroy()里面释放所有资源。例如,一个音乐回放service可以在onCreate()中创建播放音乐的线程, 而在onDestroy()中停止这个线程。
  • service活动生命周期始于对onStart()的调用。这个方法负责处理传递给startService()Intent对象。音乐service会打开此Intent来探明将要播放哪首音乐,并开始播放。

service停止时没有相应的回调方法——不存在onStop()方法。

onCreate()onDestroy()方法在所有service中都会被调用,不论它们是由Context.startService()还是由Context.bindService()所启动的。而onStart()仅会被由startService()所启动的service调用。

如果一个service允许其他进程绑定到它,则它还会有以下额外的回调方法需要实现:

IBinder onBind(Intent intent)
boolean onUnbind(Intent intent)

void onRebind(Intent intent)

传递给bindServiceIntent对象也会传递给onBind()回调方法,而传递给unbindService()Intent对象同样传递给onUnbind()。如果service允许绑定,onBind()将返回一个供客户端与service进行交互的通讯渠道。如果有新的客户端连接至该service,则onUnbind()方法可以要求调用onRebind()

下图描绘了service的回调方法。尽管图中对由startServicebindService()方法启动的service做了区分,但要记住,不论一个service是如何启动的,它都应该允许客户端绑定到它,所以任何service都可以接受onBind()onUnbind()调用。

 

Broadcast receiver的生命周期

broadcast receiver只有一个回调方法:

void onReceive(Context curContext, Intent broadcastMsg)

当广播消息抵达接收器时,Android调用它的onReceive()方法并将包含消息的Intent对象传递给它。broadcast receiver仅在它执行这个方法时处于活动状态。当onReceive()返回后,它又处于非活动状态。

拥有一个处于活动状态的broadcast receiver的进程会被保护起来,而不会被杀死。但仅拥有非活动状态组件的进程则会在其它进程需要它所占有的内存时被杀掉。

这种方式引出一个问题:如果响应一个广播信息需要很长的一段时间,我们一般会将其纳入一个单独的线程中去完成,而不是在主线程内完成它,从而保证用户交互过程的流畅。如果onReceive()衍生了一个这样的线程然后返回,则包括新线程在内的整个进程都会被判为处于非活动状态(除非此进程内的其它应用程序组件仍处于活动状态),这样它就存在着可能被杀掉的危险。此问题的解决方案是令onReceive()启动一个新的service,利用其完成任务,于是系统就会知道此进程中仍然在处理工作。

下一节中,我们会讨论更多关于进程容易被误杀的问题。

进程与生命周期

Android系统会尝试尽可能长的延续一个应用程序进程,但在内存过低的时候,仍然会不可避免需要移除旧的进程。为决定哪一个进程该保留、哪一个进程该移除,Android将每个进程都放入一个重要性层次中,依据则是进程中正在运行的组件及其状态。重要性最低的进程首先被消灭,然后是次低的,依此类推。重要性共分五层,依据重要性列表如下:

  1. 前台进程是用户操作所必须的。当满足如下任一条件时,进程被认为是处于前台的:

任一时间点,仅有少数进程会处于前台。仅当内存实在无法供给它们维持同时运行时才会被杀死。一般来说,在这种情况下,设备已然处于使用虚拟内存的状态,必须要杀死一些前台进程以确保用户界面响应性。

  1. 可视进程没有前台组件,但仍可影响用户在屏幕上的所见。当满足如下任一条件时,进程被认为是可视的:
    • 它包含着一个不在前台,但仍然为用户可见的activity(它的onPause()方法已被调用)。这种情况可能出现在以下情况:例如,如果前台activity是一个对话框,而之前的activity位于其下并可以被看到。
    • 它持有一个绑定至一个可视的activityservice

可视进程依然被视为是极其重要的,非到不杀死它们便无法维持前台进程运行时,才会被杀死。

  1. 服务进程是运行着一个通过startService()方法启动的service的进程,它不属于上述两类。尽管服务进程不与用户所见的任何东西有直接联系,但它们一般都在做着用户所关心的事情(比如在后台播放mp3或者从网上下载数据)。所以系统会尽量维持它们的运行,除非系统内存不足以同时维持它们和前台进程以及可视进程的运行。
  2. 后台进程包含当前不为用户所见的activityActivity对象的onStop()方法已被调用)。这些进程与用户体验没有直接的联系,可以在任意时间被杀死以回收内存,供前台进程、可视进程以及服务进程使用。一般来说,会有很多后台进程在运行,所以它们一般存放于一个LRU(最近最不常用)列表中以确保最后被用户使用的activity最后被杀死。如果一个activity正确的实现了其生命周期方法,并捕获了其当前状态,则杀死它的进程对用户体验不会有不良影响。
  3. 空进程不包含任何活动应用程序组件。这种进程存在的唯一原因是作为缓存以减少组件再次于其中运行时所需的启动时间。系统经常会杀死这种进程以保持进程缓存和系统内核缓存之间的平衡。

Android会依据进程中当前活动组件的重要程度来尽可能高的估量一个进程的级别。例如,如果一个进程中同时有一个service和一个可视的activity,则进程会被判定为可视进程,而不是服务进程。

此外,一个进程的级别可能会由于其它进程依赖于它而升高。一个为其它进程提供服务的进程级别永远不会低于使用它服务的进程。比如说,如果进程A中的content provider为进程B中的客户端提供服务,或进程A中的service为进程B中的一个组件所绑定,则进程A至少会被视为与进程B拥有同样的重要性。

因为运行着一个service的进程其级别总高于一个后台activity,所以如果一个activity打算执行一个需要长时间运行的操作,应该通过启动一个service来完成,而不是简单地衍生一个线程来完成——尤其是当处理过程比activity本身存在时间要长的情况下。我们以背景音乐播放和上传一个相机拍摄的图片至网站为例。使用service至少可以保证操作拥有服务进程的权限,无论activity发生了何事。如上一节Broadcast receiver生命周期所提到的,这也正是broadcast receiver应该使用service而不是简单使用线程来处理时间消耗型任务的原因。

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值