学习Android开发,Looper与Handler是必须学会使用的,本人作为一个测试人员,在做Android测试时一直在探索,对Android开发更加是一窍不通,只能摸石头过河把基础学一下。
1.主线程
说到Looper与Handler就不得线程,如现在有一个需求,需要等待10S执行,在主线程中实现非常简单,代码如下
private TextView tv;
private Button start;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView)findViewById(R.id.tv);
start = (Button)findViewById(R.id.start_button);
start.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
try {
Thread.sleep(10000);
tv.setText("休眠后的文字");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
接下来使用设备运行,当多次点击按钮后,模拟器出现“Do you want to close it?”的弹窗
这种情况是很常见的,当请求发送到服务器,长时间没有响应,Android操作系统就会提示用户是否继续等待。这种弹窗提示,99%的用户都会选择关闭。而造成这种不友好场景之一就是在main thread主线程中使用了等待,所以这种是不可取的。
2.子主线程
既然在主线程不可取,那就自己创建一个子线程运行,应该可以了吧?
private TextView tv;
private Button start;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView)findViewById(R.id.tv);
start = (Button)findViewById(R.id.start_button);
start.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
System.out.println("点击事件的线程"+Thread.currentThread().getName());
new Thread(){
public void run(){
System.out.println("线程组里执行等待的线程"+Thread.currentThread().getName());
try {
Thread.sleep(10000);
tv.setText("休眠后的文字");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
}
});
}
监听与线程都使用了匿名内部类实现,分别在点击事件触发后,以及线程执行等待时候打印一句话来区别new Thread新建线程中运行,而子线程中执行;运行代码后,报错
可以很明确的看到,点击事件发生在主线程,而改变TextView内容的代码确实也是在子线程中执行的,但是报错并导致模拟器奔溃了。报错内容android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.报错行数是34行,正是改变内容的那一行代码。
这个错误的原因就是因为TextView这个控件元素的改变只能发生在创建它的线程中操作。创建它的是主线程,所以只能在主线程中改变这个控件的属性内容。总结来说就是主线程、非主线程的新开线程都有一定的问题,那该如何做呢?就是Looper跟Handler干活的时候到了。
3.Looper、Handler
(1)Handler是什么?
Handler是用来结合线程的消息队列来发送、处理“Message对象”和“Runnable对象”的工具。每一个Handler实例之后会关联一个线程和该线程的消息队列。当你创建一个Handler的时候,从这时开始,它就会自动关联到所在的线程/消息队列,然后它就会陆续把Message/Runnalbe分发到消息队列,并在它们出队的时候处理掉。
从这段解释来看有点懵逼,从中发现的是线程,不知道是不是与上面留下的坑有关联呢?接下来继续分析。
(2)Message是什么?
Message为消息对象,存放在MessageQueue消息队列中。在Android中经常使用Message.obtain()来获取Message实例。当然Message消息对象有很多成员属性,这些属性可以携带一些值通过Message消息对象存放在MessageQueue消息队列传递。而消息对象存放是通过Handler发送后存放在MessageQueue消息队列中。
(3)MessageQueue是什么?
MessageQueue为消息队列,用来思义队列就是存放多个Message。每个线程最多只有一个MessageQueue。而MessageQueue是由Looper来管理的,主线程创建时,默认会创建一个Looper对象。非主线程创建时,需要代码创建Looper对象,创建Looper对象时,会自动创建一个MessageQueue消息队列。
(4)Looper是什么?
Looper管理MessageQueue消息队列,并处理存放在消息队列中的Message消息对象。与MessageQueue一样,Looper也是一个线程只有一个,并且绑定线程。所以Looper处理Message时都是在与其绑定的线程中执行的。注意,Looper只是处理消息对象出去MessageQueue,并不处理消息对象存放进MessageQueue的这个过程。
在主线程创建Handler时,Handler会与主线程绑定,而Looper也是与线程绑定的,所以Handler与Looper是一一对应关系的。关系图如下
4.主线程中使用Looper与Handler
private TextView tv;
private Button start;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView)findViewById(R.id.tv);
start = (Button)findViewById(R.id.start_button);
handler = new Handler(){
@Override
public void handleMessage(Message msg){
System.out.println("我是message");
String text = (String)msg.obj;
tv.setText(text);
}
};
start.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
System.out.println("点击事件的线程"+Thread.currentThread().getName());
new Thread(){
@Override
public void run(){
try {
System.out.println("新生成线程"+Thread.currentThread().getName());
Thread.sleep(10000);
Message msg = Message.obtain();
msg.obj = "点击后的内容";
handler.sendMessage(msg);
} catch (Exception e) {
// TODO: handle exception
}
}
}.start();
}
});
}
分析代码
(1)定义全局的handler对象
private Handler handler;
(2)实例化Handler对象,匿名内部类写法重写了handleMessage方法;首先实例化Handler对象的同时这个Handler会绑定线程,目前的Handler对象是在主线程Main Thread实例化的,所以绑定的是主线程。其次handleMessage方法在MessageQueue消息队列中Message为空的情况下不会执行。
handler = new Handler(){
@Override
public void handleMessage(Message msg){
System.out.println("我是message");
String text = (String)msg.obj;
tv.setText(text);
}
};
(3)匿名内部类实例化一个子线程,重写run()方法。首先通过Message.obtain()得到Message消息对象,使用obj属性存放点击后的内容,再通过handler的sendMessage()方法发送这个消息对象。
new Thread(){
@Override
public void run(){
try {
System.out.println("新生成线程"+Thread.currentThread().getName());
Thread.sleep(10000);
Message msg = Message.obtain();
msg.obj = "点击后的内容";
handler.sendMessage(msg);
} catch (Exception e) {
// TODO: handle exception
}
}
}.start();
再结合Handler、Looper、MessageQueue、Message讲解一下。当代码运行,创建主线程时,Looper被创建并绑定主线程,然后在主线程中实例化Handler对象,Handler对象也绑定主线程。然后来到点击监听事件,当点击start按钮,触发监听10s后实例化Message对象,并由Handler的sendMessage方法把Message消息对象放进MessageQueue消息队列中;因为Handler对象是全局的,所以可以在Worker Thread中使用Handler对象。然后回到主线程的Handler,Looper开始干活了,当消息队列中有Message消息对象,Looper循环取出交给Handler的handleMessage方法处理,整个流程结束。这样就解决了新开Thread中不能操作UI层控件,同时可以在新开Thread中执行数据的变化。
Handler源码分析
上面案例Handler是在主线程运行,当主线程创建时Looper自动创建并工作。而Handler的sendMessage又做了什么事情呢?下面看源码
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
调用了sendMessageDelayed()方法,继续往下
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
然后sendMessageDelayed()调用了sendMessageAtTime()方法,代码如下
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
通过源代码可以看到,sendMessageAtTime()方法实际就是把Message消息对象加入到了MessageQueue消息队列中,然后通过Looper对象从MessageQueue中取出Message对象交给Handler处理。只是说Looper对象在主线程中是自动创建并执行的,所以没有直接看到Looper的代码,把Looper对象在主线程中如何工作的彩蛋埋到后面。
5.子线程使用Looper与Handler
private TextView tv;
private Button start;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView)findViewById(R.id.tv);
start = (Button)findViewById(R.id.start_button);
start.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
System.out.println("点击事件的线程"+Thread.currentThread().getName());
Message msg = handler.obtainMessage();
msg.obj = "点击后的内容";
handler.sendMessage(msg);
}
});
new Thread(){
@Override
public void run(){
try {
System.out.println("新生成线程"+Thread.currentThread().getName());
//准备Looper对象
Looper.prepare();
handler = new Handler(){
@Override
public void handleMessage(Message msg){
System.out.println("");
String text = (String)msg.obj;
//tv.setText(text);
System.out.println("我是message,处理消息内容为:"+text);
}
};
Looper.loop();
} catch (Exception e) {
// TODO: handle exception
}
}
}.start();
}
代码分析
(1)这次要讲解的是从Main Thread主线程中生成消息对象,通过Handler来传递消息给子线程。首先就是要在主线程中生一个消息对象,并发送消息,代码如下:
start.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
System.out.println("点击事件的线程"+Thread.currentThread().getName());
Message msg = handler.obtainMessage();
msg.obj = "点击后的内容";
handler.sendMessage(msg);
}
});
(2)消息对象的代码是放在按钮点击事件里面的,当用户操作点击时,才会触发生成。接下来就是创建一个子线程来执行消息处理。
new Thread(){
@Override
public void run(){
try {
System.out.println("新生成线程"+Thread.currentThread().getName());
//准备Looper对象
Looper.prepare();
handler = new Handler(){
@Override
public void handleMessage(Message msg){
System.out.println("");
String text = (String)msg.obj;
//tv.setText(text);
System.out.println("我是message,处理消息内容为:"+text);
}
};
Looper.loop();
} catch (Exception e) {
// TODO: handle exception
}
}
}.start();
(3)可以看到在handler实例化前面,执行了Looper.prepare()方法,然后再执行Handler处理消息对象,接着执行Looper.loop()方法
6.其他源码分析
在第4点源码介绍中除了handleMessage()方法外, Looper.prepare()、Looper.loop()并没有出现,下面介绍这两个方法:
Looper.prepare()方法
执行Looper的prepare()方法,这个方法的主要作用就是准备Looper对象。在主线程并没有见到这个方法的执行,是因为主线程创建时,自动会创建Looper对象,无需代码再创建。
再来看看源码
public static void prepare() {
prepare(true);
}
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));
}
从源码可以看到首先调用了prepare()静态方法,再调用了prepare(boolean quitAllowed)这个静态方法,并且这里使用了ThreadLocal本地线程来管理Looper对象,当前线程中没有Looper对象则会执行new Looper()代码创建新的Looper对象。
Looper.loop()方法
回顾之前Handler的工作方式,很容易就能猜到loop()方法的作用;它就是完成循环从MessageQueue中取出Message对象,然后调用Handler的handleMessage()方法处理。与prepare()方法一样,在主线程中loop()方法自动调用。
源码分析,loop方法的源码如下:
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
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 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.recycle();
}
}
Looper的loop方法代码有点多,关注核心的几点:
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
首先得到的是Looper对象,然后通过Looper对象得到消息队列对象;然后再往下看代码
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
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);
消息队列对象queue调用next()方法取出消息对象,然后通过消息对象的target属性执行dispatchMessage()方法。从Message类可以知道target实际就是发送Message消息对象的Handler;dispatchMessage()方法实际就是处理Message消息对象啦。从这几行代码再结合Handler工作流程中说到的Loope取出消息对象,通过Handler的handleMessage()处理,这样结合来看,就很容易理解了。
总结:一个Handler对应一个Looper对象,一个Looper对应一个MessageQueue对象。Handler发送Message对象放到MessageQueue消息队列中,然后通过Looper取出再交给Handler处理。
Handler的post()方法
通过Handler的sendMessage()方法可以发送消息,在Looper对象中转一圈后,又回到Handler调用handleMessage()方法处理。这其中一个弊端就是需要通过Message传递的消息只能通过what、arg1、arg2、obj等等属性来传递数据,并没有办法去执行复杂的功能。
而post方法就是解决这种需求的,它可以执行整个代码块内容,下面进入讲解。首先看代码
private TextView tv;
private Button start;
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView)findViewById(R.id.tv);
start = (Button)findViewById(R.id.start_button);
start.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
System.out.println("点击事件的线程"+Thread.currentThread().getName());
MyThread mt = new MyThread();
mt.start();
}
});
}
class MyThread extends Thread{
@Override
public void run(){
Runnable run = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("Runnable线程"+Thread.currentThread().getName());
tv.setText("点击后内容");
}
};
handler.post(run);
}
}
简单介绍下,写了一个子线程的内部类,在线程的run()方法中实例化Runnable对象,并修改了tv这个控件的文本内容;把Runnable对象放入Handler的post方法中。这里有个疑惑就是讲Handler之前留下的问题,在子线程中不是不能修改UI的任何内容吗?且看下面。
然后点击按钮的时候触发事件,运行这个子线程。回到上面的疑惑,下面开始分析。
(1)首先Handler是在主线程中创建的,所以handler.post()方法自然也是在主线程中执行的。
(2)接下来看post方法源码
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
通过调用getPostMessage()方法把Runnable对象传入,接下来看getPostMessage()方法的源码,如下:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
首先通过Message的obtain得到Message对象;重点来了,把Runnable对象赋值给了Message对象的callback属性,callback属性是一个Runnable对象,然后返回这个Message对象。从这里来看有点懵懵懂懂,继续往下回到Handler的post方法,post方法调用的sendMessageDelayed()方法如下
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
然后sendMessageDelayed()调用了sendMessageAtTime()方法,代码如下
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
看到这段代码已经很熟悉了,在Handler的sendMessage()方法发送Message消息对象是同一个方法。而这里的post()方法不同的操作就是把Runnable对象赋值给了Message的callback属性,并且把Message消息对象加入了MessageQueue消息队列中。然后往下看,就是消息的处理啦,回到Looper的loop()方法,其中一段代码“msg.target.dispatchMessage(msg); ”应该还有印象,下面贴出dispatchMessage()方法的源代码,如下:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在没有使用post()方法之前,是用Handler的sendMessage()方法发送消息,这时候Message的callback是空会执行else语句块,mCallback也是空,所以直接执行了handleMessage()方法,而在创建Handler对象时,就重写了这个方法,所以会执行重写里面的代码块。
而到了使用post()方法,Message对象的callback就有东西了,不是空了,这时候执行的是handleCallback()方法,下面看代码
private static void handleCallback(Message message) {
message.callback.run();
}
执行的是Message对象callback属性的run()方法;callback属性就是Runnable对象,所以就是执行了post()方法中传入的Runnable对象的run()方法。知道一点java线程的同学应该都知道,直接调用Thread或Runnable的run()方法是不会开辟新线程,而是在原线程中执行,所以执行的run()方法依然还是在与Handler同一个线程中执行,也就是主线程。所以就解决了我们的困惑,在Runnable中修改了UI,实际依然是在主线程中执行的,是完全OK的。
主线程的Looper工作代码
在起初讲到Handler机制时,一直谈的话题就是主线程中Looper是自动创建并执行的,总是感觉不太爽快,还是将这层雾揭开比较爽。找到Android主线程创建时的代码如下:
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
从代码中可以看到,在实例化ActivityThread之前,执行了Looper的prepareMainLooper()方法,代码如下:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
从代码中可以看到,执行的是prepare()方法,这个方法很熟悉了;在子线程使用Handler机制时,初始化Looper对象用的就是这个prepare()方法。所以得出结论就是主线程实际也是执行了相同的操作,包括后面的loop()方法,都是一致的,只是主线程在创建之际,都已经做了这些事情啦。
至于使用主线程调用prepare()方法传入的为什么是false,查看源代码可以知道,这个false在实例化MessageQueue消息队列继续传入,代码如下:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
然后在MessageQueue中,这个false给了mQuitAllowed这个变量,这个变量的作用是如果在主线程中调用MessageQueue的quit()方法退出,则会抛出异常。同样在主线程中调用Looper的quit()方法退出,也是执行了同一个方法,也会抛出异常。这就是为什么在主线程调用prepare()方法为什么要传入false,就是因为在主线程中不允许退出。
接下来看Handler的代码:
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
这段代码很直接的告诉了我们,为什么在主线程中创建Handler对象时,也就是new Handler后,它是可以绑定到主线程的?原因就是这段代码。