美文网首页
进程和线程

进程和线程

作者: wind_sky | 来源:发表于2019-07-31 15:26 被阅读0次

    当某个应用组件启动且该应用没有任何组件在运行时,Android 系统会使用单个执行线程为应该开启一个新的Linux 进程。默认情况下,同一应用的所有组件运行在同一个进程中,如果某个组件启动且该应用已存在进程,则该组件会在此进程内启动并使用相同的执行线程。

    进程

    默认情况下,同一应用的所有组件均在相同的进程中运行,且大多数应用都不会改变这一点。 如果发现需要控制某个组件所属的进程,则可在清单文件中执行此操作。

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

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

    如果内存不足,而其他为用户提供更紧急服务的进程又需要内存时,Android 可能会决定在某一时刻关闭某一进程。在被终止进程中运行的应用组件也会随之销毁。 当这些组件需要再次运行时,系统将为它们重启进程。

    决定终止哪个进程时,Android 系统将权衡它们对用户的相对重要程度。例如,相对于托管可见 Activity 的进程而言,它更有可能关闭托管屏幕上不再可见的 Activity 的进程。 因此,是否终止某个进程的决定取决于该进程中所运行组件的状态。 下面,我们介绍决定终止进程所用的规则。

    进程生命周期

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

    1. 前台进程

    指用户当前操作所依赖的进程,满足以下任一条件就是前台进程

    • 托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)

    • 托管某个 Service,后者绑定到用户正在交互的 Activity

    • 托管正在“前台”运行的 Service(服务已调用 startForeground())

    • 托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())

    • 托管正执行其 onReceive() 方法的 BroadcastReceiver

    通常在任意给定时间前台进程都不多,只有在内存不足以支持他们同时运行的情况下,系统才会终止他们,此时设备往往已经达到内存分页的情况,所以需要终止一些前台进程来确保用户界面正常显示。

    2. 可见进程

    没有任何前台组件,但是仍然影响用户在屏幕上所见内容的进程,满足下面任一条件则为可见进程

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

    • 托管绑定到可见(或前台)Activity 的 Service。

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

    3. 服务进程

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

    4. 后台进程

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

    5. 空进程

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

    注:

    根据进程中当前活动组件的重要程度,Android 会将进程评定为它\color{#F54040}{可能达到的最高级别}。例如,如果某进程托管着服务和可见 Activity,则会将此进程评定为可见进程,而不是服务进程。

    此外,一个进程的级别可能会因其他进程对它的依赖而有所提高,\color{#F54040}{即服务于另一进程的进程其级别永远不会低于其所服务的进程}。 例如,如果进程 A 中的内容提供程序为进程 B 中的客户端提供服务,或者如果进程 A 中的服务绑定到进程 B 中的组件,则进程 A 始终被视为至少与进程 B 同样重要。

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

    线程

    应用启动时,系统会为应用创建一个名为“主线程”的执行线程。 此线程非常重要,因为它负责将事件分派给相应的用户界面小部件,其中包括绘图事件。 此外,它也是应用与 Android UI 工具包组件(来自 android.widgetandroid.view 软件包的组件)进行交互的线程。因此,主线程有时也称为 UI 线程。

    系统不会为每个组件实例创建单独的线程。运行于同一进程的所有组件均在 UI 线程中实例化,并且对每个组件的系统调用均由该线程进行分派。 因此,响应系统回调的方法(例如,报告用户操作的 onKeyDown() 或生命周期回调方法)始终在进程的 UI 线程中运行。

    例如,当用户触摸屏幕上的按钮时,应用的 UI 线程会将触摸事件分派给小部件,而小部件反过来又设置其按下状态,并将失效请求发布到事件队列中。 UI 线程从队列中取消该请求并通知小部件应该重绘自身。

    在应用执行繁重的任务以响应用户交互时,除非正确实现应用,否则这种单线程模式可能会导致性能低下。 具体地讲,如果 UI 线程需要处理所有任务,则执行耗时很长的操作(例如,网络访问或数据库查询)将会阻塞整个 UI。 一旦线程被阻塞,将无法分派任何事件,包括绘图事件。 从用户的角度来看,应用显示为挂起。 更糟糕的是,如果 UI 线程被阻塞超过几秒钟时间(目前大约是 5 秒钟),用户就会看到一个ANR 对话框。如果引起用户不满,他们可能就会决定退出并卸载此应用。

    此外,Android UI 工具包并非线程安全工具包。因此,不可以通过工作线程操纵 UI,而只能通过 UI 线程操纵用户界面。 因此,Android 的单线程模式必须遵守两条规则:

    • 不要阻塞 UI 线程

    • 不要在 UI 线程之外访问 Android UI 工具包

    线程安全方法

    在某些情况下,我们实现的方法可能会从多个线程调用,因此编写这些方法时必须确保其满足线程安全的要求。

    这一点主要适用于可以远程调用的方法,如绑定服务中的方法。如果对 IBinder 中所实现方法的调用源自运行 IBinder 的同一进程,则该方法在调用方的线程中执行。但是,如果调用源自其他进程,则该方法将在从线程池选择的某个线程中执行(而不是在进程的 UI 线程中执行),线程池由系统在与 IBinder 相同的进程中维护。 例如,即使Service 的 onBind() 方法将从服务进程的 UI 线程调用,在 onBind() 返回的对象中实现的方法(例如,实现 RPC 方法的子类)仍会从线程池中的线程调用。 由于一个Service 可以有多个客户端,因此可能会有多个池线程在同一时间使用同一 IBinder 方法。因此,IBinder 方法必须实现为线程安全方法。

    同样,ContentProvider 也可接收来自其他进程的数据请求。尽管 ContentResolver 和 ContentProvider 类隐藏了如何管理进程间通信的细节,但响应这些请求的 ContentProvider 方法(query、insert、delete、update 和 getType 方法)将从内容提供程序所在进程的线程池中调用,而不是从进程的 UI 线程调用。 由于这些方法可能会同时从任意数量的线程调用,因此它们也必须实现为线程安全方法。

    相关文章

      网友评论

          本文标题:进程和线程

          本文链接:https://www.haomeiwen.com/subject/riktdctx.html