Android中的消息机制

Android的消息机制其实就是Handler的运行机制,因为Handler是Android消息机制的上层接口,所以在开发过程中只需要和Handler交互就可以。在开发过程中,Handler多用于控制UI线程更新,这其实是Handler的一个具体的作用。由于在Android中UI线程是非线程安全的,由于加锁会阻塞一些线程导致降低UI更新效率,所以Handler就充当了这个切换UI访问的执行线程的工具。
Handler的运行需要底层的Looper(消息循环)和MessageQueue(消息队列)支撑,Looper会无限循环去查找MessageQueue这个单项表有没有新的消息,没有就一直等待。这三个是一个整体,所以Handler需要当前线程的Looper来构造消息系统,这就需要ThreadLocal来获取每个线程中的Looper。线程默认是没有Looper的,所以需要为线程创建Looper,在UI线程中由于ActivityThread创建时候会初始化一个Looper,所以我们就可以在UI线程中使用Handler。Handler的主要任务就是切换到某个指定的线程中去执行,ViewRootImpl的checkThread方法如果发现不是在UI线程中访问UI就会抛出异常。所以我们需要在子线程中做耗时操作通过Handler将UI工作切换到主线程中执行。

Handler工作原理

那么下面我们来看一下Looper和MessageQueue与Handler如何协同的工作

  1. 创建Looper对象
    在自定义的线程中,调用Looper.prepare()方法,在这个prepare()方法中,ThreadLocal< Looper > 对象在当前线程会创建一个Looper,并通过一个Map映射表(Values类)保存当前线程的Looper对象,形成一个线程–Looper的映射表。
    注意一个线程最多可以映射一个looper对象(UI线程已经存在Looper不需手动创建)

  2. 创建Handler对象
    创建handler时,会通过LocalThread的get方法检测Map映射表(Values)中是否包含当前线程的looper对象,如果包含,则将handler内的消息队列指向looper内部的消息队列,即handler.messageQueue = Looper.messageQueue,否则,抛出异常

  3. Handler发送消息
    通过Handler的send或post方式进行消息传输,post方法本身也是在调用send方式(sendMessageAtTime),当Handler的send方法被调用时,会调用MessageQueue的enqueueMessage方法将消息放入消息队列中

  4. Looper接收消息
    Looper在队列中发现新消息就会及时处理,这里的Looper是运行在创建Handler所在的线程中,这样就实现了返回到了创建Handler的线程中来,然后会回调原handler的handlerMessage方法

ThreadLocal的原理

ThreadLocal是一个线程内部数据存贮类,所有线程共享相同的ThreadLocal对象,但是每个线程通过他取value值都是不一样的,不同的值也使得不同线程不相互影响,通过get()方法就可以返回当前线程对应的value值。ThreadLocal在开发过程中应用的比较少,在线程做作用域时就体现出了价值,比如Looper就是这样,每个线程只有一个Looper,Handler要获取当前线程中的Looper,可见Looper的作用域就是线程,那么ThreadLocal就发挥了作用。
为什么不同线程访问同一个ThreadLocal对象,通过get()返回的结果会不一样呢,我们通过内部实现来看一下

set方法

    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

在Thread类中,ThreadLocal.Values类专为存贮线程的ThreadLocal而设计,在Values内部有一个数组Object[] table,ThreadLocal的value值(Looper对象)就存贮在这个table的数组中,这里是通过values方法返回当前线程对应的Values对象,然后通过得到的Values对象的put方法将T(value)对象放入,形成了table[index+1] = value这样的对应形式

get方法

    public T get() {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }

可以看到,他也是取出当前线程的Values对象,如果对象为null,则返回初始值默认情况下为null,不为空时返回当前线程对应的Values对象。
从get和set方法就可以看出,ThreadLocal的读写操作仅限于线程内部,这也就是为什么ThreadLocal可以在多线程中互不干扰的存贮和修改数据。


消息对象


MessageQueue

MessageQueue就是消息队列,它的内部实现其实是一个单列表的数据结构,主要包含两个操作(enqueueMessage)插入和读取(next),读取会伴随着删除操作,他会从消息队列中取出一个消息并且把它从消息队列中删除,同时他是一个无线循环的方法,队列中没有消息时会阻塞。

Looper

他是一个消息循环的角色,他会不停的从MessageQueue中查看是否有新的消息,否则会持续阻塞。Looper有着对应的两个方法,Looper.prepare() 和 Looper.loop() ,分别代表着创建一个Looper和循环开始的作用。
此外Looper还提供了getMainLooper()这个方法,用于在任何地方获得主线程的Looper。同时Looper提供了退出的方法:quit和quitSafely。quit会直接退出Looper,而quitSafely只是设置一个退出标记,会在队列信息处理完后安全的退出Looper,退出后Handler消息发送就会失败。在子线程中如果手动创建了Looper,那么建议在不需要时通过quit方式来终止Looper,否则这个子线程会一直处于等待状态。
Looper的loop()方法是一个死循环,唯一跳出循环的方式就是next为null,上面说的两种退出方式也是将next值返回为空。所以一定要退出Looper否则loop方法会无限循环下去。

Handler

Handler负责消息的发送和接收,发送本质就是post和send的一系列方法,handler 发送Message 给MessageQueue,Looper 来轮询消息,如果有Message,然后再交由Handler处理,Handler 拿到消息就可以在所在的线程执行了。post和send这两种消息都会插在message queue队尾并按先进先出执行。但通过这两种方法发送的消息执行的方式略有不同:
通过sendMessage发送的是一个message对象,会被Handler的handleMessage()函数处理;而通过post方法发送的是一个runnable对象,则会自己执行。
注意handler.post(new Runnable()),这里的Runnable同样是运行在handler所在线程中的,并没有开启一个新线程,如果Handler是主线程的,那么这个post就运行在UI主线程中

   public final boolean post(Runnable r){
       return  sendMessageDelayed(getPostMessage(r), 0);
   }

   public final boolean postAtTime(Runnable r, long uptimeMillis){
        return sendMessageAtTime(getPostMessage(r), uptimeMillis);
   }

   public final boolean postDelayed(Runnable r, long delayMillis)
   {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
   }    

   public final boolean postAtFrontOfQueue(Runnable r){
        return sendMessageAtFrontOfQueue(getPostMessage(r));
   }

   public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        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);
    }


    public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

项目应用

说道Handler无非就是两种情况,UIThread 发送给WorkThread 和WorkThread 发送给UIThread,我们要知道Handler创建到哪个线程就和其绑定在一起,如果需要这个线程处理后续信息,需要用这个线程的Handler来做对应的send操作

UIThread 发送给WorkThread

这种场景就是我们在WorkThread 执行一些耗时的操作(网络请求,文件读写),返回的数据来更新MainThread,我们来看一下

public class MainActivity extends Activity {

    private static class MyHandler extends Handler {
        private final WeakReference<Activity> mAc;

        private MyHandler(Activity activity) {
            mAc = new WeakReference<Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 0x22:
                    Toast.makeText(mAc.get(), "copy", Toast.LENGTH_SHORT).show();
            }
        }
    }

    private MyHandler myHandler = new MyHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                myHandler.sendEmptyMessage(0x22);
            }
        }).start();
    }
}


WorkThread 发送给UIThread

另一种就是WorkThread 发送给MainThread

public class MainActivity extends Activity {

    private Handler mainHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 0x22) {
                Bundle data = msg.getData();
                Toast.makeText(MainActivity.this, data.getString("key") + "", Toast.LENGTH_SHORT).show();
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final MyThread mm = new MyThread();
        mm.start();
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mm.workHandler.sendEmptyMessage(0x11);
            }
        });

    }

    class MyThread extends Thread {

        public Handler workHandler;

        @Override
        public void run() {
            super.run();
            Looper.prepare();
            workHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case 0x11:
                            //模拟一些耗时操作
                            try {
                                Thread.sleep(5000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            Bundle b = new Bundle();
                            b.putString("key", "copy");
                            Message mMsg = Message.obtain();
                            mMsg.setData(b);
                            mMsg.what = 0x22;
                            mainHandler.sendMessage(mMsg);
                            //关闭该线程中的Looper
                            Looper looper = getLooper();
                            looper.quitSafely();
                        default:
                            break;
                    }
                    super.handleMessage(msg);
                }
            };
            Looper.loop();
        }
    }

}

