Android的进程和线程

当某个应用组件启动且该应用没有启动其他任何组件时,Android系统会使用单个执行线程为应用启动新的Linux进程。默认情况下,同一应用的所有组件运行在相同的进程和线程中(称为“主”进程)。 如果某个应用组件启动且该应用已存在进程(因为存在其他组件),则该组件会在此进程中启动并使用相同的执行线程。但,可以安排其他组件在单独的进程中,并为任何进程创建额外的线程。


进程

如果需要控制某个组件所属的进程,可在清单文件中执行此操作。

各组件元素均支持android:process属性,此属性可以指定该组件应在哪个进程运行。具体地,可以

  • 使每个组件均在各自的进程中运行
  • 使一些组件共享一个进程,而其他组件则不共享
  • 使不同应用的组件在相同的进程中运行,前提是这些应用共享相同的Linux用户ID并使用相同的证书进行签署

此外,<application>元素也支持android:process属性,以设置适用于所有组件的默认值。

如果内存不足,而其他为用户提供更紧急的服务的进程又需要内存时,Android可能会决定在某一时刻关闭某一进程。被终止的进程中的所有组件也会随之销毁。当这些组件需要再次运行时,系统将为它们重启进程。

决定终止哪个进程时,Android系统会权衡它们对用户的相对重要程度。

进程生命周期

Android会根据进程中国正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中,必要时,将会首先从重要性最低的进程开始,依次回收系统资源。

重要性层次结构共分5级,以下按照重要程度列出了各类进程(第一个最重要,最后被终止):

1. 前台进程

用户当前操作所必需的进程。如果一个进程满足以下任一条件,即视为前台进程:
- 托管用户正在交互的Activity(已调用Activity的onResume()方法)
- 托管某个Service,后者绑定到用户正在交互的Activity
- 托管正在“前台”运行的Service(服务已调用startForegound())
- 托管正执行一个生命周期回调的Service(onCreate()、onStart()或onDestroy())
- 托管正执行其onReceive()方法的BroadcastReceiver

2. 可见进程

没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。满足以下任一条件,即视为可见进程:
- 托管不在前台、但仍对用户可见的Activity(已调用其onPause()方法)
- 托管绑定到可见(或前台)Activity的Service
可见进程只有在为了维持所有前台进程同时运行时才会被终止。

3. 服务进程

正在运行的已使用startService()方法启动的服务且不属于以上两个更高级别进程的进程。

4. 后台进程

包含目前对用户不可见的Activity的进程(已调用Activity的onStop()方法)。
这些进程对用户体验没有直接影响,它们会保存在LRU(最近最少使用)列表中,
以确保包含用户最近查看的Activity的进程最后一个被终止。

5. 空进程

不包含任何活动应用组件的进程。保留这种进程的唯一目的是用作缓存,
以缩短下次再其中运行组件的启动时间。

根据进程中当前活动组件的重要程度,Android会将进程评定为其中的最高级别。

此外,一个进程的级别可能会因其他进程对它的依赖而有所提高,即服务于另一个进程的进程其级别永远不会低于其所服务的进程。

由于运行服务的进程的级别高于托管后台Activity的进程,启动长时间运行操作的Activity最好为该操作启动服务,而不是简单的创建工作线程,当操作有可能比Activity更加持久时尤要如此。

例如,正在将图片上传到网站的Activity应该启动服务来执行上传,这样一来,即使用户退出了Activity,仍可在后台继续执行上传操作。使用服务可以保证,无论Activity发生什么情况,该操作至少具备“服务进程”优先级。同理,广播接收器也应使用服务,而不是简单地将耗时操作放入线程。


线程

应用启动时,系统会为应用创建一个名为“主线程”的执行线程。此线程非常重要,因为它负责将时间分派给相应的用户界面小部件,其中包括绘图事件。此外,它也是应用与AndroidUI工具包组件(来自android.widget和android.view软件包的组件)进行交互的线程。因此,主线程有时也称为UI线程。

系统不会为每个组件创建单独的线程,运行于同一进程的所有组件都在UI线程中实例化,并且对每个组件的系统调用均由该线程进行分派。因此,响应回调的方法(如onKeyDown()或生命周期回调方法)始终在进程的UI线程中运行。

