Android 进程和线程

版权声明:本文出自朋永的博客,转载必须注明出处。 https://blog.csdn.net/VNanyesheshou/article/details/75195036

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


进程 Process

默认情况下,同一应用程序的所有组件在同一进程中运行,大多数应用程序不应该更改此操作。 但是,如果您发现需要控制某个组件所属的进程,则可以在清单文件中进行。
清单条目中每个类型的组件元素,,和都支持一个android:process属性,可以指定该组件应该运行的进程。您可以设置此属性,以便每个组件在其自身的进程中运行,或者某些组件共享进程,而其他组件则不共享进程。您还可以设置android:process,使不同应用程序的组件在同一进程中运行,前提是应用程序共享相同的Linux用户ID,并使用相同的证书进行签名。

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

因此在被杀死的进程中运行的应用程序组件被销毁。当他们再次工作时,这些组件再次启动一个进程。

当决定要杀死哪个进程时,Android系统会重新评估相对用户的重要性。 例如,与可见Activiy的进程相比,它更容易关闭在屏幕上不再可见Activity的进程。因此,是否终止进程的决定取决于在该进程中运行的组件的状态。

进程生命周期

Android系统尝试尽可能长时间地维护应用程序进程,但最终需要删除旧进程来为新的或更重要的进程回收内存。为了确定要保留的进程和要杀死的进程,系统将根据进程中运行的组件和这些组件的状态将每个进程置于“重要性层次”中。首先消除重要性最低的进程,然后重要性较低的进程,依此类推,以恢复系统资源。

重要性层次结构有五个层次。以下列表按重要性顺序列出不同类型的进程(第一个进程最重要,最后被 杀死):

  1. 前台进程
    用户正在操作的进程。如果满足以下任一条件,则将进程视为前台。通常,在任何给定时间只存在少量前台进程。 他们只能作为最后的手段被杀死 - 如果记忆如此之低,以至于它们都不能继续运行。 通常,在这一点上,设备已经达到存储器分页状态,因此需要杀死一些前台进程来保持用户界面的响应。

    • 它承载用户正在交互的Activity(已调用该Activity的onResume()方法)。
    • 它承载与Activity绑定、用户正在交互的Service。
    • 它承载运行在“前台”的Service (Service已经调用了startForeground())。
    • 它承载一个正在执行其生命周期回调之一(onCreate(),onStart()或onDestroy())的Service。
    • 它承载正在执行其onReceive()方法的BroadcastReceiver。
  2. 可见进程
    一个没有任何前台组件的进程,但仍然可以影响用户在屏幕上看到的内容。 如果满足以下条件之一,则认为该进程是可见的:

    • 它承载不在前台的Activity,但对用户仍然可见(其onPause()方法已被调用)。 这可能会发生,例如,如果前台Activity启动了一个dialog,这允许在其后面看到先前的Activity。
    • 它承载绑定到可见(或前台)Activity的服务。
      一个可见的进程被认为是非常重要的,不会被杀死,除非这样做是为了保持所有前台进程的运行。
  3. 服务进程
    一个正在运行已经以startService()方法启动但不属于两个较高类别的服务的进程。 虽然服务进程并不直接与用户看到的任何内容相关联,但它们通常是用户关心的事情(例如在后台播放音乐或在网络上下载数据),因此系统保持运行,除非没有足够的内存 保留它们与所有前景和可见进程。
  4. 后台进程
    持有当前不可见的Activity的进程(Activity的onStop()方法已被调用)。 这些进程对用户体验没有直接影响,系统可以随时杀死它们,为(前台,可见或服务进程)回收内存。 通常有许多后台进程运行,因此它们保留在LRU(最近最少使用的)列表中,以确保用户最近看到的活动的进程最后被杀死。 如果Activity正确地实现了其生命周期方法,并且保存其当前状态,那么杀死其进程将不会对用户体验产生明显的影响,因为当用户导航回Activity时,Activity将恢复其所有可见状态。
  5. 空进程
    一个不包含任何活动应用程序组件的进程。 保持这种进程的唯一原因是为了缓存目的,以便在下一次组件需要运行时改进启动时间。 系统通常会杀死这些进程,以平衡进程缓存和底层内核高速缓存之间的整体系统资源。

基于目前在该进程中活跃的组件的重要性,Android可以在最高级别进行排名。例如,如果进程保持Service和可见Activity,则该进程将被排列为可见进程,而不是服务进程。

此外,由于其他进程依赖于进程的排名可能会增加,因此,服务于另一进程的进程绝对不能低于其所服务的进程。例如,如果进程A中的内容提供商正在为进程B中的客户端服务,或者如果进程A中的服务绑定到进程B中的组件,则进程A总是被认为至少与进程B一样重要。

由于运行Service的进程的排名高于具有后台Activity的进程,所以启动长时间运行的操作的Activity可能会很好地为该操作启动Service,而不是简单地创建一个工作线程,特别是如果操作可能超出Activity。例如,将图片上传到网站的Activity应启动Service以执行上传,以便即使用户离开Activity,上传也可以在后台继续。使用服务保证操作至少具有“服务进程”的优先级,不管Acitivity发生了什么。这是BroadcastReceiver应该采用服务的原因,而不是简单地在线程中耗费时间的操作。


线程 Thread

当应用程序启动时,系统会为应用程序创建一个名为“main”的执行线程。这个线程非常重要,因为它负责将事件发送到适当的用户界面小部件,包括绘图事件。它也是您的应用程序与Android UI工具包(来自android.widget和android.view包的组件)进行交互的线程。因此,主线程有时也称为UI线程。

系统不会为组件的每个实例创建一个单独的线程。在同一进程中运行的所有组件都在UI线程中实例化,并从该线程调度对每个组件的系统调用。因此,响应系统回调(例如onKeyDown()来报告用户操作或生命周​​期回调方法)的方法总是在进程的UI线程中运行。

例如,当用户触摸屏幕上的按钮时,您的应用程序的UI线程将触摸事件调度到窗口小部件,该窗口小部件又设置其按下的状态,并向事件队列发送无效请求。 UI线程对请求进行排队,并通知小部件它应该重绘自己。

当您的应用程序执行紧急工作以响应用户交互时,此单线程模型可能会导致性能下降,除非您正确实施应用程序。具体来说,如果在UI线程中发生了一切,执行诸如网络访问或数据库查询之类的长时间操作会阻止整个UI。当线程被阻止时,不会调度任何事件,包括绘制事件。从用户的角度来看,应用程序似乎挂起。更糟糕的是,如果UI线程被阻塞超过几秒钟(目前约5秒),用户将会看到臭名昭着的“应用程序无响应”(ANR)对话框。

另外,Andoid UI工具包不是线程安全的。所以,你不能从一个工作线程操纵你的UI - 你必须从UI线程对所有的用户界面进行操作。因此,Android的单线程模型只有两个规则:

  • 不要阻塞UI线程
  • 不要从UI线程外部更新UI

工作线程

由于上述单线程模型,对应用程序的UI的响应性至关重要,您不会阻塞UI线程。如果您的操作执行不是即时的,那么您应该确保在单独的线程(“后台”或“工作”线程)中执行操作。

例如,下面是一个点击监听器的一些代码,它从一个单独的线程中下载一个映像并将其显示在ImageView:

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            Bitmap b = loadImageFromNetwork("https://img-blog.csdn.net/20170427142128030");
            mImageView.setImageBitmap(b);
        }
    }).start();
}

起初,这似乎工作正常,因为它创建一个新的线程来处理网络操作。但是,它违反了单线程模型的第二条规则:不要从UI线程外面更新UI - 这个示例ImageView从工作线程而不是UI线程修改。这可能导致未定义和意外的行为,这可能是困难和耗时的追踪。

要解决这个问题,Android提供了几种从其他线程访问UI线程的方法。以下是可以帮助的方法列表:

Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
例如,您可以使用以下View.post(Runnable)方法修复上述代码:

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            final Bitmap bitmap = loadImageFromNetwork("https://img-blog.csdn.net/20170427142128030");
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

现在这个实现是线程安全的:网络操作是从一个单独的线程完成的,而ImageView从UI线程被操纵。

然而,随着操作的复杂性的增长,这种代码可能会变得复杂和难以维护。要处理与工作线程的更复杂的交互,您可以考虑Handler在工作线程中使用一个来处理从UI线程传递的消息。也许最好的解决方案是扩展AsyncTask类,这简化了需要与UI交互的工作线程任务的执行。AsyncTask的使用可以参考这两篇文章:
AsyncTask 异步任务基本使用--下载视频
AsyncTask 源码分析

线程安全的方法

在某些情况下,可以从多个线程调用实现的方法,因此必须将其编写为线程安全的。

这主要适用于可以远程调用的方法,例如绑定服务中的方法。当在IBinder实现的方法的调用源于IBinder正在运行的相同进程时 ,该方法在调用者的线程中执行。然而,当呼叫源于另一个进程时,该方法在从系统维护在与IBinder相同的进程(不在进程的UI线程中执行)的线程池中选择的线程中执行。例如,虽然服务的 onBind()方法将从服务进程的UI线程调用,但是将onBind()返回的对象(例如,实现RPC方法的子类)中实现的方法将从线程池中调用。因为服务可以有多个客户端,所以多个池线程可以同时使用相同的IBinder方法。 因此IBinder方法必须实现为线程安全。

类似地,Content Provider可以接收源自其他进程的数据请求。虽然ContentResolver和ContentProvider 类隐藏的进程间通信是如何管理的细节,ContentProvider会响应这些请求—(query(),insert(),delete(),update(),和getType())被调用来自内容提供商进程中的线程池,而不是该进程的UI线程。因为这些方法可能会同时从任意数量的线程调用,所以它们也必须被实现为线程安全的。


进程间通信

Android提供了使用远程进程调用(RPC)的进程间通信(IPC)的机制,其中一个方法由Activity或其他应用程序组件调用,但是远程执行(在另一个进程中),任何结果返回给调用者。这需要将方法调用及其数据分解为操作系统可以理解的级别,将其从本地进程和地址空间发送到远程进程和地址空间,然后重新组合并重新启动该呼叫。然后返回值以相反方向传输。Android提供执行这些IPC事务的所有代码,因此您可以专注于定义和实现RPC编程接口。

要执行IPC,您的应用程序必须绑定到使用的服务bindService()。有关详细信息,可以参考 Android 进程间通信——Service、Messenger

我新申请的简书博客地址我的简书

猜你喜欢

转载自blog.csdn.net/VNanyesheshou/article/details/75195036