Android 进程和线程

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


进程 Process

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

基于目前在该进程中活跃的组件的重要性,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("http://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("http://img.blog.csdn.net/20170427142128030");
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

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

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

线程安全的方法

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

这主要适用于可以远程调用的方法,例如绑定服务中的方法。当在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]

欢迎大家关注、评论、点赞
你们的支持是我坚持的动力。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 进程和线程 快速查看 默认情况下,每个应用程序运行在各自的进程中,应用程序中的所有组件也都运行在其中。activi...
    Czppp阅读 325评论 0 0
  • Android 进程和线程 当一个应用程序组件启动和应用程序没有任何其他组件在运行时,Android系统开始一个新...
    ProZoom阅读 480评论 0 1
  • Table of Contents generated with DocToc 【Android 进程和线程】 ...
    Rtia阅读 386评论 0 1
  • Android进程模型 在安装Android应用程序的时候,Android会为每个程序分配一个Linux用户ID,...
    Yeung_Yeung阅读 206评论 0 0
  • 转自:https://developer.android.com/guide/components/process...
    画峰阅读 212评论 0 0