进程和线程

来源:互联网 发布:linux arping 编辑:程序博客网 时间:2024/05/17 08:54

打算换工作了,好多东西都忘掉了,因此这一系列的文章主要是为了面试,把一些基础的知识点捋一遍,眼看千遍不如手过一遍,我要把他们都写出来。打算用一个月的时间来准备面试!

今日主题:

进程和线程

先回顾下在项目开发中用到的跟进程和线程有关的东西:

  1. 为App开启多进程
  2. 进行跨进程通信IPC
  3. 进程优先级有哪些?
  4. 使用前台服务可以提高进程优先级
  5. 不要在主线程中进行耗时操作
  6. 不要在非主线程中更新UI
  7. 线程和线程池

想想上面的这些问题,进程和线程的知识在实际开发中用处还是挺多的,本文主要讲解关于进程和线程的相关内容!

其中主要涉及如下内容:

  1. Android中进程和线程的简介
  2. 进程以及如何开启进程
  3. 进程的生命周期有哪些?
  4. 线程
  5. 工作线程
  6. 线程安全方法
  7. 进程间通信

1. Android中进程和线程的简介

先看官方文档给出的解释:

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

我想说下我所理解的进程:

在Android中,只要一个App处于活动状态,那么这个App就存在至少一个进程,这还不包括为我们服务的各种系统进程,如果细说起来,那就多了。所谓的活动状态可以是在前台、后台,可以是Activity处于活动状态,也可以是Service处于活动状态!

再说下我所理解的线程:

在Android App中,至少存在一个主线程,如果我们开启了额外的线程进行耗时操作,那么久存在多个线程。

2. 进程以及如何开启进程

默认,同一应用的所有组件均在相同的进程中运行,且大多数应用都不会改变这一点。如果要控制某个组件所需的进程,可以在清单文件中进行配置。

那么如何配置组件所需的进程呢?

各类组件元素在清单文件条目activity,service,receiver 和 provider都支持android:process属性,这个属性可以指定该组件在哪个进程中运行。你可以设置此属性,使每个组件均在各自的进程中运行,或者使一些组件共享一个进程,而其他组件则不共享。此外,您还可以设置 android:process,使不同应用的组件在相同的进程中运行,但前提是这些应用共享相同的 Linux 用户 ID 并使用相同的证书进行签署。

此外,元素还支持android:process属性,以设置使用于所有组件的默认值。

在系统内存不足的情况下,系统会关闭一些进程为那些紧急需要内存的进程提供内存,那么关闭哪些进程呢?这就涉及到终止进程所用到的规则:进程的生命周期。

3. 进程的生命周期有哪些?

为了新建进程或运行更重要的进程,因此需要移除旧进程来回收内存。杀掉哪些进程来释放内存,这就要涉及到了进程的优先级即进程的生命周期。

下面来看看这几大生命周期:

  • 前台进程
    用户正在进程交互的进程。如果满足以下任一条件,即可视为前台进程:

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

    对于这些前台进程来说,不到万不得已,系统不会终止他们。那么,什么情况是万不得已的情况?

  • 可见进程
    没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。如果一个进程满足以下任一条件,可视为可见进程:

    • 托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。
    • 托管绑定到可见(或前台)Activity 的 Service。

除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。

  • 服务进程

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

  • 后台进程

包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。

  • 空进程

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

Notice:

由于运行服务的进程其级别高于托管后台 Activity 的进程,因此启动长时间运行操作的 Activity 最好为该操作启动服务,而不是简单地创建工作线程,当操作有可能比 Activity 更加持久时尤要如此。例如,正在将图片上传到网站的 Activity 应该启动服务来执行上传,这样一来,即使用户退出 Activity,仍可在后台继续执行上传操作。使用服务可以保证,无论 Activity 发生什么情况,该操作至少具备“服务进程”优先级。 同理,广播接收器也应使用服务,而不是简单地将耗时冗长的操作放入线程中。

4. 线程

android中线程主要分为:

  1. 主线程,也就是:UI线程
  2. 子线程,也就是:工作线程,当进行耗时操作的时候开启的线程。

在应用执行繁重的任务以响应用户交互时,除非正确实现应用,否则这种单线程模式可能会导致性能低下。具体来说,UI线程需要处理所有任务,则执行耗时很长的操作(例如,网络访问或数据库查询)将会阻塞整个UI。一旦线程被阻塞,将无法分派任何事件,包括绘图事件。这个时候,应用将会被挂起。如果UI线程被阻塞超过5秒中,就会抛出ANR。如果引起用户不满,他们可能会决定退出或卸载此应用。

此外,Android UI工具包并非线程安全工具包。因此,您不得通过工作线程操作UI,而只能通过UI线程操作用户界面。

因此,Android的单线程模式必须遵守两条规则:

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

工作线程

根据上面两个规则,看下面的代码有什么问题:

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)

因此,可以这样修改上面的代码:

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

随着操作日趋复杂,这类代码也会变得复杂且难以维护。 要通过工作线程处理更复杂的交互,可以考虑在工作线程中使用 Handler 处理来自 UI 线程的消息。当然,最好的解决方案或许是扩展 AsyncTask 类,此类简化了与 UI 进行交互所需执行的工作线程任务。

关于AsyncTask的内容,此篇不再赘述,参见另篇博客:

Android多线程知识总结——源码分析

5. 线程安全方法

在某些情况下,您实现的方法可能会从多个线程调用,因此编写这些方法时必须确保其满足线程安全的要求。
这一点主要适用于可以远程调用的方法,如绑定服务中的方法。如果对 IBinder 中所实现方法的调用源自运行 IBinder的同一进程,则该方法在调用方的线程中执行。但是,如果调用源自其他进程,则该方法将在从线程池选择的某个线程中执行(而不是在进程的 UI 线程中执行),线程池由系统在与 IBinder 相同的进程中维护。 例如,即使服务的 onBind() 方法将从服务进程的 UI 线程调用,在 onBind() 返回的对象中实现的方法(例如,实现 RPC 方法的子类)仍会从线程池中的线程调用。 由于一个服务可以有多个客户端,因此可能会有多个池线程在同一时间使用同一 IBinder方法。因此,IBinder 方法必须实现为线程安全方法。
同样,内容提供程序也可接收来自其他进程的数据请求。尽管 ContentResolver 和 ContentProvider 类隐藏了如何管理进程间通信的细节,但响应这些请求的 ContentProvider 方法(query()、insert()、delete()、update() 和 getType() 方法)将从内容提供程序所在进程的线程池中调用,而不是从进程的 UI 线程调用。 由于这些方法可能会同时从任意数量的线程调用,因此它们也必须实现为线程安全方法。

6. 进程间通信

Android 利用远程过程调用 (RPC) 提供了一种进程间通信 (IPC) 机制,通过这种机制,由 Activity 或其他应用组件调用的方法将(在其他进程中)远程执行,而所有结果将返回给调用方。 这就要求把方法调用及其数据分解至操作系统可以识别的程度,并将其从本地进程和地址空间传输至远程进程和地址空间,然后在远程进程中重新组装并执行该调用。 然后,返回值将沿相反方向传输回来。 Android 提供了执行这些 IPC 事务所需的全部代码,因此您只需集中精力定义和实现 RPC 编程接口即可。

关于进程间通信IPC、服务的内容将在下一篇博客中进行中介绍!

参考文档:

1.Google官方文档:进程和线程
2.[Android开发艺术探索第11章]
3.[Android开发艺术探索第2章]