进程和线程

来源:互联网 发布:怎样把淘宝店用户过户 编辑:程序博客网 时间:2024/06/06 16:30

当一个应用程序组件启动和这个应用程序没有其他的组件在运行时,Android系统会为这个应用程序启动一个新的Linux进程来单独执行它。默认情况下,相同应用程序的全部组件运行在同一个进程和线程(即主线程,UI线程)中。如果一个应用程序组件启动并且该应用程序已经存在一个进程(因为其他属于这个应用程序的组件已经存在),那么这个组件将会启动在这个进程中秉承使用相同的UI线程(主线程)来执行。然而,你可以安排你的应用程序中的不同组件运行到其他独立的进程中,并且还可以创建其他的线程来进行其他操作。

进程

在默认情况下,相同应用程序的全部组件运行在相同的进程中并且大多数应用程序不应该去改变这个情况。然而,如果你发现你需要控制一个组件所属于的进程,那么你需要再配置文件中配置一下。

在配置文件中,每种组件元素<Activity>,<Service>,<Receiver>和<Provider>都支持android:process属性,这个属性能够指定组件应该运行在的进程。你可以设置这个属性来让每个组件运行在自己的进程中,或者让某些组件共享一个进程同时不让其他的进程共享。你也能够设置android:process属性来让不同应用程序的组件运行在相同的进程中——为应用程序提供相同的共享Linux用户ID,并且分配相同的证明。

<application>元素也支持android:process属性,它能够设置一个默认值供全部组件使用。

Android系统有时需要杀死一个进程,例如,在内存过低并且请求其他更需要马上为用户服务的进程时。应用程序运行在这个被杀死的进程中的组件将会因此被销毁。当需要重新使用这些组件时,进程将会重新为他们开启。当需要杀死进程时,Android系统将会权衡哪些进程对用户来说相对重要。举个例子,相比那些还存在可见Activity的进程,Android系统会更乐意杀死一个所有Activity都不再可见的进程。因此,决定终止哪个进程将会视运行在该进程中的组件的状态而定。接下来将会讨论如何决定终止哪个进程的规则。

进程的生命周期

Android系统尝试尽可能的维持一个应用程序进程长时间存在,但最终还是需要为新开的或者更重要的进程移除旧的进程来回收内存。为了决定保留哪个进程和杀死哪个进程,系统会将根据每个进程运行的组件和这些组件的状态来把这些进程进行重要性分级。因为回收系统资源的需要,拥有最低重要性的进程将会最先被杀死,接着会杀死重要性次低的进程,以此类推。

重要性等级分为五个级别。下面的列表按重要性顺序排列了不同类型的进程(排第一的进程最重要,它会最慢被杀死)

1.     前台进程

一个进程当前必须要为用户做些什么。如果下列的状态都是真的话,那么进程将会被认定为是前台进程。

l  进程中有一个正在与用户交互的Activity。(Activity的onResume()方法被调用了)

l  进程中有一个Service绑定到正在与用户交互的Activity上

l  进程中有一个Service运行在前台。——Service调用了startForeground()方法

l  进程中有一个Service正在执行一个它的生命周期的回调方法(onCreate(),onStart(),onDestroy())

l  进程中有一个BroadcastReceiver正在执行它的onReceive()方法。

通常,在指定时间内,只有少数前台进程存在。他们只会在不得已的时候被杀死——如果内存太低了,以致不能支持全部进程运行,那么就会被杀。通常在那种情况下,设备已经达到了内存分页状态,因此,为了保持UI能够继续响应,就必须要杀死某些前台进程。

2.     可见进程

一个进程没有任何前台组件,但是依然能够影响到用户在屏幕上看到的。如果在下列状态都是真的情况下,那么这个进程被认为是可见的。

l  进程中有一个Activity不在前台但是依然对用户可见(它的onPause()方法被调用了)。例如:前台的Activity启动一个对话框,这个Activity可以在对话框后面看到。

l  进程中有一个Service绑定到了一个可见的(或前台的)Activity

一个可见的进程被认为是极度重要的,并且它不会被杀死,除非系统需要保持所有前台进程正常运行。

3.     服务进程

一个进程运行在一个已经通过startService()启动的Service中并且不属于上述两个范围。尽管Service进程不直接联系到任何用户可以看到的东西上,但是它通常在做一些用户关心的事情(例如后台播放音乐或者是从网络下载数据),因此系统会保持他们运行,除非没有足够的内存来维持它和全部前台进程以及可见进程。

4.     后台进程

一个进程为用户支撑了一个当前不可见的Activity(Activity的onStop()方法被调用了)。这些进程不会直接影响用户体验,并且能够随时杀死它们来回收内存供前台进程,可见进程或Service进程使用。通常情况下,会存在很多运行着的后台进程,它们被保存在最近使用列表中,以此来确定最近被用户浏览的Activity所在的进程最后被杀死。如果一个Activity正常的实现了它生命周期的方法,并且保存了它现在的状态,那么杀死它所在的进程并不会在用户体验上造成一个视觉影响,因为当用户导航返回这个activity时,activity会恢复它所有课件的状态。关于activity的状态保存和恢复的详细介绍,请参见Activities文档。

5.     空进程

