一.Handler的简介
首先来了解一下Handler:
Handler为Android操作系统中的线程通讯工具,来自包:android.os.Handler
Handler绑定了两个队列:
1.消息队列:发送--接受--处理消息(消息队列使用sendMessage和HandleMessage的组合来发送和处理消息。)
2.线程队列:启动--结束--休眠线程(线程队列类似一段代码,或者说一个方法的委托,用户传递方法。使用post,postDelayed添加委托,使用 removeCallbacks移除委托。)
在进一步了解Handler之前,先说一下为什么需要Handler这个类:
当一个应用程序启动的时候,Android首先会开启一个主线程(UI线程),主线程管理界面中的UI控件,进行事件分发!比如说:当你点击一个按钮的时候,Android系统会分发事件到Button上,来响应你的操作。如果此时需要一个耗时的操作,例如: 联网读取数据,或者读取本地较大的一个文件的时候,你不能把这些操作放在主线程中,如果你放在主线程中的话,界面会出现假死现象,如果5秒钟还没有完成的话,会收到Android系统的一个错误提示“强制关闭”。这个时候我们需要把这些耗时的操作,放在一个子线程中!然而这涉及到子线程更新UI。在java里,可以直接在子线程中刷新UI界面,可是在android编程中,是不可以的!因为他违背了单线程模型:Android中,UI操作必须在主线程(UI线程)中进行的,这个跟Android的线程安全有关!那么更新UI只能在主线程中更新,子线程中操作是危险的,因此想更新UI,可以通过子线程发送信息给主线程,让主线程来更新UI,所以就引入了Handler!
那么Handler是用来干嘛的?通俗一点来说:Handler就是在各个进程间发送数据的处理对象!在任何的线程中,只要获得了另一个线程的Handler,就可以向那个线程发送数据!基于这个机制,我们可以在UI线程中建一个Handler,然后新建一个子线程进行一些费时的操作,操作完成后,可以调用主线程中的handler,来发送信息告诉主线程,UI线程(主线程)进行更新UI界面!
二.Handler常用的API
方法签名 |
描 述 |
public void handleMessage (Message msg) |
子类对象通过该方法接收信息 |
public final boolean sendEmptyMessage (int what) |
发送一个只含有what值的消息 |
public final boolean sendMessage (Message msg) |
发送消息到Handler, 通过handleMessage方法接收 |
public final boolean hasMessages (int what) |
监测消息队列中是否还 有what值的消息 |
public final boolean post (Runnable r) |
将一个线程添加到消息队列 |
Handler Handler处理者
是Message的主要处理者,负责Message的发送,Message内容的执行处理。后台线程就是通过传进来的Handler对象引用来sendMessage(Message)。而使用Handler,需要implement 该类的 handleMessage(Message) 方法,它是处理这些Message的操作内容,例如更新UI。 通常需要子类化Handler来实现handleMessage方法。
Message QueueMessage Queue消息队列
用来存放通过Handler发布的消息,按照先进先出执行。 每个message queue都会有一个对应的Handler。Handler会向message queue通过两种方法发送消息:sendMessage或post。这两种消息都会插在message queue队尾并按先进先出执行。但通过这两种方法发送的消息执行的方式略有不同:通过sendMessage发送的是一个message对象,会被Handler的handleMessage()函数处理;而通过post方法发送的是一个runnable对象,则会自己执行。
Looper Looper是每条线程里的Message Queue的管家。
Android没有Global的Message Queue,而Android会自动替主线程(UI线程)建立Message Queue,但在子线程里并没有建立Message Queue。所以调用Looper.getMainLooper()得到的主线程的Looper不为NULL,但调用Looper.myLooper()得到当前线程的Looper就有可能为NULL。Looper消息队列(MessageQueue)的管家,在消息队列中,只能有一个管家,管理着整个消息队列,而消息队列可以有多个消息(Message),Hadnler(工人)也可以有多个,管家负责派遣他们向消息队列中存储或取出消息后执行任务!
三.Handler的使用
(1) Handler的消息处理机制
界面效果图:
代码:
package com.liangdianshui.handlermessage; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class MainActivity extends Activity { private int number = 1; private static final int CHANGE_TITLE = 0; public static final String TAG = "MainActivity"; private TextView mTvTitle; private Button mChange; private MyThread myThread; Handler handler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case CHANGE_TITLE: Log.d(TAG, "Handler中线程的id= " + Thread.currentThread().getId() + "/n"); mTvTitle.setText("Title " + number); break; default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "MainActivity中线程的id= " + Thread.currentThread().getId() + "/n"); init(); } private void init() { mTvTitle = (TextView) findViewById(R.id.tv_title); mChange = (Button) findViewById(R.id.bt_change); mChange.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { myThread = new MyThread(); myThread.start(); try { myThread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } myThread.destroy(); number++; } }); } class MyThread extends Thread { private boolean finished = false; @Override public void run() { while (!finished) { Message message = new Message(); message.what = CHANGE_TITLE; message.obj = "Title " + number; handler.sendMessage(message); Log.i(TAG, "MyThread中线程的id= " + Thread.currentThread().getId() + "/n"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void destroy() { finished = true; } } }Handler消息处理机制Demo:http://download.csdn.net/detail/two_water/9332033
从Log可以看出消息处理是在主线程中处理的,在消息处理函数中可以安全的调用主线程中的任何资源,包括刷新界面。工作线程和主线程运行在不同的线程中,所以必须要注意这两个线程间的竞争关系。
总结:
1、如果通过工作线程(子线程)刷新界面,可以使用Handler对象来实现。
2、注意工作线程和主线程之间的竞争关系。推荐handler对象在主线程中构造完成(并且启动工作线程之后不要再修改之,否则会出现数据不一致),然后在工作线程中可以放心的调用发送消息SendMessage等接口。
3、handler对象的handleMessage接口将会在主线程中调用。在这个函数可以放心的调用主线程中任何变量和函数,进而完成更新UI的任务。
(2)子线程创建消息队列更新UI
效果图:
Button被按下时,从主线程向子线程发送一个数字,然后子线程将数字用Toast显示,而主线程将TextView也被设置成该数字。
代码:
package com.liangdianshui.handlermessage3; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { private int number = 1; public static final String TAG = "MainActivity"; private TextView mTvTitle; private Button mChange; private LooperThread looperThread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "MainActivity中线程的id= " + Thread.currentThread().getId() + "/n"); looperThread = new LooperThread(); looperThread.start(); init(); } private void init() { mTvTitle = (TextView) findViewById(R.id.tv_title); mChange = (Button) findViewById(R.id.bt_change); mChange.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Message message = Message.obtain(); message.arg1 = number; mTvTitle.setText("主线程发送了 :" + String.valueOf(message.arg1)); looperThread.handler.sendMessage(message); number++; } }); } class LooperThread extends Thread { public Handler handler; @Override public void run() { Looper.prepare(); handler = new Handler() { public void handleMessage(Message msg) { Log.d(TAG, "LooperThread中线程的id= " + Thread.currentThread().getId() + "/n"); Toast.makeText(MainActivity.this, "LooperThread handler 收到消息 :" + msg.arg1, Toast.LENGTH_LONG).show(); }; }; Looper.loop();// loop()会调用到handler的handleMessage(Message // msg)方法,所以,写在下面; } } }
子线程创建消息队列更新UI:http://download.csdn.net/detail/two_water/9332045
(3)Handler的线程队列
效果图:代码:
package com.liangdianshui.handlerthreadqueue; import android.app.Activity; import android.app.ProgressDialog; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MainActivity extends Activity { private Button mBtDownload; private ProgressDialog mProDialog; Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { mProDialog.setProgress(msg.arg1); mHandler.post(mThread); }; }; Runnable mThread = new Runnable() { int i = 0; @Override public void run() { i += 1; Message message = mHandler.obtainMessage(); message.arg1 = i; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } mHandler.sendMessage(message); if (message.arg1 == 100) { mHandler.removeCallbacks(mThread); mProDialog.setMessage("Finished"); mProDialog.dismiss(); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mBtDownload = (Button) findViewById(R.id.bt_download); mProDialog = new ProgressDialog(MainActivity.this); mProDialog.setTitle("Download"); mProDialog.setMessage("Downloading....."); mProDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); mProDialog.setIndeterminate(false); mProDialog.setCancelable(true); mBtDownload.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mProDialog.show(); mHandler.post(mThread); } }); } }Handler的线程队列Demo:http://download.csdn.net/detail/two_water/9332007