• Android Handler消息机制深入浅出


    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38408493

    作为Android开发者,Handler这个类应该是再熟悉只是了。由于差点儿不论什么App的开发。都会使用到Handler这个类,有些同学可能就要说了,我全然能够使用AsyncTask取代它,这个确实是能够的,可是事实上AsyncTask也是通过Handler实现的,详细的大家能够去看看源代码即可了,Handler的主要功能就是实现子线程和主线程的通信。比如在子线程中运行一些耗时操作。操作完毕之后通知主线程跟新UI(由于Android是不同意在子线程中跟新UI的)。

    以下就使用一个简单的样例開始这篇文章吧

    public class MainActivity extends Activity
    {
      public static final int MSG_DOWNLOAD_FINISH=0X001;
      //创建一个Handler的匿名内部类
      private Handler handler=new Handler()
      {
    
        @Override
        public void handleMessage(Message msg)
        {
          switch(msg.what)
          {
            case MSG_DOWNLOAD_FINISH:
              Log.v("yzy", "handler所在的线程id是-->"+Thread.currentThread().getName());
              break;
          }
        }
        
      };
    
      @Override
      protected void onCreate(Bundle savedInstanceState)
      {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
      }
      
      //启动一个下载线程
      public void download(View view)
      {
        new Thread()
        {
          public void run() {
            try
            {
              Log.v("yzy", "下载子线程的Name是--->"+Thread.currentThread().getName());
              //在子线程运行,模拟一个下载任务
              Thread.sleep(2000);
              //下载完毕后,发送下载完毕消息
              handler.sendEmptyMessage(MSG_DOWNLOAD_FINISH);
            } catch (InterruptedException e)
            {
              e.printStackTrace();
            }
          };
        }.start();
      }

    运行结果:
    08-03 16:31:46.418: V/yzy(30486): 下载子线程的Name是--->Thread-22588
    08-03 16:31:48.421: V/yzy(30486): handler所在的线程Name是-->main


    上面样例就是模拟了一个下载任务,当下载完毕后,通过Handler对象发送一个消息,最后被handlerMessage方法接收并处理。通过运行结果能够发现。download方法是在子线程中完毕的。而handlerMessage是在UI线程中被调用的。

    到了这里一个简单的Handler使用案例就结束了,假设你已经明确了上面的代码,那么说明你已经明确了Handler的最基本使用。
    以下再来一个简单的样例:

    public void postRun(View view)
      {
        new Thread(){
          public void run() {
            try
            {
              Log.v("yzy", " 下载子线程的Name是--->"+Thread.currentThread().getName());
              Thread.sleep(2000);
              handler.post(new Runnable()
              {
                
                @Override
                public void run()
                {
                  Log.v("yzy", "handler post run -->"+Thread.currentThread().getName());
                }
              });
            } catch (InterruptedException e)
            {
              e.printStackTrace();
            }
           
          };
          
        }.start();
       
      }

    运行结果:
    08-03 16:31:52.528: V/yzy(30486):  下载子线程的Name是--->Thread-22589
    08-03 16:31:54.531: V/yzy(30486): handler post run -->main

    在Handler中除了能够发送Message对象还能够发送Runnable对象。从运行结果来看发送的Runnable也是在main线程中运行的
    那么是不是通过handler发出的Message和Runnable都是在UI线程中运行的呢,这个可不一定。如今我在onCreate方法中创建一个handler2

    HandlerThread thread=new HandlerThread("yzy");
        thread.start();
        
        handler2=new Handler(thread.getLooper())
        {
          public void handleMessage(Message msg) {
            switch(msg.what)
            {
              case MSG_DOWNLOAD_FINISH:
                Log.v("yzy", "handler所在的线程Name是-->"+Thread.currentThread().getName());
                break;
            }
            
          };
        };

    然后将上面代码中使用handler的地方换为handler2,然后运行代码:代码中创建了一个名称为"yzy"的线程。然后再yzy线程中初始化了handler2
    结果例如以下:
    08-03 17:07:10.571: V/yzy(8224): 下载子线程的Name是--->Thread-23005
    08-03 17:07:12.574: V/yzy(8224): handler所在的线程Name是-->yzy

    我们发现通过handler2发出的Message和Runnable都是在yzy线程中运行的。


    假设对于上面的代码和运行结果你都知道原因。那么说明你已经对Handler的使用非常熟悉了。假设不清楚上面的运行结果,那么请往下继续吧

    開始分析原因之前我想提出以下几个问题:
    1、Handler发出的Message和Runnable是怎样传递到UI线程的?
    2、在什么情况下通过Handler发出的Message和Runnable不会传递到UI线程?
    3、为什么我是在HandlerThread初始化一个Handler?

    以下我们就逐一解决上述问题吧,在解决这个问题之前我们须要分析一个Hanlder这个类的源代码。


    首先看看Handler的构造函数,它有好几个构造函数。我们一个个的看吧

    //无參构造函数
      public Handler() {
    		//检查Handler是否是static的,假设不是的。那么有可能导致内存泄露。详细能够百度
            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());
                }
            }
    		//非常重要的一个属性。临时就将Looper理解成一个消息队列的管理者吧。用来从消息队列中取消息的,稍后会分析这个类
            mLooper = Looper.myLooper();
            if (mLooper == null) {
    			//这个异常非经常见哦。Handler一定要在有Looper的线程上运行,这个也就是为什么我在HandlerThread中初始化Handler
    			//而没有在Thread里面初始化,假设在Thread里面初始化须要先调用Looper.prepare方法
                throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
            }
    		//将mLooper里面的消息队列拷贝到自身的mQueue,这也就意味着Handler和Looper是公用一个消息队列
            mQueue = mLooper.mQueue;
    		//回调函数默认是Null
            mCallback = null;
        }
    
    	//这个和上面一样的。仅仅只是传入了一个回调函数
        public Handler(Callback callback) {
            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 = callback; } /** * 传入一个Looper,并和Looper公用一个消息队列 */ public Handler(Looper looper) { mLooper = looper; mQueue = looper.mQueue; mCallback = null; } /** * 和上面一个几乎相同,就不在赘述 */ public Handler(Looper looper, Callback callback) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; }


    看了Handler的构造函数。发现Hanlder里面有一个消息队列,这个消息队列是从Looper里面传入的,而且Handler须要在有Looper的线程中调用。

    既然它和Looper关系这么紧密,我们看看Looper究竟是什么东西吧。打开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();
      *      }
      * 

    这个就是为了说明Handler仅仅能在有Looper的线程中创建,UI线程是有Looper的。对于没有Looper的线程,须要调用Loope.prepare,Looper.loop等函数,如上例。


    那我们正式进入Looper的源代码看看吧

    //Looper的构造函数,主要是对消息队列等属性初始化
         private Looper() {
            mQueue = new MessageQueue();
            mRun = true;
    		//记录所在线程
            mThread = Thread.currentThread();
        }
    	
       //在上面的样例中,看到当在一个没有Looper的线程中创建Handler,就须要运行这个函数,
       //这个函数主要是new 一个Looper,燃火放入ThreadLocal中保存。做到一个线程就创建一次。

    第二次调用这种方法会抛出异常的 public static final void prepare() { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper()); } //prepare是创建并保存。这种方法就是取出Looper public static final Looper myLooper() { return (Looper)sThreadLocal.get(); } //这种方法是给系统调用的,UI线程通过调用这个线程,从而保证UI线程里有一个Looper //须要注意:假设一个线程是UI线程,那么myLooper和getMainLooper是同一个Looper,通过这个代码非常好理解 public static final void prepareMainLooper() { prepare(); setMainLooper(myLooper()); if (Process.supportsProcesses()) { myLooper().mQueue.mQuitAllowed = false; } } //获得UI线程的Looper,通常我想Hanlder的handleMessage在UI线程运行时一般会new Handler(getMainLooper()); public synchronized static final Looper getMainLooper() { return mMainLooper; } /** * 上面的样例是运行了这种方法的,这种方法是一个死循环。一直从消息队列中读取消息,并分发出去 */ public static final void loop() { //得到本线程的Looper Looper me = myLooper(); //拿到消息队列 MessageQueue queue = me.mQueue; while (true) { //从消息队列中拿一个消息 Message msg = queue.next(); // might block //取到了一个消息 if (msg != null) { //这个一般不等于Null,通常就是发出这个Message的Handler if (msg.target == null) { // No target is a magic identifier for the quit message. return; } if (me.mLogging!= null) me.mLogging.println( ">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what ); //调用Handler的dispatchMessage方法 msg.target.dispatchMessage(msg); if (me.mLogging!= null) me.mLogging.println( "<<<<< Finished to " + msg.target + " " + msg.callback); //将消息放入消息池 msg.recycle(); } } } //返回Looper的消息队列 public static final MessageQueue myQueue() { return myLooper().mQueue; }


     看了Looper的源代码之后总结一下:
      在一个线程的run方法里面先调用prepare方法。主要保证了该线程里面有了一个Looper对象,假设在这个线程里面创建一个Hanlder,那么看看上面Handler的空的构造方法。Handler里面的Looper就是线程里面保存的Looper,从而能够创建的Handler和线程公用一个Looper,也就是公用一个消息队列。

    说的更详细一点就是Hanlder和Looper所在的线程公用一个消息队列。

    最后调用loop方法。这个线程不断从消息队列取消息。然后调用Hanlder的dispatchMessage方法。
      
    最后看看Handler的其它几个比較重要的方法吧

    //在Handler中发送一个消息到消息队列。相似的方法非常多,我仅仅选了这一个
       public boolean sendMessageAtTime(Message msg, long uptimeMillis)
        {
            boolean sent = false;
            MessageQueue queue = mQueue;
            if (queue != null) {
    			//将target设置成了Handler
                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;
        }
    	//这种方法是在loop方法中被调用的
    	 public void dispatchMessage(Message msg) {
    		//首先检查msg中callback(是一个Runnable对象)是否为Null,假设不为null,则直接调用callback中的run方法
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
    			//检查Handler中的callback是否为空,假设不为空,则直接调用callback中的handleMessage,假设返回TRUE,则直接返回不在调用Handler中的handleMessage
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
    			//最后调用Handler中的handlerMessage
                handleMessage(msg);
            }
        }

    在前面的样例中,我们以前使用过Handler中的post方法,我们看看它是怎么实现的

     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;
        }
    
    	
    	public final boolean sendMessageDelayed(Message msg, long delayMillis)
        {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }

    我想假设前面明确了的,这个就不用我说了的吧。


    事实上在Activity中有一个方法runOnUiThread,就是使用这个原理实现的。例如以下:

    //让Runnable在UI线程运行
    	    public final void runOnUiThread(Runnable action) {
    		//假设当前线程不是UI线程。那么通过Handler转发到UI线程
            if (Thread.currentThread() != mUiThread) {
                mHandler.post(action);
            } else {
                action.run();
            }
        }

    View里面也有相似的方法:

    public boolean post(Runnable action) {
            Handler handler;
            if (mAttachInfo != null) {
                handler = mAttachInfo.mHandler;
            } else {
                // Assume that post will succeed later
                ViewRoot.getRunQueue().post(action);
                return true;
            }
    
            return handler.post(action);
        }

    原理是上面是一样的。


      依据上面的学习,能够得出结论:
      1、假设Handler是在UI线程创建的。那么Handler和UI线程共享消息队列,所以通过Hanlder发出的消息都是在UI线程中运行的
      2、假设要让Handler发出的消息在子线程中运行,非常easy,在创建Handler的时候传入子线程的Looper(通过Looper.prepare创建)
      到这里相信在前面我提及的三个问题都已经有答案了吧,假设有没有明确的,欢迎留言。。。。


  • 相关阅读:
    在linux下使用sqlcmd
    sqlserver 索引的结构及其存储,索引内容
    据库被标记为RESTORING的处理方式,正在还原中,正在恢复
    sql语句的优化分析
    SQL Server表分区-水平分区
    JavaScript Structure
    JS----Issue
    JavaScript Book Plan
    LINQ
    About JavaScript
  • 原文地址:https://www.cnblogs.com/lytwajue/p/6971873.html
Copyright © 2020-2023  润新知