一.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