关于 Android 进程和线程,你必须了解的东西

前言

按照操作系统中的描述。线程是 CPU 调度的最小单元,同时线程也是一种有限的资源。而进程一般指一个执行单元,在 PC 和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程。对于 Android 来说,它是一种基于 Linux 内核的移动操作系统,它的进程和线程有着其特有的性质。我们这篇文章就来聊聊关于 Android 中的进程和线程,我们需要了解的知识。

进程


当一个程序第一次启动的时候,Android 会启动一个 Linux 进程和一个主线程。默认情况下,同一应用的所有组件均在相同的进程中运行,且大多数应用都不会改变这一点。如果我们发现需要控制某个组件所属的进程,则可在清单文件中执行此操作。

组件运行在哪个进程中,是在 AndroidManifest 文件中进行设置的,<activity>、<service>、<receiver> 和 <provider> 均支持 android:process 属性,此属性可以指定该组件应在哪个进程运行。我们可以设置此属性,使每个组件均在各自的进程中运行,或者使一些组件共享一个进程,而其他组件则不共享。此外,<application> 元素还支持 android:process 属性,用来设置适用于所有组件的默认值。

进程的优先级

Android 系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终需要移除旧进程来回收内存。为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入 “重要性层次结构” 中。必要时,系统会首先消除重要性最低的进程,然后是重要性相对较高的进程,以此类推,以回收进程。

重要性层次结构一共有 5 级

1、前台进程 — Foreground process

用户当前操作所必需的进程。如果一个进程满足以下任一条件,即是为前台进程:

  • 托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)
  • 托管某个 Service,后者绑定到用户正在交互的 Activity
  • 托管正在 “前台” 运行的 Service(服务已调用 startForeground())
  • 托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
  • 托管正执行其 onReceive() 方法的 BroadcastRecevier

通常,在任意给定时间前台进程都为数不多。只有在内存不足以支撑他们同时运行这一万不得已的情况下,系统才会终止它们。此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。

2、可见进程 — Visible process

  没有任何前台组件、但仍会影响用户在屏幕上所见内存的进程

  • 托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)
  • 托管绑定到可见(或前台)Activity 的 Service

可见进程被视为极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。

3、服务进程 — Service process

正在运行已使用 startService() 方法启动的服务且不属于和上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但它们通常在「执行一些用户关心的操作」(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维护所有前台进程和可见进程同时运行,否则会让服务进程保持运行状态。

4、后台进程 — Background process

包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。

5、空进程 — Empty process

不含任意活动应用组件的进程。保留这种进程的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

比较常见的使用场景

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

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

线程


线程在 Android 中是一个很重要的概念,从用途上来说,线程分为主线程和子线程,主线程的作用是「运行四大组件以及处理它们和用户的交互」,而子线程的作用则是「执行耗时任务,比如网络请求、I/O 操作等」,由于 Android 的特性,如果在主线程中执行耗时操作那么就会导致程序无法及时地响应。因此耗时操作必须放在子线程中执行。

Android 中的线程形态

除了 Thread 本身以外,在 Android 中可以扮演线程角色的还有很多,比如 AsyncTask 和 IntentService,同时 HandlerThread 也是一种特殊的线程。尽管 AsyncTask、IntentService 以及 HandlerThread 的「表现形式」都有别于传统的线程,但是它们的本质仍然是传统的线程。对于 AsyncTask 来说,它的底层用到了线程池,对于 IntentService 和 HandlerThread 来说,他们的底层则直接使用了线程。

不同形式的线程虽然都是线程,但是它们仍然有不同的特性和使用场景。AsyncTask 封装了线程池和 Handler,它主要是为了方便开发者在子线程中更新 UI。HandlerThread 是一种具有消息循环的线程,在它的内部可以使用 Handler。IntentService 内部采用 HandlerThread 来执行任务,当任务执行完毕后 IntentService 会自动退出。

从任务执行的角度来看,IntentService 的作用很像一个后台线程,但是 IntentService 是一种服务,它不容易被系统杀死从而可以尽量保证任务的执行,而如果是一个后台线程的话,由于这个时候进程中没有活动的四大组件,那么这个进程的优先级就会非常低,会很容易被系统杀死,这就是 IntentService 的优点。

主线程的一些事

从 Android 3.0 开始,系统要求网络访问必须在子线程中进行,否则网络访问将会失败并抛出 NetworkOnMainThreadException 这个异常,这样做是为了避免主线程由于被耗时操作阻塞从而出现 ANR 现象。

而 Android 规定访问 UI 只能在主线程中进行,如果在子线程中访问 UI,那么程序就回抛出异常。ViewRootImpl 对 UI 操作做了验证,这个验证工作是由 ViewRootImpl 的 checkThread() 方法来完成的。

void checkThread(){
      if(mThread != Thread.currentThread()){
          throw new CalledFromWrongThreadException(
                 "Only the original thread that created a view hierarch can
                  ouch its views.");
      }
}

Android 系统为什么不允许在子线程中访问 UI 呢?这是因为 Android 的 UI 控件不是线程安全的,如果在多线程中并发访问可能会导致 UI 控件处于不可预期的状态,那为什么系统不对 UI 控件的访问加上锁机制呢?

缺点有两个

  • 加上锁机制会让 UI 访问的逻辑变得复杂

  • 锁机制会降低 UI 访问的效率

鉴于这两个缺点,最简单且高效的方法就是采用单线程模型来处理 UI 操作,对于开发者来说也不是很麻烦,只是需要通过 Handler 切换一下 UI 的访问执行线程即可。


参考资料

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