andler是android给我们提供用来更新UI的一套机制,也是一套消息处理机制,我们可以通过它发送消息,也可以通过它处理消息,它既可以发送消息也可以接收消息。当我们在子线程中对UI进行更改的操作的时候,应用会崩溃,系统提示我们不能在子线程中进行更新UI的操作。这时候Handler就可以派上用场了。为什么要使用Handler呢,其实谷歌工程师估计考虑到程序员对于多线程的苦恼,所以android在设计的时候就封装了一套消息创建、传递、处理机制,如果不遵循这样的机制就没有办法更新UI信息的,就会抛出异常信息。这就是Handler。
- Handler的用法
1.基本用法
在Android中我们对Handler的最基本用法是子线程和主线程之间的通讯,将耗时的操作放在子线程中进行,将操作后的结果或者数据通过Handler传递给UI线程,UI再通过这些数据更新UI和进行相应的用户操作。
比如我们有如下代码:
public class MainActivity extends Activity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView =(TextView) findViewById(R.id.textView);
new Thread(){
public void run() {
try {
Thread.sleep(1000);
textView.setText("Hanlder");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
};
}.start();
}
}
这是用子线程的sleep模拟耗时操作,然后在子线程中去更新textView,运行之后程序崩溃。日志中记录:Only the original thread that created a view hierarchy can touch its views.意思就是,只有创建了View的原始现成能够处理操作View,通俗的就是只有UI现成能够更新UI。
那我们的代码更改为下面:
public class MainActivity extends Activity {
private TextView textView;
private Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
textView.setText("Hanlder");
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView =(TextView) findViewById(R.id.textView);
new Thread(){
public void run() {
try {
Thread.sleep(1000);
handler.sendEmptyMessage(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
};
}.start();
}
}
这样就可以实现UI的更新了。这是Handler最基本也是最简单的用法了,实现子线程和主线程之间的通讯。
2.Handler的post之类的方法
首先看Handler有个post(Runnable)的方法,传递一个runnable对象给主线程,runnable中的run方法是在UI现成中执行的。我们通常在java中执行现成中某种操作的时候会用一个布尔类型的flag变了来控制run方法里面是否循环执行。在这里使用handler中的post(runnable)的方法 可以简单的实现循环,而且我们可以随时控制循环的停止和开始。先看代码
public class MainActivity extends Activity {
private TextView textView;
private int i=0;
private boolean flag =false;
private Runnable runnable = new Runnable() {
@Override
public void run() {
i++;
textView.setText(i+"");
handler.postDelayed(runnable, 500);
}
};
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView =(TextView) findViewById(R.id.textView);
handler.postDelayed(runnable, 500);
}
//button的点击事件的回调方法
public void remove(View view){
if(!flag){
flag =true;
handler.removeCallbacks(runnable);
}else{
flag =false;
handler.postDelayed(runnable, 500);
}
}
}
界面的简单实现效果就是在TextView上面显示i的值,这里的i的值是循环++的,我们在onCreate中使用handler来延迟500毫秒执行runnable对象,使i++,在runnable的run方法中我们也发送一个runnable对象,所有就可以循环执行i++的操作了额。方法remove根据flag的标志可以随时停止和开始执行i++的循环操作,是不是感觉有点屌屌哒呢。这样也可以实现简单的循环操作,而且状态自己随时可以控制。
3.Handler的相关方法的使用。
a.public Handler(Callback callback)构造函数
大家应该在使用Handler的时候,都会使用它的默认的无参数的构造函数,然后重写其handleMessage方法,进行相应的逻辑操作。 Handler还有这样一个构造函数public Handler(Callback callback)。先看下面的代码
public class MainActivity extends Activity {
private TextView textView;
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Toast.makeText(getApplicationContext(), "callback handlemessage", 1000).show(); //代码1
return true; //这里返回值需要注意 // 代码3
}
}){
public void handleMessage(Message msg) {
Toast.makeText(getApplicationContext(), "handler handlemessage", 1000).show(); //代码2
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView =(TextView) findViewById(R.id.textView);
}
public void show(View view){
handler.sendEmptyMessage(1);
}
}
上面的代码中使用了
public Handler(Callback callback)的构造函数。我们这里可以进行消息传递的拦截。当我们的“代码3”中return false的时候 “代码1” “代码2”会依次执行,当“代码3”中return true的时候“代码1”会先执行 ,但是“代码2”不会执行,此时有点类似事件传递中返回true事件消费,false继续向上传递的意思。我们可以使用Handler的这个构造方法,来进行消息传递的拦截。
b.Handler与Looper,MessageQueue的关系
学习Hanlder必须要弄清楚Hanlder与Looper,MessageQueue之间的关系。
Hanlder:在Android中主要是封装了消息的发送。
Looper:类似一个“消息泵”,产生动力,接收Handler发送过来的消息,并且在Looper中存在一个loop方法和一个MessageQueue对象,通过loop方法一直轮询,从MessageQueue中取出消息,并回传给Hanlder自己
MessageQueue:就是一个存储消息的容器。
下面我们可以从源代码的角度来认识一下Handler和这两个类之间的关系以及Handler中消息的处理逻辑。在我们一个引用创建的时候,其实也是通过一个主线程中的main方法执行的,这个“主线程(其实并不是一个线程,就是一个普通java类,但是有入口函数main)也即是ActivityThread。看ActivityThread的main函数怎么写的
public static void main(String[] args) {
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
if (sMainThreadHandler == null) {
sMainThreadHandler = new Handler();
}
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
前面的一些代码就 不详细讲解了 ,在Acitivity的启动过程中有讲解到。我们看代码的11行Looper.prepareMainLooper();方法,跟踪进去,会发现这里其实就是为我们整个应用程序关联一个Looper对象,这个Looper其实就是我们UI线程关联的Looper对象,当我们在主线程中穿件Handler对象的时候,其实关联的也是这个Looper对象。
代码24行,也就是调用Looper的轮询方法。轮询消息,开始的时候可能消息队列中没有消息。
当我们在主线程中创建自己的Hanlder对象的时候,我们一般的入口就是他的无参数构造函数
public Handler() {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = null;
}
从构造函数中,可以看出,handler中的Loopder对象通过Looper.myLooper()方法赋值,实质也是从主线程的ThreadLocal中取出Looper并且赋值,然后初始化MessageQueue对象来存放消息。
当我们Handler和Looper,MessageQueue初始化完毕之后,就看看消息的发送了,我们以一般的SendMessage(Message)来讲解,跟踪此方法可以看出,最后调用的是sendMessageAtTime方法,来看看这个方法
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
else {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}
在这个函数中首先有一句代码msg.target=this;这个东西后面有用,target指回handler自己,这就是我们上面所说的Looper接受handler发送的消息,并且将消息回传给Handler自己。
代码07行,就是将消息塞入MessageQueue中,此时我们的Looper有了,消息队列MessageQueue中也有消息了。上面说过我们Looper.loop其实是一个死循环一直轮询消息队列中的消息那我们来具体看看loop方法
/
public static void loop() {
Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
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();
while (true) {
Message msg = queue.next(); // might block
if (msg != null) {
if (msg.target == null) {
// No target is a magic identifier for the quit message.
return;
}
long wallStart = 0;
long threadStart = 0;
// 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);
wallStart = SystemClock.currentTimeMicro();
threadStart = SystemClock.currentThreadTimeMicro();
}
msg.target.dispatchMessage(msg);
if (logging != null) {
long wallTime = SystemClock.currentTimeMicro() - wallStart;
long threadTime = SystemClock.currentThreadTimeMicro() - threadStart;
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
if (logging instanceof Profiler) {
((Profiler) logging).profile(msg, wallStart, wallTime,
threadStart, threadTime);
}
}
// 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();
}
}
}
的确有一个while(true)的死循环一直在轮询消息队列。
代码34行有通过msg.target.dispatchMessage(msg),这就是上面所说的target的作用回传给Handler自己来处理消息,来看dispatchMessage函数
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这里才是真正的处理消息的方法,里面调用了我们复写的handleMessage方法。从函数的处理逻辑看,首先检查msg的callback(其实就是上面用到的一个runnable对象)是否为null,如果不为null执行,就执行他的run方法,如果为null,检查我们的Handler的callback是否为空,不为空的时候,这就要涉及到我们上面的消息拦截的处理逻辑了。mCallback.handleMessage(msg)方法有一个返回值,放返回true的时候就不在执行我们handler的handleMessage方法了,只有返回false的时候才会执行handler的handleMessage方法。处理我们更新UI的逻辑操作了。
这里handler的发送消息的逻辑差不多跟着源代码读了一遍,其实也不难哈。
c.自定义与线程相关的Handler之引出HandlerThread的用法。
先看下面的一段代码:
public class MainActivity extends Activity {
private Handler handler;
class MyThread extends Thread{
public Looper looper;
@Override
public void run() {
looper.prepare();
System.out.println("current thread"+Thread.currentThread());
looper.loop();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyThread thread = new MyThread();
thread.start();
Handler handler = new Handler(thread.looper){
@Override
public void handleMessage(Message msg) {
System.out.println("handle message");
}
};
}
}
我们创建自己的handler的时候,可以传递一个Looper对象,正如上面的实例而言,但是运行代码我们会发现,会报空指针异常,log提示在代码的21行出现空指针,thread不可能为空,因为thread的run方法执行了,那只有looper为空了,这就是多线程造成的困扰,有可能是当我们的handle在new出来的时候,子线程并没有执行完成,looper就没有成功生成,就会报空指针异常。
HandlerThread的用法:
package com.example.handlertest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
public class MainActivity extends Activity {
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
HandlerThread thread = new HandlerThread("Handler Thread");
thread.start();
Handler handler = new Handler(thread.getLooper()){
@Override
public void handleMessage(Message msg) {
System.out.println("handle message");
}
};
handler.sendEmptyMessage(1);
}
}
这样就不会出现上面所说的空指针异常了。我们查看源代码发现HandlerThread继承Thread.
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
但是在HandlerThread的getLooper方法中其实是做了同步线程保护的处理的,只要looper为空,线程就处于等待状态,再看看handlerThread的run方法
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
会调用notifyAll,唤醒所有的线程。
更新UI的几种方法
1.在Activity中调用runOnUiThread(runnable)方法,里面传递一个Runnable对象,在他的run方法之后中进行更新UI的操作。其实这最终还是调用handler.post(runnable)方法
2.handler.post(runnable),
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private final Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
看了上面2段代码片段其实质还是讲runnable对象作为Message的callbakc对象。最后还是调用Handler的消息处理逻辑
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
开始是检查msg.callback是否为空,不为空就调用handleCallback(msg)方法。
private final void handleCallback(Message message) {
message.callback.run();
}
其实就是调用Runnable对象的run方法。
3.handler的sendMessage方法,此方法我们经常使用,上面也讲解到,所有不在多说了。
4,view.post(runnable)方法。举例子TextView.post方法
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().post(action);
return true;
}
其实质还是handler的post方法。
非UI线程真的不能更新UI 吗?
先看下列2段代码:
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity {
private TextView textview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textview = (TextView) findViewById(R.id.textView);
new Thread(){
public void run() {
textview.setText("ok");
};
}.start();
}
}
这个方法的确可以是textview上面显示ok,不相信大家可以测试一下。
package com.example.handlertest;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity {
private TextView textview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textview = (TextView) findViewById(R.id.textView);
new Thread(){
public void run() {
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
textview.setText("ok");
};
}.start();
}
}
而这段代码执行的时候会使应用崩溃。提示“Only the original thread that created a view hierarchy can touch its views.”也就是我们通常所说的非UI线程不能更新UI。我们知道其实在textview最终更新内容的时候会调用其invilidate方法。最后其实调用viewParent的invidateChild,而Viewparent是个抽象类,他的实现了是ViewRootImpl。查看ViewRootImpl的invidateChild的方法
public void invalidateChild(View child, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
if (dirty == null) {
// Fast invalidation for GL-enabled applications; GL must redraw everything
invalidate();
return;
}
if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}
if (!mDirty.isEmpty() && !mDirty.contains(dirty)) {
mAttachInfo.mSetIgnoreDirtyState = true;
mAttachInfo.mIgnoreDirtyState = true;
}
mDirty.union(dirty);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
第一句就是checkThread方法。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
看到所抛出的异常是否非常之熟悉呢,哈哈,就是上面所说的非UI线程不能更新UI。这里是调用了ViewRootImpl中的invilidateChild方法,才会抛出这个异常,而我们的ViewRootImpl的初始化操作是在Activity中的onResume方法中进行的。我们这里可以自己去跟踪Activity的onResume方法的执行逻辑,看看HandleResumeActivity方法,再仔细跟踪阅读。在我的Acitivity源码解读藜麦也有讲到。其实最后是调用了WindowManager的实现类WindowManagerImpl的addView方法中初始化了ViewRootImpl。
感觉这里可以在面试的时候和面试官装逼使用。运用得当可以加很大的印象分额。