一个进程不再包含任何活跃的应用程序组件。保留这种进程的唯一原因是出于缓存的目的,这样能够提高下次组件需要运行它的时候的启动时间。系统经常会为了平衡进程缓存和内核缓存之间的全部系统资源而把这些进程杀死。


线程

当一个应用程序启动时,系统将会为应用程序启动一个线程,这个线程被称为主线程。主线程是非常重要的,因为它负责把事件调度到适当的UI空间中,包括绘制事件。它也是在你的应用程序中,与Android UI工具包(android.widget和android.view包中的控件)中的控件进行交互的线程。因此,主线程有时也被称为UI线程。

系统并不会为每一个控件实例创建一个独立的线程。全部运行在同一个进程中的控件都是在UI线程中实例化的,系统是从UI线程中调用每个控件的。因此,那些响应系统回调的方法(例如onKsyDown()汇报用户的动作,还有一系列的生命周期回调方法)都是运行在UI线程所在的进程中的。

例如,当用户在屏幕上触摸了一个按钮时,应用程序的UI线程会分发这个触摸时间到该控件中,它会轮流设置它的按下状态并且发送一个刷新请求到事件队列中,UI线程把请求抽出队列并通知该控件需要重新绘制自己。

当你的应用程序执行一些高强度(耗时长的)工作来响应用户交互时,这种单线程模式可能会变得性能很差除非你能正确地实现你的应用程序。特别地,如果什么事都放在UI线程执行,执行一些耗时长的操作,例如访问网络或查询数据库,这样会阻塞整个UI线程。当UI线程被阻塞了,任何事件将都不会被UI线程分发了,包括绘制事件。从用户的角度看,这个应用程序挂掉了。更糟的是,如果UI线程被阻塞超过五秒钟,那么用户将会看到一个提示“应用程序没有反应”的对话框。然后用户可能会决定退出你的程序,如果他们不高兴了的话,还会卸载掉应用程序。

另外,Android UI 工具包并不是线程安全的。因此,你不能在一个工作线程中操作UI线程——你只能在UI线程中完成这些操作。因此,在Android的单线程模式中,提出了两条规则:

1.    不要阻塞UI线程

2.    不要在其他线程中访问Android 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线程外访问Android UI 工具包中的控件——这个例子在工作线程中修改了ImageView。这导致不明确和不被期望的行为,这些行为追踪起来将会很难,并且很费时。

为了修正这个问题,Android提供了几个从其他线程访问UI线程的方法。这是能够帮助到你的方法列表:

l  Activity.runOnUiThread(Runnable)

l  View.post(Runnable)

l  View.postDelayed(Runnable,long)

例如:你可以使用View.post(Runnale)方法来修改上面的代码:

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();}

现在这个实现就是线程安全的了:网络操作在一个独立的线程中完成,同时,在UI线程中对ImageView 进行了相关操作。

然而,随着操作的复杂度的提高,这种代码将会变得复杂并且难以维护。为了在工作线程中控制这些复杂的交互操作,可以考虑在工作线程中使用Handler来处理从UI线程发来的信息。尽管如此,但是最好的解决方法大概就是继承A市AsyncTask类,这样能够在工作线程中简化需要和UI进行交互的任务。

 

使用AsyncTask

AsyncTask允许你在UI中执行异步任务。它能够在不要求自己控制线程或是使用Handler的情况下,自己在工作线程中执行阻塞操作,然后在UI线程中发布结果。

为了使用它,你需要继承AsyncTask并实现doInBackground()回调方法,这个方法能够后台线程池中。为了刷新你的UI,你应该实现onPostExecute(),这个方法从doInBackground()中传递结果到UI线程中运行。因此,你能够安全的更新你的UI。然后你需要再UI线程中调用execute()方法来执行任务。

举个例子,你可以使用AsyncTask来实现之前的例子。
public void onClick(View v) {    new DownloadImageTask().execute("http://example.com/image.png");}private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {    /** The system calls this to perform work in a worker thread and      * delivers it the parameters given to AsyncTask.execute() */    protected Bitmap doInBackground(String... urls) {        return loadImageFromNetwork(urls[0]);    }        /** The system calls this to perform work in the UI thread and delivers      * the result from doInBackground() */    protected void onPostExecute(Bitmap result) {        mImageView.setImageBitmap(result);    }}

现在,UI就是安全的了,并且代码也更简单了,因为它分离了需要工作在工作线程的任务和需要再UI线程中完成的任务。

为了更全面的了解如何使用这个类,你应该认真阅读AsyncTask类的参考文档。不过这里提供了一个它是怎样工作的概括:

l  你可以使用反省来指定参数类型,进度值,还有最终的任务结果值。

l  doInBackground()方法自动执行在一个工作线程中

l  onPreExecute(),onPostExecute(),onProgressUpdate()全都在UI线程中调用。

l  doInBackground()方法返回的值被送到onPostExecute()方法中

l  你能在doInBackground()中随时调用publishProgress()来在UI线程中执行onProgressUpdate()

l  你可以在任何线程中随时删除这个任务

注意:你可能会遇上另外一个问题。当使用一个工作线程因为运行时配置改变(例如当用户改变屏幕方向)而意外重启时,将会销毁你的工作线程。你怎样才能在重启中保留你的任务和怎样在Activity销毁时,恰当地删除任务呢?请看一个简单应用程序Shelves的源代码。






0 0