运行上面代码点击按钮后5s后收到copy信息,但是第二次点击后就无法收到信息了,原因就是我们在第一次消息传递后关闭了子线程中的Looper。


处理Warning提示

有时我们在使用Handler更新UI的时候,Android Lint 会给出如下警告

This Handler class should be static or leaks might occur

意思是说:这个Handler 必须是static的,否则就会引发内存泄露。
这是因为在java中非静态匿名内部类将持有一个对外部类的隐式引用,而静态内部类则不会。 我们知道当Activity被finish()掉,Message 可能将存在于消息队列中很长时间才会被执行到。这个Message持有一个对Handler的引用,Handler也会持有一个对于外部类(Activity)的隐式引用,这些引用在Message被执行前将一直保持,这样会使得Activity的上下文不被垃圾回收机制回收,会有泄露应用程序的资源的风险。

为解决这个问题,下面这段代码中的Handler是一个静态匿名内部类。静态匿名内部类不会持有一个对外部类的隐式引用,因此Activity将不会被泄露。如果你需要在Handler中调用外部Activity的方法,就让Handler持有一个对Activity的WeakReference,这样就不会泄露Activity的上下文了

 private static class MyHandler extends Handler {
      private final WeakReference<MyActivity> mActivity;

      public MyHandler(MyActivity activity) {
          mActivity = new WeakReference<MyActivity>(activity);
      }

    @Override
      public void handleMessage(Message msg) {
          MyActivity activity = mActivity.get();
          if (activity != null) {
              // ...
          }
      }
  }

Handler的变体形式

runOnUiThread和view.post其实与handler.post方式类似,都是一种类似方法的委托,用户传递方法,使用post,postDelayed 添加委托,使用 removeCallbacks移除委托。这个特性我们可以简单看出handler类似一个容器对象,它携带了消息的集合和委托的集合,不光是传递数据并且封装了一些操作行为。java里没有委托delegate的概念,但是可以通过class来持有一个可执行的方法代理,所以具体总结如下

Runnable是一个接口,不是一个线程,这些方法的变形体其实就是将Runnable对象转换为一个Message,然后将其添加到handler所在的线程队列中,然后在适当的时间来委托执行这个Runnable,这样就可以看出这一系列方法都是运行在handler所在线程中的(runOnUiThread默认位于主线程),并没有开启一个新线程,如果这些方法在handler所在线程中会立即执行Runnable,如果在其他线程中会通过传递Message到handler所在队列排队的形式等待该线程执行Runnable,如果位于UI线程中千万别做复杂的计算逻辑,否则会导致ANR发生

runOnUiThread(Runnable action)

利用Activity.runOnUiThread(Runnable)把更新ui的代码创建在Runnable中,然后在需要更新ui时,把这个Runnable对象传给Activity.runOnUiThread(Runnable)。 这样Runnable对像就能在ui程序中被调用。如果当前线程是UI线程,那么行动是立即执行。如果当前线程不是UI线程,操作是发布到事件队列的UI线程

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(MainActivity.this, "mainThread", Toast.LENGTH_SHORT).show();
            }
        });

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(MainActivity.this, "otherThread", Toast.LENGTH_SHORT).show();
                    }
                });
            }
        }).start();
    }
}


view.post(Runnable action)

我们看一下这里的代码是怎么写的

   public boolean post(Runnable action) {
         final AttachInfo attachInfo = mAttachInfo;
         if (attachInfo != null) {
             return attachInfo.mHandler.post(action);
         }
         // Assume that post will succeed later
         ViewRootImpl.getRunQueue().post(action);
         return true;
    }

可以看到又回到了handler.post方法中,所以这两个变体的方式本身就是handler.post方法的一种延伸,所以我们应该更深刻的理解一下类似于委托代理方法的本质

以上就是对Handler机制的一点总结

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值