UI线程不应执行耗时操作或者阻塞性操作,如果UI线程被阻塞超过大约5秒钟,用户将会看到ANR(Application Not Response)对话框。

此外,Android UI工具包并非线程安全工具包。因此,不得通过工作线程操纵UI,而只能通过UI线程操纵用户界面。因此,Android的单线程模式必须遵守两条规则:

  1. 不要阻塞UI线程
  2. 不要在UI线程之外访问Android UI工具包

工作线程

根据上述单线程模式,要保证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工具包——此示例从工作线程(而不是UI线程)修改了ImageView。这可能导致出现不明确、不可预见的行为,但要跟踪此行为困难而又费时。

为解决此问题,Android提供了几种途径来从其他线程访问UI线程。如:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable, long)

比如,可以通过使用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();
}

现在,上述实现属于线程安全:在单独的线程中完成网络操作,而在UI线程中操纵ImageView。

但是,这类代码可能会难以维护。要通过工作线程处理更复杂的交互,可以考虑在工作线程中使用Handler处理来UI线程的消息。但是,最好的解决方案或许是扩展AsyncTask类,此类简化了与UI进行交互所需执行的工作线程。

使用AsyncTask

AsyncTask允许对用户界面执行异步操作。它会首先阻塞工作线程中的操作,然后在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线程内完成。

AsyncTask的工作方法简要概述:

  • 可以使用泛型指定类型参数、进度值和任务最终值
  • 方法doInBackground()会在工作线程上自动运行
  • onPreExecute()、onPostExecute()和onProgressUpdate()均在UI线程中调用
  • doInBackground()返回的值将发送到onPostExecute()
  • 可以随时在doInBackground()中调用publishProgress(),以在UI线程中执行onProgressUpdate()
  • 可以随时取消任何线程中的任务

注意:使用工作线程可能会遇到另一个问题,即:运行时配置变更(例如,用户更改了屏幕方向)导致Activity意外重启,这可能会销毁工作线程。要了解如何在这种重启情况下继续执行任务,以及如何在Activity被销毁时正确地取消任务,参见示例(http://code.google.com/p/shelves/)源代码。

线程安全方法

对于可以远程调用的方法,如绑定服务中的方法,如果对IBinder中所实现方法的调用源自运行IBinder的同一进程,则该方法在调用方的线程中执行。

但是,如果调用源自其他进程,则该方法将在从线程池选择的某个线程中执行(而不是进程的UI线程中执行),线程池由系统在与IBinder相同的进程中维护。

例如,即使服务的onBind()方法将从服务进程的UI线程调用,在onBind()返回的对象中实现的方法(例如,实现RPC方法的子类)仍会从线程池中的线程调用。

由于一个服务可以有多个客户端,因此可能会有多个线程池在同一时间使用同一IBinder方法。因此,IBinder方法必须实现为线程安全的方法。


进程间通信

Android利用远程过程调用(RPC)提供了一种进程间通信(IPC)机制,通过这种机制,由Activity或其他应用组件调用的方法将(或其他进程中)远程执行,而所有结果将返回给调用方。
这就要求

  • 把方法调用及其数据分解至操作系统可以识别的程度,并将其从本地进程和地址空间传输至远程进程和地址空间,
  • 然后在远程进程中重新组装并进行该调用,
  • 最后返回值将沿相反方向传输回来。

Android提供了执行这些IPC事物所需的全部代码,因此,只需集中精力定义和实现PRC编程接口即可。

要执行IPC,必须使用bindService()将应用绑定到服务上。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,918评论 25 707
  • 前言:本文所写的是博主的个人见解,如有错误或者不恰当之处,欢迎私信博主,加以改正!原文链接,demo链接 当某个应...
    PassersHowe阅读 531评论 0 1
  • React Native 是大脸书出品的一个移动开发框架, 可以用前端的技术, 写出 Android iOS 原生...
    王大屁帅2333阅读 583评论 1 1
  • 暑假招生紧, 伏天就下乡。 只身边远校, 头顶伴朝阳。 07.07.16
    开宗明义阅读 201评论 0 0
  • 2015*9*24 真正的离别是不忍想象的 我一直不想上大学有一个原因,我怕等我走远了再回来有些人可能就不在了,...
    不再抓你的手阅读 186评论 0 0