Looper, Handler,Message关系解析


参考1

参考2


提到这三个类,我们很容易联系到异步消息机制,那么异步消息机制的作用是什么呢?

解决如下问题:

   由于1,Android中的只有UI线程才能更新UI线程

            2,并且UI线程里不能进行耗时操作,否则会报出ANR异常。

那么我们如何进行耗时操作,并且更新UI界面的变化,这时异步消息机制就可以大展拳脚了!


Looper:负责维护一个MessageQueue,并且与当前线程绑定(利用ThreadLocal),一个线程只能对应一个looper对象,

Looper.prepare(): sThreadLocal是一个ThreadLocal对象,下面的代码说明只能调用一次prepare,否则会异常,


  static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<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()应该在loop()之前,因为,必须先sThreadLocal.set(XXX)之后,才能sThreadLocal.get(XXX).否则会报空指针异常,除非创建ThreadLocal实例时,需要覆盖initialValue()方法。具体为什么,可以详细参看ThreadLocal源码。


下面看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; //Looper维护的MessageQueue

        // 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(); // 无限循环,取出消息
            if (msg == null) {
                // 如果消息为空,就退出
                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);//处理消息的地方,这里msg.target就是Handle对象

            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内部维护一个MessageQueue


public static MessageQueue myQueue() {
        return myLooper().mQueue;
    }

myLooper():获取当前线程对应的Looper对象


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


从上面的源码中可以看出来,Looper先通过prepare()获得一个looper实例,然后通过loop(),维护一个MessageQueue,逐条取出消息,并分发消息。由于分发消息要用到msg.target(其实就是Handler对象)的dispatchMessage方法,因此在调用Looper.loop()方法前需要先初始化Handle对象。

下面追踪一下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方法,大家一定不陌生,这个方法就是我们实例化Handler时用匿名内部类覆盖方法handleMessage,里面包含了我们具体处理消息的逻辑。


下面具体看一下Handler内部的实现,看看它是怎么和Looper以及MessageQueue关联起来的。

先看构造函数

  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实例
        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实例化过程中,和Looper,Message关联起来。


前面已经分析了Looper,Handler,Message之间的关系,

流程是这样的:Looper.prepare()获取Looper实例,并维护一个MessageQueue,然后初始化一个handler对象(一般会复写handleMessage方法,采用匿名内部类),此时Looper,MessageQueue,Handler都有了,然后调用Looper.loop()循环取出消息,并分发处理消息(分发消息的过程中会调用handleMessage方法)。


其实还遗漏了一点,就是如何得到Message的?

其实,除了分发消息以外,发送消息也是Handler要做的工作,要不怎么说回调呢,下面看看Handler.sendMessage()

 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;//是不是很熟悉?前面已经提到,msg.target就是Handler对象
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }



最后谈谈关于Handler.post(Runnable r)的一些看法

有时候,我们会不用Handler.sendMessage方法发送信息,而是用Handler.post(Runnable r)方法,这样一来实例化handler对象就不用覆盖handleMessage方法了,比较方便。

Handler .post方法代码如下:

  public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
 


private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
 <span style="white-space:pre">	</span><span style="color:#ff0000;"> m.callback = r;</span>
        return m;
    }


那么就会有人有疑问了,如果连handleMessage方法都不需要编写了,那消息具体是如何处理的呢?,问的好!大家是否还记得Handler分发消息的方法:

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!=null时,会走handleCallback(msg)逻辑,从上面getPostMessage(Runnable r)方法里面加红的代码中可以看出,当调用post方法时,msg.target!=null因此会走handleCallback(msg)逻辑。

private static void handleCallback(Message message) {
        message.callback.run();
    }
注意这里调用的是run方法,如果大家知道run方法与start方法的区别,就会知道run方法并没有开启一个独立的线程,调用run方法就相当于普通方法的调用,并没有产生新的线程。从上面的代码可以知道message.callback.run()运行的是post(Runnable r)里面的run方法里面的内容.


示例1,

MainActivity1里面的代码:

private Button btn;
private TextView text;
    
private Handler handler = new Handler(){
  private int process = 0;
  @Override
  public void handleMessage(Message msg) {
    switch(msg.what){
    case 0://更细下载进度
      process += 1;
      text.setText("下载" + process + "%");//在主线程中更新UI界面
      break;
    case 1://提示下载完成
      text.setText("下载完成");//在主线程中更新UI界面
      break;
    default:
      break;
    }
  }
};
//onCreate之类的生命周期的方法就是允许在UI主线程中
@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
        
  btn = (Button) findViewById(R.id.btn);
  text = (TextView) findViewById(R.id.text);
        
  btn.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    new Thread(){
      @Override
      public void run() {
        //为了不阻塞主线程,在子线程中进行下载耗时操作
        for(int i = 0; i < 100; i++){
          try {
            Thread.sleep(200);//休眠0.2秒,模拟耗时操作
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          handler.sendEmptyMessage(0);//发送消息到handler,通知下载进度
        }
        handler.sendEmptyMessage(1);//发送消失到handler,通知主线程下载完成
        }
      }.start();
    }
  });
}


 但是如果你觉得每次都要重写handlerMessage()比较麻烦,我们完全可以用更加简略的方法来解决我们的需求,就是用handler中的post方法。于是就有了示例2

示例2,

MainActivity2里面的代码:

private Button btn;
private TextView text;
    
private Handler handler = new Handler();//简化了实例化过程,没有handleMessage(msg)
 
//onCreate之类的生命周期的方法就是允许在UI主线程中
@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
        
  btn = (Button) findViewById(R.id.btn);
  text = (TextView) findViewById(R.id.text);
        
  btn.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
   new Thread(){
  @Override
  public void run() {
    //防止阻塞UI线程,所以在子线程中进行下载耗时操作
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    handler.post(new Runnable() {
      @Override
      public void run() {
        text.setText("下载完成");<span style="font-family: Arial, Helvetica, sans-serif;">/<span style="color:#ff0000;">/发送消息到handler,通知主线程下载完成,这里的run()里其实是在主线程里,因此不能有耗时操作</span>
</span>      }
    });
  }
 }.start();
}
对于加红色的注释,大家也许会有很大疑问,质疑这句话的正确性,下面会再次提到,这里先简单说一下(当前UI线程会自动调用Looper.prepare()和Looper.loop()方法,大家还记得Looper.loop()里面的msg.target.dispatchMessage(msg)吗?)好了,看下面流程

在UI线程中:Looper.prepare()->合适的时候发送消息:handle.postMessage(Runnable r)->Looper.loop()->mag.target.dispatchMessage(msg)->handleMesssage(Runnable r)()->msg.callback.run(此时的run方法就是我标注红色注释不能有耗时操作的地方)

看完流程大家应该就没疑问了。

写了那么多内容,大家是否还有点疑惑,为甚么在UI线程里面使用异步消息机制时,我们只是实例化了Handler(覆盖了handleMessage方法),并在合适的地方调用sendMessge方法或者调用postMessage(Runnable r),我们似乎并没有看到Looper的身影,是的,在UI线程中,我们虽然显示的看不到Looper.prepare(),和Looper.loop()这样异步消息机制所必须的方法,但是实际上是系统已经帮我们隐式的执行了Looper.prepare(),和Looper.loop()方法,注意我一直强调UI线程,因为在Activity的启动代码中,已经在当前UI线程调用了Looper.prepare()和Looper.loop()方法。当然如果是非UI线程,那么就必须按照我们以前提到的流程调用代码,因为此时不会有其他人帮我们调用了。一般我们会把这一系列流程放入单独的Thread线程里。


最后再啰嗦一句UI线程:


在一个Android 程序开始运行的时候,会单独启动一个Process。默认的情况下,所有这个程序中的Activity或者Service(Service和 Activity只是Android提供的Components中的两种,除此之外还有Content Provider和Broadcast Receiver)都会跑在这个Process。
一个Android 程序默认情况下也只有一个Process,但一个Process下却可以有许多个Thread。在这么多Thread当中,有一个Thread,我们称之为UI Thread。UI Thread在Android程序运行的时候就被创建,是一个Process当中的主线程Main Thread,主要是负责控制UI界面的显示、更新和控件交互。在Android程序创建之初,一个Process呈现的是单线程模型,所有的任务都在一个线程中运行。因此,我们认为,UI Thread所执行的每一个函数,所花费的时间都应该是越短越好。而其他比较费时的工作(访问网络,下载数据,查询数据库等),都应该交由子线程去执行,以免阻塞主线程。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值