Android进阶:Android的进程,线程

简介: 本文翻译自Android官方文档当一个Android应用程序组件启动时候,如果此时这个程序的其他组件没有正在运行,那么系统会为这个程序以单一线程的形式启动一个新的Linux 进程。


本文翻译自Android官方文档


当一个Android应用程序组件启动时候,如果此时这个程序的其他组件没有正在运行,那么系统会为这个程序以单一线程的形式启动一个新的Linux 进程。默认情况下,同一应用程序下的所有组件都运行再相同的进程和线程(一般称为程序的“主”线程)中。如果一个应用组件启动但这个应用的进程已经存在了(因为这个应用的其他组件已经在之前启动了),那么这个组件将会在这个进程中启动,同时在这个应用的主线程里面执行。然而,你也可以让你的应用里面的组件运行在不同的进程里面,也可以为任何进程添加额外的线程。

这片文章讨论了Android程序里面的进程和线程如何运作的。

进程


默认情况下,同一程序的所有组件都运行在相同的进程里面,大多数的应用都是这样的。然而,如果你发现你需要让你的程序里面的某个组件运行在特定的进程里面,你可以在manifest 文件里面设置。

manifest 文件里面为每一个组件元素—<activity><service><receiver>, 和<provider>—提供了 android:process 属性。通过设置这个属性你可以让组件运行在特定的进程中。你可以设置成每个组件运行在自己的进程中,也可以让一些组件共享一个进程而其他的不这样。你还可以设置成不同应用的组件运行在同一个进程里面—这样可以让这些应用共享相同的Linux user ID同时被相同的证书所认证。

<application> 元素也支持 android:process 属性,设置这个属性可以让这个应用里面的所有组件都默认继承这个属性。

Android 可能在系统剩余内存较少,而其他直接服务用户的进程又要申请内存的时候shut down 一个进程, 这时这个进程里面的组件也会依次被kill掉。当这些组件有新的任务到达时,他们对应的进程又会被启动。

在决定哪些进程需要被kill的时候,Android系统会权衡这些进程跟用户相关的重要性。比如,相对于那些承载这可见的activities的进程,系统会更容易的kill掉那些承载不再可见activities的进程。决定是否终结一个进程取决于这个进程里面的组件运行的状态。下面我们会讨论kill进程时所用到的一些规则。

进程的生命周期

作为一个多任务的系统,Android 当然系统能够尽可能长的保留一个应用进程。但是由于新的或者更重要的进程需要更多的内存,系统不得不逐渐终结老的进程来获取内存。为了声明哪些进程需要保留,哪些需要kill,系统根据这些进程里面的组件以及这些组件的状态为每个进程生成了一个“重要性层级” 。处于最低重要性层级的进程将会第一时间被清除,接着时重要性高一点,然后依此类推,根据系统需要来终结进程。

在这个重要性层级里面有5个等级。下面的列表按照重要性排序展示了不同类型的进程(第一种进程是最重要的,因此将会在最后被kill):

  1. Foreground 进程 一个正在和用户进行交互的进程。 如果一个进程处于下面的状态之一,那么我们可以把这个进程称为 foreground 进程:

    一般说来,任何时候,系统中只存在少数的 foreground 进程。 只有在系统内存特别紧张以至于都无法继续运行下去的时候,系统才会通过kill这些进程来缓解内存压力。在这样的时候系统必须kill一些 (Generally, at that point, the device has reached a memory paging state,这句如何翻译较好呢)foreground 进程来保证 用户的交互有响应。

  2. Visible 进程 一个进程没有任何 foreground 组件, 但是它还能影响屏幕上的显示。 如果一个进程处于下面的状态之一,那么我们可以把这个进程称为 visible 进程:
    • 进程包含了一个没有在foreground 状态的 Activity ,但是它仍然被用户可见 (它的 onPause() 方法已经被调用)。这种情况是有可能出现的,比如,一个 foreground activity 启动了一个 dialog,这样就会让之前的 activity 在dialog的后面部分可见。
    • 进程包含了一个绑定在一个visible(或者foreground)activity的 Service 。

    一个 visible 进程在系统中是相当重要的,只有在为了让所有的foreground 进程正常运行时才会考虑去kill visible 进程。

  3. Service 进程 一个包含着已经以 startService() 方法启动的 Service 的进程,同时还没有进入上面两种更高级别的种类。尽管 service 进程没有与任何用户所看到的直接关联,但是它们经常被用来做用户在意的事情(比如在后台播放音乐或者下载网络数据),所以系统也只会在为了保证所有的foreground and visible 进程正常运行时kill掉 service 进程。
  4. Background 进程 一个包含了已不可见的activity的 进程 (这个 activity 的 onStop() 已经被调用)。这样的进程不会直接影响用户的体验,系统也可以为了foreground 、visible 或者 service 进程随时kill掉它们。一般说来,系统中有许多的 background 进程在运行,所以将它们保持在一个LRU (least recently used)列表中可以确保用户最近看到的activity 所属的进程将会在最后被kill。如果一个 activity 正确的实现了它的生命周期回调函数,保存了自己的当前状态,那么kill这个activity所在的进程是不会对用户在视觉上的体验有影响的,因为当用户回退到这个 activity时,它的所有的可视状态将会被恢复。查看 Activities 可以获取更多如果保存和恢复状态的文档。
  5. Empty 进程 一个不包含任何活动的应用组件的进程。 这种进程存在的唯一理由就是缓存。为了提高一个组件的启动的时间需要让组件在这种进程里运行。为了平衡进程缓存和相关内核缓存的系统资源,系统需要kill这些进程。

