继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

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

一只斗牛犬
关注TA
已关注
手记 494
粉丝 49
获赞 300

前言

按照操作系统中的描述。线程是 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 的访问执行线程即可。

原文链接:http://www.apkbus.com/blog-866962-77967.html

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP