第10章 Android的消息机制

1. 前因

Handler的主要作用是讲一个任务切换到某个指定的线程中工作,主要用于更新UI,因为Android规定访问UI只能在主线程中进行(Android的UI控件不是线程安全的,上锁机制会让UI的访问逻辑复杂,且锁机制会降低UI访问效率,所以最简单高效的方法就是用单线程模型来处理UI操作)。

2. 工作原理

Handler创建时会采用当前线程的Looper来构建内部的消息循环系统。

通过Handler的post方法将一个Runnable或通过send方法发一个消息到Looper中,post方法最终也是调到send,send方法调用MessageQueue的enqueueMessage方法将消息放入消息队列,然后Looper发现会进行处理,最终消息中的Runnable或者Handler的handleMessage会被调用,由于Looper是运行在创建Handler所在的线程中的,这样Handler中的业务逻辑就被切换到创建Handler的线程中去执行了。

3. ThreadLocal

ThreadLocal 是一个线程内部的数据存储类,通过它可以再指定的线程中存储数据,Looper、ActivityThread、AMS中都有用到。有 set 和 get 方法

使用场景分析:

1)对于Handler来说,它需要获取当前线程的looper,显然Looper的作用域就是线程并且不同的线程有不同的Looper,这时就可以使用ThreadLocal实现Looper在线程中的存取。

2)复杂逻辑下的对象传递,比如监听器的传递,采用ThreadLocal可以让监听器作为线程内的全局对象而存在。

3. 消息队列MessageQueue

是一个单链表

1)插入 enqueueMessage,单链表的插入

2)读取并删除 next,是一个无限循环的方法,如果消息队列中没有消息,next方法会一直阻塞在这里,当新消息到来时,next方法会返回这条消息并将其从单链表中移除

3)这里创建Message对象的时候,有三种方式:

Message msg1 = new Message();	// 就是直接初始化一个Message对象
Message msg2 = Message.obtain();
Message msg3 = handler.obtainMessage();

2、3种方式一样,从整个Messge池中返回一个新的Message实例,Messge池为空才创建新的对象,能避免重复Message创建对象,源码:

/**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
4. Looper

不停的从MessageQueue中查看是否有新消息,如果有立刻处理,否则阻塞在那里。

1)构造方法:创建一个MessageQueue;将当前线程的对象保存起来

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

2)创建Looper:Looper.prepare()为当前线程创建一个Looper,Looper.loop() 开启消息循环

new Thread("123") {
        @Override
        public void run() {
            super.run();
            Looper.prepare();
            Handler handler = new Handler();
            Looper.loop();
        };
    }.start();

3)Looper.getMainLooper() 获取主线程Looper

4)退出:quit 和 quitSafely

quit直接退出,quitSafely设定一个退出标记,等消息队列中已有的消息处理完毕后退出。

Looper退出后,Handler发送消息会失败,send返回false。

子线程如果手动创建Looper在所有事情完成后记得用quit终止循环,退出Looper后,这个线程也立即终止。

5)loop方法:死循环,唯一跳出循环的方式是MessageQueue的next方法返回null

Looper的quit方法调用,会调用MessageQueue的quit或quitSafely方法通知消息队列退出,此时next返回null

6)loop 方法调用MessageQueue的next方法获取新消息,而next是一个阻塞操作,当没有消息时,next会一直阻塞在那里,导致loop也一直阻塞在那里;如果MessageQueue的next返回了新消息,Looper就会处理这条消息:

msg.target.dispatchMessage(msg),这里的msg.target 是发送这条消息的Handler对象,这样Handler发送的消息最终又交给它的dispatchMessage来处理了,注意,Handler的dispatchMessage方法是在创建 Handler时所使用的Looper中执行的,这样就成功将代码逻辑切换到指定的线程中去执行了。

5. Handler

发送消息和接收

1)发送通过post或者send,post最终也是通过send,发送消息的过程就是向消息队列中插入一条消息,MessageQueue的next方法会将这条消息给Looper

2)Looper收到消息后开始处理,最终交由Handler

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

首先检查 Message的callback是否为null,不为bukk就通过handleCallback 来处理消息,Message的callback 是一个 Runnable 对象,实际上就是Handler 的 post方法所传递的Runnable参数

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

其次检查mCallback 是否为null,不为null就调用 mCallback 的 handleMessage 方法。

Callback 是一个接口,可以用来创建一个Handler的实例但不需要派生Handler的子类

Handler handler = new Handler(callback);

最后调用Handler 的 handleMessage 方法来处理消息。

3)Handler 还可以通过一个Looper来构造

public Handler(Looper looper) {
        this(looper, null, false);
}
6. 主线程的消息循环

Activity 的 主线程就是 ActivityThread,入口为main,main方法中系统通过 Looper.prepareMainLooper() 来创建主线程的 Looper 以及 MessageQueue,并通过 Looper.loop() 来开启主线程的消息循环

ActivityThread.H 是其Handler,定义了一组消息类型,包含了四大组件的启动和停止等过程

ActivityThread 通过 ApplicationThread 和 AMS 进行进程间通信,AMS 以进程间通信的方式完成 ActivityThread 的请求后会回调 ApplicationThread 中的Binder方法,然后 ApplicationThread 会向 H发送消息,H收到消息后会将 ApplicationThread 中的逻辑切换到 ActivityThread中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型

7. 使用示例

1)在主线程创建

    // 方法1 使用内部类方式创建Handler
    class mHandler extends Handler {

        // 复写handlerMessage() 
        @Override
        public void handleMessage(Message msg) {
         ...// 需执行的UI操作
            
        }
    }   
    
    // 方法2 使用匿名内部类创建Handler
    Handler handler = new Handler(){
    
    		// 复写handlerMessage() 
        @Override
        public void handleMessage(Message msg) {
            //msg就是子线程发送过来的消息。
        }
    };

    //开启一个子线程 发消息方式1
    new Thread(new Runnable() {
            @Override
            public void run() {
                //在子线程发送一个消息。
                Message msg = new Message();
                handler.sendMessage(msg);
            }
        }).start();
   
   //开启一个子线程 发消息方式2
    new Thread(new Runnable() {
            @Override
            public void run() {
                //在子线程post一个Runnable对象
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        //这里是消息处理的方法
                        //这里运行在主线程。
                    }
                });
            }
        }).start();

注意:Handler的内存泄漏

as不建议我们用上述方式生成Handler,这是应为非静态内部类会持有外部内的引用。那么Handler将会持有Activity的引用,我们知道handler是会被msg.target持有的,而msg又在MessageQueue队列中,那么当消息队列中拥有未消费的Message时,会导致Activity即使finish了也无法被GC回收,最终导致内存泄漏。为了避免这个问题我们可以将Handler写成外部类或者静态的内部类,并且传递的Activity引用可以用WeakReference弱引用来持有,同时可以在Activity的onDestory中使用Handler.removeCallbacksAndMessages(null);来清空消息队列

2)在子线程创建方式1 使用子线程的Looper 消息处理在子线程

    // 声明Handler
    Handler handler;
    new Thread(new Runnable() {
        @Override
        public void run() {
        // 创建当前线程的Looper
            Looper.prepare();
            // 在子线程创建handler对象
            handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                // 这里是消息处理,它是运行在子线程的
                }
           };
           // 开启Looper的消息轮询
           Looper.loop();
       }
   }).start();

   mBanner.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View v) {
       		 // 在主线程发送一个消息到子线程
           Message msg = new Message();
           handler.sendMessage(msg);
       }
   });

3)在子线程创建方式2 使用主线程的Looper 消息处理在主线程

    new Thread(new Runnable() {
        @Override
        public void run() {
            // 获取主线程的Looper
            Looper looper = Looper.getMainLooper();
            // 用主线程的Looper创建Handler
            handler = new Handler(looper) {
                @Override
                public void handleMessage(Message msg) {
                // 这里是运行在主线程的
                }
            };
        }
    }).start();

4)使用 Callback接口

public class MyClass implements Handler.Callback {
		...
		private Handler handler;
		
		// 主线程的Looper
		handler = new Handler(Looper.getMainLooper(), this);
		
		// 子线程的Looper
		HandlerThread handlerThread = new HandlerThread("subthread");
    handlerThread.start();
    handler = new Handler(handlerThread.getLooper(), this);
    
		@Override
    public boolean handleMessage(Message msg) {
        ...
    }
}
8. 其他

**1)Android已经提供了很多实现了Handler的类和方法,方便我们使用。**如Activity类的runOnUiThread()方法,View的post()方法,HandlerThread类等,关于这些知识,有待补充

  • runOnUiThread
public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}
  • postInvalidate底层是使用了Handler,向主线程发消息更新UI

  • HandlerThread 是Android API提供的一个方便、便捷的类,使用它我们可以快速的创建一个带有Looper的线程。Looper可以用来创建Handler实例

2)Android中为什么主线程不会因为Looper.loop()里的死循环卡死

3)一个线程只能有一个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));
    }

本文参考:

《Android开发艺术探索》第十章

https://juejin.im/post/5910533dac502e006cfe01cd

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值