Android是根据进程中组件的重要性尽可能高的来评级的。比如,如果一个进程包含来一个 service 和一个可见 activity,那么这个进程将会被评为 visible 进程,而不是 service 进程。

另外,一个进程的评级可能会因为其他依附在它上面的进程而被提升—一个服务其他进程的进程永远不会比它正在服务的进程评级低的。比如,如果进程A中的一个 content provider 正在为进程B中的客户端服务,或者如果进程A中的一个 service 绑定到进程B中的一个组件,进程A的评级会被系统认为至少比进程B要高。

因为进程里面运行着一个 service 的评级要比一个包含background activities的进程要高,所以当一个 activity 启动长时操作时,最好启动一个 service 来做这个操作,而不是简单的创建一个worker线程—特别是当这个长时操作可能会拖垮这个activity。比如,一个需要上传图片到一个网站的activity 应当开启一个来执行这个上传操作。这样的话,即使用户离开来这个activity也能保证上传动作在后台继续。使用 service 可以保证操作至少处于”service process” 这个优先级,无论这个activity发生了什么。这也是为什么 broadcast receivers 应该使用 services 而不是简单的将耗时的操作放到线程里面。

 

线程


当一个应用启动的时候,系统会为它创建一个线程,称为“主线程”。这个线程很重要因为它负责处理调度事件到相关的 user interface widgets,包括绘制事件。你的应用也是在这个线程里面与来自Android UI toolkit (包括来自 android.widget 和 android.view 包的组件)的组件进行交互。因此,这个主线程有时候也被称为 UI 线程。

系统没有为每个组件创建一个单独的线程。同一进程里面的所有组件都是在UI 线程里面被实例化的,系统对每个组件的调用都是用过这个线程进行调度的。所以,响应系统调用的方法(比如 onKeyDown() 方法是用来捕捉用户动作或者一个生命周期回调函数)都运行在进程的UI 线程里面。

比如,当用户点击屏幕上的按钮,你的应用的UI 线程会将这个点击事件传给 widget,接着这个widget设置它的按压状态,然后发送一个失效的请求到事件队列。这个UI 线程对请求进行出队操作,然后处理(通知这个widget重新绘制自己)。

当你的应用与用户交互对响应速度的要求比较高时,这个单线程模型可能会产生糟糕的效果(除非你很好的实现了你的应用)。特别是,当应用中所有的事情都发生在UI 线程里面,那些访问网络数据和数据库查询等长时操作都会阻塞整个UI线程。当整个线程被阻塞时,所有事件都不能被传递,包括绘制事件。这在用户看来,这个应用假死了。甚至更糟糕的是,如果UI 线程被阻塞几秒(当前是5秒)以上,系统将会弹出臭名昭著的 “application not responding” (ANR) 对话框。这时用户可能选择退出你的应用甚至卸载。

另外,Android的UI 线程不是线程安全的。所以你不能在一个worker 线程操作你的UI—你必须在UI线程上对你的UI进行操作。这有两条简单的关于Android单线程模型的规则:

  1. 不要阻塞 UI 线程
  2. 不要在非UI线程里访问 Android UI toolkit
Worker 线程

由于上面对单一线程模型的描述,保证应用界面的及时响应同时UI线程不被阻塞变得很重要。如果你不能让应用里面的操作短时被执行玩,那么你应该确保把这些操作放到独立的线程里(“background” or “worker” 线程)。

比如,下面这段代码在一个额外的线程里面下载图片并在一个 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 toolkit—这个例子在一个worker线程修改了 ImageView 。这会导致不可预期的结果,而且还难以调试。

为了修复这个问题,Android提供了几个方法从非UI线程访问Android UI toolkit 。详见下面的这个列表:

那么,你可以使用 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 进行操作。

然而,随着操作复杂性的增长,代码会变得越来越复杂,越来越难维护。为了用worker 线程处理更加复杂的交互,你可以考虑在worker线程中使用Handler ,用它来处理UI线程中的消息。也许最好的方案就是继承 AsyncTask 类,这个类简化了需要同UI进行交互的worker线程任务的执行。

