为了更好地理解Handler的工作原理,说下与Handler一起工作的几个组件。
1. MessageQueue:Handler接收和处理的消息对象。
2.Looper:每个线程只能拥有一个Looper。它的loop方法负责读取MessageQueue中的消息,读到信息
之后就把消息交给发送该消息的Handler进行处理。
3.MessageQueue消息队列,它采用先进先出的方式来管理Message。程序创建Looper对象时,会在它
的构造器中创建MesssQueue对象。
//贴份Looper的构造器源代码:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
该构造方法使用了private修饰,不是public表明是不可以通过构造器去创建Looper对象。
从构造方法中不难看出,程序在初始化Looper时会创建一个与之关联的MessageQueue,而这个MessageQueue就负责管理消息。
Handler有两个要点,前篇有说过——发送消息和处理消息,程序使用Handler发送消息,由Handler发送的消息必须被送到指定的MessageQueue。也就是说,如果希望Handler正常工作,必须在当前线程有一个MessageQueue;否则消息就没有MessageQueue进行保存了。不过MessageQueue是由Looper负责管理的,也就是说,如果希望Handler正常工作,必须在当前线程中有一个Looper对象,为了保证当前线程中有Looper对象,可分为以下两种情况处理:
1.在主UI线程中,系统已经初始化了一个Looper对象,因此程序直接创建Handler即可,然后就
可通过Handler来发送消息、处理消息。
2.你自己启动的子线程,必须自己创建一个Looper对象,并启动它,创建Looper对象并调用它的
prepare()方法保证每个线程最多只有一个Looper对象。
//贴份Prepare()方法的源代码
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
接下来调用Looper的静态loop()方法来启动它。loop()方法使用一个死循环不断取出MessageQueue中的消息,并将取出的消息分给该消息对应的Handler进行处理。
//贴份Looper类中的loop()方法,部分关键的源代码
for (;;) {
// might block
//获取消息队列中的下一个消息,如果没有消息,就会阻塞
Message msg = queue.next();
if (msg == null) {
// No message indicates that the message queue is quitting.
//如果消息为null,表明消息队列正在退出.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
//使用final修饰该标识符,保证在分发消息的过程中线程标识符不会被修改
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
组织一下,Looper、MessageQueue、Handler各自的作用
Looper:每个线程只有一个Looper,它负责管理MessageQueue,会不断地从MessageQueue中取出消息,
并将消息分给对应的Handler处理。
MessageQueue:由Looper负责管理。它采用先进先出的方式来管理Message。
Handler:它能把消息发送给Looper管理的MessageQueue,并负责处理Looper分给它的消息。
在线程中使用Handler的步骤
1.调用Looper的prepare()方法为当前线程创建Looper对象,创建Looper对象时,它的构造器
会创建与之配套的MessageQueue。
2.有了Looper之后,创建Handler子类的实例,重写handlerMessage()方法,该方法负责处理来自于其他线程的消息。
3.调用Looper的loop()方法启动Looper。
简述了它们之间的作用,不上个实例感觉缺少点啥
该实例允许用户输入一个数值上限,当用户点击按钮时,该应用会将该上限数值发送到新启动的线程中,让该线程来计算该范围内的所有质数。
为了将用户在UI界面输入的数值上限动态地传给新启动的线程,此实例将会在线程中创建一个Handler对象,然后UI线程的事件处理方法就可以通过该Handler向新线程发送消息。
实例代码:
/**
* Created by Administrator on 2017/8/5 0005.
* 之所以不直接在UI線程中計算該範圍內的所有質數,是因爲UI線程需要響應用戶動作
* 如果在UI線程中執行一個“耗時”操作,將會導致UI線程被阻塞,從而讓應用程序失去響應,在Demo中,如果用戶輸
* 入的數據太大,系統可能需要較長時間才能
* 計算出所有質數,這就可以能導致UI線程失去響應。
*/
public class mainsActivity extends Activity {
private EditText etnum;
private CalThread calThread;
public static final int MESSAGE =1;
public static final String UPPER_NUM ="upper";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//布局文件比较简单,就一个文本框和一个按钮
setContentView(R.layout.activity_main);
etnum = (EditText) findViewById(R.id.et_account);
calThread = new CalThread();
//启动新线程
calThread.start();
}
//为按钮的点击事件提供事件处理方法
public void onClickCal(View view){
//创建消息
Message msg =new Message();
msg.what =MESSAGE;
Bundle bundle =new Bundle();
bundle.putInt(UPPER_NUM,Integer.parseInt(etnum.getText().toString()));
msg.setData(bundle);
//向新线程中的Handler发送消息
calThread.mHandler.sendMessage(msg);
}
class CalThread extends Thread{
Handler mHandler;
@SuppressLint("HandlerLeak")
@Override
public void run() {
/_________________________________________________________________________/
Looper.prepare();
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
if (msg.what ==1){
int anInt = msg.getData().getInt(UPPER_NUM);
List<Integer> nums =new ArrayList<>();
//计算从2开始、到anInt的所有质数
outer:
for (int i =2 ;i<=anInt;i++){
//用i除以从2开始、到i的平方根的所有数
for ( int j =2;j<=Math.sqrt(i);j++){
//如果可以整除,则表明这个数不是质数
if (i != 2 && j==0){
continue outer;
}
}
nums.add(i);
}
//使用Toast显示统计出所有质数
Toast.makeText(mainsActivity.this, nums.toString(), Toast.LENGTH_SHORT).show();
}
}
};
Looper.loop();
/_________________________________________________________________________/
}
}
}
上面隔开的关键代码,在新线程内创建了一个Handler,由于在新线程中创建Handler时必须先创建Looper,因此程序先调用Looper的prepare()方法为当前线程创建了一个Looper实例,并创建了配套的MessageQueue。新线程有了Looper对象之后,接下创建了一个Handler对象,该Handler可以处理其他线程发送过来的消息。程序最后还调用了Looper的loop()方法。
运行此Demo,无论用户输入多大的数值,计算该范围内的质数都将会交给新线程来完成,而前台UI线程不会受到影响。
尽量避免在UI线程中执行耗时操作,这样可能就会导致出一个 著名的ANR( Application Not Responding).
只要在UI线程中执行需要消耗大量时间的操作,都会引发ANR,引发的问题就会导致Anroid应用程序无法响应输入事件和Broadcast。