Android的进程与线程(3)线程安全问题

简介: 当一个程序启动的时候,系统会为程序创建一个名为main的线程。这个线程重要性在于它负责把事件分发给适合的用户组件,这些事件包括绘制事件。

当一个程序启动的时候,系统会为程序创建一个名为main的线程。这个线程重要性在于它负责把事件分发给适合的用户组件,这些事件包括绘制事件。并且这个线程也是你的程序与Android UI工具包中的组件(比如android.widgetandroid.view包中的组件)进行交互的线程。正因为如此,这个main线程有时也被称为UI线程。

系统并不会为组件的每个实例都创建一个单独的线程。运行在同一个进程中的所有组件都是在UI线程中实例化的,并且系统对这些组件的调用都是由UI分发的。所以,对系统的回调做出回应的方法都是运行在进程中的UI线程中的(比如,用于报告用户操作的onKeyDown()方法,或者生命周期回调方法)。

比如,当用户按下了屏幕上的一个按钮,你程序的UI线程就会将这个触屏事件分发给这个button组件,然后这个组件会依次设置它的按下状态,并向事件队列发送一个无效请求(post an invalidate request to the event queue)。然后UI线程将这个请求弹出队列,并通知这个组件使其重绘。

当你的程序在为了响应用户事件,而频繁的执行操作的时候,除非你能很恰当的实现你的程序,否则的话,可能会给用户带来不好的体验。特别是,如果让程序中所有的工作都在主线程中完成的时候,像访问互联网或者数据库这些需要长时间的操作将会堵塞整个UI。当UI线程被阻塞的时候,所有的事件都不能被分发,包括绘制事件。从用户的角度来讲,这时这个程序就像终止了一样。更糟糕的是,如果UI线程被堵塞超过5秒钟的话,系统将会弹出一个“application not responing(ANR)对话框。然后用户可能就会决定放弃你的这个程序甚至选择卸载。

另外,AndroidUI toolkit不是线程安全的。所以,你不能在你的其他线程中处理UI,你的所有的UI方面的工作都应该在UI线程来完成。这里,有两条关于Android单线程模型的规则:

1.不要堵塞UI线程

2.不要在UI线程之外访问Android UI toolkit

worker thread

正是因为上面讨论的这种单线程模型,为了程序的响应速度,你是绝不能堵塞UI线程的。如果你要做的工作不是瞬时就能完成的,那么你就应该在新的线程"background"或者worker”线程)中处理它们。

比如,下面是关于单击一个监听器来在一个单独的线程中下载一张图片,并使用ImageView显示该图片的代码:

public void onClick(View v){
new Thread(new Runnable(){
Bitma b = loadImageFromNetwork("http://examble.com/image.png");
mImageView.setImageBitmap(b);
}).start();
}

乍看起来的话,这段代码好像是没问题的,它创建一个新的线程来处理联网操作。然而它违反了上面的第二条规则:不能在UI线程之外访问Android Ui tookit,因为这个例子中在新的worker线程中修改了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 线程中完成的。

然而,当随着操作的复杂性提高的时候,上面的代码可能会变得复杂,并且不容易维护。为了使用worker线程处理更复杂的操作,你可以考虑在你的worker线程使用Handler来处理从UI线程中分发过来的消息。或许最好的方式,是通过继承AsyncTask这个类,这简化了worker线程需要同UI进行交互的过程。下面介绍怎样使用AsyncTask

AsyncTask允许你对于UI执行异步的操作。它在worker线程中执行耗时的操作,然后在UI线程中更新结果,在这个过程中不需要你去处理线程或者handlers

为了使用AsyncTask,你必须继承AsyncTask并实现doInBackground回调方法,这些回调方法运行在后台线程的池中(run in a pool of background threads)。为了更新你的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 sytem calls this to perform work in worker thread and delivers it the parameters given to AsyncTask.execute()*/
procted Bitmap doInBackground(String... urls){
return loadImageFromNetwork(urls[0]);
}
/**系统调用该方法来在更新UI线程,并将doInbackground()的结果返回出来*/
protected void onPostExecute(Bitmap result){
mImageView.setImageBitmap(result);
}
}


现在这个Ui是安全的,并且代码更简单。下面是对AsyncTask的一个简单介绍:

-你可以通过使用泛型,来指定参数的类型,进度值(the progress values)和the final value of the task

-方法doInBackground()将自动在worker线程中执行

-onPreExecute()onPostExecute()onProgressUpdate()方法都是在UI线程中触发的

-doInBackground()方法的结果将返回到onPostExecute()方法中

-doInbackground()方法中,你可以在任意的时间通过调用publishprogress()来在主线程中执行onProgressUpdate()

-你可以从任何线程中取消当前的task


这里有一点需要注意的是当使用worker线程时,可能会因为设备运行时配置发生了改变(比如屏幕翻转),而导致worker线程重启。


参考文档:http://developer.android.com/guide/topics/fundamentals/processes-and-threads.html

相关文章
|
1月前
|
调度 开发者 Python
深入浅出操作系统:进程与线程的奥秘
在数字世界的底层,操作系统扮演着不可或缺的角色。它如同一位高效的管家,协调和控制着计算机硬件与软件资源。本文将拨开迷雾,深入探索操作系统中两个核心概念——进程与线程。我们将从它们的诞生谈起,逐步剖析它们的本质、区别以及如何影响我们日常使用的应用程序性能。通过简单的比喻,我们将理解这些看似抽象的概念,并学会如何在编程实践中高效利用进程与线程。准备好跟随我一起,揭开操作系统的神秘面纱,让我们的代码运行得更加流畅吧!
|
3月前
|
消息中间件 并行计算 安全
进程、线程、协程
【10月更文挑战第16天】进程、线程和协程是计算机程序执行的三种基本形式。进程是操作系统资源分配和调度的基本单位,具有独立的内存空间,稳定性高但资源消耗大。线程是进程内的执行单元,共享内存,轻量级且并发性好,但同步复杂。协程是用户态的轻量级调度单位,适用于高并发和IO密集型任务,资源消耗最小,但不支持多核并行。
65 1
|
9天前
|
消息中间件 调度
如何区分进程、线程和协程?看这篇就够了!
本课程主要探讨操作系统中的进程、线程和协程的区别。进程是资源分配的基本单位,具有独立性和隔离性;线程是CPU调度的基本单位,轻量且共享资源,适合并发执行;协程更轻量,由程序自身调度,适合I/O密集型任务。通过学习这些概念,可以更好地理解和应用它们,以实现最优的性能和资源利用。
42 11
|
8天前
|
Java Linux 调度
硬核揭秘:线程与进程的底层原理,面试高分必备!
嘿,大家好!我是小米,29岁的技术爱好者。今天来聊聊线程和进程的区别。进程是操作系统中运行的程序实例,有独立内存空间;线程是进程内的最小执行单元,共享内存。创建进程开销大但更安全,线程轻量高效但易引发数据竞争。面试时可强调:进程是资源分配单位,线程是CPU调度单位。根据不同场景选择合适的并发模型,如高并发用线程池。希望这篇文章能帮你更好地理解并回答面试中的相关问题,祝你早日拿下心仪的offer!
26 6
|
1月前
|
消息中间件 Unix Linux
【C语言】进程和线程详解
在现代操作系统中,进程和线程是实现并发执行的两种主要方式。理解它们的区别和各自的应用场景对于编写高效的并发程序至关重要。
70 6
|
1月前
|
调度 开发者
深入理解:进程与线程的本质差异
在操作系统和计算机编程领域,进程和线程是两个核心概念。它们在程序执行和资源管理中扮演着至关重要的角色。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
70 5
|
1月前
|
算法 调度 开发者
深入理解操作系统:进程与线程的管理
在数字世界的复杂编织中,操作系统如同一位精明的指挥家,协调着每一个音符的奏响。本篇文章将带领读者穿越操作系统的幕后,探索进程与线程管理的奥秘。从进程的诞生到线程的舞蹈,我们将一起见证这场微观世界的华丽变奏。通过深入浅出的解释和生动的比喻,本文旨在揭示操作系统如何高效地处理多任务,确保系统的稳定性和效率。让我们一起跟随代码的步伐,走进操作系统的内心世界。
|
1月前
|
调度 开发者
核心概念解析:进程与线程的对比分析
在操作系统和计算机编程领域,进程和线程是两个基本而核心的概念。它们是程序执行和资源管理的基础,但它们之间存在显著的差异。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
67 4
|
1月前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
2月前
|
API Android开发 iOS开发
深入探索Android与iOS的多线程编程差异
在移动应用开发领域,多线程编程是提高应用性能和响应性的关键。本文将对比分析Android和iOS两大平台在多线程处理上的不同实现机制,探讨它们各自的优势与局限性,并通过实例展示如何在这两个平台上进行有效的多线程编程。通过深入了解这些差异,开发者可以更好地选择适合自己项目需求的技术和策略,从而优化应用的性能和用户体验。

热门文章

最新文章

相关实验场景

更多