[Android] 进程(Process)和线程(Thread)

当应用的某一个组件(四大组件: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或许是最好的解决方法。

posted @ 2015-06-16 14:50  msh  阅读(1624)  评论(0编辑  收藏  举报