一、概述
因为子线程的run()方法无法修改UI线程(主线程)的UI界面,所以Android引入了Handler消息传递机制,实现在新创建的线程中操作UI界面
二、消息类(Message)
消息类是存放在MessageQueue中的,而一个MessageQueue中可以包含多个Message对象
每一个Message对象可以通过Message.obtain()或者Handler.obtainMessage()方法获得
一个Message具有的属性:
属性 |
类型 |
介绍 |
arg1 |
int |
存放整型数据 |
arg2 |
int |
存放整型数据 |
obj |
Object |
存放Object类型的任意对象 |
replyTo |
Message |
指定此Message发送到哪里的可选Message对象 |
what |
int |
指定用户自定义的消息代码,接受者可以了解这个消息的信息 |
一个Message对象可以携带int类型的数据,而如果要携带其他类型的数据,可以将要携带的数据保存到Bundle对象中,然后通过Message类的setDate()方法将其添加到Message中
注:
1、尽量使用Message.what标识信息,方便用于不同的方式处理Message
2、通常情况下,尽量使用Message.obtain()或者Handler.obtainMessage()方法从消息池中获得空消息对象,以便节省资源,而不是使用Message的默认构造方法
3、当一个Message对象只需要携带int型数据的时候,优先使用Message.arg1或Message.arg2属性,这要比用Bundle携带int数据节省内存
三、消息处理类(Handler)
Handler 允许 发送或者处理 Message或者Runnable 类的对象到其(Handler)所在线程的MessageQueue中
主要有两个作用:
1、连接主线程和子线程进行通信(UI线程和工作线程通信)
2、将Message对象 通过post()或者sendMessage()方法发送到MessageQueue中,
当MessageQueue循环到该对象时,调用相应的Handler对象的handlerMessage()方法进行处理
Handler类提供的部分方法:
方法 |
介绍 |
handleMessage(Message msg) |
处理消息的方法。 通常使用该方法处理消息, 在发送消息时,该方法会自动回调 |
Post(Runnable r) |
立即发送Runnable对象, 注:该Runnable对象最终将被封装成Mwssage对象 |
PostAtTime(Runnable r,long uptimeMillis) |
定时发送Runnable对象, 注:该Runnable对象最终将被封装成Mwssage对象 |
postDelayed(Runnable r,long delayMillis) |
延迟发送Runnable对象, 注:该Runnable对象最终将被封装成Mwssage对象 |
sendEmptyMessage(int what) |
发送空消息 |
sendMessage(Message msg) |
立即发送消息 |
sendMessageAtTime(Message msg) |
定时发送消息 |
sendMessageDelayed(Message msg,long delayMillis) |
延迟发送消息 |
注:在一个线程中,只能有一个Looper和MessageQueue,却可以有多个Handler,这些Handler共享同一个Looper和MessageQueue
四、循环着(Looper)
1、从上面只是可以知道:一个线程中,只能有一个Looper和MessageQueue。
也就是说一个线程对应一个Looper对象,而一个Looper对象又对应一个MessageQueue(消息队列),一个MessageQueue又存放着多条Message
注意:MessageQueue中,消息的存放时FIFO(先进先出)的。
2、Looper对象是为了一个线程开启一个消息循环,来操作MessageQueue
注:在主线程中,系统会为主线程自动创建一个Looper对象来开启消息循环。而在非主线程中,是没有Looper对象的,没有Looper对象就不能创建Handler对象
so,在主线程中直接创建Handler对象是可以的。但是在非主线程中创建Handler对象则会产生异常
3、如果需要在非主线创建Handler对象
(1)使用Looper类的prepare()方法来初始化一个Looper对象
(2)创建Handler对象
(3)使用Looper类的loop()方法启动Looper,开启消息循环
//需要先有Looper对象 Looper.prepare();//会创建一个Looper对象,并把该对象放入到该线程的本地变量中,在Looper的构造方法中创建了MessageQueue对象 //在子线程中实例化handler,子线程中没有Looper对象 handler = new Handler(){ };//如果直接实例化Handler,会异常 new RuntimeException,原因是子线程中没有Looper对象 //让Looper对象循环读取MessageQueue中的消息 //循环从队列中读取消息,当读到消息时,会去调用 msg.target.dispatchMessage(msg); //在Message类中有一个target成员,当发送消息时,target成员被赋值为当前的 handler对象 Looper.loop();
Looper提供的部分方法:
方法 |
描述 |
prepare() |
用于初始化Looper |
loop() |
启动Looper线程,线程循环消息队列(MessageQueue)获取和处理信息 |
myLooper() |
获得当前线程的Looper对象 |
getThread |
获得Looper对象所属的线程 |
quit() |
结束Looper循环 |
*注*:Looper.loop()方法,这个方法是循环消息队列!即这个方法内部相当于正在执行一个死循环,所以在Looper.loop()代码之后的代码都不会执行
,而只有再调用 Looper.quit()之后才会执行后面的代码
-------------------------------------------------------------华丽的分割线------------------------------------------------------------------
让我们看几个例子来深入理解下Handler消息传递机制
1、在主线程中启动一个子线程下载图片,子线程传消息递给主线程,让主线程处理。(了解子线程发送Runnable对象和发送Message对象的两种方法)
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" 6 android:gravity="center_horizontal" 7 > 8 9 <ImageView 10 android:layout_width="200dp" 11 android:layout_height="200dp" 12 android:id="@+id/show_image" 13 android:src="@drawable/ic_launcher" 14 /> 15 16 <Button 17 android:layout_width="wrap_content" 18 android:layout_height="wrap_content" 19 android:id="@+id/down_image" 20 android:text="下载" 21 /> 22 23 </LinearLayout>
布局文件代码
(1)发送Message对象的方法
1 package com.xqx.handle; 2 3 import java.io.IOException; 4 5 import org.apache.http.HttpResponse; 6 import org.apache.http.client.ClientProtocolException; 7 import org.apache.http.client.HttpClient; 8 import org.apache.http.client.methods.HttpGet; 9 import org.apache.http.impl.client.DefaultHttpClient; 10 import org.apache.http.util.EntityUtils; 11 12 import android.app.Activity; 13 import android.graphics.Bitmap; 14 import android.graphics.BitmapFactory; 15 import android.os.Bundle; 16 import android.os.Handler; 17 import android.os.Message; 18 import android.view.View; 19 import android.view.View.OnClickListener; 20 import android.widget.Button; 21 import android.widget.ImageView; 22 23 24 public class MainActivity extends Activity { 25 private String path = "http://images2015.cnblogs.com/blog/493196/201509/493196-20150911220222090-1272536050.jpg";//图片下载地址 26 private ImageView showImage; 27 private Button downImage; 28 //哪一个线程是接受消息的,就在哪个线程中实例化Handler对象 29 //因为主线程中,系统会为自动创建Looper对象,开启循环消息,所以只需要在主线程中定义Handler对象 30 private Handler handler = new Handler(){ 31 @Override //处理消息的方法,当消息发送过来时,该方法自动回调 32 public void handleMessage(android.os.Message msg) { 33 //处理方法,将图片显示在ImageView中 34 showImage.setImageBitmap((Bitmap) msg.obj); 35 36 }; 37 }; 38 @Override 39 protected void onCreate(Bundle savedInstanceState) { 40 // TODO Auto-generated method stub 41 super.onCreate(savedInstanceState); 42 setContentView(R.layout.activity_main); 43 44 showImage = (ImageView) findViewById(R.id.show_image); 45 downImage = (Button) findViewById(R.id.down_image); 46 47 downImage.setOnClickListener(new OnClickListener() { 48 49 @Override 50 public void onClick(View v) { 51 // TODO Auto-generated method stub 52 //开启一个线程下载图片 53 new Thread(new Runnable() { 54 55 @Override 56 public void run() { 57 // TODO Auto-generated method stub 58 HttpGet get = new HttpGet(path); 59 HttpClient client = new DefaultHttpClient(); 60 HttpResponse response = null; 61 62 try { 63 response = client.execute(get); 64 if(response.getStatusLine().getStatusCode()==200) 65 { 66 //获得下载后的图片的字节数据 67 byte b[] = EntityUtils.toByteArray(response.getEntity()); 68 //将图片字节数据转换成Bitmap格式 69 Bitmap bitmap = BitmapFactory.decodeByteArray(b, 0, b.length); 70 //下载完成,将图片发送给主线程 71 //从MessageQueue中获取可用的Message对象,如果没有可用的,则会创建一个新的Message对象 72 Message msg = Message.obtain(); 73 //把发送的图片放到msg对象中 74 msg.obj = bitmap; 75 //使用Handler.sendMessage(Message msg)方法传递消息 76 handler.sendMessage(msg); 77 } 78 79 80 } catch (ClientProtocolException e) { 81 // TODO Auto-generated catch block 82 e.printStackTrace(); 83 } catch (IOException e) { 84 // TODO Auto-generated catch block 85 e.printStackTrace(); 86 } 87 88 89 } 90 }).start();//不要忘记开启线程 91 } 92 }); 93 94 } 95 96 97 }
Handler.sendMessage(Message msg)
(2)、发送Runnable对象的方法
1 package com.xqx.handle; 2 3 import java.io.IOException; 4 5 import org.apache.http.HttpResponse; 6 import org.apache.http.client.ClientProtocolException; 7 import org.apache.http.client.HttpClient; 8 import org.apache.http.client.methods.HttpGet; 9 import org.apache.http.impl.client.DefaultHttpClient; 10 import org.apache.http.util.EntityUtils; 11 12 import android.app.Activity; 13 import android.graphics.Bitmap; 14 import android.graphics.BitmapFactory; 15 import android.os.Bundle; 16 import android.os.Handler; 17 import android.os.Message; 18 import android.view.View; 19 import android.view.View.OnClickListener; 20 import android.widget.Button; 21 import android.widget.ImageView; 22 23 24 public class Handler2 extends Activity { 25 private String path = "http://images2015.cnblogs.com/blog/493196/201509/493196-20150911220222090-1272536050.jpg";//图片下载地址 26 private ImageView showImage; 27 private Button downImage; 28 //哪一个线程是接受消息的,就在哪个线程中实例化Handler对象 29 //因为主线程中,系统会为自动创建Looper对象,开启循环消息,所以只需要在主线程中定义Handler对象 30 private Handler handler = new Handler(); 31 @Override 32 protected void onCreate(Bundle savedInstanceState) { 33 // TODO Auto-generated method stub 34 super.onCreate(savedInstanceState); 35 setContentView(R.layout.activity_main); 36 37 showImage = (ImageView) findViewById(R.id.show_image); 38 downImage = (Button) findViewById(R.id.down_image); 39 40 downImage.setOnClickListener(new OnClickListener() { 41 42 @Override 43 public void onClick(View v) { 44 // TODO Auto-generated method stub 45 //开启一个线程下载图片 46 new Thread(new Runnable() { 47 48 private Bitmap bitmap; 49 50 @Override 51 public void run() { 52 // TODO Auto-generated method stub 53 HttpGet get = new HttpGet(path); 54 HttpClient client = new DefaultHttpClient(); 55 HttpResponse response = null; 56 57 try { 58 response = client.execute(get); 59 if(response.getStatusLine().getStatusCode()==200) 60 { 61 //获得下载后的图片的字节数据 62 byte b[] = EntityUtils.toByteArray(response.getEntity()); 63 bitmap = BitmapFactory.decodeByteArray(b, 0, b.length); 64 //下载完成,将图片发送给主线程 65 //Handler.post(Runnable r)方法 66 handler.post(new Runnable() { 67 68 @Override 69 public void run() { 70 // TODO Auto-generated method stub 71 showImage.setImageBitmap(bitmap); 72 } 73 }); 74 } 75 76 77 } catch (ClientProtocolException e) { 78 // TODO Auto-generated catch block 79 e.printStackTrace(); 80 } catch (IOException e) { 81 // TODO Auto-generated catch block 82 e.printStackTrace(); 83 } 84 85 86 } 87 }).start();//不要忘记开启线程 88 } 89 }); 90 91 } 92 93 94 }
Handler.post(Runnable r)
最后因为下载网络图片,不要忘记在清单文件中 添加网络访问权限
<uses-permission android:name="android.permission.INTERNET"/>
效果图:
下载前:
下载后: