android中的Handler机制原理解析

通过一个独立线程下载图片,主线程中更新UI,在主线程中更新imageView显示的例子,解析Handler的原理和机制。

功能需求

从网络上下载一张图片,将其显示到ImageView上。

原始UI 点击button后下载图片并更新UI

设计思路

考虑到需要从网络下载图片,下载操作较耗时,不能放在主线程操作,因此设计一个线程去执行下载动作,下载完成后再更新图片。代码编写如下:

public class TestImgActivity extends Activity  {

    protected static final String TAG = "TestActivity";
    protected static final int MSG_LOAD_IMAGE = 1;
    private String URL ="http://img5.imgtn.bdimg.com/it/u=854852651,3145367340&fm=23&gp=0.jpg";
    private Button mTestBtn;
    private ImageView mImgView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_img);
        mImgView = (ImageView) findViewById(R.id.test_img);
        mTestBtn = (Button) findViewById(R.id.test_btn);
        mTestBtn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new LoadImageRunnable(mHandler, MSG_LOAD_IMAGE, URL)).start();
            }
        });
    }

    public class LoadImageRunnable implements Runnable {  

        private int mThreadId ;  
        private Handler mHandler ;  
        private String sUrl;  
        public LoadImageRunnable(Handler h, int id, String str) {  
            mHandler = h;  
            mThreadId = id;  
            sUrl = str;  
        }  

        @Override  
        public void run() {  
            Log.i(TAG, "LoadImageRunnable-----"+Thread.currentThread().getName());  
            Bitmap bmp = loadImageFromNetwork();
            refreshImageView(bmp);
        }  

        /**更新imageview图片显示*/  
        private void refreshImageView(Bitmap bmp) {
            if (bmp!=null) {
                mImgView.setImageBitmap(bmp);
             }
        }

        /**从外部链接加载图片*/  
        private Bitmap loadImageFromNetwork() {  
            Bitmap bmp = null;  
            try {  
                URL url = new URL(sUrl);  
                URLConnection conn = url.openConnection();  
                InputStream is = conn.getInputStream();  
                bmp = BitmapFactory.decodeStream(is);   
            } catch (MalformedURLException e){  
                e.printStackTrace();  
                mHandler.sendEmptyMessage(10+mThreadId);  
            } catch(IOException e) {  
                mHandler.sendEmptyMessage(10+mThreadId);  
                e.printStackTrace();  
            }  
            return bmp;  
        }  
    }  

}

实际跑一下,crash发生了。log如下:
handler_crash
查看异常代码堆栈,发现是CalledFromWrongThreadException,即只有在UI主线程才能操作View控件!
想起Handler来了(Handler可以更新UI),修改代码,下载完成后,向Handler发送一条消息,在handler的消息处理方法中进行ImageView更新。

    /**更新imageview图片显示*/  
    private void refreshImageView(Bitmap bmp) {
         mHandler.obtainMessage(mThreadId, bmp).sendToTarget();
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            switch (msg.what) {
            case MSG_LOAD_IMAGE:
                if (msg.obj instanceof Bitmap) {
                    Bitmap bmp = (Bitmap) msg.obj;
                    mImgView.setImageBitmap(bmp);
                    Log.i(TAG, "refresh image bitmap.");
                } 
                break;

            default:
                break;
            }
        }
    };

再运行一遍,还真是可以正常更新图片了。日志如下:
log_handler_ok

概述

Handler可以用在UI主线程接收工作线程传递过来的消息,执行更新UI操作。其示意图如下:

handler示意图


由此例子,引发以下问题:

1. Handler的作用是什么
Handler在主线程上维护一个消息队列,并可以接收其他线程发送过来的消息,在主线程上依次处理消息(通常是更新UI)。一般情况下,在主线程中创建Handler,并在某事件触发时创建新的线程用于完成耗时的操作,当子线程中的工作完成之后,向Handler发送一个消息,在Handler中处理该消息,执行更新UI的操作。当然,Handler提供的是一种线程间通信的方式,不仅限于更新UI。
2. Handler是否意味着主线程?
虽然通常使用Handler来更新UI,但Handler并不等同于主线程。
3. Handler.post(Runnable r)中r的执行是在哪个线程?
此方法中的参数r是一个Runnable类型,那么是否其作用是开启一个独立的线程来执行r呢?
并不是。
此方法的作用只是将一个操作封装到一个runnable对象中,其执行仍然是在Handler所属线程,而没有开辟一个独立的线程来执行。

