在我的<线程和进程在Android中的工作方式>中,明确地说明了应用启动时,系统会为应用创建一个名为“主线程”的执行线程。 此线程非常重要,因为它负责将事件分派给相应的用户界面小部件,其中包括绘图事件。 此外,它也是应用与 Android UI 工具包组件(来自 android.widget 和 android.view 软件包的组件)进行交互的线程。所以我们所有的涉及UI的操作都必须在这个线程执行,这是Android的一套机制。 最根本的原因: 是解决多线程并发问题。如果在一个activity中有多个线程去更新UI,并且没有加锁机制,那样会造成更新界面错乱。 也不能对所有更新UI的操作进行加锁,否则会导致性能下降 。
a).问题:
如果应用启动的时候UI线程和工作线程同时启动了起来,当工作线程执行最后需要通知更新UI以便告诉用户工作线程执行完毕了。但是Android中并不支持在工作线程(非UI线程)中去更新UI啊,怎么办?
比如现在我们有一个按钮,按钮的监听事件中我们开启了一个新的工作线程去处理我们的业务,处理完毕之后,我们需要改变按钮上的文字以便通知用户工作线程需要做的事情已经完毕:
public class MainActivity extends AppCompatActivity { private Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //开启了新的线程 new Thread(new Runnable() { @Override public void run() { //这里是其他的代码 //最后更新UI button.setText("处理完毕"); } }).start(); } }); } } 123456789101112131415161718192021222324252627我们需要的是这个效果: 显然我们这监听事件是会抛出异常的: b).Handler登场 Handler的出现就是为了解决问题a)的。那么它是如何解决的呢。。。。。 Handler会在UI线程中实例化一个对象,然后在工作线程中使用这个引用,然后直接通过这个引用来发送消息,然后UI线程中的handler接收到这个消息之后会处理消息。
这就好比,一个人在办公司说我内急了,我要上厕所,但是办公司不是厕所啊,所以他得到厕所去。但是解决内急这个事始终是它自己在解决的,只是换了一个场所。
而Handler就是这个人,Message就是上厕所这件事;
解决办法:
package com.example.geekp.mhandler; import android.os.Handler; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextClock; import android.widget.TextView; public class MainActivity extends AppCompatActivity { private Button button; private Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //开启了新的线程 new Thread(new Runnable() { @Override public void run() { //这里是其他的代码 handler.post(new Runnable() { @Override public void run() { button.setText("处理完毕"); } }); } }).start(); } }); } } 1234567891011121314151617181920212223242526272829303132333435363738394041a).部分源码 我们打开Handler源码,
final Looper mLooper; final MessageQueue mQueue; 12看到有一个Looper和一个MessageQueue引用,这就是Handler机制的两个核心成员变量了。 i.Looper
Looper是用来使一个线程变成循环工作的线程的,具体他是怎么在Handler机制中体现的,在这里就不细说了,看一个Looper源码中的例子吧:
* class LooperThread extends Thread { * public Handler mHandler; * * public void run() { * Looper.prepare(); * * mHandler = new Handler() { * public void handleMessage(Message msg) { * // process incoming messages here * } * }; * * Looper.loop(); * } * } 123456789101112131415ii.MessageQueue 是一个消息队列
这是一个可以装载有消息的队列,所谓队列就是遵循 “先进先出“顺序的数据结构
iii.Handler、Looper和MessageQueue的关系 这里还是以更细UI的目的来说明问题:
打开Looper类的源码,可以看到: 这里很明确地指出了一个线程只能创建一个Looper。很容易理解,如果一个线程能够创建多个Looper的话,那就意味着一个线程可以创建多个消息循环队列。想一想这会造成什么后果? 如果有多个消息循环队列的话,那么Handler又怎么稳准狠地从队列中取出消息。 那么Android是如何保证只能创建一个Looper的:
这主要归功于ThreadLocal. 看看JDK中对ThreadLocal的解释: 举一个例子,假入我们有三个线程同时操作了一个变量,但是我们又要保证各个线程之间的操作互不影响,我们可以这样使用ThreadLocal来实现:
package jdk.lang; public class MThreadLocal { // 通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值 private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() { public Integer initialValue() { return 0; } }; // 获取下一个序列值 public int getNextNum() { seqNum.set(seqNum.get() + 1); return seqNum.get(); } public static void main(String[] args) { MThreadLocal sn = new MThreadLocal(); // 3个线程共享sn,各自产生序列号 TestClient t1 = new TestClient(sn); TestClient t2 = new TestClient(sn); TestClient t3 = new TestClient(sn); t1.start(); t2.start(); t3.start(); } private static class TestClient extends Thread { private MThreadLocal sn; public TestClient(MThreadLocal sn) { this.sn = sn; } public void run() { // 每个线程打出5个序列值 for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + ": " + sn.getNextNum()); } } } } 123456789101112131415161718192021222324252627282930313233343536373839404142这时候控制台输出: 我们可以看到控制台分别依次输出了各个线程的”局部变量“的值(1-5)。 然后我们再看看Looper中的代码:
// sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 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)); } 123456789可以看到在Looper的类中,使用了ThreadLocal以保证每个线程只能创建一个Looper对象。
a).boolean post (Runnable r) 示例代码: 在主线程中实例化一个Handler对象,然后工作线程中调用post函数,参数是一个Runnable 对象,可以在Runnable 对象的run()方法中写你的要进行的操作。
public class MainActivity extends AppCompatActivity { private Button button; private Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //开启了新的线程 new Thread(new Runnable() { @Override public void run() { //这里是其他的代码 handler.post(new Runnable() { @Override public void run() { button.setText("处理完毕"); } }); } }).start(); } }); } } 123456789101112131415161718192021222324252627282930b).boolean postDelayed (Runnable r, long delayMillis) 这个方法和方法a)差不多,只不过这个方法推迟了Runnable 对象的run()函数中的执行,推迟的时间为第二个参数long delayMillis,如下面推迟了5秒:
handler.postDelayed(new Runnable() { @Override public void run() { button.setText("处理完毕"); } }, 5000); 123456c).boolean sendMessage (Message msg)
当有很多个工作线程需要同时更新UI的时候,这个方法很有用。
下面来看一个例子,activity中有两个按钮,两个按钮被点击之后都将触发监听事件给UI线程发送消息。UI线程可以根据Message对象在工作线程传递过来之前的信息来区分到底是哪个线程发送过来的消息,使用Message.what属性。
public class MainActivity extends AppCompatActivity { private Button button1; private Button button2; private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 1: Toast.makeText(getApplicationContext(), "UI线程收到了工作线程一的消息", Toast.LENGTH_SHORT).show(); break; case 2: Toast.makeText(getApplicationContext(), "UI线程收到了工作线程二的消息", Toast.LENGTH_SHORT).show(); break; default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button1 = (Button) findViewById(R.id.button1); button2 = (Button) findViewById(R.id.button2); button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //开启了新的线程一 new Thread(new Runnable() { @Override public void run() { //这里是其他的代码 //创建消息 Message message = new Message(); message.what = 1; message.obj = "这是线程一的消息"; // 发送到消息循环队列 handler.sendMessage(message); } }).start(); } }); button2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Message message = handler.obtainMessage(); message.what = 2; message.obj = "这是线程二的消息"; // handler.sendMessage(message); message.sendToTarget(); } }); } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960看下效果:
这里需要提的是,我再两个按钮的监听事件中使用了两种不同的Message写法:
方法一: Message message = new Message(); message.what = 1; message.obj = “这是线程一的消息”; // 发送到消息循环队列 handler.sendMessage(message);
方法二: Message message = handler.obtainMessage(); message.what = 2; message.obj = “这是线程二的消息”; // handler.sendMessage(message); message.sendToTarget();
注意:使用方法一的时候发送消息只能够使用handler.sendMessage(message);,但是在第二种方法却可以使用handler.sendMessage(message);或者message.sendToTarget();
d).boolean sendMessageDelayed (Message msg, long delayMillis) 这个方法和c)中的方法是差不多的,唯一不同的就是将方法一的操作延迟执行,延迟时间为第二个参数long delayMillis