面试必问的:Processes and Threads问题解答

2016-03-11 16:45 3,438人阅读 暂无评论
版权声明:本文著作权归 TeachCourse所有,未经许可禁止转载,谢谢支持!
转载请注明出处: http://teachcourse.cn/android-processes-threads-concept

摘要

目录:

  • Processes
    Processes lifecycle
  • Threads
    Worker threads
    Thread-safe methods
  • InterProcesses Communication

当一个应用程序组件启动,同时该应用程序没有别的组件在运行,Android系统为单一线程的应用程序开启一个新的Linux进程。默认情况下,同一个应用程序的组件都运行在相同的进程和线程(称为UI线程)中,如果一个应用程序开启并已存在一个进程,那么该组件在该进程中开启同时使用同一线程。然而,你可以在一个应用程序中为不同组件指定运行的独立进程,同时创建、添加线程到任意的进程中。

001-processes threads concept

Processes

默认情况下,同一应用程序的所有组件都运行在同一个进程,大多应用程序不应该改变这个原则。然而,你发现你需要控制一个组件属于哪个进程,你需要修改manifest文件的配置。

manifest实体为,,,提供android:process,属性指定当前组件运行的进程。你可以设置该属性以便每个组件运行在它自己独立的进程或多个组件共享同一个进程,同时你也可以设置android:process属性以便于不同应用程序的组件可以运行在同一个进程—,该应用程序共享同一个Linux user ID同时使用同一证书签名。

标签也支持android:process属性,用它设置一个默认的值将会应用到所有的组件中。

当当前系统的内存不足同时另外的进程请求需要服务更多的用户时,Android可能在某个时间点想要关闭一个进程,应用程序正在运行的进程将会被杀掉销毁。当需要再次工作时,该进程会再次开启。

当确认需要kill掉哪个进程时,Android系统权衡该进程与用户的重要关系,比如:相对于可见的activities进程来说,系统更会kill掉不在屏幕可见的activities进程,决定是否终止一个进程,依赖于组件在进程中运行的状态。

Process lifecycle

Android系统试图尽可能长时间地保存一个应用程序进程,但也需要移除一个旧的进程来为一个新的或更重要的进程释放内存空间。确认保存哪个进程和kill掉哪个进程,当前系统会基于正在组件运行的进程和组件的状态排列每个进程的重要结构层次。最低层次的进程首先被kill掉,然后是较低的进程等等,在必要时回收系统资源。

这里罗列了重要层次结构的五个级别,排在前面越重要,最后才会被kill掉:

1、Foreground process

为用户交互提供一个进程,当下面条件满足时当前进程被认为是前台进程:

  • 它承载了与用户交互的Activity(ActivityonResume()方法被回调)
  • 它承载了绑定到activity与用户交互的Service
  • 它承载了运行在前台的Service
  • 它承载了执行其中一个回调方法的Service
  • 它承载了BroadcastReceiver执行onReceive()方法

通常,只有少数的进程可以一直存在,它们被kill掉作为最好的选择-如果当前内存如此少以至于它们无法继续运行。同时,当前设备已达到内存分页状态,kill掉一些前台进程保证用户接口响应。

2、Visible process

一个进程它没有任何的前台组件,但是仍然影响着用户在屏幕上看到的,如果下面的条件返还true,这样的一个进程被认为是一个可见的:

  • 它承载着一个不在前台的Activity,但对用户仍然是可见的(回调它的onPause()方法),例如:如果前台的activity启动了一个dialog,从而运行排在堆栈前的activity被看到
  • 它承载着绑定到可见的activity中的Service

一个可见的进程被认为是相当重要的,直到请求保证前台进程运行的情况下才会被kill掉

3、Service process

正在运行的服务进程使用startService()方法启动,不降落到两个更高的级别。尽管服务进程不会像用户看到的那样子直接被杀死,他们通常在处理用户关心的事情(比如:在后台播放音乐或从网上下载数据),以至于系统保证它们一直运行除非没有足够的内存留住他们连同前台进程和可视化进程。

4、Background process

一个进程持有当前对用户不可见的activity(onStop()被回调),这样的进程不会直接影响用户的体验,同时系统会不定时kill掉它们为前台进程、可视进程、服务进程腾出更多内存空间。通常有很多的后台进程在运行,所以把他们保留在一个LRU(最近使用最少)的列表保证最近被用户看到的activity进程是最后被kill掉的。如果一个activity正确实现了它的生命周期方法和保存activity当前的状态,kill掉它的进程将不会明显影响用户体验,因为当用户导航返回当前activity时,当前activity恢复所有可见的状态。

