翻译==Android 进程和线程

来源:互联网 发布:网站域名如何解析 编辑:程序博客网 时间:2024/05/27 16:41
翻译==Android 进程和线程原文章http://developer.android.com/guide/components/processes-and-threads.html以下译文不能保证100%对应,因为加入了个人的理解进去,所以翻译不好请见谅进程和线程1.进程    1.进程生命周期2.线程    1.工作线程    2.线程安全的方法3.内部进程沟通当有一个应用组件启动,而且并没有任可其他的相关组件在运行下,android系统会启动一个新的linux进程作为应用的单个执行线程。默认情况下,同一应用的所有组件会运行在同一个进程和线程(通常叫做主线程)。如果启动一个应用组件并且已经存在一个这样应用的进程(有可能其他的组件从已经存在的应用里启动),然而启动的组件附在相同的进程里和同时使用着同样的执行线程。无论怎样,在你的应用里,你也可以管理不同组件在不同的进程里,也可以为任可进程去创建额外的线程。这篇文章讨论进程和线程怎样共同工作在一个android应用里的。进程默认情况下,同一个应用的所有组件都是运行在同一个进程里,并且大多数应用不应该改变这行为。无论怎样,如果你需要控制哪个组件要附上指定的进程,那你可以在manifest文件里这样做。在manifest文件里,每一个组件类型标签里<activity>, <service>, <receiver>, <provider> 都支持android:process属性,设置这属性可以指定组件在指定的进程里运行。通过设置这属性也可以使每个组件运行在各自的进程里,也可以某些组件共用一个进程,其他组件不能共用。也可以设置这属性使不同应用程序运行在同一个进程里,这样做应用程序其实共享了同一个linux用户ID同时也签名了同一张证书。<application>标签也支持android:process属性,设置的值会应用到所有的组件。当系统内存紧张或者其他需要马上服务用户的进程,android系统都有可能在某个时刻会关闭一个进程。所以有时进程里的应用组件会理所当然的被干掉。当然如果应用组件重新启动就会重新启动一个进程。当系统确定干掉哪个进程时,系统会衡量哪些进程是对用户重要的才确定。例如,通常不再出现在屏幕上的进程比在出现在屏幕的进程更容易会给系统干掉。系统决定是否要干掉一个进程,依赖进程里运行组件的状态。以下我们就讨论下系统确定干掉哪个进程的规则。进程的生命周期android系统会尽可能努力去保留一个应用的进程,但有时会需要移除旧的进程而回收内存给新的进程或者是更重要的进程使用。决定进程的去和留,基于进程里运行的组件状态和每个进程的一个重要等级机制。优先级最低的进程会最先淘汰,跟着是下一个低优先级,以此而推,这样可以回收系统资源。重要等级制度分为五个等级。以下所列出的是不同类型进程的重要顺序(第一类的进程是最重要的,同时也是最后给干掉的)1.前端进程 前端进程就是用户现在工作的进程。如果符合以下条件其中一条都视作为前端进程:    Activity    用户正在交互的Activity(Activity's onResume()方法已经调用)    Service     Service绑定到用户正在交互的Activity    Service     Service运行在“前端”里,startForeground()方法已经调用    Service     Service正在执行它生命周期中的其中一个回调方法(onCreate(), onStart(), or onDestroy())    BroadcastReceiver   BroadcastReceiver正在执行onReceive()方法 通常只有很少前端进程可以在任可时间段里存活。只要内存紧张而进行内存回收的动作下,它们就会被系统给干掉而不能继续工作了。常规就是在设备内存到达一定程度上,为了更好更快的响应用户,而不得干掉一些前端进程。2.可见进程 一个没有任可前端组件,但是可以影响用户在屏幕上看到的界面的进程。如果符合以下条件其中一条都视作为可见进程:    Activity    不在前端,但用户仍然可以看得见(onPause()方法已经调用)。这是有可能发生的,例如,如果一个前端activity弹出了一个对话框,这种情况下对话框下的                Activity是可以看到的。    Service     Service绑定到一个可见或者前端的activity.     可见进程被视为极为重要的,却不会给系统干掉。除非这样做目的是为了保存所有正在运行的前端进程。3.服务进程 服务进程就是正在运行一个服务,它是启动于startService()方法,同时不属于两个更高级类别的其中一个。虽然服务进程不会直接显示东西给用户看,但是它们通常工作的东 西都是用户关心的(例如后台播放音乐或者使用网络下载数据),所以系统会保持它们一直运行,除非是内存不足而不能保留所有相关前端和可见进程。4.后台进程  后台进程就是activity当前暂时不在用户可见范围里(activity onStop()方法已经调用)。这些进程没有直接影响到用户体验的,在任可时候系统都可以把它们干掉而回收内存给前端,可见,或者服务进程。通常情况下都有很多后台进程正在运行的,所以它们都会保存在一个LRU(最近使用的)列表来确保用户最近使用的activity到最后才给系统干掉。如果一个activity正确地实现它的生命周期回调函数方法,并且保存了自身当前的状态,进程被干掉也不会影响到用户的体验,因为当用户返回到前一个activity时,activity会恢复之前的所有可见状态。详情看关于怎样保存和恢复Activities状态的文章。5.空进程 空进程就是没有任可活动的应用组件在内的进程。保留它们存在的唯一原因就是为了缓存的目的,这样做可以提升下一次组件启动的时间。系统也会经常干掉这些进程来达到进程缓存和底下内壳缓存的系统资源的平衡负载。android系统确定一个进程的最高优先级是根据当前进程的活动组件的重要性来决定的。例如,一个服务进程和一个可见activity进程,优先级最高就是可见activity进程,而不是服务进程。此外,进程优先级排名有可能会提高的,那是因为有些进程是依赖着其它进程的服务而运行的,而这些其他进程的优先排名绝不低于被服务的进程。例如,如果有一个内容提供者进程A服务一个客户端进程B,又或者一个服务进程A绑定到一个进程B里的组件,那么进程A总是会认为比进程B更重要。因为一个正在运行服务的进程的排名优先级会比一个后台activities的进程要高,所以最好把activity里长时间初次化的操作转移到启动一个service来操作更好,而不是简单使用新开一个线程去操作。例如,一个上传图片到网站的activity,应该启动一个服务去执行上传操作,即使用户离开了activity后,后台也会继续工作。使用一个service来操作至少可以保证是服务进程的优先级,无论activity会发生什么事情,也不影响工作。同样原理,广播接收者应该使用services而不是简单新开一个耗时操作的线程。线程当启动一个应用,系统会创建一个应用执行线程,其实就是应用主线程。主线程是很重要的,因为它负责调度系统的事件到正确地用户界面部件,包括绘画事件。它也是与其它组件沟通的线程,其它组件通常是来自于android界面工具箱 Android UI toolkit(组件包括android.widget和android.view包)。所以主线程有时会叫做UI线程。系统是不会为每个实例组件去创建独立的线程。所有组件都运行在相同的进程里的UI线程,同时系统调用对组件的调度工作都由这个线程处理。所以响应系统回调的方法,(例如,onKeyDown()报告用户动作事件和生命周期相关的回调函数调用)总是运行在进程里的UI线程。例如,当用户在屏幕上触摸一个按钮,应用的UI线程就会调度一个触摸事件到指定的部件里,同时也设置它的按压状态和邮发出一个验证失效的请求加入到事件队列里。UI线程从队列里抽出请求同时通知指定的部件去跟新重绘自己的界面。当应用在响应用户的过程中,执行大量的工作,这种单线程的模型会减低性能效率,除非你很好合理地实现好应用功能。特别是如果什么事情都要发生在UI线程里,执行长时间操作例如网络访问或者是数据库请求这些,都会阻塞整个应用的界面。当UI线程阻塞时,就没有系统事件可以调度了,包括绘画事件。从用户角度来看,应用就会给挂起,没响应。甚至最差的情况下,如果UI线程阻塞超过几秒(当前大概5秒钟)系统就会弹出应用没有响应的对话框。如果用户说个不开心,就会可能强制退出应用或者直接卸载它算了。另外,android的UI工具箱不是线程安全的。所以,所有的UI维护管理操作只可以放在UI线程里,不能放在新开的线程里。因此,对于android的单线程模型有两条简单的规则: 1.不能阻塞UI线程 2.android UI工具箱的访问操作只能在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();}首先,这样的代码看起来好像没有什么问题,创建一个线程去处理网络的操作,正常套路是这样的。但是它违反了单线程模型的第二条规则:android 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://example.com/image.png");            mImageView.post(new Runnable() {                public void run() {                    mImageView.setImageBitmap(bitmap);                }            });        }    }).start();}现在这样的代码就是线程安全的:网络操作是在独立的线程里,同时ImageView的访问在UI线程里。这样就符合android单线程模型的规则。但是,由于复杂操作的增加,会导致这种类型的代码维护起来很困难也变得复杂。为了解决在工作线程里更多复杂的交互,你应该考虑在工作线程里使用Handler,这样有利于处理从UI线程发出的消息调度。大概最好的方法是去继承 AsyncTask类,简单执行工作线程,也可以同UI线程进行交互。使用AsyncTaskAsyncTask允许你在用户界面上运行不同步的工作任务。工作任务可以是线程阻塞的,然后返回的结果是在UI线程里的,也不需要你去上述所说的增加额外的处理代码。使用AsyncTask,你必须要继承AsyncTask同时实现doInBackground()回调方法,而言它就会运行在一个后台的线程池里面。你更新界面的时候,你需要实现onPostExecute()这个方法,他会接受从doInBackground()穿过来的结果,同时运行在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[0]);    }    /** 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线程的部分给分开独立了,清晰又简洁。但是你最好还是阅读一下AsyncTask类api介绍和详细使用方法,这里可以快速浏览它是怎样工作的: *通常情况下,你可以指定参数的类型,工作进度的值,工作任务完成的结果 *doInBackground()方法调用会自动运行在工作线程里 *onPreExecute(), onPostExecute(), onProgressUpdate()方法全部都是运行在UI线程里 *doInBackground()的返回值会传到onPostExecute() *任可时候在doInBackground()里可以调用publishProgress()去调用在UI线程里的onProgressUpdate()方法 *在其他线程里,你可以任可时候都可以取消工作任务注意:在activity里,工作线程有可能会出现异常重启的问题,这是由于一个运行时配置的改变(例如当用户把屏幕旋转了),这样会烧毁停止工作线程的。在这种情况下如果你想继续保留工作任务的运行,详情你可以参考Shelves应用的例子源代码。线程安全的方法在某些情况下,你实现的方法有可能在多个工作线程里进行调用,所以实现的方法有必要是线程安全的。线程安全的方法在远程调用里是真的很重要的 - 例如在绑定服务里的方法。当在相同进程的IBinder里去执行调用一个方法,同时IBinder正在运行中,调用的方法是处于在调用者的线程里。无论怎样,当一个原始调用在另一个进程里,执行调用的方法所在的线程是由系统在同一个IBinder进程里的线程池里运行的(不是进程里的UI线程)例如,一个服务的onBind()方法从服务进程的UI线程里调用,onBind()返回的值的对象里的方法(例如一个子类实现的RPC方法)调用会运行在线程池里。因为一个服务可以拥有多个客户,同时更多的线程池可以执行调用相同IBinder方法。所以IBinder方法必须是线程安全的。同样,一个内容提供者可以获取在其他进程的数据请求。虽然ContentResolver和ContentProvider类隐藏内部详细交互的细节,ContentProvider响应了请求的方法,例如,query(), insert(), delete(),update(), getType() - 运行调用在一个内容提供者进程的一个线程池里,除了进程的UI线程。因为这些方法有可能在同一时间里被许多大量线程调用的,所以它们必须也是线程安全的。进程之间的沟通Android提供了一个进程之间沟通的机制(IPC)就是方法远程调用(RPCs),一个方法被一个activity或者其他应用组件所调用执行,但是远程执行来的(在另一个进程里),同时调用的结果会返回到调用者那里。首先会把一个方法进行分解,同时方法的数据会转化成操作系统可以理解,由本地进程和地址空间到远程的进程和地址空间进行传送,封包和解包的一系列动作。把方法返回的值传递到正确地方向。Android提供了这些所有IPC的封装代码,所以你可以专注在RPC编程接口的定义和实现了。为了执行IPC,你的应用必须绑定到一个服务里,使用bindService()。更多详情请看服务的开发指引。

0 0
原创粉丝点击