一、进程和线程
当应用首个组件启动时,Android系统会使用单个执行线程为应用启动新的Linux进程。同一应用内默认所有组件运行在相同的进程和线程中,即主线程。但可以指定组件在单独的进程运行,并为任何进程创建额外线程。
二、进程
四大组件均可以通过android:process属性,指定其运行的进程,指定相同属性可使几个组件共享进程。也能使不同应用的组件在相同的进程中运行,前提是这些应用共享Linux用户ID并使用相同的证书签署。
<application>也支持该属性,指定所有组件均运行在该进程。
一般在内存不足时,且更重要的用户需要内存时,Android会决定关闭某些进程。其中运行的组件也被销毁。这些组件需要再次运行时,系统将重启进程。
Android根据进程对用户的重要程度来决定终止哪个进程。倾向于关闭托管屏幕上不可见的Activity进程。因此,是否终止某个进程取决于该进程所运行组件的状态。
进程生命周期:
Android系统将尽量保持应用进程,但如果为了创建新进程,需要移除旧进程回收内存,系统会根据正在运行的组件以及这些组件的状态,将每个组件放入“重要性层次结构”中。必要时,会消除重要性最低的进程,之后是略逊的,以回收系统资源。
重要性层次结构(5层):
1.前台进程
用户当前操作所必须的进程。满足以下任一条件,视为前台进程:
1)托管用户正在交互的Activity(已调用onResume());
2)托管某个service,且该service被绑定到用户正在交互的activity;
3)托管正在前台运行的Service(即调用了startForeground());
4)托管正执行一个生命周期回调的Service(onCreate()、onStart()或onDestroy());
5)托管正执行onReceive()方法的BroadcastReceiver;
通常,在任意时间前台进程都不多。只有当内存不足以同时继续运行时,系统才会终止他们。此时,设备已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。
2.可见进程
没有任何前台组件、但仍会影响用户在屏幕上的所见内容。满足以下任一条件,视为可见进程:
1)托管不在前台、但仍对用户可见的Activity(已调用其onResume()方法)。例如前台activity启动了一个对话框,其后显示的activity,有可能发生这种情况。
2)托管绑定到可见(或前台)Activity的Service。
可见进程是极其重要的进程,除非为了维护所有前台进程同时运行必须终止,否则系统不会终止他们。
3.服务进程
正在运行使用startService()方法启动的服务且不属于上述两个更高类别的进程。服务进程不会被用户见到,但通常执行一些用户关心的操作(音乐及下载等)。除非内存不足以维持所有可见和前台进程同时运行,否则系统不会终止服务进程。
4.后台进程
对用户不可见的Activity的进程(已调用onStop())。这些进程对用户体验没有直接影响,系统可能随时终止它们。通常会有很多后台进程在运行,因此它们会保存在LRU(最近最少使用)列表中,以确保包含用户最近查看的Activity的进程最后被终止。当Activity正确实现了生命周期方法,保存了其当前状态,终止进程后回到该Activity时,会恢复其所有可见状态,不会对用户体验产生明显影响。
5.空进程
不含任何活动组件的进程。这种进程的唯一目的是用作缓存以缩短下次在其中运行组件所需的启动时间。为使系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。
Android会将进程评定为活动组件可能达到的最高级别。例如,进程托管服务和可见Activity,则会将此进程评定为可见进程,而非服务进程。
此外,一个进程的级别可能会因为其他进程对他的依赖有所提高,即服务于另一进程的进程其级别高于其所服务的进程。例如,进程A中的contentprovider为进程B中的客户端提供服务,或者进程A中的服务绑定到进程B中的组件,则进程A被视为至少与进程B同样重要。
服务进程级别高于后台进程,因此长时间运行的操作最好放在服务中,而不是使用在Activity中启动工作线程,当操作比Activity更加持久时更应如此。例如,正在将上传图片的Activity应该启动服务来执行上传,即使用户退出Activity,仍可在后台继续执行上传操作。使用服务可以保证,无论Activity发生什么情况,该操作至少具备“服务进程”优先级。同理,广播接收器也应使用服务,而不是将耗时操作放入线程中。
三、线程
1.UI线程
应用启动时,系统为应用创建名为主线程的执行线程。负责将事件分派给响应的用户界面小部件,包括绘图事件。也是应用与AndroidUI工具包组件(android.widget和android.view组件)进行交互的线程。因此,主线程也叫UI线程。
系统不会为每个组件实例创建单独的线程。运行于同一进程的所有组件均在UI线程中实例化,并且对每个组件的系统调用均由该线程进行分派。因此,响应系统回调的方法(如onKeyDown()或生命周期回调)始终在进程的UI线程中运行。
例如,用户触摸按钮,UI线程将触摸事件分派给小部件,而小部件反过来又设置其按下状态,并将失效请求发布到事件队列中。UI线程从对列中取消该请求并通知小部件重绘。
在应用执行繁重的任务时,这种单线程模式可能会导致性能低下。如果UI线程执行耗时操作(网络、数据库查询等)将会阻塞整个UI。一旦线程被阻塞,将无法分派事件,包括绘图事件。如果被阻塞超过几秒(约5s),将会显示ANR对话框。
此外,View并非线程安全的,只能通过UI线程操作用户界面。因此,Android的单线程模式必须遵守两条规则:
1)不要阻塞UI线程;
2)不要在UI线程外访问View。
2.工作线程
如果操作不能很快完成,为了不阻塞UI线程,则应确保在单独的线程中运行,即后台线程或工作线程。
Android提供了几种途径来从其他线程访问UI线程。
1)Activity.runOnUiThread(Runnable);
2)View.post(Runnable);
3)View.postDelayed(Runable, long);
当然,也可以考虑在工作线程中用Handler处理来自UI线程的消息。还有一种方案,即扩展AsyncTask类(文档上说是最好的解决方案),简化了与UI进行交互所需执行的工作线程任务。
3.AsyncTask
AsyncTask允许对用户界面执行异步操作(其实也是在子线程执行异步操作,然后到UI线程更新界面)。其会先阻塞工作线程中的操作,然后在UI线程中发布结果,无需手动处理线程或程序。
通过创建AsyncTask子类并实现必要的回调方法。
public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
/** 系统自动在工作线程中调用,并且传递AsyncTask.execute()方法传递来的参数 */
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
/** 系统在UI线程进行调用,并且传递doInBackground()方法的返回结果 */
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
任务分为两部分:一部分在工作线程内完成,另一部分在UI线程内完成。
1.可以使用泛型指定参数类型、进度值和任务最终值;
2.doInBackground()方法在工作线程中自动执行;
3.onPreExecute()、onPostExecute()和onProgressUpdate()在UI线程中调用;
4.donInBackgroun()返回的值将发送到onPostExecute();
5.可以随时在doInBackground()中调用publishProgress(),以在UI线程中执行onProgressUpdate();
6.可以随时取消任何线程中的任务。
注意:使用工作线程时可能会遇到另一个问题,即:运行时配置变更(屏幕旋转等)导致Activity意外重启,这可能会销毁工作线程。
4.线程安全方法
某些情况下,方法可能会从多个线程调用,因此编写时要确保其线程安全。
这一点适用于可以远程调用的方法。如果对IBinder中所实现方法的调用来自运行IBinder的同一进程,则方法在调用者所在的线程中执行。但如果来自其他进程,则该方法将在线程池中的某个线程中执行,而非UI线程,线程池由系统在与IBinder相同的进程中维护。例如,即使服务的onBind()方法将从服务进程的UI线程调用,在onBind()返回的对象中实现的方法(例如,实现PRC方法的子类)仍会从线程池中的线程调用。由于一个服务可以有多个客户端,因此可能会有多个池线在同一时间使用同一IBinder方法。因此,IBinder方法必须实现为线程安全方法。
同样,contentprovider也可接受来自其他进程的数据请求。尽管ContentResolver和ContentProvider类隐藏了如何管理进程间通信的细节,但响应这些请求的ContentProvider方法(CRUD和getType()方法)将从contentProvider所在进程的线程池中调用,而不是从进程的UI线程调用。由于这些方法可能会同时从任意数量的线程调用,因此它们必须实现为线程安全方法。
四、进程间通信
Android利用远程过程调用(RPC)提供了一种进程间通信(IPC)机制,通过这种机制,由Activity或者其它应用组件调用的方法将(在其它进程中)远程执行,而所有结果将返回给调用者。这就要求把方法调用及其数据分解至操作系统可以识别的程度,并将其从本地进程和地址空间传输至远程进程和地址空间,然后在远程进程中重新组装并执行该调用。然后,返回值将沿相反方向传输回来。Android提供了执行这些IPC事务所需的全部代码,因此我们只需关注定义和实现RPC编程接口。