5、Empty process

一个进程不持有任何激活的应用程序组件,唯一保持这种类型进程存在的原因是缓存目的。比如:如果一个进程承载着一个服务和一个可见的activity,当前进程被列为一个可见的进程而不是一个服务进程。

此外,一个进程的排名可能因为其他进程依赖排名被提升的,一个activity发起一个长时间运行的操作可能更多启动一个服务操作,而不是简单创建一个工作线程-特别是如果该操作将可能永久的激动该activity。例如:一个activity上传一张图片到web网站应当启动一个服务来执行上传操作以便上传可以继续在后台执行,甚至用户退出当前activity。使用一个服务保证该操作至少拥有一个服务优先权限,而不理会activity的情况。这就是为什么一个广播接收器应该开启一个empty process服务而不是简单把耗时的操作放在一个thread中。

Threads

当一个应用程序启动时,系统创建了一个执行应用程序的线程,称为“main”线程,该线程非常重要,因为它负责调度事件到相应的用户接口小工具,包括:drawing events,它也是你的应用程序与来自Android UI toolkit组件交互的线程(组件来自android.widgetandroid.view包),有时候main thread也称为UI thread。

系统不会为每一个组件创建一个独立的线程,运行在同一个进程的所有组件在UI thread中实例化,系统从线程调用每一个被调度的组件。所以,相应系统回调的方法(比如:onKeyDown()报道用户的行为或一个生命周期回调方法或一个生命周期回调方法)总是运行在当前进程的UI thread。

例如:当用户触摸屏幕上的一个按钮,你的应用中的UI thread调度触摸事项到widget,widget设置它的按下状态同时发送一个废止的请求到事件堆栈。UI thread排列请求同时通知widget重新绘制自己。

当你的应用程序响应用户交互进行深入细致的工作,当前单一的线程模式可能产生性能不佳除非你正确实现应用程序。特别是,如果所有事情都发生在UI thread,执行长时间的操作,比如:网络访问、数据库查询,都会阻塞整个UI thread。当当前的UI thread是阻塞时,将无法调度事件,包括绘制事件。从用户的的角度看,应用程序似乎挂起了,更糟糕的是,付过UI thread阻塞超过几秒(大概5秒),就会弹出“application not responding”(ANR)对话框,这将会影响用户的使用和安装。

另外,Android UI toolkit是不安全的,如果你一定要在worker thread中操作你的UI,你必须通过UI thread向用户接口处理所有的操作。针对Android单一线程模式,两个简单的规则:

1、禁止阻塞UI thread

2、禁止从UI thread的外部访问Android的UI工具包

Worker threads

正如正面所说的单一线程模式,不要阻塞UI thread对应用程序的UI响应很重要,如果你的操作执行无法瞬间完成,你应该确保在独立线程处理它们(“background”或“worker” threads)。

这是一个例子,单击监听器,通过独立线程下载一张图片并在ImageView控件上显示:

  1. public void onClick(View v) {  
  2.      new Thread(new Runnable() {  
  3.         public void run() {  
  4.             Bitmap b = loadImageFromNetwork("http://teachcourse.cn/logo.png");  
  5.             mImageView.setImageBitmap(b);  
  6.         }  
  7.     }).start();  
  8. }  

起先,这似乎可以正常执行,因为它创建了一个新的thread来处理网络的操作。然而,它违反了单一线程的第二个规则:禁止从UI thread的外部访问Android的UI工具包-这个例子使用worker thread代替UI thread改变ImageView。这可能导致未定义和意外的行为,这可能是困难和耗时的排查。

为了解决这个问题,Android提供了从外部线程访问UI thread的几个方法,这是几个可能对你有帮助的方法:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable,long)

上面的例子可以使用View.post()方法改写成下面的样子:

  1. public void onClick(View v) {  
  2.     new Thread(new Runnable() {  
  3.         public void run() {  
  4.             final Bitmap bitmap =  
  5.                     loadImageFromNetwork("http://teachcourse.cn/logo.png");  
  6.             mImageView.post(new Runnable() {  
  7.                 public void run() {  
  8.                     mImageView.setImageBitmap(bitmap);  
  9.                 }  
  10.             });  
  11.         }  
  12.     }).start();  
  13. }  

现在,实现线程安全的方法是:当ImageView是被UI thread操作时,访问网络需要开启独立的线程。

