Android知识点<5>消息机制

Android的消息机制,主要指的是Handler机制。

1. 背景介绍:

一个程序的运行,就是一个进程的在执行,一个进程里面可以拥有很多个线程。
主线程:也叫UI线程,或称ActivityThread,用于运行四大组件和处理他们用户的交互。 ActivityThread管理应用进程的主线程的执行(相当于普通Java程序的main入口函数),在Android系统中,在默认情况下,一个应用程序内的各个组件(如Activity、BroadcastReceiver、Service)都会在同一个进程(Process)里执行,且由此进程的主线程负责执行。
ActivityThread既要处理Activity组件的UI事件,又要处理Service后台服务工作,通常会忙不过来。为了解决此问题,主线程可以创建多个子线程来处理后台服务工作,而本身专心处理UI画面的事件。
用于执行耗时操作,比如 I/O操作和网络请求等。(安卓3.0以后要求耗访问网络必须在子线程种执行)更新UI的工作必须交给主线程,子线程在安卓里是不允许更新UI的。
ANR发生条件:
  • 在activity中超过5秒的时间未能响应下一个事件
  • BroadcastReceive超过10未响应
避免ANR
  • 主线程不能执行耗时操作(避免ANR)
  • 子线程不能直接更新UI界面

2. 基本概念

2. 1. Message
    消息,理解为线程间通讯的数据单元。例如后台线程在处理数据完毕后需要更新UI,则可发送一条包含更新信息的Message给UI线程。
2.2. Message Queue
    消息队列,用来存放通过Handler发布的消息,按照先进先出执行。
2.3. Handler
    Handler是Message的主要处理者,负责将Message添加到消息队列以及对消息队列中的Message进行处理。
2.4. Looper
    循环器,扮演Message Queue和Handler之间桥梁的角色,循环取出Message Queue里面的Message,并交付给相应的Handler进行处理。
2.5. 线程
    UI thread 通常就是main thread,而Android启动程序时会替它建立一个Message Queue。
    每一个线程里可含有一个Looper对象以及一个MessageQueue数据结构。在你的应用程序里,可以定义Handler的子类别来接收Looper所送出的消息。

3. Handler的简单使用
既然子线程不能更改界面,那么我们现在就借助Handler让我们更改一下界面:主要步骤是这样子的:
3.1、new出来一个Handler对象,复写handleMessage方法
3.2、在需要执行更新UI的地方 sendEmptyMessage 或者 sendMessage
3.3、在handleMessage里面的switch里面case不同的常量执行相关操作


4. Handler的工作机制简单来说是这样的

4.1、Handler发送消息仅仅是调用MessageQueue的enqueueMessage向插入一条信息到MessageQueue
4.2、Looper不断轮询调用MeaasgaQueue的next方法
4.3、如果发现message就调用handler的dispatchMessage,ldispatchMessage被成功调用,接着调用handlerMessage()


5. MessageQueue 工作原理
MessageQueue 中文翻译就是 消息队列 ,它内部存储了一组信息,存放的是Message,以队列的形式对外提供了 插入 删除 的工作(虽然名字叫做队列,但是其内部的 存储结构是单链表)
主要 插入 和 读取 两个操作,这两个操作对应着两个方法:插入(入队) enqueueMessage(Message msg, long when)读取(出队) next()

next方法
next方法在这里是一个无限循环的方法,如果消息队列里面没有消息,那么他就会处于阻塞状态,当有新的消息到来的时,next就会返回这条消息并且将其从单链表中移除。


6. Looper的工作原理
Looper中文翻译是轮询器或者消息泵或者循环。个人还是叫做轮询器比较形象一些。
6.1、Looper的作用
Looper是一个轮询器,它的作用不断轮询MessageQueue,当如果有新的消息就交给Handler处理,如果轮询不到新的消息,那就自身就处于阻塞状态。
6.2、Looper的构造函数创建了MessageQueue
我们通过查看Loop而这个类,可以发现的他的构造方法里面创建了一个MessageQueue,然后将当前线程的对象保存起来
6.3 new Handler的hanlder不能没有Looper
new出来一个Handler但是没有创建Looper的话就会报错。
解决办法就是new Handler的时候加上Looper.prepare();
如下代码中,如果handler2加上Looper.prepare();没有就会报错
6.4、主线程(ActivityThread),被创建的时候就会创建一个Looper
线程默认是没有Looper的,但是为什么在主线程没有创建的Looper就可以使用Handler?主线程是特别的。主线程,也就是ActivityThread,主线程被创建的时候就会创建一个Looper,这点是比较特殊的,也正因为这点,所以我们在主线程创建了Handler就直接能用了
6.5、Looper的ThreadLocal
Looper有一个特殊的概念,那就是ThreadLocal,(他并不是线程),他的作用是帮助Handler获得当前线程的Looper(多个线程可能有多个Looper)
Looper 的几个方法
  • 创建:
  • Looper.prepare() : 为当前线程创建一个Looper
  • prepareMainLooper() : UI线程(ActivityThread)创建Looper的
  • 开启:
  • Looper.loop() : 开启消息轮询
  • 退出
  • quit() : 直接退出Looper
  • quitSafely() : 设定一个标记,只有当目前已有消息处理完毕之后才会执行退出操作。
注意:当Looper退出后,Handler就无法发送消息,send出去的消息会返回false;当我们在子线程中创建了Looper并且所有的消息都处理完毕的时候,要记得调用 quit 方法,不让这个Looper就一直处于阻塞状态一直那么等待下去

7 Handler 的创建 :
主线程创建新的对象 :
private Handler mHandler = new Handler()
{
    @Override
    public void handleMessage(Message msg)
    {
        switch (msg.what){
         //根据msg.what的值来处理不同的UI操作
           case WHAT:
                     break;
           default:
                     super.handleMessage(msg);
                     break; }
}};

我们还可以不去创建一个Handler的子类对象,直接去实现Handler里的CallBack接口,Handler通过回调CallBack接口里的handleMessage方法从而实现对UI的操作。
private Handler mHandler = new Handler(new Handler.Callback()
{
    @Override
    public boolean handleMessage(Message msg)
    { return false; }});

子线程创建 :
上面的Handler对象必须是在主线程创建的。如果我们想在子线程中去new一个Handler对象的话,就需要为Handler指定Looper。

Handler 发送消息 :
send :
//发送方式一 直接发送一个空的Message
mHandler.sendEmptyMessage(WHAT);
//发送方式二 通过sendToTarget发送
mHandler.obtainMessage(WHAT,arg1,arg2,obj).sendToTarget();
//发送方式三 创建一个Message 通过sendMessage发送
Message message = mHandler.obtainMessage();
message.what = WHAT;
mHandler.sendMessage(message);

如果我们的Handler只需要处理一条消息的时候,我们可以通过post一系列方法进行处理。
private Handler mHandler = new Handler();
new Thread(new Runnable() {
    @Override
        public void run() {
            mHandler.post(new Runnable() {
                @Override
                 public void run() { //UI操作 ... }
        });
}}).start();

Handler 源码分析 :
post部分
我们发现,5个关于post的方法里面,调来调去就是3个方法
sendMessageDelayed
sendMessageAtTime
sendMessageAtFrontOfQueue (postAtFrontOfQueue方法一家独有)
send部分
我们发现,send相关的方法也有5个,这5个方法调用的就是这么几个方法
sendMessageDelayed
sendMessageAtTime
sendEmptyMessageDelayed (sendEmptyMessage方法一家独占)
殊途同归,最后10 个方法都进入了enqueueMessage方法

sendMessageDelayed (post和send都有调用)sendMessageAtTime (post和send都有调用)sendMessageAtFrontOfQueue (postAtFrontOfQueue方法一家独有)sendEmptyMessageDelayed (sendEmptyMessage方法一家独占)
我们发现,sendMessageAtTime和sendMessageAtFrontOfQueue这两个方法最终都是调用Handler里面enqueueMessage方法

5个send的方法和5和post的方法,post和send加起来的9个方法都利用postAtTime进入了enqueueMessage方法,
剩下1个的独特的postAtFrontOfQueue方法利用sendMessageAtFrontOfQueue也进入了enqueueMessage方法
Handler的enqueueMessage方法调用了MessageQueue里面的enqueueMessage,enqueueMessage就是让Hadler通过post或者send发送过来的Message进入到MessageQueue的队列。


工作原理 :

1、首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。
2、Looper.loop()会让当前线程进入一个无限循环,不端从MessageQueue的实例中读取消息,
      然后回调msg.target.dispatchMessage(msg)方法。
3、Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue想关联。
4、Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。
5、在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。

在Activity中,我们并没有显示的调用Looper.prepare()和Looper.loop()方法,为啥Handler可以成功创建呢,这是因为在Activity的启动代码中,已经在当前UI线程调用了Looper.prepare()和Looper.loop()方法。

扩展 :

Handler导致内存泄漏
产生原因:
Handler对象会隐式地持有一个外部类对象的引用(这里的外部类是Activity)。一般在一个耗时任务中会开启一个子线程,如网络请求或文件读写操作,我们会使用到Handler对象。但是,如果在任务未执行完时,Activity被关闭了,Activity已不再使用,此时由GC来回收掉Activity对象。由于子线程未执行完毕,子线程持有Handler的引用,而Handler又持有Activity的引用,这样直接导致Activity对象无法被GC回收,出现内存泄漏。
解决方案:
解决方法主要在于两点:
1.将Handler声明为静态内部类。静态内部类不会持有外部类的引用,所以不会导致外部类实例出现内存泄露。
2.在Handler中添加对外部Activity的弱引用。由于Handler被声明为静态内部类,不再持有外部类对象的引用,导致无法在handleMessage()中操作Activity中的对象,所以需要在Handler中增加一个对Activity的弱引用。


ThreadLocal :

之所以说它,是因为Looper的存储是使用了ThreadLocal的。

ThreadLocal类用来提供线程内部的局部变量。这些变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量,ThreadLocal实例通常来说都是private static类型。 
总结:ThreadLocal不是为了解决多线程访问共享变量,而是为每个线程创建一个单独的变量副本,提供了保持对象的方法和避免参数传递的复杂性。

ThreadLocal的主要应用场景为按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。例如:同一个网站登录用户,每个用户服务器会为其开一个线程,每个线程中创建一个ThreadLocal,里面存用户基本信息等,在很多页面跳转时,会显示用户信息或者得到用户的一些信息等频繁操作,这样多线程之间并没有联系而且当前线程也可以及时获取想要的数据。

ThreadLocal可以看做是一个容器,容器里面存放着属于当前线程的变量。ThreadLocal类提供了四个对外开放的接口方法,这也是用户操作ThreadLocal类的基本方法: 
(1) void set(Object value)设置当前线程的线程局部变量的值。 
(2) public Object get()该方法返回当前线程所对应的线程局部变量。 
(3) public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。 

(4) protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次,ThreadLocal中的缺省实现直接返回一个null。

ThreadLocal内部是如何为每一个线程维护变量副本的呢?

其实在ThreadLocal类中有一个静态内部类ThreadLocalMap(其类似于Map),用键值对的形式存储每一个线程的变量副本,ThreadLocalMap中元素的key为当前ThreadLocal对象,而value对应线程的变量副本,每个线程可能存在多个ThreadLocal。

为不同线程创建不同的ThreadLocalMap,用线程本身为区分点,每个线程之间其实没有任何的联系,说是说存放了变量的副本,其实可以理解为为每个线程单独new了一个对象。

ThreadLocal导致的内存泄漏

  在上面提到过,每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal. 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收. 


参考 : 

http://blog.csdn.net/lhqj1992/article/details/52451136



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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值