翻译:Android程序开发入门
版权:
原文档版权归Google所有,该翻译版权属于尹树荣(surance@gmail.com)。
原文档版本:android-sdk-windows-1.1_r1
翻译版本(指第几次翻译该版本):V1.0
开源协议:遵循创作共用版权协议,要求署名、非商业、保持一致。在满足创作共用版权协议的基础上可以随意转载,但请以超链接形式注明出处。本文链接地址为:http://www.fltek.com.cn
免责声明:该翻译免费提供,不对正确性做出任何保证,由此造成任何问题,与翻译者一概无关。
Android程序使用java书写。编译好的java程序可以通过aapt tool 压缩为Android包,后缀名为.apk
.
这样便于发布、部署在移动设备上。用户直接下载apk包用来安装,一个apk包被视为一个程序。
每个android程序有自己的运行空间
默认每个程序有一个Linux进程。当程序运行的时候,Android建立一个进程;当程序关闭或者不再被系统使用的时候,Android回收这个进程。
每个程序有自己的java虚拟机(VM),所以每个程序是和其他程序隔离的。
默认每个程序有一个独立的Linux用户ID,文件只对这个用户可见,但是有其他的方法可以让程序之间通信。
2个程序使用同一个用户ID也是可能的,这样这2个程序可以看到对方的文件。为了方便共享资源,使用同一个用户ID的程序分配同一个Linux进程,同一个VM
应用程序组件
Android的一个特性是一个程序可以使用另一个程序的方法。比如,如果你的程序需要显示图片列表的滚动条,而另外一个程序已经开发了一个合适的滚动条,并且使它可以共享给其他程序,就可以使用这个滚动条,而不是自己开发一个。这样操作的时候,不需要引入或者连接其他的源代码,只是简单的开启另外一个程序即可。
具体操作为:系统必须可以启动所需要的进程并初始化java对象。所以,与其他操作系统不同,Android 程序没有静态入口点函数 (如没有 main()
函数). 取而代之的是,使用组件components )。
Activities
activity 用来呈现用户界面。例如:菜单、文本框。虽然很多activity组成一个用户界面,但是每个activity 都是独立的,并且继承自Activity 类。(注:activity即窗体)
一个程序可能只有一个activity,而另外一些程序,如短信发送程序,有许多activity。Activitiy是什么,需要多少,取决于程序的设计。一般来说,众多 activities中的一个被设置为程序启动后第一个呈现给用户的,然后会显示其他的activity。
每个 activity 有一个默认的窗口。一般来说,窗口文件填满屏幕,但是也有可能比窗口小或者在其他窗口上方。activity 也可以作为额外窗口— 如一个弹出对话框或者警告对话框.
窗口的内容靠有层次的views来呈现,views是继承自 View 类的对象。每个view控制着窗口中的一个特殊区域。父views包含子views。叶节点views响应用户的操作。所以,views 是与用户互动的。例如:view可能是显示一个小图片,Android已经有很多内置的views,如按钮、文本框、滚动条、菜单、多选框等。(注:view即控件)
view 的显示是靠Activity.setContentView() 方法。 (请参见 User Interface 文档views部分)
Services
service 没有用户界面,而是运行在后台。例如:播放背景音乐、从网络下载数据、计算数据并提供给activity。Service继承自 Service 类。
常见的例子:media player 从播放列表播放音乐,播放器有一些activities来叫用户选定歌曲并开始播放。但是,播放本身不需要activity处理,因为用户希望关闭activity后歌曲会继续播放。因此media player 的activity 会启动一个service 。当用户离开窗体后,系统还可以保持歌曲的播放。
可以与正在运行的service通信或者启动一个service 。与service通信依赖于service公开的接口。例如音乐播放,接口可能允许用户暂停、启动、停止、继续播放列表。
象activities和其他组件一样,services运行在应用程序的主线程。因此他们不会阻塞其他的组件或者用户界面。可以在服务中启用另外的线程来做耗时的工作,如音乐播放。请参见Processes and Threads.
Broadcast receivers
broadcast receiver 是一个接收并处理广播通知(broadcast announcements)的组件。多数的广播是系统发起的,如地域变幻、电池电量不足、图片获取、语言转换。程序也可以初始化一个广播,例如让其他的程序知道他们需要的某些数据被下载了。(注:通知广播即事件)
程序可以有任意数量的broadcast receivers 来相应它觉得重要的通知。所有的receivers 继承自 BroadcastReceiver 类。
Broadcast receivers 不会显示用户界面,但是他们可能启动一个activity 来相应收到的通知,或者使用 NotificationManager 通知用户。 可以通过多种方式通知用户-开启背景灯、振动设备、播放声音等。最典型的是在状态栏显示一个图标,这样用户就可以点它打开看通知内容。
Content providers
content provider 创建其他程序使用的数据集。数据可以存在系统的SQLite数据库或者其他地方。content provider 继承自 ContentProvider 类,实现一组标准的方法,来使其他程序可以存取数据。但是,程序并不是直接调用这些方法,而是使用ContentResolver 对象来调用这些方法。ContentResolve的作用是管理provider和程序之间的多进程交互。
请参见 Content Providers 文档。
只要有需要调用特定组件的需求,Android就需要确保组件的程序进程正在运行,如果组件的线程没有启动,还要启动所需的进程。如果所需组件没有初始化,Android也会把它初始化。
激活组件: intents
当ContentResolver 发起一个请求的时候Content providers 激活. 另外三个组件: — activities, services, 和 broadcast receivers — 被叫做intents的异步的消息激活。intent 是一个保持了消息信息的 Intent 对象。对于activities 和 services,它传送了被请求的数据和特定的URI。例如,它可能传达一个请求,叫activity来呈现一个图片或者叫用户编辑某段文本。对于broadcast receivers,Internt对象传送了通知的动作。例如,它可能通知相机的按钮被按下。
(注:可以看作是打开另外窗口(或服务)、窗口间通信的工具)
有多种方法可以激活不同的组件:
· Activity靠传送一个Intent对象到Context.startActivity() 或Activity.startActivityForResult().被激活。 被激活的activity可以通过调用 getIntent() 方法获取激活它的intent。Android 调用acitivity的onNewIntent() 方法来传送其他后来的intent。(注:一个activity可以同时被多个intent激活)
Activity经常会调用其他的activity,如果它需要被调用的activity返回的数据,可以调用startActivityForResult() 方法,而不是 startActivity(). 例如,某个activity调用另外一个activity用来让用户选择一个图片,另外一个activity应该返回用户选择的图片。结果通过第一个activity的onActivityResult() 返回,返回的形式也是intent。
· service通过调用 Context.startService()被开始。Android会调用服务的onStart() 方法,并把intent传送给它。
类似的,intent可以通过 Context.bindService() 方法建立同正在运行的服务的联系。服务通过onBind() 方法获取intent。如果服务没有启动, bindService() 方法可以选择是否启动服务。例如,一个activity可能会想建立与后台播放音乐的服务的通信,这样用户就可以通过activity控制后台的播放。因此activity可能会调用 bindService() 方法来建立连接,然后再调用service公开的接口。
· 程序可以调用Context.sendBroadcast(), Context.sendOrderedBroadcast(), 和 Context.sendStickyBroadcast()来传递intent初始化一个broadcast。broadcast 通过 onReceive() 方法接收。
请参见 Intents and Intent Filters.
关闭组件
只有当回应ContentResolver时,content provider被激活。只有当回应 broadcast message时 broadcast receiver被激活。 因此不必显式的关闭组件。
Activities,从另外一个方面看,提供用户界面,需要长时间的与用户交互,保持激活状态,甚至空闲状态的时候都要激活。service需要长时间保持激活状态。因此Android有关闭activities 和 services 的方法:
· activity 关闭:调用 finish() 方法。一个activity可以关闭其他activity (必须是它通过调用 startActivityForResult()创建的) by calling finishActivity().
· 服务关闭:调用 stopSelf() 或者Context.stopService()方法。
当不再使用或者系统回收内存的时候,组件会被android关闭。请参见 Component Lifecycles
The manifest file(装箱单)
在Android开始一个应用程序之前,它必须知道有哪些文件,因此程序必须在一个manifest文件中声明所有用到的组件。这些声明的组件会被编译到apk文件中。.apk
文件含有程序源代码和资源。(注:相当于项目文件)
装箱单是一个xml文件,在所有的程序中命名都是AndroidManifest.xml 。它还有一些其他 的功能,如:指明需要连接的库、识别程序希望被授予的权限。
但是装箱单的主要任务还是向android声明程序的组件。一份装箱单的例子:
<?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
属性指明了 Activity 的类名. icon
和 label
属性指明了展示activity时用到的资源。
另一个节点的含义差不多 — <service>
是用来声明services的, <receiver>
用来声明broadtcast, <provider>
用来声明 content providers. Activities, services。 content providers需要声明,对系统不可见。另外, broadcast receivers可以不在装箱单中声明,可以动态得在代码中声明。(见BroadcastReceiver 对象)通过 Context.registerReceiver()
方法向系统注册。
请参见 The AndroidManifest.xml File
Intent filters
Intent对象可以显式的指明目标组件,这样Android很容易激活对应的组件。但是,如果目标组件不是显式的声明,Android必须定位到最适合的组件来响应intent。系统如何实现这个功能呢?是通过比较intent对象和潜在目标的intent filter。组件的intent filters通知操作系统它可以处理什么样的intent。象其他组件的信息一样,intent filter定义在装箱单中,这里是上面的装箱单加入了intent filters的例子:
<?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:type="image/jpeg" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
. . .
</application>
</manifest>
例子中的第一个filter的属性:"android.intent.action.MAIN
" 和category "android.intent.category.LAUNCHER
" —是很常用的。它使得activity变成程序第一个响应的,可以占用屏幕。换句话说,是用户启动程序后看到的第一个activity。
第二个filteractivity可以执行特定数据类型的动作。
一个组件可以有多个intent filters,每个声明一组capabilities.如果没有声明filters,那么它只能被显示指明的intent激活。
对于在创建和注册在代码中的broadcast receiver来说, intent filter 是一个默认的 IntentFilter 对象。其他所有的filters 必须在装箱单中声明
请参见 Intents and Intent Filters.
Activities 和任务( Tasks)
之前提到,一个activity可以启动另外一个包含它其他程序中的activity。想象一下,例如,你想要用户显示一个街道的地图,已经有另外一个程序可以做这件事情,所以你的程序只需要建立一个intent对象,这个intent对象包含请求的数据,并调用startActivity().这时候,地图组件可以显示地图,当用户点击“后退”按钮,你的activity会重新显示在屏幕上。
对用户来说,即使地图程序是定义在另外一个程序中、在另外一个进程中运行,但是它看起来好像你的程序的一部分。Android通过将2个activities放在用一个任务中,使得用户有这样的体验。这一系列的activities,安排在一个堆栈中。堆栈中的根activity是任务的开始,或者说是用户选择的应用程序的第一个activity。堆栈顶部的activity 是当前正在运行的activity— 目前正在吸引用户注意的一个.当一个activity启动另外一个的时候,新的activity被压栈,变为当前activity. 之前的activity 还在任务中。当用户点击后退(BACK)键,当前的acitivity被弹出栈,之前的activity成为正在运行的。
如果堆栈中有多个同样的activity的实例,如有多个地图查看器(如程序设定了多个打开地图查看器的入口),那么堆栈不可以重新排序,只能弹出和压入。
任务是activities的堆栈,而非装箱单中的一个类,因此不能离开activity单独设定一个堆栈的值。例如,下面一章将要谈到‘任务的吸引力’,这个值就是设定在任务的根activity里面的。
任务中的所有activities作为一个整体移动。整个任务可以放在前台(foreground)或者后台(background)运行。想象一下,例如,有个任务有4个activities,用户点击“HOME”键,回到程序开始的地方,选择了一个新的程序(其实是一个新的任务),当前的任务移到后台运行,新任务的根activity开始显示。过了一会,用户又回到HOME界面,选择了刚才的程序(刚才的任务),则这个含有4个activities的任务回到前台运行。当用户点击BACK按钮,屏幕不会显示用户刚刚离开时候的activity,而是在堆栈顶部的activity被弹出,显示前一个activity。
默认的情况下,上面这些描述是没有问题的。当然,也有很多办法来改变以下:任务和activity的关系、任务中一个activity的动作。这些是被Intent对象(启动此acivity的)的标志集(flag set)和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
下面的章节说明了这些标记和属性的意义和互动、需要注意的问题:
Affinities 和新任务
每个acitivity有一个默认的affinity。然而,特定affinity可以通过<activity>的taskAffinity属性被设置到特定的activity的上。不同程序的activities可以共享一个affinity;同一个程序的activity可以使用不同的afninity。afninity在符合以下2个条件的时候启动:当启动acitity的Intent中含有FLAG_ACTIVITY_NEW_TASK 节点,或者一个activity的allowTaskReparenting 属性设置为"true".
之前提到,任务的默认通过startActivity(). 激活的默认activity也和其他activity在一个堆栈里面。但是引发这个activity的Intent通过startActivity() 方法传递了 FLAG_ACTIVITY_NEW_TASK 节点。望文知意,这个节点表示新的任务。但是,也不一定;如果任务中没有含有此afinity的activity,那么它从默认的activity开始。
如果activity的 allowTaskReparenting
属性设置为 "true
", 它就可以脱离开始它的任务,然后通过它的afinity指引,走入下一个任务。例如,假设“旅游软件”有一个activity可以获取指定城市的天气。这个activity和此程序中的其他activity有同样的afinity(默认的afinity),并且允许重新转入另外的任务。某个activity开始了天气预报动作,它一开始属于这个任务;但是旅游程序到前台运行的时候,天气预报acivity被分配到旅游程序运行。
如果从用户角度看,一个.apk文件包含许多程序,你必须适当的分配不同的affinity给activitis。
启动模式
一个 <activity>
有4种启动模式,要设置 launchMode
属性:
"standard" (the default mode)
"singleTop"
"singleTask"
"singleInstance"
这些模式有以下区别:
哪个任务将包含intent指定要运行的activity:对于 "standard
" 和 "singleTop
" 模式,启动activity的intent (即调用 startActivity()
方法的) —除非intent对象包含FLAG_ACTIVITY_NEW_TASK
节点,如果是的话,会像上个章节描述的一样, Affinities and new tasks,另外一个任务会承载这个activity。
相对的, "singleTask
" 和 "singleInstance
" 模式使得activity永远在tast的根,他们初始化一个任务,不会融入到其他任务中 。
activity是否会有多个实例. "standard
" 或 "singleTop
"模式的activity可以被示例化多次,属于多个任务,或者一个任务中有多个此activity的实例。
相对的,, "singleTask" 和 "singleInstance" 模式的activities限制为只能有一个实例。因为这些activity在任务的根,这表示,任务也只有一个。
同一个任务中是否可以有其他的activity: "singleInstance" 模式的activity所在的任务中不能有其他的activity ,如果启动其他activity, 那么其他activity 会被附加到新的任务(不管新activity的启动模式是什么) — 就像intent使用了 FLAG_ACTIVITY_NEW_TASK 标记。在其他方面, "singleInstance" 和 "singleTask"没有什么区别。
其他三种模式允许多个acitvity在同一个任务中。singleTask
的activity必须在任务的根,它可以启动其他设置到该任务的activity。而standard
" 和 "singleTop
"可以用于任务的任何位置。
一个类的新实例是否可以接受、处理其他部分发来的intent: 默认的"standard
" 模式可以响应任何新的intent,每个实例处理一个intent。 "singleTop
" 模式的activity如果在任务的顶部,则用已有的实例处理所有请求该实例的intent;反之创建一个新的实例,并压入任务的顶部。
例如,一个任务包含根activityA,和其他activityB,C,D,D在任务堆栈的顶部,即堆栈为A-B-C-D. 一个intent请求D,如果D是"standard" 启动模式,则D的一个新实例被创建,任务堆栈变为A-B-C-D-D. 但是,如果D的启动模式是"singleTop", 则现有的D的实例来响应intent,所以任务堆栈仍然是A-B-C-D.
另外一种情况,如果intent来请求B,如果B是 "standard" 或者 "singleTop" 模式,B都将创建一个新的实例来响应,则任务堆栈为 A-B-C-D-B.
就像上面提到的,"singleTask" 或 "singleInstance" 的activity只有1个实例,所以这个实例要处理所有的intent。"singleInstance" 的activity永远在任务堆栈的顶部(因为它是任务中的唯一个activity),所以这个实例处理所有的intent。可能有其他的activity在"singleTask" 的activity的顶部,因此这个实例不会处理intent请求,因此intent的请求会失败;(即使失败,intent也会激活这个程序)
当已存在的acitity用来处理intent的时候,intent会引发activity的onNewIntent()
方法. (activiy可以调用getIntent()
获取触发它的intent)
注意:当一个新的activity的实例为了响应intent而创建时,用户可以按BACK键回到上一个状态(上一个activity)。但是当一个已经存在的activity处理新的intent的时候,用户不能按BACK键后退了。
清空任务
如果一个用户长时间没有使用一个任务,系统会请空这个任务中根以外的acitivity。当用户再次使用此程序的时候,只有最开始的activity显示出来。这样做的原因是:当用户很长时间以后回来的时候,它倾向于重新开始工作,而不是接着上次的工作做。
当然,这是默认的模式。activity有一些属性可以控制请空的动作。
alwaysRetainTaskState
属性
当任务的根acitvity的此属性设置为 "true" 的时候,以上描述的动作不会执行。系统会保留上次程序加载时候的所有acitivity。
clearTaskOnLaunch
属性
当任务的根acitvity的此属性设置为 "true" 的时候,无论用户何时返回应用程序,程序都会请空队列。从另一个角度说,这个属性和alwaysRetainTaskState
属性完全相反。即使离开一会,用户也会被导航到初始状态。
finishOnTaskLaunch
属性
这个属性有些像clearTaskOnLaunch
, 但是它是针对某个activity而非整个任务的。它可以清除任何activity ,当然包括任务堆栈的根activity。当此属性设置为"true" 的时候,此activity只在用户当前会话中显示,当用户离开再回来的时候,此activity已经被清除。
有另外一种方式来请空任务。如果intent含有 FLAG_ACTIVITY_CLEAR_TOP
节点,并且目标任务已经有一个指定activity的实例来处理这个intent了,那么这个activity顶上的节点将被全部清除,这个activity的实例就位于任务堆栈的顶部了。如果指定的activity设置为"standard
", 它将从任务中移除,建立一个新的实例来响应此intent。因为启动模式设置为 "standard
"的时候,会建立一个新的实例来响应intent。
FLAG_ACTIVITY_CLEAR_TOP
经常和FLAG_ACTIVITY_NEW_TASK
.一起使用。当他们一起使用的时候,可以定位到另外一个任务中已经存在的activity上,使得这个acitivity响应intent。
开始任务Starting tasks
怎样把一个activity设置为整个任务的入口呢?可以设置一个intent filter: "android.intent.action.MAIN
" 作为默认动作;"android.intent.category.LAUNCHER
" 作为类别。 (是 Intent Filters 章节的例子)。这种类型的的filter对应的activity的icon和label显示在任务中,这样用户可以方便的回到任务
第二个能力尤其重要,用户必须可以离开任务,并且重返任务。因此, "singleTask
" 和 "singleInstance
"这2个启动模式必须用在含有 MAIN
和 LAUNCHER
filter的activity中.想象一下,如果不加这2个filter会怎样?一个intent请求"singleTask
" 的activity,初始化了一个新的任务,用户操作了一会这个任务。然后用户点击“HOME”键。现在这个任务就被HOME窗体覆盖了。但是因为这个任务不能在application launcher(注:类似于进程管理器)里面看到,因此用户没有办法打开它。
FLAG_ACTIVITY_NEW_TASK
标志也有同样的问题。这个标志将使得activity在一个新的任务执行,当用户点击HOME 键离开任务的时候,必须能让用户回到这个任务。其他的一些机制(如通知管理器)经常在外部的任务开启某个activity,而不是activity所在的任务本身,因此必须在intent触发 startActivity()
方法时使用FLAG_ACTIVITY_NEW_TASK
标记。如果你有一个可以使用这个标记并且可以被外部调用的activity,要注意使得用户可以重新回到这个任务。
当你不想用户重新回到任务的时候,将 <activity> 节点的finishOnTaskLaunch 设置为"true". 请参见前面的 Clearing the stack。
进程和线程Processes and Threads
当某个组件第一次运行的时候,Android启动了一个进程。默认的,所有的组件和程序运行在这个进程和线程中。
也可以安排组件在其他的进程或者线程中运行
进程
组件运行的进程由装箱单控制。组件的节点 — <activity>
, <service>
, <receiver>
, 和 <provider>
— 都包含一个 process
属性。这个属性可以组件运行的进程。这个属性可以配置组件在一个独立进程运行,或者多个组件在同一个进程运行。甚至可以多个程序在一个进程中运行-如果这些程序共享一个User ID并给定同样的权限。<application>
节点也包含 process
属性,用来设置程序中所有组件的默认进程。
所有的组件在此进程的主线程中示例化,系统对这些组件的调用从主线程中分离。并非每个对象都会从主线程中分离。一般来说,响应例如View.onKeyDown()用户操作的方法和通知(后面会在Component Lifecycles谈到)的方法也在主线程中运行。这就表示,组件被系统调用的时候不应该长时间运行或者阻塞操作(如网络操作或者计算大量数据),因为这样会阻塞进程中的其他组件。可以把这类操作从主线程中分离,后面的Threads, 章节会降到。
当更加常用的进程无法获取足够内存,Android可能会关闭不常用的进程。下次启动程序的时候会有一次启动进程。
当决定哪个进程需要被关闭的时候, Android会考虑哪个对用户更加有用。如,Android会倾向于关闭一个长期不显示在界面的进程来支持一个经常显示在界面的进程。是否关闭一个进程决定于组件在进程中的状态,参见后面的章节Component Lifecycles.
线程
即使为组件分配了不同的进程,有时候也需要再分配线程。比如用户界面需要很快对用户进行响应,因此某些费时的操作,如网络连接、下载或者非常占用服务器时间的操作应该放到其他线程。
线程通过java的标准对象Thread 创建. Android 提供了很多方便的管理线程的方法:— Looper 在线程中运行一个消息循环; Handler 传递一个消息; HandlerThread 创建一个带有消息循环的线程。
远程调用Remote procedure calls
Android有一个远程调用(RPCs) 的轻量级机制— 通过这个机制,方法可以在本地调用,在远程执行(在其他进程执行),还可以返回一个值。要实现这个需求,方法调用必须分解方法调用,并且所有要传递的数据必须是操作系统可以访问的级别。从本地的进程和内存地址传送到远程的进程和内存地址并在远程处理和返回。返回值必须向相反的方向传递。Android提供了做以上操作的代码,所以开发者可以专注于实现RPC的接口。
一个RPC接口只能有一个方法。所有的方法都是同步执行的(直到远程方法返回,本地方法才结束阻塞),没有返回值的时候也是如此。
简单来说,这个机制是这样的:使用IDL (interface definition language).定义你想要实现的接口, aidl
工具可以生成用于java的接口定义,本地和远程都要使用这个定义。它包含2个类,见下图:
inner类包含了所有的管理远程程序(符合IDL描述的接口)所需要的代码。所有的inner类实现了IBinder 接口.其中一个在本地使用,可以不管它的代码;另外一个叫做Stub继承了 Binder 类。为了实现远程调用,这个类包含RPC接口。开发者可以继承Stub类来实现需要的方法。
一般来说,远程进程会被一个service管理(因为service可以通知操作系统这个进程的信息并和其他进程通信),它也会包含aidl
工具产生的接口文件,Stub类实现了远处那个方法。服务的客户端只需要aidl
工具产生的接口文件。
以下是如何连接服务和客户端调用:
· 服务的客户端(本地)会实现onServiceConnected() 和onServiceDisconnected() 方法,这样,当客户端连接或者断开连接的时候可以获取到通知。通过 bindService() 获取到服务的连接。
· 服务的 onBind() 方法中可以接收或者拒绝连接,取决它收到的intent (intent通过 bindService()方法连接到服务). 如果服务接收了连接,会返回一个Stub类的实例.
· 如果服务接受了连接,Android会调用客户端的onServiceConnected() 方法,并传递一个Ibinder对象(系统管理的Stub类的代理),通过这个代理,客户端可以连接远程的服务。
以上的描述省略很多RPC的机制。请参见Designing a Remote Interface Using AIDL 和 IBinder 类。
线程安全的方法
在某些情况下,方法可能调用不止一个的线程,因此需要注意方法的线程安全。
对于可以远程调用的方法,也要注意这点。当一个调用在Ibinder对象中的方法的程序启动了和Ibinder对象相同的进程,方法就在Ibinder的进程中执行。但是,如果调用者发起另外一个进程,方法在另外一个线程中运行,这个线程在和IBinder对象在一个线程池中;它不会在进程的主线程中运行。例如,一个service从主线程被调用onBind()
方法,onBind()
返回的对象(如实现了RPC的Stub子类)中的方法会被从线程池中调用。因为一个服务可能有多个客户端请求,不止一个线程池会在同一时间调用IBinder的方法。因此IBinder必须线程安全。
简单来说,一个content provider 可以接收其他进程的数据请求。即使ContentResolver和ContentProvider类没有隐藏了管理交互的细节,ContentProvider中响应这些请求的方法(s query()
, insert()
, delete()
, update()
, and getType()
)— 是在content provider的线程池中被调用的,而不是ContentProvider的本身进程。因为这些方法可能是同时从很多线程池运行的,所以这些方法必须要线程安全。
组件生命周期
应用程序组件有一个生命周期――开始于Android让他们去响应一个Intent请求,结束于对象销毁。在这期间,他们可能处于激活或者未激活、可见不可见的状态。这一节会讨论activity、service和broadcast receivers的生命周期――包括在生命周期内他们可能的状态,和如何切换状态,以及这些状态对程序被中止或者销毁的影响。
Activity 的生命周期
activity主要有3种状态
· 当它在屏幕的前台运行的时候(在任务堆栈的顶端),状态为 active 或 running 。这个时候用户的注意力集中在此activity上。
· 当对用户仍然可见,但是失去焦点的时候,状态为paused 。在它上面的acitivity要么就是半透明,要么就是没有占满屏幕,因此被暂停的acitivity仍然可见。一个暂停的activity完全是活动的(保持状态、成员信息、绑定到窗口管理器),但是当系统内存不足的时候会被关闭。
· 当被其他activity完全遮盖的时候,状态为 stopped ,它仍可以保存状态和成员信息。但是当其他程序需要内存的时候它随时可能被销毁。
如果一个activity被停止或者暂停了,系统可能会通过调用它的finish方法或者简单的关闭进程来回收内存。但是当这个activity重新可见的时候,它会重新启动并返回上一次的状态。
当activity的状态改变的时候,它被以下受保护的方法通知:
void onCreate(Bundle savedInstanceState)
void onStart()
void onRestart()
void onResume()
void onPause()
void onStop()
void onDestroy()
这些方法都是可以重写来实现特定的要求,就像activity在对象第一次初始化的时候必须实现onCreate()
方法,来做初始化的工作。很多情况下,需要实现onPause()
来做数据保存以防程序关闭。
这7个方法一起构成了activity的生命周期,你可以监控这个不断循环的过程,此过程包括3种:
· activity的整个生命周期 开始于第一次调用 onCreate() 结束于调用onDestroy(). activity onCreate()完成全局的状态的初始化,在onDestroy()方法里面应该释放所有的资源。例如,在后台有一个下载数据的线程,那么应该在程序的onCreate() 调用启动线程的方法,在onDestroy()里面调用关闭线程的方法。
· 可见的生命周期开始于 onStart() 结束于 onStop()方法。在这个期间,用户可以在屏幕上看到此acitvity(即使它不在前台和用户交互)。在这个2个方法之间,可以保存需要更新界面的资源。例如,在 onStart()方法注册了一个 BroadcastReceiver 来监视影响UI的操作,就需要在onStop() 方法里面注销,因为在调用这个方法后用户不再需要操作界面。 随着用户不断切换程序的可见和不可见的时候,onStart() 和 onStop() 方法会被调用很多次。
· 前台生命周期开始于 onResume() 方法,结束于 onPause()方法。在这个期间,可以从屏幕上看到,此acitivity在所有其他activity前面,并且和用户交互。因为用户会频繁的从暂停、恢复状态切换,例如,当设备待机或者一个新的activity启动的时候, onPause() 会被调用。当传入一个新的intent进来的时候, onResume() 方法被调用。因此,这2个方法内的代码应该是轻量级的。
以下的图片显示了activity在状态间切换的轨迹。有色的椭圆表示了activity最常见的状态。正方形表示了切换状态时可以监控的方法。
下面的表格描述了这些方法的细节和在生命周期的何处
方法 |
描述 |
关闭? |
下一个 |
||
Activity第一次建立的时候调用,应该做一般的初始化-创建views,绑定数据等。传入了一个 包含了此activity之前状态的Bundle 对象。 (参见后面的 Saving Activity State). 后面一般会是:onStart(). |
No |
onStart() |
|||
|
当activity被停止后开始之前,后面会是:onStart() |
No |
onStart() |
||
当activity对于用户可见的时候 当activity在前台运行的时候,后面会是onResume() ,如果是被关闭,后面会是onStop() |
No |
onResume() |
|||
|
只是当activity开始与用户交互的时候调用,这个时候,此activity在任务堆栈的顶部,后面会是:onPause(). |
No |
onPause() |
||
当系统将要调用其他activity的时候触发,这个方法里面通常用来将未保存的数据持久化,停止动画或者其他占用cpu的活动。这个动作应该做的很快,因为下一个activity要在这个过程之后才能启动。如果此activity从后台到前台,后面是onResume() ;如果这个activity变得不可见,后面是onStop() |
Yes |
onResume() |
|||
当这个activity 对用户再也不可见时候触发,可是是因为activity被销毁,或者其他activity (已有的或者新的)被恢复并且覆盖了它。 如果后面会是 onRestart()如果程序被关闭,后面会是onDestroy() |
Yes |
onRestart() |
|||
程序被关闭前调用。这个是activity接收的最后一个方法。可能是actvity结束的时候调用(某个程序调用了它的finish() 方法), 或者系统临时销毁这个程序来释放资源。可以根据isFinishing() 方法识别到底是哪一种. |
Yes |
nothing |
请注意表格中的是否可被闭列,这个列表明了系统是否会在调用此方法后不执行此activity中的其他代码得关闭此activity所在的进程,这些方法 (onPause(), onStop(), and onDestroy())是 "Yes." onPause()方法是唯一一个进程结束前一定会调用的,其他2个— onStop() 和 onDestroy() 不一定会,因此 onPause() 方法中,一定要把未保存的数据持久化。
这一列设置为"No"的方法,触发后,可以保证actvity所在的内存不会被回收。因此,有些状态下的activity是“可关闭”状态。例如,触发onPause() 后,程序是可被关闭的,但是如果后面又触发了onResume() ,程序又可以不被关闭了, onPause() 又一次触发后,程序有变成“可被关闭”了。
后面的章节会提到( Processes and lifecycle,)定义成不可以关闭的 activity也可能被系统关闭,但是这个只是在系统资源非常不足的情况极端情况下才会发生。
保存 activity的状态
当系统而非用户关闭一个activity后,用户重返此activity后,希望回到以前的状态
为了保存系统关闭activity后的状态,需要实现onSaveInstanceState()
方法。Android会在回收activity(onPause()
方法调用)之前调用此方法。当重启的该activity的时候,Android会传入一个name-value集合的Bundle 对象来恢复状态,重启后调用的方法是onCreate()
和在onStart()
后调用的一个方法―― onRestoreInstanceState()
, 这2个方法中的任何一个都可以恢复状态。
不同于 onPause() 以及前面提到的方法onSaveInstanceState() 和 onRestoreInstanceState() 并不是生命周期方法,并不是经常调用。例如,Anroid在activity要被销毁之前调用onSaveInstanceState() ,但是并不会在此实例通过用户动作被销毁(如按下Back键),因为在这样的情况下,用户不会再回来此界面,因此不用保持状态。
因为 onSaveInstanceState() 不是经常被调用,只需要保存非持久化的数据,而非持久化的数据.如果需要保存持久化的数据,调用 onPause() 。
同步 activities
当一个activity启动另外一个activity的时候,这2个activity都会经历一个生命周期。当另外一个还在运行的时候,其中一个可能已经中止了。有时,需要同步2个activity。
生命周期的回调已经定义好的,除非2个activity在同一个进程:
1. 当前activity调用 onPause()
2. 然后,第二个actvity依次调用onCreate(), onStart(), 和 onResume() 。
3. 如果第二个窗体不再可见,调用 onStop()
Service的生命周期
service有2种运行模式:
· 如果没有程序停止它或者它自己停止,service将一直运行。在这种模式下,service开始于调用Context.startService() ,停止于Context.stopService(). service可以通过调用Service.stopSelf() 或 Service.stopSelfResult()停止自己。不管调用多少次startService() ,只需要调用一次 stopService() 就可以停止service。
· 可以通过接口被外部程序调用。外部程序建立到service的连接,通过连接来操作service。建立连接调开始于Context.bindService(), 结束于Context.unbindService(). 多个客户端可以绑定到同一个service,如果service没有启动, bindService() 可以选择启动它。
这2种模式不是完全分离的。你可以可以绑定到一个通过startService()
启动的服务。如一个intent想要播放音乐,通过startService()
方法启动后台播放音乐的service。然后,也许用户想要操作播放器或者获取当前正在播放的乐曲的信息,一个activity就会通过bindService()
建立一个到此service的连接. 这种情况下 stopService()
在全部的连接关闭后才会真正停止service。
像activity一样, service也有可以通过监视状态实现的生命周期。但是比activity要少――只有3个――而且是public的而不是protected的
void onCreate()
void onStart(Intent intent)
void onDestroy()
通过实现这3个方法,可以监视service生命周期的2个嵌套循环:
· 整个生命周期 从onCreate() 开始,从onDestroy() 结束,像activity一样, a service 在 onCreate()中执行初始化操作,在 onDestroy()中释放所有用到的资源。如:后台播放音乐的service可能在 onCreate()创建一个播放音乐的线程,在onDestroy()中销毁这个线程。
· 活动生命周期 开始于 onStart(). 这个方法处理传入到startService()方法的intent。 音乐服务会打开intent查看要播放哪首歌曲,并开始播放。
当服务停止的时候,没有方法检测到――没有 onStop() 方法。
onCreate() 和 onDestroy() 用于所有通过Context.startService() or Context.bindService() 启动的service。onStart() 只用于通过startService()开始的service。
如果一个service是可以从外部绑定的,它就可以触发以下的方法:
IBinder onBind(Intent intent)
boolean onUnbind(Intent intent)
void onRebind(Intent intent)
onBind() 回调被传递给调用bindService 的intent, onUnbind() 被unbindService()中的intent处理。如果服务允许被绑定,那么onBind() 方法返回客户端和sercie的沟通通道。如果一个新的客户端连接到服务,onUnbind() 会触发onRebind() 调用。
下面的图表说明了sercice的回调方法。下面的图片将通过 startService 和通过bindService()启动的service分开了,但是要注意不管他们怎么启动的,都有可能被客户端连接,因此都有可能触发到onBind() 和 onUnbind() 方法。
Broadcast receiver 的生命周期
broadcast receiver只有一个回调方法:
void onReceive(Context curContext, Intent broadcastMsg)
当经过receiver 请求,broadcast message到达的时候, Android 调用持有message的intent的 onReceive() 方法,只有broadcast receivers执行此方法的时候才是激活的,当 onReceive()返回的时候,它就是非激活状态
一个含有激活的broadcast receiver的进程是不会被中止的。但是只含有非激活组件的进程在它占用的内存被其他程序请求的时候,任何时间都可以被中止。
当响应broadcast message的程序因为消耗很多时间而在另外一个线程 而非UI所在的线程处理的时候会出现一个问题,,当 onReceive() 开启一个线程并返回后,整个程序(包括新建的线程)状态是非激活的(除非此进程中有其他激活的组件), 因此这个进程就有被中止的危险。解决这个问题的办法是onReceive() 方法启动一个service,让sercie去做耗时的工作,这样系统就知道此进程中还有活动的工作。
下面的章节有更多关于进程被中止的讨论。
进程生命周期
Android尽量不去中止一个进程,但是当内存不足的时候它必须中止一些老的线程。为了决定哪些进程保留哪些进程中止,Android给这些进程一个“重要级”,这个级别取决于进程中的组件和组件的状态。最不重要的进程先被中止,然后是次不重要的,依此类推。重要级别有5个程度,以下详细说明:
1. 前台进程是用户当前正在使用的进程。如果满足以下条件之一则进程可以作为前台进程。
o 有一个用户正在交互的activity, (Activity对象的onResume()方法被调用).
o 有一个响应用户正在交互的activity的sercie.
o 有一个 Service 对象正在执行生命周期的方法 (onCreate(), onStart(), or onDestroy()).
o 有一个 BroadcastReceiver 对象在执行onReceive() 方法.
只有一些前台进程可以在任何时候都存在。他们是最后一个被结束的――当内存低到根本连他们都不能运行的时候。一般来说,在这种情况下,设备会进行内存调度,中止一些前台进程来保持对用户交互的响应。.
2. 可见进程不包含前台的组件但是会在屏幕上显示。如果满足了以下任意一个条件,进程就可以视为可见:
o 有一个非前台的但是仍然对用户可见的activity ( onPause() 方法被调用). 这种情况发生于,例如,当前前台的activity是一个对话框,上一个activity还是可见的。
o 具有一个绑定到可见activity的service。
一个可见的进程是的重要程度很高,除非前台进程需要获取它的资源,不然不会被中止。
3. 服务进程 中运行着一个通过 startService() 方法启动的service,这个service不属于上面提到的2种更高重要性的。service所在的进程虽然对用户不是直接可见的,但是他们执行了用户非常关注的任务(比如播放mp3,从网络下载数据)。只要前台进程和可见进程有足够的内存,系统不会回收他们。
4. 后台进程 中运行着一个对用户不可见的activity(调用过 onStop() 方法).这些进程对用户体验没有直接的影响,可以在服务进程、可见进程、前台进程需要内存的时候回收。通常,系统中会有很多不可见进程在运行,他们被保存在LRU (least recently used) 列表中,以便内存不足的时候被第一时间回收。如果一个activity正确的执行了它的生命周期,关闭这个进程对于用户体验没有太大的影响。
5. 空进程 中未运行任何程序组件。运行这些进程的唯一原因是作为一个缓存,缩短下次程序需要重新使用的启动时间。系统经常中止这些进程,这样可以调节程序缓存和系统缓存的平衡。.
Android 对进程的重要性评级的时候,选取它最高的级别。例如一个进程中有一个service和可以可见的activity,则Android将此进程评判为“可见进程”而非“服务进程。”
另外,当被另外的一个进程依赖的时候,某个进程的级别可能会增高。一个为其他进程服务的进程永远不会比被服务的进程重要级低。例如,进程A中的content provider 正在为客户进程B提供数据,或者A中的service绑定到进程B中,那么A的重要级最少都是B的重要级。
因为服务进程比后台activity进程重要级高,因此一个要进行耗时工作的acitvity最好启动一个service来做这个工作,而不是开启一个子进程――特别是这个操作需要的时间比activity存在的时间还要长的时候。例如,在后台播放音乐,向网上上传摄像头拍到的图片,使用service可以使进程最少获取到“服务进程”级别的重要级,而不用考虑acrivity目前是什么状态。向 Broadcast receiver lifecycle 章节讲到的, broadcast receivers做费时的工作的时候,也应该启用一个服务而不是开一个线程。
转载请注明出处