4. 是否任意线程中都能使用Handler机制,有何注意事项?
实际上,可以在任意线程Thread上使用Handler(通过handler接收其他线程发送过来的消息)。当然,需要注意其使用规范:

    Looper.prepare();
    Handler mHandler = new Handler();
    Looper.loop();

5. 一个线程中能否拥有多个Handler?
一个线程中可以创建多个handler,它们互相独立,但引用的是该线程上的同一个MessageQueue和Looper,消息队列中的各个消息根据其对应的时间顺序,依次被处理。

    Looper.prepare();
    Handler mHandler1 = new Handler();
    Handler mHandler2 = new Handler();
    Looper.loop();

详细分析

带着以上问题,通过阅读分析Handler及其相关类的源码来解析Handler的工作原理。

  • 首先分析Handler及其相关的类图。

handler类图

    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));
    }

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

从Looper类的static(全局)变量sThreadLocal中取出此线程对应的looper,若存在,说明此线程已经创建过looper,抛异常;否则,新建一个looper对象(同时创建一个MessageQueue,并将该线程的引用赋值给mThread),并将该looper对象设置进入sThreadLocal。
由此可以看出,一个线程只能对应一个looper,所有线程上创建的looper都记录在一个静态变量sThreadLocal中,可以简单将sThreadLocal看成是一张looper登记表。其类ThreadLocal在java.lang包里面,此类在android中是对之前的JDK中的版本改良过的。主要提供两个方法,get()和set(),对外透明,可以直接存储进入和从中取出两个操作。对于同一个线程的looper,取出的对象和存入的是同一个。
深入进去看源码,可以发现ThreadLocal类中定义了一个内部类Values,其中持有一个Object[] table,仔细分析代码,发现这个ThreadLocal类并没有持有Values实例,而是在线程Thread类中持有该对象。在ThreadLocal的get方法中,首先取得当前线程对象currentThread,然后从currentThread中取得其Values对象,再从Values中的table来查找当前线程的looper。

    public Handler() {
        this(null, false);
    }

    public Handler(Callback callback, boolean async) {
      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();  //获取到自己的looper(当前线程所对应的looper)
      if (mLooper == null) {
          throw new RuntimeException(
              "Can't create handler inside thread that has not called Looper.prepare()");
      }
      mQueue = mLooper.mQueue;  //将looper的MessageQueue引用过来
      mCallback = callback;
      mAsynchronous = async;
  }

Handler的成员mLooper 是直接引用的自己的looper (当前线程所对应的looper)。Looper类的myLooper方法如下:

    public static Looper myLooper() {
        return sThreadLocal.get();
    }

最终还是从ThreadLocal类的静态变量sThreadLocal中获取looper。贴上ThreadLocal的set和get两个方法:

ThreadLocal.java

    public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }

    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

ThreadLocal.Values.java

    void put(ThreadLocal<?> key, Object value) {
        cleanUp();

        // Keep track of first tombstone. That's where we want to go back
        // and add an entry if necessary.
        int firstTombstone = -1;

        for (int index = key.hash & mask;; index = next(index)) {
            Object k = table[index];

            if (k == key.reference) {
                // Replace existing entry.
                table[index + 1] = value;
                return;
            }

            if (k == null) {
                if (firstTombstone == -1) {
                    // Fill in null slot.
                    table[index] = key.reference;
                    table[index + 1] = value;
                    size++;
                    return;
                }

                // Go back and replace first tombstone.
                table[firstTombstone] = key.reference;
                table[firstTombstone + 1] = value;
                tombstones--;
                size++;
                return;
            }

            // Remember first tombstone.
            if (firstTombstone == -1 && k == TOMBSTONE) {
                firstTombstone = index;
            }
        }
    }

ThreadLocal类中有一个静态变量hashCounter,每个ThreadLocal对象中有一个hash值,每次利用hashCounter创建一个hash值(来标记当前线程),hashCounter值自增0x61c88647的两倍(它是一个魔数,使得下一个hash值总是跟以前产生的不同!),每次对新的线程产生一个hash值,将ThreadLocal的弱引用当作key,而线程的Values值一起存入线程Values值的表table中。下次取的时候,依据ThreadLocal来获取Values值;
这种设计很巧妙,线程中有各自的table,而把ThreadLocal实例作为key,一方面,当线程销毁时相关的线程局部变量会被销毁另一方面,这种利用数组方式存取键值对的方法,与JDK中旧的HashMap方法相比性能更高。这种设计很巧妙,线程中有各自的map,而把ThreadLocal实例作为key,这样除了当线程销毁时相关的线程局部变量被销毁之外,还让性能提升了很多。
如此,一个线程中就可以创建多个handler对象,而这些handler对象都关联同一个Looper:

Looper与Handler

附上JDK的ThreadLocal类源码,有兴趣的可以对比看一下。

  • 最后来看loop方法
    顾名思义,loop()方法体里是一个无限循环,反复从消息队列MessageQueue中取出一条消息并处理,直到looper线程退出。实际执行处理消息动作的,是从消息msg的target成员执行dispathMessage,而target就是此msg所引用的Handler对象。

Handler.java

    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();
        }
    }

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

在dispatchMessage处理消息时,msg的callback回调存在时就执行callback操作,否则执行handler的mCallback的handleMessage操作。
再进入Handler的源码看,当对handler进行发送消息时,最终都会sendMessage或是postRunnable时,会对其msg的callback赋值:postRunnable就将r赋给callback,sendMessage就将callback置空。
以下面这句代码(向mHandler发送一条消息)为例:

mHandler.obtainMessage(mThreadId, bmp).sendToTarget();

Handler.java

    public final Message obtainMessage(int what, Object obj)
    {
        return Message.obtain(this, what, obj);
    }

    public final boolean sendMessage(Message msg) 
    {
        return sendMessageDelayed(msg, 0);
    }

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    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);
    }

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

Message.java

    //获取一条message
    public static Message obtain(Handler h, int what, Object obj) {
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.obj = obj;

        return m;
    }

    //从消息池中取出一条消息,若消息池空则新建一条消息
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

    public void sendToTarget() {
        target.sendMessage(this);
    }

向Handler发送一条消息的过程为,从消息池MessageQueue中获取一条消息,将其target指向此handler,然后将此消息msg入列到消息池中(按照消息需要被执行的时间顺序)。

Handler发送消息

  • 到此,Handler的消息池创建过程已经清晰了。当有新的消息被压入到消息池中后,Looper的处理处理循环会依次处理各条消息。

    handler消息队列

然则,回到最初的demo,为何可以在Activity中直接创建Handler而不必执行Looper.prepare()和Looper.loop()?这就要看Activity相关的源码了。
Activity类图

在Activity中有一个成员

Activity.java

ActivityThread mMainThread;

而ActivityThread的主函数main中,执行了Looper相关的准备和循环操作。

ActivityThread.java

    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);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        Security.addProvider(new AndroidKeyStoreProvider());

        Process.setArgV0("<pre-initialized>");

        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();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

实际上,Android应用程序的各个组件(Activity、Service、BroadcastReceiver、ContentProvider)的生命周期都是在其APP主线程内,系统在创建APP进程时其java代码执行堆栈:
NaiveStart.main()
ZygoteInit.main
ZygoteInit$MethodAndArgsCall.run
Method.Invoke method.invokeNative
ActivityThread.main()
Looper.loop()
每个应用程序都以ActivityThread.main()为入口进入到消息循环处理。对于一个进程来讲,需要这个闭合的处理框架。 Android.app.ActivityThread进程建立后,将跳入到ActivityThread的main函数开始运行,进入消息循环。

  • 在非UI线程中使用Handler有一个很好的例子,直接调用HandlerThread类即可。HandlerThread将线程Thread和Looper结合封装起来,使得在此类线程中可以直接创建Handler并使用。

HandlerThread.java

    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
  • 我们将最初的需求改一下,把1个按钮变为3个按钮,点击分别下载不同的网络图片。
    如下图:
    加载默认图片 加载高圆圆图片 加载范冰冰图片 加载林心如图片

设计思路:
在UI主线程和HandlerThread下载线程中分别创建Handler,点击按钮时,UI主线程向HandlerThread发送一条消息以启动下载,下载完成后HandlerThread向UI主线程发送一条消息以更新ImageView显示。
代码如下:

TestImgActivity .java

public class TestImgActivity extends Activity implements Callback  {

    protected static final String TAG = "TestActivity";
    protected static final int MSG_LOAD_IMAGE = 1;
    private String[] mURLs = new String[]{
            "http://img5.duitang.com/uploads/item/201412/22/20141222204442_4CfEy.jpeg",//高圆圆
            "http://imgsrc.baidu.com/forum/pic/item/e1d46c380cd79123d79756b4ad345982b3b7804b.jpg",//范冰冰
            "http://np29.yule.com.cn/html/UploadPic/2009-6/200961514323957477.jpg"//林心如
            };
    private Button mTestBtn1;
    private Button mTestBtn2;
    private Button mTestBtn3;
    private ImageView mImgView;
    private Handler mHandler = new WeakReferenceHandler(this);
    static final int MSG_LOAD_IMG_FINISH = 2;
    LoadImageHandlerThread mHandlerThread;

