Android消息机制 Handler,Looper,MessageQueue

本文深入探讨了Android中如何使用Handler机制实现UI更新的过程,包括Handler的工作原理、常见异常及解决方案,同时还介绍了如何正确创建Looper和MessageQueue来确保消息传递的准确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

我们都知道在android程序当中,只能在主线程当中去更新UI的,而把一些耗时的操作例如:I/O操作,访问网络等操作都需要放在非主线程当中去执行,通常我们在子线程(非主线程)中进行一系列操作之后需要将结果显示在UI上,例如从网上下载图片,更新在界面上,但是子线程是没有办法修改UI的,这个时候就需要使用android为我们提供的Handler来进行UI的更新

Android系统为什么不推荐在子线程当中更新UI?

  1. UI控件不是线程安全的,如果多线程并发的访问可能会导致UI控件处于不可预期的状态。
  2. 如果采用锁机制来解决并发问题,会使逻辑变得复杂,线程工作效率下降
    因此android系统采用单线程模型来处理UI操作,并提供Handler来进行UI操作。

使用Handler的两个常见的异常

一开始我们可能会使用以下代码在子线程当中更新UI

package com.xiezhen.handlerstudy;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {

    private TextView tvHandler;
    private Button btnUpdate;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvHandler = (TextView) findViewById(R.id.tv_handler);

        btnUpdate = (Button) findViewById(R.id.update_text);
        btnUpdate.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        tvHandler.setText("update text");
                    }
                }).start();
            }
        });
    }
}

系统会报错,如图:
这里写图片描述

我们可能会在子线程当中使用如下代码创建Handler:

package com.xiezhen.handlerstudy;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {

    private TextView tvHandler;
    private Button btnUpdate;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvHandler = (TextView) findViewById(R.id.tv_handler);

        new Thread(new Runnable() {

            @Override
            public void run() {
                new Handler();
            }
        }).start();
    }
}

系统会报错,如图:
这里写图片描述

我们最常见的使用Handler的方式可能是这样的:

package com.xiezhen.handlerstudy;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {

    private TextView tvHandler;
    private Button btnUpdate;

    private class MyHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case 1:
                tvHandler.setText("update");
                break;

            default:
                break;
            }

        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvHandler = (TextView) findViewById(R.id.tv_handler);
        btnUpdate = (Button) findViewById(R.id.update_text);

        final MyHandler myHandler = new MyHandler();
        btnUpdate.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message msg=Message.obtain();
                        msg.what=1;
                        myHandler.sendMessage(msg);
                    }
                }).start();
            }
        });
    }
}

我们点击按钮之后,textview上的文本就会显示为update。那么在这个过程中,系统做了哪些事情,message是怎么从子线程传递到主线程的呢?在程序初始化的时候,在主线程的main方法中会创建一个Looper对象,之后在这个Looper对象当中创建一个MessageQueue,然后我们使用创建的Handler对象发送Message到MessageQueue当中,Looper会调用自己的loop()方法循环取出MessageQueue中的Message交给我们创建的Handler的handleMessage()方法去处理。

下面我们从源码的角度来看看Handler的工作过程:

源码分析

我们从创建Handler来进行分析,首先看Handler的构造方法:

    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;
    }
  • 从2-7行可以看出如果你的Handler声明成非静态内部类的话是有可能造成内存泄漏的,原因及解决方法请看泡在网上的日子
  • 第8行可以看见我们通过myLooper()方法获取了当前线程的looper,并通过looper的构造方法获取到looper中的mQueue(MessageQueue),这样Handler、Looper、MessageQueue就结合在一起
  • 从9-11行可以看到我们之前在子线程创建Handler出现的异常,从这里可以看出Handler创建之前必须要调用Looper.prepare()方法来创建Looper对象才可以。

或许你会问,我们在主线程当中创建Handler的时候,也没有先创建Looper对象啊,这是因为我们程序启动的时候在主线程的main方法中就创建了Looper对象:

public static void main(String[] args) {
    SamplingProfilerIntegration.start();
    CloseGuard.setEnabled(false);
    Environment.initForCurrentUser();

    // Set the reporter for event logging in libcore
    EventLogger.setReporter(new EventLoggingReporter());
    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");
}

第十行执行了Looper的prepareMainLooper()方法,这个方法其实就是创建一个Looper对象,并且在21行执行了loop()方法用来循环取出MessageQueue的message并派发给Handler进行处理,我们来看看prepareMainLooper()的相关源码:

    private Looper() {
        mQueue = new MessageQueue();
        mRun = true;
        mThread = Thread.currentThread();
    }
    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static Looper myLooper() {
        return sThreadLocal.get();
    }

    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare();
        setMainLooper(myLooper());
        myLooper().mQueue.mQuitAllowed = false;
    }

    private synchronized static void setMainLooper(Looper looper) {
        mMainLooper = looper;
    }

     /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }

prepareMainLooper()方法的内部调用了prepare()方法来创建Looper对象并且将Looper对象存储在ThreadLocal中,ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,有关ThreadLoacl的介绍请看ThreadLocal工作原理我们已经知道在创建handler之前必须创建Looper对象,Looper会在创建的同时获取一个MessageQueue,主线程通过main方法中的prepareMainLooper()方法创建Looper,而在子线程中我们直接调用Looper.prepare()方法来创建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的使用上去,通过分析Handler的源码可以知道,我们new Handler()的时候,Handler就和当前线程的Looper对象以及Looper内部的MessageQueue关联在一起,之后我们调用Handler的sendMessage()方法发送消息,我们来看看Handler发送消息的源码:

public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}
//sendMessage方法会调用sendMessageDelayed方法
public final boolean sendMessageDelayed(Message msg, long delayMillis)  
{  
    if (delayMillis < 0) {  
        delayMillis = 0;  
    }  
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);  
}
//之后sendMessageDelayed方法会调用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;  
}

最后会调用到sendMessageAtTime方法,在这个方法的第19行,我们将消息通过MessageQueue的enqueueMessage方法插入到了MessageQueue中,这样我们在子线程当中发送的消息就已经插入到了主线程的MessageQueue当中了。之后Looper中的loop方法会循环的从这个MessageQueue中取出消息派发给目标Handler:

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the 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;
        //...
        while (true) {                
            Message msg = queue.next();
            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);
                //...
                msg.recycle();
            }
        }
    }

在代码的第12行开启了一个无限循环,第13行我们调用next方法从MessageQueue中取出消息,没有消息则阻塞(wait)等待下一条消息的到来(notify),在循环体中的第29行我们调用了 msg.target.dispatchMessage(msg)方法,还记得我们发送消息的时候最后会掉用Handler的sendMessageAtTime方法吗,这个方法的18行我们给msg.target赋值:msg.target = this;this就是发送这个消息的Handler,也就是Looper最后会将消息交由发送这个消息的Handler的dispatchMessage(msg)来处理,我们接着来看看dispatchMessage方法的源码:

    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }

    private final void handleCallback(Message message) {
        message.callback.run();
    }

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

现在一目了然了最后我们会调用handleMessage方法来处理消息。
其实Handler为我们提供了很多发送消息的方法,我们也可以这样来发送一个消息:

btnUpdate.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        myHandler.post(new Runnable() {

                            @Override
                            public void run() {
                                //更新ui的操作
                            }
                        });
                    }
                }).start();
            }
        });

其实这里面调用Handler的post方法传入一个Runable对象,不要以为这里又开了一个线程,我们来看一下源码

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

在getPostMessage方法中将Runnable对象封装到Message对象中,最后还是会调用sendMessageAtTime方法,之前在dispatchMessage方法中有对msg.callback做非空判断,这个时候我们就会执行
handleCallback(msg);
message.callback.run()
;
也就是执行Runnable对象中run方法,并没有开启一个新的线程。
大家可以去看源码大部分提供的发送消息的方法最后都会走到这个逻辑当中。

总结

图片取自MrSimp1e的Blog
这里写图片描述

应用程序开启的时候,在main方法当中会创建一个Looper对象,这个对象存储在ThreadLocal当中,在Looper对象当中会维护一个MessageQueue(消息队列),MessageQueue的数据结构是链表并不是队列,我们可以创建Handler,通过Handler在子线程中发送消息到MessageQueue当中,Looper从MessageQueue当中取得消息返回给Handler,Handler通过handleMessage或者调用callback来取得消息并执行一些操作。

补充:可以使用 Message.obtain()来从消息池中获得空消息对象,以节省资源。
提问:那么在非ui线程能不能更新ui呢?
答:可以

package com.xiezhen.handlerstudy;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity {

    private TextView tvHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvHandler = (TextView) findViewById(R.id.tv_handler);

        new Thread(new Runnable() {
            @Override
            public void run() {
                tvHandler.setText("update");
            }
        }).start();
    }
}

以上代码就可以在子线程当中更新ui,原因请访问:
AigeStudio的Blog
Android更新Ui进阶精解(一)
本文参考blog:
android开发艺术探索
MrSimp1e的Blog
Codingmyworld

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值