使用 AsyncTask

AsyncTask 能让你在UI上进行异步操作。它在一个worker线程里进行一些阻塞操作然后把结果交给UI主线程,在这个过程中不需要你对线程或者handler进行处理。

使用它,你必须继承 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把worker线程里做的事和UI线程里要做的事分开了。

你应该阅读一下 AsyncTask 的参考文档以便更好的使用它。下面就是一个对 AsyncTask 如何作用的快速的总览:

注意: 你在使用worker线程的时候可能会碰到的另一个问题就是因为runtime configuration change (比如用户改变了屏幕的方向)导致你的activity不可预期的重启,这可能会kill掉你的worker线程。为了解决这个问题你可以参考 Shelves 这个项目。

相关文章
|
19天前
|
消息中间件 并行计算 安全
进程、线程、协程
【10月更文挑战第16天】进程、线程和协程是计算机程序执行的三种基本形式。进程是操作系统资源分配和调度的基本单位,具有独立的内存空间,稳定性高但资源消耗大。线程是进程内的执行单元,共享内存,轻量级且并发性好,但同步复杂。协程是用户态的轻量级调度单位,适用于高并发和IO密集型任务,资源消耗最小,但不支持多核并行。
37 1
|
3天前
|
Linux 调度 C语言
深入理解操作系统:进程和线程的管理
【10月更文挑战第32天】本文旨在通过浅显易懂的语言和实际代码示例,带领读者探索操作系统中进程与线程的奥秘。我们将从基础知识出发,逐步深入到它们在操作系统中的实现和管理机制,最终通过实践加深对这一核心概念的理解。无论你是编程新手还是希望复习相关知识的资深开发者,这篇文章都将为你提供有价值的见解。
|
5天前
深入理解操作系统:进程与线程的管理
【10月更文挑战第30天】操作系统是计算机系统的核心,它负责管理计算机硬件资源,为应用程序提供基础服务。本文将深入探讨操作系统中进程和线程的概念、区别以及它们在资源管理中的作用。通过本文的学习,读者将能够更好地理解操作系统的工作原理,并掌握进程和线程的管理技巧。
15 2
|
7天前
|
调度 Python
深入浅出操作系统:进程与线程的奥秘
【10月更文挑战第28天】在数字世界的幕后,操作系统悄无声息地扮演着关键角色。本文将拨开迷雾,深入探讨操作系统中的两个基本概念——进程和线程。我们将通过生动的比喻和直观的解释,揭示它们之间的差异与联系,并展示如何在实际应用中灵活运用这些知识。准备好了吗?让我们开始这段揭秘之旅!
|
1月前
|
存储 消息中间件 人工智能
进程,线程,协程 - 你了解多少?
本故事采用简洁明了的对话方式,尽洪荒之力让你在轻松无负担的氛围中,稍微深入地理解进程、线程和协程的相关原理知识
40 2
进程,线程,协程 - 你了解多少?
|
17天前
|
Python
Python中的多线程与多进程
本文将探讨Python中多线程和多进程的基本概念、使用场景以及实现方式。通过对比分析,我们将了解何时使用多线程或多进程更为合适,并提供一些实用的代码示例来帮助读者更好地理解这两种并发编程技术。
|
20天前
|
消息中间件 并行计算 安全
进程、线程、协程
【10月更文挑战第15天】进程、线程和协程是操作系统中三种不同的执行单元。进程是资源分配和调度的基本单位,每个进程有独立的内存空间;线程是进程内的执行路径,共享进程资源,切换成本较低;协程则更轻量,由用户态调度,适合处理高并发和IO密集型任务。进程提供高隔离性和安全性,线程支持高并发,协程则在资源消耗和调度灵活性方面表现优异。
43 2
|
24天前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
40 4
|
2月前
|
存储 消息中间件 资源调度
「offer来了」进程线程有啥关系?10个知识点带你巩固操作系统基础知识
该文章总结了操作系统基础知识中的十个关键知识点,涵盖了进程与线程的概念及区别、进程间通信方式、线程同步机制、死锁现象及其预防方法、进程状态等内容,并通过具体实例帮助理解这些概念。
「offer来了」进程线程有啥关系?10个知识点带你巩固操作系统基础知识
|
26天前
|
算法 安全 调度
深入理解操作系统:进程与线程的管理
【10月更文挑战第9天】在数字世界的心脏跳动着的,不是别的,正是操作系统。它如同一位无形的指挥家,协调着硬件与软件的和谐合作。本文将揭开操作系统中进程与线程管理的神秘面纱,通过浅显易懂的语言和生动的比喻,带你走进这一复杂而又精妙的世界。我们将从进程的诞生讲起,探索线程的微妙关系,直至深入内核,理解调度算法的智慧。让我们一起跟随代码的脚步,解锁操作系统的更多秘密。
34 1