Processes and Threads
当一个应用组件启动了,并且该应用没有别的正在运行的组件, Android系统会为这个应用启动一个 Linux进程,将其放入一个线程中运行。默认的,同一个应用的所有组件运行在同一个进程与线程中 (主线程 main thread)。当一个应用组件启动时,已经有一个该应用的进程存在了 (存在该应用的别的组件 ),那么这个组件就在这个进程中启动,并且使用相当的线程去执行。然而,你可以让你的应用的不同组件运行于不同的进程中,并且你可以为任何进程创建附加进程。
默认的,同一个应用的所有组件运行于同一个进程中,并且绝大多数应用不应该改变这个。然而,如果你想要控制你的一个确定的应用组件属于某一个进程,你可以在 manifest 文件中做到这件事情。
Manifest文件为每一个组件 (<activity>,<service>,<recevier>,<provider>)的入口提供了一个 android:process的属性用以指定这个组件应该在哪一个进程中运行。你可以设置这个属性使用每个组件在他自己的进程中运行,或者使一些组件享用一个进程,而别的组件不可以。你也可以设置 android:process属性,使来自不同的应用的组件在同一个进程中运行,应用程序共享同一个 Linux的 user ID,相同的签名认证。
<application>元素也支持 android:process属性,用以设置所有组件的默认值。
Android系统可能在某些时候决定关闭掉一个进程,当内存低时,且被并的应用请求立即需要更多的内存为用户服务。在那个进程中运行的组件因此而被销毁掉。当这些组件再次为用户而工作时,这个进程将再次启动。
当决定哪个进程应该被杀掉时, Android系统会权衡这些进程对于用户而言的相对重要性。例如:相对于拥有一组可视 Activty的进程而言,一个持有一组不再对用户可见的 Activity的进程更应该被关闭。
Android系统试图让一个应用进程在系统中尽可能的长的被维持,但是最终为了新的或者更为重要的进程还是需要移除掉旧的进程以回收内存。为了决定哪个组件应该保留,哪个组件应该杀掉,系统会根据运行在进程中的组件以及这些组件运行的状态来将每一个进程放入一个“重要等级树”中。最低重要度的进程首先被杀掉,然后是次低级别重要度的进程,如此下去,根据恢复系统资源的必要性。
这里有五个重要度等级。下列清单列出重要性顺序的不同类型的进程 (第一个进程是最重要的且应该最后被杀 ):
1. 前台进程 (Foreground process)
用户正在请求的进程。当以下任何一个条件成立时,该进程被认为是前台进程 (Foreground process)。
l 他持有一个用户正在与之交互的 Activity(这个 Activity的 onResume()被调用了 )。
l 他持有一个服务,且该服务被绑定到一个正在与用户交互的 Activity了。
l 他持有一个服务,且该服务在前台运行,即该服务 startForground()调用。
l 他持有一个服务,且该服务正在执行其生命周期的回调方法 (onCreate(),onStart(),onDestroy())。
l 他持有一个 BroadcastReceiver,且其正在执行 onRecevie()方法。
通常,在一个给定的时间只有很少一些前台进程 (foreground process)存在。当系统内存如此溃泛以至于他们不能全部继续运行时,他们会依序最后被杀。通常,在那时,设备已达到内存分页的状态,所以要求杀掉一些前台进程以确保用户响应。
2. 可视进程 (Visible process)
一个可视进程 (visible process)没有任何前台组件,但其仍然在屏幕上对用户可见。当以下任何一个条件成立时,该进程被认为是可视进程 (visible process)。
l 他持有一个 Activity,且该 Activity没有处于前台,但是对于用户而言他仍然可见 (他的 onPause()方法被调用 )。这是可能发生的,例如,一个前台 activity启动了一个对话框,他允许他前面的 activity可见。
l 他持有一个服务,且该服务被绑定到一个可视 (或一个前台 )activity。
一个可视进程 (visible process)被认为是极其重要的,只有为了让所有的前台进程保持运行才有可能会杀掉他。
3. 服务进程 (Service process)
一个进程他正运行一个由 startService()方法启动的服务,并且没有被掉入到两个更高级别的类别中。尽管服务进程没有直接让用户看见,但他通常做一些用户关心的事情(比如在后台播放音乐,或者在网终下载文件),所以系统会让他保持运行直到这里没有足够的内存为前台进程和可见进程保留。
4. 后台进程 (Background process)
一个后台进程持有一个对用户不可见的 Activity(他的 onStop()方法已经调用了 )。这些进程不再直接影响到用户体验,系统随时可以杀死他们为前台,可示,服务进程回收内存。通常会有许多后台进程运行,所以他们保存在一个 LRU(least recently used)列表中以确保最后被杀的这个进程是拥有最后一个最近被用户看到的 activity。如果一个 Activity的生命周期方法被 正确的实现了,保存他的当前状态,杀掉他的进程对于用户体验不会有显著的影响,因为当用户回到这个 Activity,这个 Activity会恢复他的所有的可视的状态。
5. 空进程 (Empty process)
一个进程不再持有任何活动的应用组件。保持这种类型的进程活动的唯一理由是缓存目的,为了改善下次运行该进程中的一个组件的启动时间。系统经常杀死这些进程在进程缓存与内核缓存之间保持整个系统资源的平衡。
Android会将一个进程的等级根据这个进程中的激活的组件的重要度定义为他可以被定义的最高级。例如,一个进程拥有一个服务和一个可示的 activity,该进程会被定义为可示进程,而不是服务进程。
另外,一个进程的等级可以由于别的进程的依赖而增加。一个服务于另一个进程的进程永远不能比另一个进程的等级低。例如,一个来自于进程 A中的 Content Provider服务于进程 B中的一个客户端 ,或者进程 A中的一个服务被绑定到 B中的一个组件,进程 A被认为其重要度不低于进程 B。
因为运行一个服务的进程的等级高于运行一个后台 Activity的进程的等级,一个活动准备执行一个长时间的操作可能最好是为这个操作启动 一个服务,而不是简单的创建一个工作者线程,特别是当这个操作可能会超过 Activity的生命期。例如,一个 Activity上传一个图片到一个 Web站点,应该启动一个服务来执行上传,这样当用户离开这个 Activity后,上传的操作仍然可以在后台继续。使用一个服务来守护这个操作,不论 Activity发生了什么,使其至少拥有服务进程 (service process)的优先级。这是同一个理由对于广播接收者 (Broadcast receiver)应该使用服务而不是简单的启用一个工作者线程。
当一个应用启动了,系统会创建一个线程来执行这个应用,这个线程叫主 (main)线程。这个线程非常重要,因为他负责分配事件到合适的用户接口,包括绘图事件。他也是你的应用与来自 Android的 toolkit的组件 (来自 android.widget与 android.view包的组件 )交互的线程。这样,主线程有时也叫 UI线程。
系统不会为应用每个组件实例单独创建独立的线程。所有的组件运行在同一个线程中,并且都在 UI线程中实例化,系统调用发送到从线程调度的每一个组件。所以,响应系统调用的方法 (比如 onKeyDown()报告用户的行为,或者一个生命周期的回调 )总是在该进程的 UI线程中运行。
例如,当用户触摸屏幕上的一个按钮时,你的应用的 UI线程分发你的触摸事件到那个 widget, widget依次设置他的 pressed状态,并且发送一个 invalidate请求到事件队列。然后 UI线程出队并通知 widget他们应该重绘他自己。
当你的应用程序执行密集的工作来响应用户交互,这个单一的线程模型可以产生较差的性能,除非你适合的实现了你的应用程序 。特别地,如果所有的事情都在 UI线程中发生,执行长耗时的操作如网络访问或者数据查询可能会阻塞整个 UI线程。当线程阻塞了,没有事件可以被派发,包括绘图事件。从用户角度来看,应用似乎被挂起了。更糟糕的是,如果 UI线程阻塞的时间超过了一定的时间 (现在大约设置为 5秒 ),用户将会看到一个臭名昭著的“ application not response”(ANR)对话框。用户可能会决定退出你的应用并且删除他。
另外, Android的 UI toolkit不是的线程安全的。所以,你决不允许从你的工作线程来操作你的 UI,对你的用户接口做所有的操作必须在 UI线程中完成。因此,对于单线程模型这里有两条规则:
1. 不允许阻塞 UI线程
2. 不允许从 UI线程之外来访问 Android的 toolkit。
由于以上描述的单线程模型,你的应用的 UI的响应性是至关重要的,你决不能阻塞掉你的 UI线程。如果你有要执行一个非瞬间性的操作,你应该确保将他放到单独的线程中去执行 (后台 (background)或者工作者线程 (worker thread))。
例如,以下代码是一个点击的监听者,他从一个单独的线程中下载图片并将他显示到 ImageView中。
首先,看起他们很好的工作,因为他创建了一个新的线程来处理网络操作。然而,他违背了单线程模型的第二条原则 : 不允许从 UI线程之外来访问 Android的 toolkit。这个例子修改 ImageView是在工作者线程中而不是 UI线程中。这会导致没有定义的与异常的行为,去追踪他将会困难且耗时。
解决这个问题, Android提供了几种办法从别的线程来访问 UI线程。这里列出一组可供帮助的方法:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable,long)
例如,你可以通过 view.post(Runnable)方法来修复以上代码:
现在这个实现是线程安全的了。网络操作是从一个独立的线程中完成的, ImageView也是在 UI线程中操作的。
然而,随着操作的复杂性的增加,这种代码会变的复杂和难以维护。去处理与工作者线程复杂的交互,你可能要考虑在你的工作者线程中使用 Handler来处理从 UI线程中发送过来的消息。也许最好的解决方案,或者是扩展 AsyncTask类,他简化了工作者线程与 UI线程交互的操作。
AsyncTask允许你在你的用户接口上执行异步工作。他在工作者线程中执行阻塞操作,然后将结果发布给 UI线程,不需要你自己处理线程或者 handler。
为了使用他,你需要继承 AsyncTask并且实现他的 doInBackground()回调方法,他在一个后台线程池中运行。为了更新你的 UI,你需要实现 onPostExecute()方法,他传递来自 doInBackgroud()的结果且在 UI线程中运行。所以你可以安全的更新你的 UI。你可以在 UI线程通过 execute()来运行你的任务。
例如,你可以使用 AsyncTask的方法来实现以上的代码,
现在的UI是安全的,并且代码也更简单了,因为他将哪部分应该交由工作线线程做哪部分交由UI线程做的工作分开了。
AsyncTask如何工作的概述如下:
你可以使用泛 型 指定参数的类型,进度值的类型,和任务的最终值
doInBackgroud() 方法自动在工作者线程中执行。
onPreExecute() , onPostExecute() , onProgressUpdate() 在 UI 线程中被激活。
doInBackground() 的值被发到 onPostExecuted() 中。
任何时候你都可以在 doInBackground() 中调用 publishProgress() 去执行 UI 线程的 onProgressUpdate() 方法。
任何时候你可以从任何线程取消任务。
在一些情况下,你实现的方法可能会被多个线程调用,因此他应该设计为线程安全的。
这是真实的,一些方法可能会被远程调用,比如绑定服务 (bound service)的一些方法。当一个方法 (在一个 IBinder中实现的 )的调用发起于同一个进程 (IBinder正运行的 ),这个方法在调用者线程中执行。然而,当这个调用发起另一个进程,这个方法执行的线程是,系统在同一进程中作为 IBinder维护的线程池中选出的, (他不在这个进程的 UI线程中执行 )。例如,鉴于一个服务的 onBind()方法可能从服务所在的进程调用,在 onBind()返回的对象的方法 (例如,一个子类实现了 RPC的方法 )应该从线程池中调用。因为一个服务可以有多个客户端,多个线程池的线程可能同时访问同一个 IBinder方法。因此, IBinder方法必须实现为线程安全的。
类似的, content provider可能接受从别的进程发启数据请求。尽管 ContentResolver与 ContentProvider类隐藏了进程间通信如何管理的细节, ContentProvider方法响应这些请求,它的方法有, query(), insert(), delete(), udpate(),和 getType(),这些都会在 ContentProvider进程的线程池中被调用,而不是进程的 UI线程。由于这些方法可能被多个线程同时调用,他们也需要实现为线程安全的。
Android 使用远程程式调用 (RPC)提供了一个进程间通信 (IPC),通过这种方式可以调用别的 activity或者别的应用组件,但是远程的 (在别的进程中的 ) 执行,所有的结果将返回给调用者。这需要分解一个方法调用和他的数据到一个操作系统可以理解的级别,将他从本进程和地址空间转递给远程的进程和地址空间,然后在那里重新组装和执行这个调用。然后返回值按相反的方向传递。 Android提供了所有的来完成这些 IPC传输的代码,所以你只需关注于定义和实现 RPC的接口。