    private OnClickListener mOnClickListener = new OnClickListener() {

        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
            switch (v.getId()) {
            case R.id.test_btn1:
                mHandlerThread.LoadImage(mURLs[0]);
                Log.i(TAG, "Download gaoyuanyuan!");
                break;
            case R.id.test_btn2:
                mHandlerThread.LoadImage(mURLs[1]);
                Log.i(TAG, "Download fanbingbing!");
                break;
            case R.id.test_btn3:
                mHandlerThread.LoadImage(mURLs[2]);
                Log.i(TAG, "Download linxinru!");
                break;

            default:
                break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_img);
        mImgView = (ImageView) findViewById(R.id.test_img);
        mTestBtn1 = (Button) findViewById(R.id.test_btn1);
        mTestBtn1.setOnClickListener(mOnClickListener);
        mTestBtn2 = (Button) findViewById(R.id.test_btn2);
        mTestBtn2.setOnClickListener(mOnClickListener);
        mTestBtn3 = (Button) findViewById(R.id.test_btn3);
        mTestBtn3.setOnClickListener(mOnClickListener);
        if (null == mHandlerThread) {
            mHandlerThread = new LoadImageHandlerThread(TAG, mHandler);
            mHandlerThread.start();         
        }
    }

    @Override
    public boolean handleMessage(Message msg) {
        // TODO Auto-generated method stub
        switch (msg.what) {
        case MSG_LOAD_IMG_FINISH:
            if (msg.obj instanceof Bitmap) {
                Bitmap bmp = (Bitmap) msg.obj;
                mImgView.setImageBitmap(bmp);
                Log.i(TAG, "display star!");
            }
            break;

        default:
            break;
        }
        return false;
    }  

}

LoadImageHandlerThread.java

public class LoadImageHandlerThread extends HandlerThread implements Callback {  

    private WeakReference<Handler> mMainUiHandlerRef;  
    private Handler mInnerHandler;
    static final int MSG_LOAD_IMG_START = 1;
    private static final String TAG = "LoadImageHandlerThread";

    public LoadImageHandlerThread(String name, Handler mainUiHandler) {
        super(name);
        mMainUiHandlerRef = new WeakReference<Handler>(mainUiHandler);  
    }

    @Override
    protected void onLooperPrepared() {
        // TODO Auto-generated method stub
        mInnerHandler = new WeakReferenceHandler(this);
        super.onLooperPrepared();
    }

    public void LoadImage(String url) {  
        mInnerHandler.obtainMessage(MSG_LOAD_IMG_START, url).sendToTarget();
    }  

    /**请求主线程更新imageview图片显示*/  
    private void refreshImageView(Bitmap bmp) {
        Handler extHandler = mMainUiHandlerRef.get();
        if (extHandler!=null) {
            extHandler.obtainMessage(TestImgActivity.MSG_LOAD_IMG_FINISH, bmp).sendToTarget();
        }
    }

    /**从外部链接加载图片*/  
    private Bitmap loadImageFromNetwork(String url_str) {  
        Bitmap bmp = null;  
        try {  
            URL url = new java.net.URL(url_str);  
            URLConnection conn = url.openConnection();  
            InputStream is = conn.getInputStream();  
            bmp = BitmapFactory.decodeStream(is);   
        } catch (MalformedURLException e){  
            e.printStackTrace();  
        } catch(IOException e) {  
            e.printStackTrace();  
        }  
        return bmp;  
    }

    @Override
    public boolean handleMessage(Message msg) {
        // TODO Auto-generated method stub
        switch (msg.what) {
        case MSG_LOAD_IMG_START:
            if (msg.obj instanceof String) {
                String url = (String) msg.obj;
                Log.i(TAG, "Download star: "+url);
                Bitmap bmp = loadImageFromNetwork(url);
                if (bmp!=null) {
                    refreshImageView(bmp);
                }
            }
            break;

        default:
            break;
        }
        return true;
    }  
}

运行后,功能正常,log如下:
log_handler_3_img

总结

Handler提供了一种安全的线程间通信的机制,广泛应用于UI主线程与工作线程之间的配合。UI主线程的handler消息处理是在主线程中被调用的,要注意不能有耗时的操作,以避免ANR;通常将耗时的操作放在非主线程中执行,完成后通过handler通知主线程更新UI。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值