Android 消息处理机制

来源:互联网 发布:html可以连接数据库吗 编辑:程序博客网 时间:2024/06/16 04:04

本文原创,转载请注明地址。

作为一个软件工程大二的学生党,这篇文章或许有不对的地方,欢迎大家指正。我在学习EventBus的时候,里面的机制和Android Handler + Looper + Message 类似所以就写了这篇博文,不禁感叹到Google的设计。

一:从用法说起

下面一段代码,点击一个Button,开启一个新线程,着这个线程里面do something,然后通知Handler来更新UI。

import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.support.v7.app.AppCompatActivity;import android.widget.Button;/** * 在Work Thread里面通知UI Thread更新UI */public class MainActivity extends AppCompatActivity {    Button mBtn;    Handler mHandler = new Handler() {        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            mBtn.setText("Change");        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mBtn = (Button) findViewById(R.id.mBtn);        mBtn.setOnClickListener(v -> {            //开启一个WordThread            new Thread(() ->            {                //通知UI Thread更新                Message msg = mHandler.obtainMessage();                msg.sendToTarget();            }).start();        });    }}

看似简单几行代码,背后却有巧妙地实现,下面一起来看看。

二 :Looper

何为Looper: Google文档这样说:

Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call {@link #prepare} in the thread that is to run the loop, and then {@link #loop} to have it process messages until the loop is stopped.

还给了以下的例子

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

Looper线程,就是可以存在一个消息循环的线程,这个线程一直工作,有任务执行时,执行完成一个任务之后,继续指向下一个任务。通过上述Demo,一个普通的线程就可以变为Looper线程。
看下面的图:
Looper1
咱们看看Looper源码

    /*Looper内部就是一个ThreadLocal,ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,而这段数据是不会与其他线程共享的。其内部原理是通过生成一个它包裹的泛型对象的数组,在不同的线程会有不同的数组索引值,通过这样就可以做到每个线程通过 get() 方法获取的时候,取到的只能是自己线程所对应的数据*/    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();    private static Looper sMainLooper;//主线程Looper    final MessageQueue mQueue; //Looper维护的消息队列    final Thread mThread; //当前线程    /**     *初始化Looper,该方法必须在loop()方法之前     */    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));    }    /**     * Run the message queue in this thread. Be sure to call     * {@link #quit()} to end the loop.     */    public static void loop() {        final Looper me = myLooper(); //得到当前线程的Looper        if (me == null) {            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");        }        final MessageQueue queue = me.mQueue; //得到当前Looper管理的消息队列        // 看不懂。。。        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;            }            // Log            Printer logging = me.mLogging;            if (logging != null) {                logging.println(">>>>> Dispatching to " + msg.target + " " +                        msg.callback + ": " + msg.what);            }            //将真正的处理共工作分配给target 也就是Handler对象            msg.target.dispatchMessage(msg);            if (logging != null) {                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);            }            // 好像也是Log            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.recycleUnchecked(); //回收Messages对象        }    }

Looper还有以下方法

 /**  * 返回当前线程的Looper  */ public static @Nullable Looper myLooper() {        return sThreadLocal.get();    } /**  * 返回应用程序主线程的Looper  */ public static Looper getMainLooper() {        synchronized (Looper.class) {            return sMainLooper;        }    } /**   * 返回当前Looper所属线程   */ public @NonNull Thread getThread() {       return mThread; } /**   * 返回当前Looper所管理的MessageQueue   */ public static @NonNull MessageQueue myQueue() {        return myLooper().mQueue; }

注意:

  1. 一个线程有且只有一个Looper
  2. Looper内部维护一个MessageQueue(后面会说到)。
  3. Looper.prepare()要在 Looper.loop()之前执行。

三: MessageQueueMessage(比较简单,可直接跳过)

还是先看看Google文档:
Message

Defines a message containing a description and arbitrary data object that can be sent to a Handler. This object contains two extra int fields and an extra object field that allow you to not do allocations in many cases. (一个Message对象包括任意的数据,并且可以发送给Handler,并且有what,arg1,arg2,obj 成员变量可以使用)
While the constructor of Message is public, the best way to get one of these is to call Message.obtain() or one of the Handler.obtainMessage() methods, which will pull them from a pool of recycled objects.(不推荐使用构造函数创建对象,而是使用Message.obtain()与Handler.obtainMessage()从池中获得)

需要注意以下几点:

  • Message有个成员变量target,为一个Handler对象。
  • Message内部维护一个Pool,使用Handler.obtainMessage()Message.obtain()可以提升性能,2种方法其实是一样的(下面代码解释)。
//Message.obtain()方法 public static Message obtain() {        synchronized (sPoolSync) {            if (sPool != null) {                Message m = sPool; //sPool就是所说的那个池啦                sPool = m.next;                m.next = null;                m.flags = 0; // clear in-use flag                sPoolSize--;                return m;            }        }        return new Message();    }
//Handler.obtainMessage()public final Message obtainMessage(){        //还是在调用Message.obtain(),最终还是在调用上面那个方法,并且把Message的target对象设置为当前Handler对象,即调用obtainMessage()的Handler    return Message.obtain(this);}

MessageQueue:

Low-level class holding the list of messages to be dispatched by a Looper. Messages are not added directly to a MessageQueue, but rather through Handler objects associated with the Looper.(MessageQueue是一个存储Message的容器类,并且被Looper所管理(dispatched ),但是一般不直接操作MessageQueue,而是通过Handler与Looper)

MessageQueue就是一个消息队列,里面存放Message,源码我也没怎么看懂,指导是一种数据结构就好了。

四: Handler(重点咯)

更具本文第一开始给的例子,看一下Handler怎么工作的:

  • 发送消息:Word Thread里面调用msg.sendToTarget()
  • 接受消息:在handleMessage()方法更新了UI

下面一步步跟踪源码来理解:

Step1: 不管是使用msg.sendToTarget(),mHandler.sendMessage都会调用下面这个方法 sendMessageAtTime()

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {        MessageQueue queue = mQueue; //当前线程的Looper所维护的消息队列        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);    }

Step2:将Message加入到了MessageQueue里面,就开始由Looper来继续管理了。 Looper.loop()里面有个很重要的一行代码:

msg.target.dispatchMessage(msg);

对,警察叔叔,快抓住它,上文说到msg.target是一个Handler对象。在Handler.dispatchMessage里面有个Hook,最终在handlerMessage进行真正的消息处理。

public void dispatchMessage(Message msg) {        if (msg.callback != null) {            //如果msg设置了Callback            handleCallback(msg);        } else {            if (mCallback != null) {                //如果当前handler设置了Callback                if (mCallback.handleMessage(msg)) {                    return;                }            }            handleMessage(msg);        }    }private static void handleCallback(Message message) {        message.callback.run();    } public void handleMessage(Message msg) {    }

Handler的一些补充:

Handler.post(Runnable r)Handler.sendMessage()实则是一样的,只是把Runnable对象封装为Message了。

private static Message getPostMessage(Runnable r) {        Message m = Message.obtain();        m.callback = r;        return m;    }

Handler可以在任意一个线程里面创建,并且把Message添加到自己关联的Looper里面。

用一张图表示一哈:
这里写图片描述

Handler的内存泄漏问题,参见一个大神巨作 和我的另一篇文章:
内存泄漏概念:
Java通过GC自动检查内存中的对象,如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。本该被回收的对象没被回收就是内存泄漏。

Hanlder怎么泄漏了?
当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用。而Handler通常会伴随着一个耗时的后台线程一起出现,这个后台线程在任务执行完毕之后,通过消息机制通知Handler,然后Handler把消息发送到UI线程。然而,如果用户在耗时线程执行过程中关闭了Activity(正常情况下Activity不再被使用,它就有可能在GC检查时被回收掉),由于这时线程尚未执行完,而该线程持有Handler的引用,这个Handler又持有Activity的引用,就导致该Activity暂时无法被回收(即内存泄露)。

解决方案:
方案一:通过程序逻辑来进行保护

在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。

如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了(如上面的例子部分的onStop中代码)。

方案二:将Handler声明为静态类

静态类不持有外部类的对象,所以你的Activity可以随意被回收。代码如下:

static class TestHandler extends Handler {    @Override    public void handleMessage(Message msg) {        mImageView.setImageBitmap(mBitmap);    }}

这时你会发现,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference),如下:

static class TestHandler extends Handler {    WeakReference<Activity > mActivityReference;    TestHandler(Activity activity) {        mActivityReference= new WeakReference<Activity>(activity);    }    @Override    public void handleMessage(Message msg) {        final Activity activity = mActivityReference.get();        if (activity != null) {            mImageView.setImageBitmap(mBitmap);        }    }}

最后,本文开始的代码就好理解了,在主线程里面创建一个Handler,获得主线程的Looper,然后再这个Looper线程下面(其实就是主线程)下写handlerMessage()。之后将这个Handler对象的引用传给其他线程,在其他线程里面调用post方法。把post的Message加入Looper维护的MessageQueue里面,这样在Looper.loop()过程中就会调用主线程下的handlerMessage()咯。

邮箱:1906362072@qq.com

0 0