实现在Android 进程和线程
进程和线程 如果某个应用程序组件是第一次被启动,且这时应用程序也没有其他组件在运行,则Android系统会为应用程序创建一个包含单个线程的linux进程。默认情况下,同一个应用程序的所有组件都运行在同一个进程和线程里(叫做“main”主线程)。如果组件启动时,已经存在应用程序的进程了(因为应用程序的其它组件已经在运行了),则此组件会在已有的进程和线程中启动运行。不过,可以指定组件运行在其他进程里,也可以为任何进程创建额外的线程。 本文讨论进程和线程是如何在Android应用程序中发挥作用的。 进程 默认情况下,同一个应用程序内的所有组件都是运行在同一个进程中的,大部分应用程序也不会去改变它。不过,如果需要指定某个特定组件所属的进程,则可以利用manifest 文件来达到目的。 manifest文件中的每种组件元素——<activity>、 <service>、 <receiver>和<provider>——都支持定义android:process属性,用于指定组件运行的进程。设置此属性即可实现每个组件在各自的进程中运行,或者某几个组件共享一个进程而其它组件运行于独立的进程。设置此属性也可以让不同应用程序的组件运行在同一个进程中——实现多个应用程序共享同一个Linux用户ID、赋予同样的权限。 <application>元素也支持android:process属性,用于指定所有组件的默认进程。 如果内存不足,可又有其它为用户提供更紧急服务的进程需要更多内存,Android可能会决定关闭一个进程。在此进程中运行着的应用程序组件也会因此被销毁。当需要再次工作时,会为这些组件重新创建一个进程。 在决定关闭哪个进程的时候,Android系统会权衡它们相对用户的重要程度。比如,相对于一个拥有可见activity的进程,更有可能去关闭一个activity已经在屏幕上看不见的进程。也就是说,是否终止一个进程,取决于运行在此进程中组件的状态。终止进程的判定规则将在后续内容中讨论。 进程的生命周期 Android系统试图尽可能长时间地保持应用程序进程,但为了新建或者运行更加重要的进程,总是需要清除过时进程来回收内存。为了决定保留或终止哪个进程,根据进程内运行的组件及这些组件的状态,系统把每个进程都划入一个“重要性层次结构”中。重要性最低的进程首先会被清除,然后是下一个最低的,依此类推,这都是回收系统资源所必需的。 重要性层次结构共有5级,以下列表按照重要程度列出了各类进程(第一类进程是最重要的,将最后一个被终止): 1. 前台进程 用户当前操作所必须的进程。满足以下任一条件时,进程被视作处于前台: o 其中运行着正与用户交互的Activity(Activity对象的 onResume() 方法已被调用)。 o 其中运行着被正与用户交互的activity绑定的服务Service。 o 其中运行着“前台”服务Service——服务以startForeground()方式被调用。 o 其中运行着正在执行生命周期回调方法(onCreate()、onStart()或onDestroy())的服务Service。 o 其中运行着正在执行onReceive()方法的BroadcastReceiver。 一般而言,任何时刻前台进程都是为数不多的,只有作为最后的策略——当内存不足以维持它们同时运行时——才会被终止。通常,设备这时候已经到了内存分页状态(memory paging state)的地步,终止一些前台进程是为了保证用户界面的及时响应。 2. 可见进程 没有前台组件、但仍会影响用户在屏幕上所见内容的进程。满足以下任一条件时,进程被认为是可见的: o 其中运行着不在前台的Activity,但用户仍然可见到此activity(onPause()方法被调用了)。比如以下场合就可能发生这种情况:前台activity打开了一个对话框,而之前的activity还允许显示在后面。 o 其中运行着被可见(或前台)activity绑定的服务Service。 可见进程被认为是非常重要的进程,除非无法维持所有前台进程同时运行了,它们是不会被终止的。 3. 服务进程 此进程运行着由startService()方法启动的服务,它不会升级为上述两级别。尽管服务进程不直接和用户所见内容关联,但他们通常在执行一些用户关心的操作(比如在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台、可见进程同时运行,系统会保持服务进程的运行。 4. 后台进程 包含目前用户不可见activity(Activity对象的onStop()方法已被调用)的进程。这些进程对用户体验没有直接的影响,系统可能在任意时间终止它们,以回收内存供前台进程、可见进程及服务进程使用。通常会有很多后台进程在运行,所以它们被保存在一个LRU(最近最少使用)列表中,以确保最近被用户使用的activity最后一个被终止。如果一个activity正确实现了生命周期方法,并保存了当前的状态,则终止此类进程不会对用户体验产生可见的影响。因为在用户返回时,activity会恢复所有可见的状态。关于保存和恢复状态的详细信息,请参阅Activities文档。 5. 空进程 不含任何活动应用程序组件的进程。保留这种进程的唯一目的就是用作缓存,以改善下次在此进程中运行组件的启动时间。为了在进程缓存和内核缓存间平衡系统整体资源,系统经常会终止这种进程。 依据进程中目前活跃组件的重要程度,Android会给进程评估一个尽可能高的级别。例如:如果一个进程中运行着一个服务和一个用户可见的activity,则此进程会被评定为可见进程,而不是服务进程。 此外,一个进程的级别可能会由于其它进程的依赖而被提高——为其它进程提供服务的进程级别永远不会低于使用此服务的进程。比如:如果A进程中的content provider为进程B中的客户端提供服务,或进程A中的服务被进程B中的组件所调用,则A进程至少被视为与进程B同样重要。 因为运行服务的进程级别是高于后台activity进程的,所以,如果activity需要启动一个长时间运行的操作,则为其启动一个服务service会比简单地创建一个工作线程更好些——尤其是在此操作时间比activity本身存在时间还要长久的情况下。比如,一个activity要把图片上传至Web网站,就应该创建一个服务来执行之,即使用户离开了此activity,上传还是会在后台继续运行。不论activity发生什么情况,使用服务可以保证操作至少拥有“服务进程”的优先级。同理,上一篇中的广播接收器broadcast receiver也是使用服务而非线程来处理耗时任务的。 线程 应用程序启动时,系统会为它创建一个名为“main”的主线程。主线程非常重要,因为它负责把事件分发给相应的用户界面widget——包括屏幕绘图事件。它也是应用程序与Android UI组件包(来自android.widget和android.view包)进行交互的线程。因此,主线程有时也被叫做UI线程。 系统并不会为每个组件的实例都创建单独的线程。运行于同一个进程中的所有组件都是在UI线程中实例化的,对每个组件的系统调用也都是由UI线程分发的。因此,对系统回调进行响应的方法(比如报告用户操作的onKeyDown()或生命周期回调方法)总是运行在UI线程中。 举个例子,当用户触摸屏幕上的按钮时,应用程序的UI线程把触摸事件分发给widget,widget先把自己置为按下状态,再发送一个显示区域已失效(invalidate)的请求到事件队列中。UI线程从队列中取出此请求,并通知widget重绘自己。 如果应用程序在与用户交互的同时需要执行繁重的任务,单线程模式可能会导致运行性能很低下,除非应用程序的执行时机刚好很合适。如果UI线程需要处理每一件事情,那些耗时很长的操作——诸如访问网络或查询数据库等——将会阻塞整个UI(线程)。一旦线程被阻塞,所有事件都不能被分发,包括屏幕绘图事件。从用户的角度看来,应用程序看上去像是挂起了。更糟糕的是,如果UI线程被阻塞超过一定时间(目前大约是5秒钟),用户就会被提示那个可恶的“应用程序没有响应”(ANR)对话框。如果引起用户不满,他可能就会决定退出并删除这个应用程序。 此外,Andoid的UI组件包并不是线程安全的。因此不允许从工作线程中操作UI——只能从UI线程中操作用户界面。于是,Andoid的单线程模式必须遵守两个规则: 1. 不要阻塞UI线程。 2. 不要在UI线程之外访问Andoid的UI组件包。 工作线程 根据对以上单线程模式的描述,要想保证程序界面的响应能力,关键是不能阻塞UI线程。如果操作不能很快完成,应该让它们在单独的线程中运行(“后台”或“工作”线程)。 例如:以下响应鼠标点击的代码实现了在单独线程中下载图片并在ImageView显示: public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork("http://example.com/image.png");
mImageView.setImageBitmap(b);
}
}).start();
}
乍看起来,这段代码似乎能运行得很好,因为创建了一个新的线程来处理访问网络的操作。可是它违反了单线程模式的第二条规则:不要在UI线程之外访问Andoid的UI组件包——以上例子在工作线程里而不是UI线程里修改了ImageView。这可能导致不明确、不可预见的后果,要跟踪这种情况也是很困难很耗时间的。 为了解决以上问题,Android提供了几种途径来从其它线程中访问UI线程。下面列出了有助于解决问题的几种方法: · Activity.runOnUiThread(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://example.com/image.png");
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(bitmap);
}
});
}
}).start();
}
以上代码的执行现在是线程安全的了:网络相关的操作在单独的线程里完成,而ImageView是在UI线程里操纵的。 不过,随着操作变得越来越复杂,这类代码也会变得很复杂很难维护。为了用工作线程完成更加复杂的交互处理,可以考虑在工作线程中用Handler来处理UI线程分发过来的消息。当然,最好的解决方案也许就是继承使用异步任务类AsyncTask,此类简化了一些工作线程和UI交互的操作。 使用异步任务 异步任务AsyncTask 允许以异步的方式对用户界面进行操作。它先阻塞工作线程,再在UI线程中呈现结果,在此过程中不需要对线程和handler进行人工干预。 要使用异步任务,必须继承AsyncTask类并实现doInBackground()回调方法,该对象将运行于一个后台线程池中。要更新UI时,须实现onPostExecute()方法来分发doInBackground()返回的结果,由于此方法运行在UI线程中,所以就能安全地更新UI了。然后就可以在UI线程中调用execute()来执行任务了。 例如,可以利用AsyncTask来实现上面的那个例子: public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
/** The system calls this to perform work in a worker thread and
* delivers it the parameters given to AsyncTask.execute() */
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[]);
}
/** The system calls this to perform work in the UI thread and delivers
* the result from doInBackground() */
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
现在UI是安全的,代码也得到简化,因为任务分解成了工作线程内完成的部分和UI线程内完成的部分。 要全面理解这个类的使用,须阅读AsyncTask的参考文档。以下是关于其工作方式的概述: · 可以用generics来指定参数、进度值和任务最终值的类型。 · 工作线程中的doInBackground()方法会自动执行。 · onPreExecute()、onPostExecute()和onProgressUpdate()方法都在UI线程中调用。 · doInBackground()的返回值会传给onPostExecute()。 · 在doInBackground()内的任何时刻,都可以调用publishProgress()来执行UI线程中的onProgressUpdate()。 · 可以在任何时刻、任何线程内取消任务。 注意:在使用工作线程时,可能遇到的另一个问题是由于运行配置的改变(比如用户改变了屏幕方向)导致activity意外重启,这可能会销毁该工作线程。要了解如何在这种情况下维持任务执行、以及如何在activity被销毁时正确地取消任务,请参见Shelves例程的源代码。
线程安全的方法 在某些场合,方法可能会从不止一个线程中被调用,因此这些方法必须是写成线程安全的。 对于能被远程调用的方法——比如绑定服务(bound service)中的方法,这是理所当然的。如果对IBinder所实现方法的调用发起于IBinder所在进程的内部,那么这个方法是执行在调用者的线程中的。但是,如果调用发起于其他进程,那么这个方法将运行于线程池中选出的某个线程中(而不是运行于进程的UI线程中),该线程池由系统维护且位于IBinder所在的进程中。例如,即使一个服务的onBind()方法是从服务所在进程的UI线程中调用的,实现了onBind()的方法对象(比如,实现了RPC方法的一个子类)仍会从线程池中的线程被调用。因为一个服务可以有不止一个客户端,所以同时可以有多个线程池与同一个IBinder方法相关联。因此IBinder方法必须实现为线程安全的。 类似地,内容提供者(content provider)也能接收来自其它进程的数据请求。尽管ContentResolver类、ContentProvider类隐藏了进程间通讯管理的细节,ContentProvider中响应请求的方法——[url=http://developer.android.com/reference/android/content/ContentProvider.html#query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String)]query()[/url]、insert()、[url=http://developer.android.com/reference/android/content/ContentProvider.html#delete(android.net.Uri, java.lang.String, java.lang.String[])]delete()[/url]、[url=http://developer.android.com/reference/android/content/ContentProvider.html#update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[])]update()[/url]和getType()方法——是从ContentProvider所在进程的线程池中调用的,而不是进程的UI线程。因为这些方法可能会从很多线程同时调用,它们也必须实现为线程安全的。 进程间通讯 Android利用远程过程调用(remote procedure call,RPC)提供了一种进程间通信(IPC)机制,通过这种机制,被activity或其他应用程序组件调用的方法将(在其他进程中)被远程执行,而所有的结果将被返回给调用者。这就要求把方法调用及其数据分解到操作系统可以理解的程度,并将其从本地的进程和地址空间传输至远程的进程和地址空间,然后在远程进程中重新组装并执行这个调用。执行后的返回值将被反向传输回来。Android提供了执行IPC事务所需的全部代码,因此只要把注意力放在定义和实现RPC编程接口上即可。 要执行IPC,应用程序必须用bindService()绑定到服务上。详情请参阅服务Services开发指南。 |
来源:http://android.662p.com/thread-253-1-1.html
实现在Android 进程和线程的更多相关文章
- android 进程和线程管理
进程和线程的概念: 进程:程序的运行实例. 线程:cpu调度基本单位. Activity启动的时候,启动一个主线程,两个binder线程. 主线程实如何产生的?ZygoteInit启动,经由一系列调用 ...
- [转]Android进程与线程基本知识
转自:http://www.cnblogs.com/hanyonglu/archive/2012/04/12/2443262.html 本文介绍Android平台中进程与线程的基本知识. 很早的时候就 ...
- Android 进程和线程模型
Android进程模型 在安装Android应用程序的时候,Android会为每个程序分配一个Linux用户ID,并设置相应的权限,这样其它应用程序就不能访问此应用程序所拥有的数据和资源了. 在 Li ...
- Android 进程和线程
进程和线程 如果某个应用程序组件是第一次被启动,且这时应用程序也没有其他组件在运行,则Android系统会为应用程序创建一个包含单个线程的linux进程.默认情况下,同一个应用程序的所有组件都运行在同 ...
- Android进程和线程(Android开发指南--译)
(转自:http://www.cnblogs.com/xitang/archive/2011/09/24/2189460.html) Processes and Threads 译者署名: 呆呆大虾 ...
- Android进程与线程
我们都知道,在操作系统中进程是OS分配资源的最小单位,而线程是执行任务的最小单位.一个进程可以拥有多个线程执行任务,这些线程可以共享该进程分配到的资源.当我们的app启动运行后,在该app没有其他组件 ...
- android 进程/线程管理(一)----消息机制的框架
一:android 进程和线程 进程是程序运行的一个实例.android通过4大主件,弱化了进程的概念,尤其是在app层面,基本不需要关系进程间的通信等问题. 但是程序的本质没有变,尤其是多任务系统, ...
- Android 的进程和线程
进程和线程 如果某个应用程序组件是第一次被启动,且这时应用程序也没有其他组件在运行,则android系统会为应用程序创建一个包含单个线程的linux进程.默认情况下,同一个应用程序的所有组件都运行在同 ...
- Android中进程与线程
常说的主线程(UI线程)是什么? 当一个Android程序刚启动的时候,我们的android系统就会启动一个带有一个单一线程的linux进程.默认情况下,所有的组件比如Activity都运行在同样的一 ...
随机推荐
- knh
市场调研,分析—— 决定是否创业 不要再极度的沉默无言.宅.无存在感,无趣,难熬..
- [javascript]模拟汉诺塔
看了博文自己动手写了代码. 这能值几个钱? 请写代码完成汉诺塔的算法:void Hanoi(int maxLevel); 比如2层汉诺塔,需要打印(Console.WriteLine)出如下文本: A ...
- Html与CSS快速入门01-基础概念
Web前端技术一直是自己的薄弱环节,经常为了调节一个简单的样式花费大量的时间.最近趁着在做前端部分的开发,果断把这部分知识成体系的恶补一下.内容相对都比较简单,很类似工具手册的学习,但目标是熟练掌握. ...
- CSS背景100%平铺 浏览器缩小背景显示不全解决办法
本文我们分享前端CSS背景100%平铺,浏览器缩小背景显示不全bug解决的两个方法,如果你也遇到了,那么就可以参考下面文章. 把浏览器的窗口缩小时,拖动滚动条时你会发现原本设定的CSS背景100%平铺 ...
- poj2186Popular Cows(Kosaraju算法--有向图的强连通分量的分解)
/* 题目大意:有N个cows, M个关系 a->b 表示 a认为b popular:如果还有b->c, 那么就会有a->c 问最终有多少个cows被其他所有cows认为是popul ...
- Linux命令之diff
1.命令格式: diff[参数][文件1或目录1][文件2或目录2] 2.命令功能: diff命令能比较单个文件或者目录内容.如果指定比较的是文件,则只有当输入为文本文件时才有效.以逐行的方式,比较文 ...
- Java多线程系列--“JUC原子类”02之 AtomicLong原子类
概要 AtomicInteger, AtomicLong和AtomicBoolean这3个基本类型的原子类的原理和用法相似.本章以AtomicLong对基本类型的原子类进行介绍.内容包括:Atomic ...
- [OpenCV] Samples 05: convexhull
得到了复杂轮廓往往不适合特征的检测,这里再介绍一个点集凸包络的提取函数convexHull,输入参数就可以是contours组中的一个轮廓,返回外凸包络的点集 ---- 如此就能去掉凹进去的边. 对于 ...
- 原创:新手布局福音!微信小程序使用flex的一些基础样式属性
来源:新手布局福音!微信小程序使用flex的一些基础样式属性 作者:Nazi Flex布局相对于以前我们经常所用到的布局方式要好的很多,在做微信小程序的时候要既能符合微信小程序的文档开发要求,又能 ...
- html/css基础篇——html代码编写过程中的几个警惕点
本文想说的警惕点与浏览器兼容无关,主要是几个本人在项目中遇到的几个小问题的总结,问题虽小,但是却有时很困扰人,在此记录一下,如果后期有此类问题会持续添加到这里. 1.内联标签之间的空格 正常情况下书写 ...