Android中的Handler解析

   提到Handler大家并不陌生,Handler是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。


感觉这里可以在面试的时候和面试官装逼使用。运用得当可以加很大的印象分额。


  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值