然而,随着操作复杂度的提升,这样子的代码会变得复杂和难以维护,为了使用worker thread处理更多复杂的相互作用的问题,你可能需要在worker thread中使用Handler,该Handler独立于UI thread处理发送的消息。或许,最好的解决办法是继承AsyncTask类,它可以简化需要与UI交互的worker thread线程任务的执行操作。

Using AsyncTask

AsyncTask允许你使用自己的用户接口执行异步的工作,它执行的阻塞操作在worker thread线程中,然后发送结果到UI thread中,而不用你去处理thread和handler的操作。

为了使用AsyncTask,子类必须实现在后台线程池中运行的doInBackground()方法。为了更新你的UI界面,你应该实现onPostExecute()方法,该方法获取doInBackground()中的执行结果并在UI thread中运行,以至于你可以安全地更新你的UI。你可以在UI thread中调用execute()方法运行当前任务。

针对上面的例子,使用AsyncTask方式实现如下:

  1. public void onClick(View v) {  
  2.     new DownloadImageTask().execute("http://teachcourse.cn/logo.png");  
  3. }  
  4.   
  5. private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {  
  6.     /** The system calls this to perform work in a worker thread and 
  7.       * delivers it the parameters given to AsyncTask.execute() */  
  8.     protected Bitmap doInBackground(String... urls) {  
  9.         return loadImageFromNetwork(urls[0]);  
  10.     }  
  11.   
  12.     /** The system calls this to perform work in the UI thread and delivers 
  13.       * the result from doInBackground() */  
  14.     protected void onPostExecute(Bitmap result) {  
  15.         mImageView.setImageBitmap(result);  
  16.     }  
  17. }  

现在,当前的UI thread是更加的安全,同时代码也是更简单,因为它将任务划分为一部分在worker thread中处理,一部分在UI thread中处理。

关于AsyncTask的更详细的使用说明,推荐阅读《AsyncTask==Handler+Thread对比使用说明

Thread-safe methods

在某些情况下,当前实现的方法可能被一个或多个thread回调,因此必须被写入一个thread-safe线程中。

这主要是可以远程调用的方法,例如:bound service方法,在同一个IBinder运行的进程中,当一个使用IBinder起点的方法被实现时,该方法在调用者线程中执行。然而,当回调的起点在另一个进程,该方法从系统为IBinder保留的同一进程的线程池中选择一个线程执行(不会执行在进程中的UI thread中)。例如:当一个Service的onBind()方法从服务进程的UI thread中被回调,该方法在onBinder()(子类需要实现RPC方法)
返回的对象实现将会被线程池中的方法回调。由于一个服务不只一个客户端,同一时间多个线程池的线程可能执行同一onBinder()方法。因此,onBinder()必须在一个安全的线程中实现。

类似的,一个content provider可能接收起源于其他进程的数据请求,尽管ContentResolverContentProvider类隐藏了内部进程如何管理通信的细节,ContentProvider对这些请求——query()、insert()、delete()、update()和getType()做出响应的方法在一个content provider进程的线程池中被回调,除了进程中的UI thread。因为这些方法在同一时间可能被任意的线程回调,它们也必须实现为线程安全的。

Interprocess Communication

Android提供了这样一个机制:进程间通信(IPC)使用远程过程回调(RPCs),一个方法可能被一个activity或其他application组件回调,但是在远程中执行(另一个进程),然后将所有结果返回调用者。意味着,分解一个回调方法,对于同一个等级的操作系统能够从本地进程和地址空间理解、发送到远程和地址空间,然后重新组装和重演通话。返回的数据反向传送,Android提供所有的源代码来执行这些IPC交易,这样你就可以定义和实施RPC程序接口了。

关注公众号 扫一扫二维码,加我QQ

如果文章对你有帮助,欢迎点击上方按钮关注作者

来源:TeachCourse每周一次,深入学习Android教程,关注(QQ1589359239或公众号TeachCourse)
转载请注明出处: http://teachcourse.cn/android-processes-threads-concept

资源分享

Demo源码
Map接口集合方法解析 Map接口集合方法解析
浅谈char、varchar和nvarchar的区别 浅谈char、varchar和nvarchar的区
Genymotion-eclipse-plugin插件安装 Genymotion-eclipse-plugin插件
Android Studio如何使用桌面版GitHub管理项目? Android Studio如何使用桌面版

发表评论

呲牙 憨笑 坏笑 偷笑 色 微笑 抓狂 睡觉 酷 流汗 鼓掌 大哭 可怜 疑问 晕 惊讶 得意 尴尬 发怒 奋斗 衰 骷髅 啤酒 吃饭 礼物 强 弱 握手 OK NO 勾引 拳头 差劲 爱你

表情