当应用的某一个组件(四大组件:Activity,Service,BroadcastReceiver,ContentProvider)启动同时应用没有其他的组件正在运行,也就是说应用的第一个组件启动的时候,安卓系统为这个应用开启一个新的Linux进程,这个进程中包含一个线程。默认情况下,一个应用的所有组件都运行在同一个进程的同一个线程下,这个线程叫主线程。但是不同的组件可以被人为地安排在不同的进程中,同时以可以为一个进程创建额外的线程。
进程
默认情况下,一个应用的所有组件都运行在同一个进程中,而且大部分应用不需要额外的进程。但是如果需要人为地控制不同的组件运行在不同的进程中,可以通过编辑manifest文件来完成这一点。
manifest文件中每一类组件<activity>
, <service>
, <receiver>
, 和<provider>都支持android:process属性,这个属性指明这个组件运行在某一个特定的进程中。通过设置android:process,可以让每一个组件运行在自己的进程中,或者一些组件共享同一个进程而其他组件共享另一个进程。甚至可以让不同应用的组件运行在同一个进程中,当然需要这些应用有相同的Linux ID并且签名相同。
在系统内存不足并且其他应用需要内存的时候,Android系统可能会终止一些进程。运行在这些进程中的组件也会同时被摧毁。这些组件重新启动的时候进程也会重新启动。
Android系统根据进程与用户之间的关系来决定终止哪一个进程。比如说,一个进程中Activities都不对用户可见,而另一个进程中的Activity对用户可见,那么前一个进程更有可能被终止。
进程生命周期
Android系统会尽可能长时间地保持应用进程,但是系统最终需要终止一下进程以启动更重要的进程。为了决定终止哪一个进程,系统对每一个进程进行重要程度的评估与分级。最低重要程度的进程会最先被终止,然后是次低重要程度的。
进程按重要程度被分为5个层次:
1. 前台进程
与用户正在进行交互的进程,一个进程称为前台进程需要满足这些条件:
进程拥有一个Activity,Activity正在与用户交互。(Activity的onResume()方法被调用)
进程拥有一个Service,Service与前台Activity绑定
进程拥有一个前台Service,Service调用了startForeground()函数
进程拥有一个Service,Service正在执行生命周期函数
进程拥有一个BroadcastReceiver,BroadcastReceiver正在执行onReceive()函数。
一般来说,某一时间只有少数几个前台进程存在。它们只有在非常极端的情况下才会被终止。
2.可见进程
可见进程指进程不是前台进程但是仍然能影响到用户观察到的内容。可见进程通常指下面这样的进程。
进程拥有一个Activity,Activity不是前台Activity但是依然对用户可见(调用了onPause()但是没有调用onStop())。通常指前台Activity被一个对话框覆盖的情况
进程拥有一个Service,Service与可见Activity绑定。
可见进程被认为是很重要的,一般不会被终止,除非一定要这样做来保证所有前台进程运行。
3.服务进程
进程拥有Service,Service调用了startService(),但是不属于前台进程和可见进程。虽然服务进程与用户界面没有直接的关系,但是它们也在做用户关心的行为,比如播放背景音乐,网络下载等。所以系统不会终止他们,除非系统没有足够的内存使得服务进程与前台进程,可见进程共存。
4.背景进程
进程拥有Activity,但是Activity对用户不可见(Activity的onStop()方法被调用)。这些进程对用户体验没有直接的影响,系统可以在任何时候终止他们来为前台进程,可见进程和服务进程提供足够的内存。通常系统中有很多背景进程,它们存贮在一个LRU(least recently used) 表中。如果Activity正确地实现了生命周期方法,终止背景进程不会对用户有任何影响。
5.空进程
不拥有任何活动的组件的进程,系统进程会终止这些进程。
Android系统对进程的评级总是按照进程能达到的最高级别。比如进程拥有一个service和一个可见Activity,这进程被评估为可见进程,而不是服务进程。
应用进程拥有Service会比拥有背景Activity获得更高的评级,所以,对于长时间的操作,Service会比Activity中创建的工作线程有优势。
线程
当应用启动时,系统为应用创建了一个执行线程,称为主线程。这个线程主要负责分发事件到合适的用户界面组件,绘制界面。主线程也是系统操作UI控件的线程,因此主线程也被称作UI线程。
系统并不会为每一个组件创造一个新的线程,所有的组件都运行在主线程中。每一个组件的系统调用在主线程中分发。因此响应系统调用的函数(比如onKeyDown()响应用户点击量后退按钮)总是运行在UI线程中。
当app执行高强度耗时长的工程时,单一的线程表现会很差。具体地说,如所有的事情都发生在UI线程中,长时间的操作比如网络连接,数据库查询将会阻塞整个UI线程。从用户的角度来看,系统似乎卡住了。更糟糕的是,如果UI线程阻塞了几秒钟(目前是大约5秒),用户会看到“application not responding"窗口。用户可能会因此推出应用。
另外,Android UI控件不是线程安全的,因此不能在其他线程中操作UI控件。所有的UI操作必须在UI线程中。因此关于Android线程两个最简单的规则是:
1.不要阻塞UI线程
2.不要在UI线程外(指其他线程)操作UI控件。
工作线程
如果需要执行长时间的行为,这些行为需要在其他的线程中执行,称作背景线程或工作线程。
比如,下面是一个例子:在工作线程中下载图片并显示在ImageView中。
public void onClick(View v) { new Thread(new Runnable() { public void run() { Bitmap b = loadImageFromNetwork("http://example.com/image.png"); mImageView.setImageBitmap(b); } }).start(); }
这个代码事实上是有问题的,因为它违背了第二条规则:不要在UI线程外(指其他线程)操作UI控件。代码中在工作线程修改了ImageView的状态,这会导致不确定的行为。
为了解决这个问题,Android提供了一些在工作现在中操作UI线程的方法,比如:
比如你可以用View.post(Runnable)来修正上面的代码:
public void onClick(View v) { new Thread(new Runnable() { public void run() { final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); mImageView.post(new Runnable() { public void run() { mImageView.setImageBitmap(bitmap); } }); } }).start(); }
现在这样的实现是线程安全的了,网络操作在工作线程中完成,而ImageView在UI线程中改变。
但是,随着操作复杂度的上升,这样的代码会变得负责而且难以维护。为了处理工作线程中更复杂的行为,使用UI线程中的Handler向UI线程传递信息会是一个不错的行为,同时,使用AsyncTask或许是最好的解决方法。