• Android的消息处理机制——Looper,Handler和Message浅析


    题外话:

        说来有些惭愧,对于这三者的初步认识居然是在背面试题的时候。那个时候自己接触Android的时间还不长,学习的书籍也就是比较适合入门的《疯狂Android讲义》,当然在学到Handler这一部分的时候,书中也是有提到一些简单示例,后来在工作中需要用到这个MessageQueue的时候才开始真正琢磨了一下这三者的联系。如果想要对这三者好好理解一番,个人还是比较推荐《深入理解Android卷Ⅰ》。以下对这三者之间的恩怨纠葛的介绍和分析也是参考这本书的相关章节,算是一篇读书笔记吧。


    概述:

        Android的消息传递机制是另一种形式的“事件处理”,这种机制主要是为了解决Android应用中的多线程问题——Android平台只允许UI线程修改Activity中的UI组件,这就使得新启动的线程无法去动态修改界面组件中的属性值。但是我们的程序界面不可能是一个静态的呈现,所以这就必须用到本博客中提到的三个大类了。


    简单示例:

    代码展示:

    public class LooperThreadActivity extends Activity {
    
        private final int MSG_HELLO = 0;
        private Handler mHandler;
        private CustomThread mThread = null;
        private static final String TAG = LooperThreadActivity.class.getSimpleName();
        
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            
            mThread = new CustomThread();
            mThread.start();
            
            Button sendButton = (Button) findViewById(R.id.send_button);
            final EditText contentEditText = (EditText) findViewById(R.id.content_edittext);
    
            sendButton.setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    String msgText = contentEditText.getText().toString();
                    sendMessage(msgText);
                }
            });
        }
        
        private void sendMessage(String content) {
            Toast.makeText(this, "send msg: " + content, 0).show();
            
            // TODO 1.向MessageQueue中添加消息
            
            // 通过Message.obtain()来从消息池中获得空消息对象,以节省资源
            Log.i(TAG, "------------> send msg 1.");
            Message msg = mHandler.obtainMessage(MSG_HELLO, content);
            msg.sendToTarget();
            
            Log.i(TAG, "------------> send msg 2.");
            Message msg2 = mHandler.obtainMessage(MSG_HELLO, content + "2");
            msg2.sendToTarget();
        }
    
        class CustomThread extends Thread {
            @Override
            public void run() {
                Looper.prepare();
                Log.i(TAG, "------------> loop.pre.");
                
                mHandler = new Handler() {
                    public void handleMessage(Message msg) {
                        switch (msg.what) {
                        case MSG_HELLO:
                            Log.i(TAG, "------------> receive msg.");
                            Toast.makeText(LooperThreadActivity.this, "receive msg: " + (String) msg.obj, 0).show();
                        }
                    }
                };
                Looper.loop();
            }
        }
    }

    运行效果展示:

     


    Log结果展示:



    示例结果分析:

        大家可以看到我做了连续两次的添加消息数据,在结果中也有很好的体现,不过Looper.prepare();和Handler之间的内容却只执行了一次。这是因为我们自定义的线程CustomThread只被start了一次,且start过后一直存在,没有被销毁,所以Looper一直存在,MessageQueue一直存在,从而保证了一个Thread只能有一个Looper对象。对于这一点下面会用源码进行进一步的说明。


    机制浅析:

        就应用程序而言,Android系统中Java的应用程序和其他系统上相同,都是靠消息驱动来工作的。Android系统中的消息驱动离不开Looper、Handler和Message这三者,虽说不上哪个更重要一些,不过相对突出的的确是Looper。下面就对这些类逐一地介绍。

    Looper类分析:

    1.Looper.prepare();

    跟踪prepare()进入Android的源码,我们可以发现以下源代码:

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

    sThreadLocal定义:

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    从以上源码中我们可以看到,在调用prepare的线程中,设置了一个Looper对象,这个Looper对象就保存在这个调用线程的TLV中。而Looper对象内部封装了一个消息队列。也就是说prepare通过ThreadLocal机制,把Looper和调用线程关联在了一起。

    2.Looper.loop();

    跟踪loop()进入Android的源码(此处删除了一些暂时不太关联的代码):

    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;
    
        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;
            }
    
            msg.target.dispatchMessage(msg);
    
            msg.recycle();
        }
    }
    通过上面的分析,Looper有以下几个作用:

    • 封装了一个消息队列.
    • prepare函数把当前的Looper和调用prepare的线程(即最终的处理线程)绑定在了一起.
    • 处理线程调用loop,处理来自该消息队列中的消息.
    当事件源向这个Looper发送消息的时候,其实就是把消息加到这个Looper的消息列队里了。那么,该消息就将由和Looper绑定的处理来处理。


    Handler类分析:

    学习Handler之初先来认识一下Handler中所包含的部分成员:

    final MessageQueue mQueue;
    final Looper mLooper;
    final Callback mCallback;
    在Handler类中,它的构造函数会把Handler中的消息队列变量最终都会指向Looper的消息队列。由于是被指向,那么Handler中的消息队列其实就是某个Looper的消息队列。

    Looper和Handler的同步关系说明及HandlerThread的介绍:

        Looper和Handler之间其实是存在着同步关系的。这里对它们之间的同步关系不做过多介绍,如果想了解可以参看《深入理解Android卷Ⅰ》第128页。笔者在此只提出一个提醒点:由于HandlerThread完美地解决了Looper和Handler同步过程中可能出现的空指针异常问题,所以在以后的开发过程中,我们还是多用HandlerThread吧。当然如果不想使用它,那就请使用锁机制来健壮你的代码吧,不过这就可能会落下重复造轮子的口舌了。


    参考资料:

    1.《疯狂Android讲义》

    2.《深入理解Android卷Ⅰ

    3.网络资源:http://www.cnblogs.com/codingmyworld/archive/2011/09/14/2174255.html

    4.网络资源:http://www.cnblogs.com/bastard/archive/2012/06/08/2541944.html

    5.网络资源:http://blog.csdn.net/mylzc/article/details/6771331


    尾声:

    虽然已经“稀里糊涂”到了结尾,不过对Looper、Handler和Message的认识的确进了一大步。希望看完本文的你也有所收获。

  • 相关阅读:
    上传项目到github上
    app widget设置bitmap时,无作用
    Android Studio 启动app 白屏
    android sqlite 数据库中使用的类型
    android 解决华为系列手机调试时不能打印Logcat日志信息
    android 自定义滚动条图标
    检测邮箱
    js检测是否存在中文
    表单的checkbox选中和取消
    javascript
  • 原文地址:https://www.cnblogs.com/fengju/p/6336119.html
Copyright © 2020-2023  润新知