线程、进程概念与Android系统组件的关系

Android系统是Google公司基于Linux内核开发的开源手机操作系统。通过利用 Linux 内核的优势,Android 系统使用了大量操作系统服务,包括进程管理、内存管理、网络堆栈、驱动程序、安全性等相关的服务。所以从这个角度来看,Android系统的线程和进程概念是Linux系统线程、进程的映射。

下面是操作系统层面进程和线程的概念解释。

进程(Process,从操作系统核心角度来说,进程是应用程序的一个运行活动过程,是操作系统资源管理的实体。进程是操作系统分配和调度系统内存、CPU时间片等资源的基本单位,为正在运行的应用程序提供运行环境。一个进程至少包括一个线程。每个进程都有自己独立的内存地址空间。

线程(Thread,线程是进程内部执行代码的实体,它是CPU调度资源的最小单元,一个进程内部可以有多个线程并发运行。线程没有自己独立的内存资源,它只有自己的执行堆栈和局部变量,所以线程不能独立地执行,它必须依附在一个进程上。在同一个进程内多个线程之间可以共享进程的内存资源。

线程、进程与Android系统组件的关系

在上一节我们以操作系统的角度介绍了线程和进程的概念,这些概念比较底层,而我们在Android编程时经常碰到的概念却是应用(Application)、活动(Activity)、任务(Task)、服务(Service)、广播接收器(Broadcast Receiver)等。它们之间的关系是如何的呢?我们编程后生成的.apk程序是怎么运行的呢?下面进行定性的讲解。

我们知道Android应用程序是用JAVA编程语言写的。编译后的代码包括数据和资源文件,生成以apk为后缀的Android程序包。一个apk文件就是一个应用程序(Application)。我们通过电子市场或者直接下载等方式把应用程序包(apk)安装到手机上。

在安装Android应用程序的时候,默认情况下,每个应用程序分配一个唯一的Linux用户的ID。权限设置为每个应用程序的文件仅对用户和应用程序本身可见。这样保证了其他应用程序不能访问此应用程序的数据和资源,保证了信息安全。在 Linux 系统中一个用户ID 识别一个特定用户,而在 Android 系统里,一个用户ID 识别一个应用程序。

但是,如果我们需要在两个不同的应用程序之间互相访问资源怎么办呢?我们可以给两个应用程序分配相同的Linux用户ID,这样它们之间就能互相访问对方的资源了。而拥有相同用户ID的应用程序将运行在同一个进程中,共享同一个Dalvik虚拟机。要实现这个功能,首先必须在应用的AndroidManifest文件里给android:sharedUserId属性设置相同的Linux 用户 ID,然后给这两个应用程序用相同的签名。

例如,下面表格里是两个不同程序AndroidMenifest文件的部分代码。

1.   <manifest xmlns:android="http://schemas.android.com/apk/res/android"

2.   package="com.kris.reskin"

3.   android:versionCode="1"

4.   android:versionName="1.0"

5.   android:sharedUserId="com.kris.skin">

 

 

6.   <manifest xmlns:android="http://schemas.android.com/apk/res/android"

7.   package="com.hexter.reskin1"

8.   android:versionCode="1"

9.   android:versionName="1.0"

10.  android:sharedUserId="com.kris.skin">

第2行和第7行说明这两个程序源自不同的package。

第5行和第10行android:sharedUserId="com.kris.skin"说明这两个程序设置了相同的共享id。

这两个程序用相同的签名发布后,在手机上将运行在同一个进程中,相互共享资源。

当我们单击手机上某个应用程序时,如果手机内存中没有这个应用的任何组件,那么系统会为这个应用启动一个新的Linux进程,在这个进程里运行一个新的Dalvik虚拟机实例,而应用程序则运行在这个Dalvik虚拟机实例里。Dalvik虚拟机主要完成组件生命周期管理、堆栈管理、线程管理、安全和异常管理,以及垃圾回收等重要功能。虚拟机的这些功能都直接利用底层操作系统的功能来实现。默认情况下,这个进程只有一个线程(主线程),主线程主要负责处理与UI相关的事件,如,用户的按键事件、用户接触屏幕的事件以及屏幕绘图事件,并把相关的事件分发到对应的组件进行处理,所以主线程通常又被叫做UI线程。注意,应用程序里的所有组件,活动Activity、服务Service、广播Broadcast Receiver等都运行在这个主线程线程中。

注意,应用程序里多个Activity,以及后台运行的Service甚至广播接收器,在默认情况下,都是在同一个进程和同一个主线程中被实例化和被调用运行的。因为主线程负责事件的监听和绘图,所以,必须保证主线程能够随时响应用户的需求。这说明,当应用程序运行时不应该有哪个组件(包括服务Service)进行远程或者阻塞操作(比如网络调用或者复杂运算),这将阻碍进程中的所有其他组件的运行,甚至主线程如果超过5秒没有响应用户请求,系统会弹出对话框提醒用户终止应用程序。这也就是即使使用服务Service也会导致应用卡的原因。这时,我们必须新开线程去并行执行远程操作、耗时操作等代码,而主线程里的代码应该尽量短小。

当然,我们也可以通过设置参数让一个应用程序运行时分配多个进程。应用程序的AndroidManifest.xml文件中的组件节点——Activity、Service、Receiver和Provider ——都包含一个process属性。这个属性可以设置组件运行的进程,可以配置组件在一个独立进程运行,或者多个组件在同一个进程运行。application节点也包含process属性,它用来设置程序中所有组件的默认进程。

如下表格里的两个Activity,虽然在同一个应用程序,但因为显式地设置了不同的android:process属性,它们将运行在不同的进程中。

1.   <activity android:name=".A1"

2.   android:label="@string/app_name"

3.   android:process=":process .main">

4.   </activity>

 

5.   <activity android:name=".A2"

6.   android:label="@string/app_name2"

7.   android:process=":process .sub"

8.   </activity>

所以如前所述,一个Android应用程序运行时至少对应一个Linux进程和线程。默认情况下,不同的应用在不同的进程空间里运行,每个应用程序都有它自己的Dalvik虚拟机,因此应用程序代码独立于其他所有应用程序的代码运行。而且对不同的应用使用不同的Linux用户ID来运行,最大程度地保护了应用的安全和独立运行。

我们单击的应用程序运行后,一般会在手机屏幕显示界面,界面上可能有图片、文字和按钮等控件。单击这些控件会跳转到其他界面或者跳出提示或者运行特定的程序代码。然后,我们可能按手机的Back按键返回到之前的界面或者退出应用,或者按Home键到桌面。在这些操作过程中,应用程序的各组件在主线程或各自线程中被系统调用运行。这个阶段就是组件的生命周期过程。

即使我们完全退出应用程序,这个应用程序所使用的进程、虚拟机和线程等资源还将在内存中存在,只到系统内存不足时被系统回收。Android系统会根据进程中运行的组件类别以及组件的状态来判断各进程的重要性,并根据这个重要性来决定回收时的优先级。

进程重要性从高到低一共有5个级别。

前台进程:它是用户当前正在使用的进程,是优先级最高的进程。

可见进程:它是在屏幕上有显示但却不是用户当前使用的进程。

服务进程:运行着服务Service的进程,只要前台进程和可见进程有足够的内存,系统不会回收它们。

后台进程:运行着一个对用户不可见的Activity(并调用过 onStop() 方法)的进程,在前3种进程需要内存时,被系统回收。

空进程:未运行任何程序组件。

采用这种懒人策略方式回收资源的优点是下次启动该应用程序时会更快速,而弊端是系统资源得不到及时回收,当需要新启动一个应用时有可能因资源不足而等待系统回收资源。

讲到这里,似乎没提及系统里另一个重要的概念任务Task,而我们编程时也没有找到该组件。任务Task是什么呢?

任务Task:是排成堆栈的一组相关活动Activity,Task没有实体的堆栈数据结构,它只是逻辑上的一个堆栈。在一个Task堆栈里的活动Activity可以来自同一个应用程序也可以是来自不同的应用。简单地讲,任务Task是用户体验上的一个“应用程序”。

任务Task栈底的活动Activity(根活动)是起始活动Activity,栈顶的活动Activity是正在运行的活动Activity。当一个活动Activity启动另一个时,新的活动Activity被压入栈顶,变为正在运行的活动Activity。之前的那个活动Activity保存在栈中。当用户单击返回按钮时,当前活动Activity从栈顶弹出,之前那个活动Activity恢复成为正在运行的活动Activity。

打开新浪微博过程的任务Task堆栈变化如下所示。

事    件

Task栈(粗体为栈顶组件)

点开新浪微博,到微博列表

A(一个新的task)

单击一条微博,查看详情

AB

单击里面的网址链接,用腾讯浏览器打开链接网页

ABC

单击返回,回到那条微博

AB

单击返回,到微博列表

A

退出微博

null

当在手机桌面单击新浪微博时,系统启动该应用,并展现微博列表,此时新建一个Task,栈中只有微博列表Activity一个活动A。当单击其中一条微博看详情时,启动新Acitivty活动B,活动B压入Task堆栈变成栈顶活动。当单击微博内容里的网址链接时,启动腾讯浏览器打开这个网址,此时腾讯浏览器Activity活动C压入Task变成栈顶活动。当按Back返回操作时,弹出栈顶的活动Activity,回到之前的活动,直到退出微博应用。

一个任务Task的所有活动Activity作为一个整体运行。整个任务Task可置于前台或后台。例如,一个任务有4个活动Activity在栈中,当用户按下Home键,切换到手机桌面,当选择一个新的应用程序(一个新的任务Task),当前任务Task进入后台。过了一会,用户回到手机桌面并再次选择之前的应用程序(之前的任务),这个任务Task又变为前台运行。

上面所描述的是活动Activity和任务Task之间的默认行为。活动Activity与任务Task之间的行为方式有很多种组合,这由启动活动Activity的意图Intent对象的标志(flags)和应用程序AndroidMenifest文件中活动<activity>元素的属性共同决定的。在这里不做展开讲解。

 

posted @ 2015-03-04 10:48  菜鸟的学习  阅读(1574)  评论(0编辑  收藏  举报