转载请注明地址:http://blog.csdn.net/MarkMingShuai/article/details/74973871
一、 Handler 消息传递机制初步认识:
(一)、引入:
子线程没有办法对 UI 界面上的内容进行操作,如果操作,将抛出异常: CalledFromWrongThreadException
为了实现子线程中操作 UI 界面, Android 中引入了 Handler 消息传递机制,目的是打破对主线程的依赖性。
什么是 Handler ?
handler 通俗一点讲就是用来在各个线程之间发送数据的处理对象。在任何线程中,只要获得了另一个线程的 handler ,则可以通过
handler.sendMessage(message) 方法向那个线程发送数据。基于这个机制,我们在处理多线程的时候可以新建一个 thread ,这个 thread
拥有 UI 线程中的一个 handler 。当 thread 处理完一些耗时的操作后通过传递过来的 handler 向 UI 线程发送数据,由 UI 线程去更新界面。
主线程:运行所有 UI 组件,它通过一个消息队列来完成此任务。设备会将用户的每项操作转换为消息,并将它们放入正在运行的消息
队列中。主线程位于一个循环中,并处理每条消息。如果任何一个消息用时超过 5 秒, Android 将抛出 ANR 。所以一个任务用时超过 5 秒,
应该在一个独立线程中完成它,或者延迟处理它,当主线程空闲下来再返回来处理它。
(二)、常用类:( Handler 、 Looper 、 Message 、 MessageQueue )
- Message :消息, 被传递和处理的数据。其中包含了消息 ID ,消息处理对象以及处理的数据等, 由 MessageQueue 统一列队,
终由 Handler 处理。
- Handler :处理者,负责 Message 的发送及处理。使用 Handler 时,需要实现 handleMessage(Message msg) 方法来对特定
的 Message 进行处理,例如更新 UI 等。 Handler 类的主要作用:(有两个主要作用) 1 )、在工作线程中发送消息; 2 )、在主线
程中获取、并处理消息。
- MessageQueue :消息队列, 本质是一个数据结构,用来 存放 Handler 发送过来的消息,并按照 FIFO 规则执行。当然,存
放 Message 并非实际意义的保存,而是将 Message 串联起来,等待 Looper 的抽取。
Looper : 消息泵或循环器, 不断从 MessageQueue 中抽取 Message 。因此,一个 MessageQueue 需要一个 Looper 。
Thread :线程,负责调度整个消息循环,即消息循环的执行场所。
(三)、 Handler 、 Looper 、 Message 、 MessageQueue 之间的关系:
Handler , Looper 和 MessageQueue 的三角关系
. Looper 和 MessageQueue 一一对应,创建一个 Looper 的同时,会创建一个 MessageQueue ;
2. 而 Handler 与它们的关系,只是简单的聚集关系,即 Handler 里会引用当前线程里的特定 Looper 和 MessageQueue ;
3. 在一个线程中,只能有一个 Looper 和 MessageQueue ,但是可以有多个 Handler ,而且这些 Handler 可以共享一个 Looper 和
MessageQueue ;
4. Message 被存放在 MessageQueue 中,一个 MessageQueue 中可以包含多个 Message 对象。
【备注:】
Looper 对象用来为一个线程开启一个消息循环,从而操作 MessageQueue ;
默认情况下, Android 创建的线程没有开启消息循环 Looper ,但是主线程例外。
系统自动为主线程创建 Looper 对象,开启消息循环;
所以主线程中使用 new 来创建 Handler 对象。而子线程中不能直接 new 来创建 Handler 对象就会异常。
子线程中创建 Handler 对象,步骤如下:
Looper.prepare();
Handler handler = new Handler() {
//handlemessage(){}
}
Looper.loop();
(四)、 Handler 类中常用方法:
1. handleMessage() 用在主线程中,构造 Handler 对象时,重写 handleMessage() 方法。该方法根据工作线程返回的消息标识,来分
别执行不同的操作。
2. sendEmptyMessage() 用在工作线程中,发送空消息。
3. sendMessage() 用在工作线程中,立即发送消息。
(五)、 Message 消息类中常用属性:
1. arg1 用来存放整型数据
2. arg2 用来存放整型数据
3. obj 用来存放 Object 数据
4. what 用于指定用户自定义的消息代码,这样便于主线程接收后,根据消息代码不同而执行不同的相应操作。
【重点】:使用 Message 需要注意 4 点:
1 、 Message 虽然也可以通过 new 来获取,但是通常使用 Message.obtain() 或 Handler.obtainMessage() 方法来从消息池中获得空消息对
象,以节省资源;
2 、如果一个 Message 只需要携带简单的 int 型数据,应优先使用 arg1 和 arg2 属性来传递数据,这样比其他方式节省内存;
3 、尽可能使用 Message.what 来标识信息,以便用不同的方式处理 Message ;
4 、如果需要从工作线程返回很多数据信息,可以借助 Bundle 对象将这些数据集中到一起,然后存放到 obj 属性中,再返回到主线程。
(六)、示例代码一:【重点】
private Handler handler = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text_main_info = (TextView) findViewById(R.id.text_main_info);
pDialog = new ProgressDialog(MainActivity.this);
pDialog.setMessage("Loading...");
image_main = (ImageView) findViewById(R.id.image_main);
// 主线程中的 handler 对象会处理工作线程中发送的 Message 。根据 Message 的不同编号进行相应的操作。
handler = new Handler() {
public void handleMessage(android.os.Message msg) {
// 工作线程中要发送的信息全都被放到了 Message 对象中,也就是上面的参数 msg 中。要进行操作就要先取出 msg 中传递的数据。
switch (msg.what) {
case 0:
// 工作线程发送 what 为 0 的信息代表线程开启了。主线程中相应的显示一个进度对话框
pDialog.show();
break;
case 1:
// 工作线程发送 what 为 1 的信息代表要线程已经将需要的数据加载完毕。本案例中就需要将该数据获取到,显示到指定 ImageView 控件中即可。
image_main.setImageBitmap((Bitmap) msg.obj);
break;
case 2:
// 工作线程发送 what 为 2 的信息代表工作线程结束。本案例中,主线程只需要将进度对话框取消即可。
pDialog.dismiss();
break;
}
}
}};
new Thread(new Runnable() {
@Override
public void run() {
// 当工作线程刚开始启动时,希望显示进度对话框,此时让 handler 发送一个空信息即可。
// 当发送这个信息后,主线程会回调 handler 对象中的 handleMessage() 方法。 handleMessage() 方法中
// 会根据 message 的 what 种类来执行不同的操作。
handler.sendEmptyMessage(0);
// 工作线程执行访问网络,加载网络图片的任务。
byte[] data = HttpClientHelper.loadByteFromURL(urlString);
// 工作线程将网络访问获取的字节数组生成 Bitmap 位图。
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0,
data.length);
// 工作线程将要发送给主线程的信息都放到一个 Message 信息对象中。
// 而 Message 对象的构建建议使用 obtain() 方法生成,而不建议用 new 来生成。
Message msgMessage = Message.obtain();
// 将需要传递到主线程的数据放到 Message 对象的 obj 属性中,以便于传递到主线程。
msgMessage.obj = bitmap;
// Message 对象的 what 属性是为了区别信息种类,而方便主线程中根据这些类别做相应的操作。
msgMessage.what = 1;
// handler 对象携带着 Message 中的数据返回到主线程
handler.sendMessage(msgMessage);
// handler 再发出一个空信息,目的是告诉主线程工作线程的任务执行完毕。一般主线程会接收到这个消息后,
// 将进度对话框关闭
handler.sendEmptyMessage(2);
}
}).start();
}
(七)、示例代码二:图片定时切换:
1 、思路:利用多线程,子线
handler = new Handler() {
public ImageSwitcher image_main_pic;
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
image_main_pic.setImageResource(imageId[position++]);
if (position >= imageId.length) {
position = 0;
}
break;
default:
break;
}
}
};
// 第一种解决办法:利用 Thread 和 Thread 的 sleep
// new Thread(new Runnable() {
// @Override
// public void run() {
// while (flag) {
// try {
// Thread.sleep(2000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// handler.sendEmptyMessage(0);
// }
// }
// }).start();
// 第二种解决办法:利用 Timer 定时器和定时器的 schedule() 方法。
//schedule() 方法中有三个参数:
/* 第一个:表示定时任务 TimerTask 。 TimerTask 类实现了 Runnable 接口,所以要 new TimerTask() ,一定要实现 run() 方法。
第二个:表示第一次执行前的等待延迟时间;
第三个:表示两次定时任务执行之间的间隔时间。 */
new Timer().schedule(new TimerTask() {
@Override
public void run() {
handler.sendEmptyMessage(0);
//sendEmptyMessage() 方法等同于以下几句话。所以。如果只发送一个 what ,就可以使用 sendEmptyMessage() 。这样更简单。
//Message message = Message.obtain();
// Message message2 = handler.obtainMessage();
//message.what = 0;
//handler.sendMessage(message);
}
}, 1, 1500);
}
二、Handler、Looper源码分析:
(一)、Handler的概念:
- Handler是用于发送和处理消息和一个线程的MessageQueue相关联的Runable对象。
- 每个Handler实例关联到一个单一线程和线程的messagequeue
- 当您创建一个Handler,从你创建它的时候开始,它就绑定到创建它的线程以及对应的消息队列,handler将发送消息到消息队列,
并处理从消息队列中取出的消息。
Handler的主要用途有两个:
(1) 、在将来的某个时刻执行消息或一个 runnable ;
(2) 、为运行在不同线程中的多个任务排队。
主要依靠以下方法来完成消息调度:
post(Runnable) 、
postAtTime(Runnable, long) 、
postDelayed(Runnable, long) 、
sendEmptyMessage(int) 、
sendMessage(Message) 、
sendMessageAtTime(Message) 、
sendMessageDelayed(Message, long)
【备注:】
post 方法是当到 Runable 对象到达就被插入到消息队列;
sendMessage 方法允许你把一个包含有信息的 Message 插入消息队列,它会在 Handler 的 handlerMessage(Message) 方法中执行 ( 该
方法要求在 Handler 的子类中实现 ) 。
当 Handler post 或者 send 消息的时候,可以在消息队列准备好的时候立刻执行,或者指定一个延迟处理或绝对时间对它进行处理,
后两个是实现了 timeout 、 ticks 或者其他 timing-based 的行为。
当你的应用创建一个进程时,其主线程 (UI 线程 ) 会运行一个消息队列,负责管理优先级最高的应用程序对象 (Activity 、广播接收器等 )
和任何他们创建的 windows 。你也可以创建自己的线程,通过 handler 与主线程进行通信,在新创建的线程中 handler 通过调用 post 或
sendMessage 方法,将传入的 Runnable 或者 Message 插入到消息队列中,并且在适当的时候得到处理。
(二)、 Handler 的用法:
当你实例化一个 Handler 的时候可以使用 Callback 接口来避免写自定义的 Handler 子类。这里的机制类似与 Thread 与 runable 接口的关系。
在 Handler 里面,子类要处理消息的话必须重写 handleMessage() 这个方法,因为在 handler 里面它是个空方法:
(三)、 Handler 、 Looper 、 Message 、 MessageQueue 消息传递机制源码分析:
A 、 Handler.java: ( 3 个属性, 10 个方法)
3 个属性:
final MessageQueue mQueue; 封装好的 Message 被 handler 发送出去,其实就是放到了 MessageQueue 消息队列中。
final Looper mLooper; 每个 handler 都有一个 looper 为其不断接收消息队列中的消息,并返回给 handler 。
final Callback mCallback; 被 handler 接收到的消息要通过 Callback 接口中的 handleMessage() 方法来进行处理。
10 个方法:
public boolean handleMessage(Message msg); Callback 接口中的 handleMessage() 方法,用来处理返回给 handler 的消息的。
public final Message obtainMessage() 获取 Message 对象,其本质还是调用 Message 类的 obtain() 方法来获取消息对象。
public final boolean sendMessage(Message msg) 发送消息
public final boolean sendEmptyMessage(int what) 发送只有 what 属性的消息
public final boolean post(Runnable r) 发送消息
public final boolean postAtTime(Runnable r, long uptimeMillis) 定时发送消息
public final boolean postDelayed(Runnable r, long delayMillis) 延迟发送消息
public void dispatchMessage(Message msg) 分发消息。当 Looper 循环接收消息队列中的消息时,就在不断调用
handler 的分发消息方法,从而触发 Callback 接口中的消息处理方法 handleMessage() 来对消息进行处理。
public final boolean sendMessageDelayed(Message msg, long delayMillis) sendMessage() 就是在调用延时发送消息的方
法。
public boolean sendMessageAtTime(Message msg, long uptimeMillis) 延时发送消息的方法就是在调用定时发送消息的方法。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) 将消息压入消息队列中
B 、 MessageQueue.JAVA: ( 1 个方法)
final boolean enqueueMessage(Message msg, long when) handler 发送的消息正是通过该方法被加进了消息队列中。因为消
息队列中有消息,从而触发了 Looper 通过 loop() 方法循环接收所有的消息。
C 、 Looper.JAVA: ( 3 个属性, 5 个方法)
3 个属性:
static final ThreadLocal sThreadLocal = new ThreadLocal(); 正是由于该线程本地变量,保证了一个线程
中只能保存一个 Looper 对象,也就是说一个 Thread 中只能有一个 Looper 对象。
final MessageQueue mQueue; 正是由于 MessagQueue 中的消息,才触发了 Looper 的 loop() 方法。
private static Looper mMainLooper = null; 该属性是主线程中自动创建的 Looper 对象。
5 个方法:
public static void prepare() 每个 handler 所在的线程都必须有一个 Looper 提前准备好。
public static void prepareMainLooper() 主线程中的的 Looper 已经被自动准备好。而该方法不需要手工调用。
public static Looper getMainLooper() 获取主线程中的 Looper 对象
public static void loop() Looper 的作用就是循环接收消息队列中的消息。该方法中执行了一个无限循环。
public static Looper myLooper() 该方法的作用是从线程本地变量中获取到当前线程的 Looper 对象。
D 、 Message.java: ( 10 个属性, 7 个方法)
10 个属性:
public int what; 该属性一般用来标识消息执行的状态。
public int arg1; 用来存放整型数据的属性。
public int arg2; 用来存放整型数据的属性。
public Object obj; 用来存放复杂消息数据的属性。
Bundle data; 用来存放复杂消息数据的属性。
Handler target; 标识该消息要被发送给哪个 Handler 对象。
Message sPool; 消息池对象。
int sPoolSize; 记录消息池中剩余消息的数量。
int MAX_POOL_SIZE=50; 消息池中最大消息数量。
Messenger replyTo; 定义消息的信使对象,将来可以用于跨 APP 的消息传递。
7 个方法:
public static Message obtain() 从消息池中获取消息对象。
public void recycle() 往消息池中归还消息对象。
public void setTarget(Handler target) 给消息设置接收该消息的目标 handler 对象。
public Handler getTarget() 获取消息的接收 handler 对象。
public void sendToTarget() 将消息发送到目标 handler 对象。其本质是调用 handler 对象的 sendMessage() 方法来发送当
前消息对象。
public void setData(Bundle data) 将 Bundle 对象设置到 message 对象中 Bundle 属性中。
public Bundle getData() 从消息对象中获取 Bundle 属性的数据。
消息传递机制的执行顺序: