`

线程及消息传递机制

阅读更多

一:创建线程

 

 

1.通过Thread类的构造方法创建。

Thread thread = new Thread(new Runnable() {
	@Override
	public void run() {
		// do something;
	}
});

 

 

2.通过实现Runnable接口创建。

 

public class PageActivity11 extends Activity implements Runnable{			
@Override			
protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
				Thread thread = new Thread(MainActivity.this);
		thread.start();
	}
			@Override
			public void run() {
		// do something;
	}
}
 

 

 

3.使用AsyncTask

 

public class GetAddresshAsync extends AsyncTask<Void, Integer, Void> {
	@Override
	protected Void doInBackground(Void... params) {
	// do something;
	}
	@Override
	protected void onPostExecute(Void result) {
	// 完成后调用,可操作UI
	}
}
 

 

 

注意:

A:主线程负责管理与UI相关的事件,用户自己创建的子线程不能对UI组件进行操作。

 

B:t.interrupt()不会中断正在执行的线程,只是将线程的标志位设置成true。但是如果线程在调用sleep(),join(),wait()方法时线程被中断,则这些方法会抛出InterruptedException,在catch块中捕获到这个异常时,线程的中断标志位已经被设置成false了,因此在此catch块中调用t.isInterrupted(),Thread.interrupted()始终都为false, 而t.isInterrupted与Thread.interrupted()的区别是API中已经说明很明显了,Thread.interrupted()假如当前的中断标志为true,则调完后会将中断标志位设置成false。中止线程可以在run()方法中设置标志,如果使用了sleep(),join(),wait()方法,catch块中要使用break跳出run()方法。

 

二:消息传递机制(Message、MessageQueue、Looper、Handler)

 

1.Message:消息对象,顾名思义就是记录消息信息的类。这个类有几个比较重要的字段:

arg1和arg2:我们可以使用两个字段用来存放我们需要传递的整型值,在Service中,我们可以用来存放Service的ID。

obj:该字段是Object类型,我们可以让该字段传递某个多项到消息的接受者中。

what:这个字段可以说是消息的标志,在消息处理中,我们可以根据这个字段的不同的值进行不同的处理,类似于我们在处理Button事件时,通过switch(v.getId())判断是点击了哪个按钮。

在使用Message时,我们可以通过new Message()创建一个Message实例,但是Android更推荐我们通过Message.obtain()或者Handler.obtainMessage()获取Message对象。这并不一定是直接创建一个新的实例,而是先从消息池中看有没有可用的Message实例,存在则直接取出并返回这个实例。反之如果消息池中没有可用的Message实例,则根据给定的参数new一个新Message对象。通过分析源码可得知,Android系统默认情况下在消息池中实例化10个Message对象。

 

2.MessageQueue:消息队列,用来存放Message对象的数据结构,按照“先进先出”的原则存放消息。存放并非实际意义的保存,而是将Message对象以链表的方式串联起来的。MessageQueue对象不需要我们自己创建,而是有Looper对象对其进行管理,一个线程最多只可以拥有一个MessageQueue。我们可以通过Looper.myQueue()获取当前线程中的MessageQueue。

 

3.Looper: MessageQueue的管理者,在一个线程中,如果存在Looper对象,则必定存在MessageQueue对象,并且只存在一个Looper对象和一个MessageQueue对象。倘若我们的线程中存在Looper对象,则我们可以通过Looper.myLooper()获取,此外我们还可以通过Looper.getMainLooper()获取当前应用系统中主线程的Looper对象。在这个地方有一点需要注意,假如Looper对象位于应用程序主线程中,则Looper.myLooper()和Looper.getMainLooper()获取的是同一个对象。同一线程下的Handler共享一个Looper对象。

 

4.Handler: 消息的处理者。通过Handler对象我们可以封装Message对象,然后通过sendMessage(msg)把Message对象添加到其所在的MessageQueue中;当MessageQueue循环到该Message时,就会调用该Message对象对应的handler对象的handleMessage()方法对其进行处理。由于是在handleMessage()方法中处理消息,因此我们应该编写一个类继承自Handler,然后在handleMessage()处理我们需要的操作。一个线程中可以有多个Handler,如果在非线程中创建Handler,需要在该线程中创建Looper(Looper.prepare();),并建立一个消息循环(Looper.myLooper().loop();)。也可以在创建Handler的时候以参数方式传入主线程的Looper(new Handler(Looper.getMainLooper()){…}),该handleMessage方法将在主线程中执行。

需要注意的是,当Activity finish()的时候,需要调用Handler.removeCallbacksAndMessages(null)清空消息队列,否则如果有delay消息未处理,Activity不会调用onDestory()。但如果线程还有消息继续发送,将会继续执行handleMessage处理消息。所以当有异步线程消息需要处理,而且在handleMessage里判断NullPointException或者使用try..catch块。

关于Handler Leak:在Android中,Handler类应该是静态的,否则,可能发生泄漏。在应用程序线程的MessageQueue中排队的Message对象还保留他们的目标Handler。如果Handler是一个内部类(注:无论是匿名还是非匿名,匿名是比较常见用法),它的外部类将被保留(至于为什么,请参考Java嵌套类相关说明)。为了避免泄漏外部类,声明一个Handler子类为静态内部类(注:这样就避免了Handler对象对外部类实例的自动引用),其内部持有一个对外部类对象的WeakReference。上面是HandlerLeak的详细解释。

A:排队中的Message对象对Handler的持有导致泄漏:调用Handler.removeCallbacksAndMessages(null)清空消息队列。

B:Handler对象对外部类(如Activity或Service)实例的强引用持有:Handler的实现类采用静态内部类的方式,避免对外部类的强引用,在其内部声明一个WeakReference引用到外部类的实例。

 

static class MyHandler extends Handler {
	// WeakReference to the outer class's instance.
	private WeakReference<HandlerActivity> mOuter;

	public MyHandler(HandlerActivity activity) {
		mOuter = new WeakReference<HandlerActivity>(activity);
	}
	@Override
	public void handleMessage(Message msg) {
		HandlerActivity outer = mOuter.get();
		if (outer != null) {
			// 可使用mOuter.classfunction来访问成员变量及方法。
			Log.i("Handler", "handleMessage");
		}
	}
}

private final MyHander mHandler = new MyHandler();
 

 

 

三:线程消息模式

 

1.Handler+Thread+Message模式

 

这种模式使用了线程,所以可以看到异步加载的效果。

public class TestActivity extends Activity {
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		loadImage2("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
		loadImage2("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.imageView2);
		loadImage2("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3);
		loadImage2("http://csdnimg.cn/www/images/csdnindex_logo.gif", R.id. imageView4);
		loadImage2("http://images.cnblogs.com/logo_small.gif", R.id. imageView5);
	}

	final Handler handler2 = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			((ImageView) TestActivity.this.findViewById(msg.arg1)).setImageDrawable((Drawable) msg.obj);
		}
	};

	// 采用handler+Thread模式实现多线程异步加载
	private void loadImage2(final String url, final int id) {
		Thread thread = new Thread() {
			@Override
			public void run() {
				Drawable drawable = null;
				try {
					drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");
				} catch (IOException e) {
					Log.d("test", e.getMessage());
				}

				// 模拟网络延时
				SystemClock.sleep(2000);
				Message message = handler2.obtainMessage();
				message.arg1 = id;
				message.obj = drawable;
				handler2.sendMessage(message);
			}
		};
		thread.start();
		thread = null;
	}
}

这时候我们可以看到实现了异步加载, 界面打开时,五个ImageView都是没有图的,然后在各自线程下载完后才把图自动更新上去。

 

2.Handler+ExecutorService(线程池)+MessageQueue模式

能开线程的个数毕竟是有限的,我们总不能开很多线程,对于手机更是如此。

这个例子是使用线程池。Android拥有与Java相同的ExecutorService实现,我们就来用它。

线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。

线程池的信息可以参看这篇文章:Java&Android的线程池-ExecutorService 下面的演示例子是创建一个可重用固定线程数的线程池。

public class TestActivity extends Activity {
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		loadImage3("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
		loadImage3("http://www.chinatelecom.com.cn/images/logo_new.gif",
				R.id.imageView2);
		loadImage3("http://cache.soso.com/30d/img/web/logo.gif",
					R.id.imageView3);
		loadImage3("http://csdnimg.cn/www/images/csdnindex_logo.gif",
					R.id.imageView4);
		loadImage3("http://images.cnblogs.com/logo_small.gif", R.id. imageView5);
	}

	private Handler handler = new Handler();

	private ExecutorService executorService = Executors.newFixedThreadPool(5);

	// 引入线程池来管理多线程
	private void loadImage3(final String url, final int id) {
		executorService.submit(new Runnable() {
			public void run() {
				try {
					final Drawable drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");
					// 模拟网络延时
					SystemClock.sleep(2000);
					handler.post(new Runnable() {
						public void run() {
							((ImageView) TestActivity.this.findViewById(id))
										.setImageDrawable(drawable);
						}
					});
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
			}
		});
	}
}

 

使用了 handler.post(new Runnable() {  更新前段显示当然是在UI主线程,我们还有 executorService.submit(new Runnable() { 来确保下载是在线程池的线程中。

 

3.Handler+ExecutorService(线程池)+MessageQueue+缓存模式

下面比起前一个做了几个改造:

A:把整个代码封装在一个类中

B:为了避免出现同时多次下载同一幅图的问题,使用了本地缓存

public class AsyncImageLoader3 {
	// 为了加快速度,在内存中开启缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)
	public Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>();
	private ExecutorService executorService = Executors.newFixedThreadPool(5); // 固定五个线程来执行任务
	private final Handler handler = new Handler();
	/**
	 * 
	* @param imageUrl
	 *           图像url地址
	 * @param callback
	 *           回调接口 <a
	 *           href="\"http://www.eoeandroid.com/home.php?mod=space&uid=7300\""
	 *           target="\"_blank\"">@return</a> 返回内存中缓存的图像,第一次加载返回null
	 */
	public Drawable loadDrawable(final String imageUrl, final ImageCallback callback) {
		// 如果缓存过就从缓存中取出数据
		if (imageCache.containsKey(imageUrl)) {
			SoftReference<Drawable> softReference = imageCache.get(imageUrl);
			if (softReference.get() != null) {
				return softReference.get();
			}
		}
		// 缓存中没有图像,则从网络上取出数据,并将取出的数据缓存到内存中
		executorService.submit(new Runnable() {
			public void run() {
				try {
					final Drawable drawable = loadImageFromUrl(imageUrl);
					imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));
					handler.post(new Runnable() {
						public void run() {
							callback.imageLoaded(drawable);
						}
					});
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
			}
		});
		return null;
	}

	// 从网络上取数据方法
	protected Drawable loadImageFromUrl(String imageUrl) {
		try {
			// 测试时,模拟网络延时,实际时这行代码不能有
			SystemClock.sleep(2000);
			return Drawable.createFromStream(new URL(imageUrl).openStream(),
					"image.png");
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	// 对外界开放的回调接口
	public interface ImageCallback {
		// 注意 此方法是用来设置目标对象的图像资源
		public void imageLoaded(Drawable imageDrawable);
	}
}

 

说明:

final参数是指当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。参看:Java关键字final、static使用总结 

这里使用SoftReference 是为了解决内存不足的错误(OutOfMemoryError)的,更详细的可以参看:内存优化的两个类:SoftReference 和 WeakReference

前段调用:

public class TestActivity extends Activity {
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		loadImage4("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
		loadImage4("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.imageView2);
		loadImage4("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3);
		loadImage4("http://csdnimg.cn/www/images/csdnindex_logo.gif", R.id.imageView4);
		loadImage4("http://images.cnblogs.com/logo_small.gif", R.id. imageView5);
	}
	private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3();
	// 引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程
	private void loadImage4(final String url, final int id) {
		// 如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行
		Drawable cacheImage = asyncImageLoader3.loadDrawable(url,
				new AsyncImageLoader3.ImageCallback() {
					// 请参见实现:如果第一次加载url时下面方法会执行
					public void imageLoaded(Drawable imageDrawable) {
						((ImageView) findViewById(id)).setImageDrawable(imageDrawable);
					}
				});
		if (cacheImage != null) {
			((ImageView) findViewById(id)).setImageDrawable(cacheImage);
		}
	}
}

 

分享到:
评论

相关推荐

    线程间通信方式3:消息传递方式

    本示例“线程间通信方式3:消息传递方式”着重介绍了通过消息传递实现线程间的通信。下面将详细阐述这种通信方式以及相关的技术点。 1. **消息传递**: - 在Windows环境下,消息传递是通过消息队列、消息循环和...

    线程及消息机制

    ### 线程及消息机制在嵌入式WinCE中的应用 #### 1. 嵌入式WinCE概述 嵌入式WinCE系统是一种基于Windows内核的实时操作系统,适用于移动设备、工业控制和其他嵌入式应用领域。WinCE支持多任务处理,并且提供了丰富...

    MFC 多线程之间通过消息传递数据

    要实现多线程间的数据传递,我们需要利用Windows的消息机制。在Windows操作系统中,消息队列是线程安全的,这使得它成为线程间通信的一个理想选择。每个窗口都有自己的消息队列,当一个线程发送消息到另一个线程的...

    用户界面线程以及消息传递

    在Windows编程中,消息传递是UI线程的核心机制。每当用户与应用程序交互,比如点击按钮,操作系统都会创建一个消息并将其放入应用程序的消息队列。UI线程的任务就是通过调用`GetMessage`、`TranslateMessage`和`...

    进程间通信(消息传递机制)

    消息传递机制在多线程和多进程编程中尤其重要,因为它提供了进程间的协同工作和同步。比如,一个进程可以通过发送消息通知另一个进程某个操作已完成,或者请求另一个进程执行特定的任务。 总之,进程间通信中的消息...

    多线程及消息发送传递结构体参数

    在编程领域,多线程和消息发送传递结构体参数是两个关键的概念,它们在构建高效、并发和异步处理的应用程序中发挥着重要作用。这里,我们将深入探讨这两个主题,并结合提供的文件名,推测可能涉及的Delphi编程环境。...

    delphi多线程传递参数及同步二

    在创建线程时,我们常常需要向线程传递参数以便它能执行特定的任务。在 Delphi 中,我们可以通过重写 TThread 的 Execute 方法来接收和使用这些参数。Execute 方法是线程运行的核心,通常在这里处理线程的工作逻辑。...

    windows的多线程消息机制

    Windows的多线程消息机制是操作系统提供的一种处理并发任务的方式,尤其在开发MFC应用程序时,理解这一机制至关重要。在Windows环境下,线程是程序执行的独立单元,每个线程都有自己的消息队列,用于接收和处理来自...

    易语言多线程传递多参数

    提供的压缩包文件“易语言多线程传递多参数源码”包含了实际的代码实现,这将有助于我们理解上述概念。源码中可能会展示如何创建线程、设置线程参数、以及在线程子程序中如何访问和处理这些参数。通过阅读源码,我们...

    通过消息传递数据1.rar_消息传递_消息机制

    消息传递机制是一种进程间通信(IPC,Inter-Process Communication)的方式,它允许不同的程序或线程之间交换数据和控制信息。这种机制的关键在于发送方通过发送消息到接收方,而接收方则在适当的时间处理这些消息。...

    易语言多线程及线程参数传递案例.zip

    在"易语言多线程传递参数案例文件版"中,我们可以看到如何通过文件操作来传递参数。这种情况下,线程可能会读取或写入特定的文件来获取或更新数据。这在处理大文件或者需要持久化数据的场景下非常有用,因为文件可以...

    vc中消息传递机制

    在Microsoft Visual C++ (VC++) 中,消息传递机制是Windows应用程序设计的核心部分,它使得用户界面元素(如按钮、文本框等)能够响应用户的交互。本文将深入探讨VC中的消息传递机制,包括其原理、工作流程以及如何...

    MFC线程自定义消息循环

    这通常包括调用` PeekMessage `或`GetMessage`函数来检查是否有待处理的消息,然后通过`TranslateMessage`和`DispatchMessage`将消息传递给相应的窗口函数进行处理。代码示例如下: ```cpp int CMyThread::Run() { ...

    多线程及消息的简单示例

    这个名为"多线程及消息的简单示例"的程序旨在帮助开发者理解和应用这两个概念。下面我们将深入探讨这两个主题。 多线程是操作系统提供的一种并发执行任务的方式。在一个程序中,线程是执行的基本单元,每个线程可以...

    易语言 利用超级列表框多线程参数传递文本

    2. **参数传递**:线程间传递参数通常有两种方式:全局变量和消息机制。全局变量适用于简单的数据传递,但要注意线程同步,防止数据冲突。消息机制更安全,可以使用`发送消息`和`接收消息`命令,将数据封装成消息...

    C++消息机制

    C++并没有内置的消息传递系统,但可以通过多种技术实现类似的功能,如函数调用、继承、多态、模板、信号与槽机制等。下面我们将详细探讨这些知识点。 1. 函数调用: 在C++中,最基本的消息传递方式是通过函数调用来...

    C#多线程消息处理例子

    总结来说,这个C#多线程消息处理例子展示了如何在后台线程中执行任务并安全地将结果传递给UI线程。它涉及到的关键技术包括线程创建、消息队列、事件与委托、以及线程安全的UI更新。理解并掌握这些概念对于编写高性能...

    杭电操作系统课程设计-线程调度 和 消息传递 课本p156作业答案整理

    在课程设计中,学生可能需要实现这些调度算法和消息传递机制,通过编程模拟实际操作系统的行为。这有助于深入理解操作系统的内部工作原理,并锻炼解决问题的能力。同时,线程调度和消息传递的设计与优化对于提升系统...

    实时接收发送消息(接收消息线程阻塞,发送消息线程唤醒)

    - 对于Java开发者,可以利用`java.util.concurrent`包中的工具,如`BlockingQueue`来实现线程间的消息传递,`Condition`来控制线程的阻塞和唤醒。 - 接收线程可以尝试从队列中取消息,如果没有消息则会阻塞;发送...

    线程消息传送机制源码

    在Android开发中,线程和Handler消息传递机制是至关重要的组成部分,它们允许开发者在不同的线程间进行数据通信,特别是解决UI线程与后台线程的交互问题。本篇文章将深入探讨这一机制,重点关注Handler、Looper和...

Global site tag (gtag.js) - Google Analytics