Activity
活动是最基本的Andorid 应用程序组件,应用程序中,一个活动通常就是一个单独的屏幕。每一个活动都被实现为一个独立的类,并且从活动基类中继承而来, 活动类将会显示由视图控件组成的用户接口,并对事件做出响应。 大多数的应用是由多屏幕显示组成。
Activity 生命周期
Activity 状态
1.当一个Activity 在屏幕的最上层时(对堆栈的最顶端),它就是属于active 或者running 的状态
2.如果一个Activity 失去焦点(focus)但还看得到它的画面(比如:一个新的Activity 画面并不是全屏幕或者它是一个半透明的情况),那失去焦点的Activity 则处在paused 的状态。像这个失去焦点的Activity它还是完全活着的,并没有消失。(活着的意思是指,Activity 自己本身所有的状态及数据都还是存在的,也跟窗口管理程序window manager 保持联系着),像这种paused 的Activity,会在一种情况下消失,那就是当系统的内存不够用之时,系统会自动判断,八部重要的Activity 移除。
3.如果一个Activity 被其它的Activity 完全的遮盖住时,它仍然保有全部的状态及数据,但因为它已不再被使用者看见,所以它的画面是被隐藏起来的(画面不需要更新),当系统内存不足时,这种stop 状态的Activity 时最先被系统考虑拿下来释放内存的。
4.当一个Activity 处于pause 或stop 的状态时,系统可以要求Activity 结束(finish)或直接移除(kill)它。
当它需要再度呈现在使用者面前时,它必须要能完整的重新启动及回复先前的状态。
Intent
Intent 和 Intent Filters
调用Android 专有类 Intent 进行构屏幕之间的切换。 Intent 是描述应用想要做什么。
Intent 数据结构两最重要的部分是动作和动作对应的数据。典型的动作类型有:MAIN(活动的门户)、VIEW、PICK、EDIT 等。而动作对应的数据则以URI 的形式进行表示。例如:要查看某一个人的联系方式,你需要创建一个动作类型为VIEW 的intent,以及一个表示这个人的URI。
与之有关系的一个类叫IntentFilter。当intent 被要求做某事的时候,intent filter 用于描述一个活动(或者BroadcastReceiver,看下面)能够操作哪些intent。一个活动如果要显示一个人的联系方式时,需要声明一个IntentFilter,这个IntentFilter 要知道怎么去处理VIEW 动作和表示一个人的URI。 IntentFilter 需要在AndroidManifest.xml 中定义。
通过解析各种intent,从一个屏幕切换到另一个屏幕是很简单的。当向前导航时,活动将会调用startActivity(myIntent)方法。然后,系统会在所有安装的应用程序定义的IntentFilter 中查找,找到最匹配myIntent 的Intent 对应的活动。新的活动接收到myIntent 的通知后,开始运行。当start 活动方法被调用将触发解析myIntent 的动作,这个机制提供了两个关键好处:
活动能够重复利用从其它组件中以Intent 的形式产生的一个请求
活动可以在任何时候被一个具有相同IntentFilter 的新的活动取代.
广播接收器
你可以使用BroadcastReceiver 来让你的应用对一个外部的事件做出响应。比如:当电话呼入时,数据网络可用时,或者到了晚上时。BroadcastReceivers 不能显示UI,它只能通过 NotificationManager 来通知用户这些有趣的事情发生了。
BroadcastReceivers 既可以在AndroidManifest.xml 中注册,也可以在代码中使用Context.registerReceiver()进行注册。但这些有趣的事情发生时,你的应用不必对请求调用BroadcastReceivers,系统会在需要的时候启动你的应用,并在必要情况下触发BroadcastReceivers。各种应用还可以通过使用Context.sendBroadcast()将它们自己的intent broadcasts 广播给其它应用程序。
任务
记住关键的一点:当用户看到的“应用”,无论实际是如何处理的,它都是一个任务。如果你仅仅通过一些活动来创建一个.apk 包,其中有一个肯定是上层入口(通过动作的intent-filter 以及分类android.intent.category.LAUNCHER),然后你的.apk 包就创建了一个单独任务,无论你启动哪个活动都会是这个任务的一部分。
一个任务,从使用者的观点,他是一个应用程序;对开发者来讲,它是贯穿活动着任务的一个或者多个视图,或者一个活动栈。当设置Intent.FLAG_ACTIVITY_NEW_TASK标志启动一个活动意图时,任务就被创建了;这个意图被用作任务的根用途,定义区分哪个任务。如果活动启动时没有这个标记将被运行在同一个任务里(除非你的活动以特殊模式被启动,这个后面会讨论)。如果你使用 FLAG_ACTIVITY_NEW_TASK 标记并且这个意图的任务已经启动,任务将被切换到前台而不是重新加载。
FLAG_ACTIVITY_NEW_TASK 必须小心使用:在用户看来,一个新的应用程序由此启动。如果这不是你期望的,你想要创建一个新的任务。另外,如果用户需要从桌面退出到他原来的地方然后使用同样的意图打开一个新的任务,你需要使用新的任务标记。否则,如果用户在你刚启动的任务里按桌面(HOME)键,而不是退出(BACK)键,你的任务以及任务的活动将被放在桌面程序的后面,没有办法再切换过去。
任务亲和力(Affinities)
一些情况下Android 需要知道哪个任务的活动附属于一个特殊的任务,即使该任务还没有被启动。这通过任务亲和力来完成,它为任务中一个或多个可能要运行的活动提供一个独一无二的静态名字。默认为活动命名的任务亲和力的名字,就是实现该活动.apk 包的名字。这提供一种通用的特性,对用户来说,所有在.apk 包里的活动都是单一应用的一部分。
当不带 Intent.FLAG_ACTIVITY_NEW_TASK 标记启动一个新的活动,任务亲和力对新启动的活动将没有影响作用:它将一直运行在它启动的那个任务里。然而,如果使用NEW_TASK 标记,亲和力会检测已经存在的任务是否具有相同的亲和力。如果是,该任务会被切换到前台,新的活动会在任务的最上面被启动。
你可以在你的表现文件里的应用程序标签里为.apk 包里所有的活动设置你自己的任务亲和力,当然也可以为单独的活动设置标签。这里有些例子演示如何使用:
1. 如果你的.apk 包里包含多个用户可启动的上层应用程序,那么你可能想要为每个活动分配不同的亲和力。这里有一个不错的协定,你可以将不同的名字字串加上冒号附加在.apk 包名字的后面 。 例如,"com.android.contacts"的亲和力命名可以是"com.android.contacts:Dialer" and "com.android.contacts:ContactsList"。
2. 如果你想替换一个通知,快捷键,或者其它能从外部启动的应用程序的内部活动,你需要在你想替换的活动里明确的设置任务亲和力(taskAffinity)。例如,如果你想替换联系人详细信息浏览界面(用户可以直接操作或者通过快捷方式调用),你需要设置任务亲和力(taskAffinity)为“com.android.contacts”。
启动模式以及启动标记
你控制活动和任务通信的最主要的方法是通过设置启动模式的属性以及意图相应的标记。这两个参数能以不同的组合来共同控制活动的启动结果,这在相应的文档里有描述。
这里我们只描述一些通用的用法以及几种不同的组合方式。
你最通常使用的模式是singleTop(除了默认为standard 模式)。这不会对任务产生什么影响;仅仅是防止在栈顶多次启动同一个活动。
singleTask 模式对任务有一些影响:它能使得活动总是在新的任务里被打开(或者将已经打开的任务切换到前台来)。使用这个模式需要加倍小心该进程是如何和系统其他部分交互的,它可能影响所有的活动。这个模式最好被用于应用程序入口活动的标记中。(支持MAIN 活动和LAUNCHER 分类)。
singleInstance 启动模式更加特殊,该模式只能当整个应用只有一个活动时使用。有一种情况你会经常遇到,其它实体(如搜索管理器SearchManager 或者 通知管理器NotificationManager)会启动你的活动。这种情况下,你需要使用Intent.FLAG_ACTIVITY_NEW_TASK 标记,因为活动在任务(这个应用/任务还没有被启动)之外被启动。就像之前描述的一样, 这种情况下标准特性就是当前和任务和新的活动的亲和性匹配的任务将会切换到前台,然后在最顶端启动一个新的活动。当然,你也可以实现其它类型的特性。
一个常用的做法就是将Intent.FLAG_ACTIVITY_CLEAR_TOP 和NEW_TASK 一起使用。这样做,如果你的任务已经处于运行中,任务将会被切换到前台来, 在栈里的所有的活动除了根活动,都将被清空,根活动的onNewIntent(Intent) 方法传入意图参数后被调用。当使用这种方法的时候 singleTop 或者 singleTask 启动模式经常被使用,这样当前实例会被置入一个新的意图,而不是销毁原先的任务然后启动一个新的实例。
另外你可以使用的一个方法是设置活动的任务亲和力为空字串(表示没有亲和力),然后设置finishOnBackground 属性。 如果你想让用户给你提供一个单独的活动描述的通知,倒不如返回到应用的任务里,这个比较管用。要指定这个属性,不管用户使用BACK还是HOME,活动都会结束;如果这个属性没有指定,按HOME 键将会导致活动以及任务还留在系统里,并且没有办法返回到该任务里。
请确保阅读过文档启动模式属性(launchMode attribute) 以及 意图标记(Intentflags) ,关注这些选项的详细信息。
进程
在Android 中,进程是应用程序的完整实现,而不是用户通常了解的那样。他们主要用途很简单:
1.提高稳定性和安全性,将不信任或者不稳定的代码移动到其他进程。
2. 可将多个.apk 包运行在同一个进程里减少系统开销。
3.帮助系统管理资源,将重要的代码放在一个单独的进程里,这样就可以单独销毁应用程序的其他部分。
像前面描述的一样,进程的属性被用来控制那些有特殊应用组件运行的进程。注意这个属性不能违反系统安全: 如果两个.apk 包不能共享同一个用户ID,却试图运行在通一个进程里,这种情况是不被允许的,事实上系统将会创建两个不同的进程。
请查看安全相关文档以获取更多关于安全限制方面的信息。
线程
每个进程包含一个或多个线程。多数情况下,Android 避免在进程里创建多余的线程,除非它创建它自己的线程,我们应保持应用程序的单线程性。 一个重要的结论就是所有呼叫实例, 广播接收器, 以及 服务的实例都是由这个进程里运行的主线程创建的。
注意新的线程不是为活动,广播接收器,服务或者内容提供器实例创建:这些应用程序的组件在进程里被实例化(除非另有说明,都在同一个进程处理),实际上是进程的主线程。这说明当系统调用时这些组件(包括服务)不需要进程远距离或者封锁操作(就像网络呼叫或者计算循环),因为这将阻止进程中的所有其他组件。你可以使用标准的线程 类或者Android 的HandlerThread 类去对其它线程执行远程操作。
这里有一些关于创建线程规则的例外:
1. 呼叫IBinder 或者IBinder 实现的接口,如果该呼叫来自其他进程,你可以通过线程发送的IBinder 或者本地进程中的线程池呼叫它们,从进程的主线程呼叫是不可以的。特殊情况下,,呼叫一个服务 的IBinder 可以这样处理。(虽然在服务里呼叫方法在主线程里已经完成。)这意味着IBinder 接口的实现必须要有一种线程安全的方法,这样任意线程才能同时访问它。
2. 呼叫由正在被调用的线程或者主线程以及IBinder 派发的内容提供器的主方法。被指定的方法在内容提供器的类里有记录。这意味着实现这些方法必须要有一种线程安全的模式,这样任意其它线程同时可以访问它。
3. 呼叫视图以及由视图里正在运行的线程组成的子类。通常情况下,这会被作为进程的主线程,如果你创建一个线程并显示一个窗口,那么继承的窗口视图将从那个线程里启动。
Android 应用程序的生命周期
在大多数情况下,每个Android 应用程序都运行在自己的Linux 进程中。当应用程序的某些代码需要运行时,这个进程就被创建并一直运行下去,直到系统认为该进程不再有用为止。然后系统将回收进程占用的内存以便分配给其它的应用程序。
应用程序的开发人员必须理解不同的应用程序组件(尤其是Activity, Service, 和BroadcastReceiver)是如何影响应用程序进程生命周期的,这是很重要的一件事情。
不正确地使用这些组件可能会导致系统杀死正在执行重要任务的应用程序进程。
一个常见的进程生命周期bug 的例子是BroadcastReceiver, 当BroadcastReceiver在BroadcastReceiver.onReceive()方法中接收到一个Intent 时,它会启动一个线程,然后返回。一旦它返回,系统将认为BroadcastReceiver 不再处于活动状态,因而BroadcastReceiver 所在的进程也就不再有用了(除非该进程中还有其它的组件处于活动状态)。因此,系统可能会在任意时刻杀死进程以回收内存。这样做的话,进程中创建(spawned)出的那个线程也将被终止。对这个问题的解决方法是从BroadcastReceiver 启动一个服务,让系统知道进程中还有处于活动状态的工作。为了决定在内存不足时让系统杀死哪个进程,Android 根据每个进程中运行的组件以及组件的状态把进程放入一个”重要性分级(importance hierarchy)”中。进程的类型包括(按重要程度排序):
1. 前台(foreground)进程,与用户当前正在做的事情密切相关。不同的应用程序组件能够通过不同的方法使它的宿主进程移到前台。当下面任何一个条件满足时,可以考虑将进程移到前台:
a. 进程正在屏幕的最前端运行一个与用户交互的Activity (它的onResume()
方法被调用)
b. 进程有一正在运行的BroadcastReceiver (它的BroadcastReceiver.onReceive()方法正在执行)
c. 进程有一个Service,并且在Service 的某个回调函数(Service.onCreate(),Service.onStart(), 或 Service.onDestroy())内有正在执行的代码。
2. 可见(visible)进程,它有一个可以被用户从屏幕上看到的Activity,但不在前台(它的onPause()方法被调用)。举例来说,如果前台的Activity 是一个对话框,以前的Activity 隐藏在对话框之后,就可能出现这种进程。这样的进程特别重要,一般不允许被杀死,除非为了保证前台进程的运行不得不这样做。
3. 服务(service)进程,有一个已经用startService() 方法启动的Service。虽然这些进程用户无法直接看到,但它们做的事情却是用户所关心的(例如后台MP3回放或后台网络数据的上传下载)。因此,系统将一直运行这些进程除非内存不足以维持所有的前台进程和可见进程。
4. 后台(background)进程, 拥有一个当前用户看不到的Activity(它的onStop()方法被调用)。这些进程对用户体验没有直接的影响。如果它们正确执行了Activity生命期(详细信息可参考Activity),系统可以在任意时刻杀死进程来回收内存,并提供给前面三种类型的进程使用。系统中通常有很多个这样的进程在运行,因此要将这些进程保存在LRU 列表中,以确保当内存不足时用户最近看到的进程最后一个被杀掉。
5. 空(empty)进程,不包含任何处于活动状态的应用程序组件。保留这种进程的唯一原因是,当下次应用程序的某个组件需要运行时,不需要重新创建进程,这样可以提高启动速度。
系统将以进程中当前处于活动状态组件的重要程度为基础对进程进行分类。请参考Activity, Service 和 BroadcastReceiver 文档来获得有关这些组件在进程整个生命期中是如何起作用的详细信息。每个进程类别的文档详细描述了它们是怎样影响应用程序整个生命周期的。进程的优先级可能也会根据该进程与其它进程的依赖关系而增长。例如,
如果进程A 通过在进程B 中设置Context.BIND_AUTO_CREATE 标记或使用ContentProvider 被绑定到一个服务(Service),那么进程B 在分类时至少要被看成与进程A 同等重要