深入了解Android多线程(三)Handler与多线程

前言

【深入了解Android多线程】当前分为三个部分,这三个部分一起阅读,能更好的帮助你理解,Android在多线程方面设计与优化。

  • 深入了解Android多线程(一)Java线程基础
  • 深入了解Android多线程(二)线程的性能优化
  • 深入了解Android多线程(三)Handler与多线程

Handler并不陌生,在android开发中经常使用它来进行UI线程和子线程间的通信,当然如果你不了解也没有关系,文章中会介绍它的简单用法,你只需要知道它在Android开发中与多线程之间密不可分,既然它如此多线程之间联系的如此密切,那么我们就有必要了解它的运行原理了。

Handler的简单使用

先来看一下handler在Android开发中经典使用场景

        //ui对象
        TextView textView=findViewById(R.id.textView);
        //创建handler
        Handler handler=new Handler();
        //创建一个线程池
        ExecutorService service = Executors.newCachedThreadPool();
        //创建一个后台任务
        service.execute(new Runnable() {
            @Override
            public void run() {
                //模拟耗时操作
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("修改一下");
                    }
                });
            }
        });

上述代码中,开启了一个线程并模拟一段时间的后台任务,在后台任务执行完毕后,使用handler发送(post)了一个任务并将该任务切换到UI线程执行。
需要注意的是,通过handler post或者send的任务并不一定是在UI线程中执行的,这个任务总会在创建handler的线程中执行,上述示例中的handler正好是UI线程中创建,所以post的任务才会在UI线程中执行。

再来看一个handler不在UI线程创建的例子

        //在主线程中开启handler
        Handler handler = new Handler();
        new Thread(new Runnable() {
            @Override
            public void run() {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.e(TAG, "主线程:" + Thread.currentThread().getName());
                    }
                });
            }
        }).start();

        //创建一个线程池
        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Handler handler = new Handler();
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.e(TAG, "子线程:" + Thread.currentThread().getName());
                    }
                });
                Looper.loop();
            }
        });

上面的代码分别实现了在主线程中创建handler,在子线程中通过handler发送一个打印当前线程名字的任务,和在子线程创建一个handler,在子线程中通过handler发送一个打印当前线程名字的任务。运行结果如下


运行结果

运行结果,证实了上面的结论,通过handler post或者send的任务会是在创建handler的线程中执行的。

等等!代码里面的Looper.prepare是什么?
Looper是Handler运行机制的一部分,它负责将handler发送到MessageQuene中任务取出来,在UI线程中使用handler我们不需要创建Looper,是因为UI线程中已经创建好了一个Looper,但是如果在其他线程中创建Handler,我们就需要通过Looper.prepare创建一个Looper。
实际上Looper、Handler以及MessageQuene三者的关系密不可分,它们共同组成了Android的消息机制,下面就将详细讲解。

Android的消息模型

在Android中,消息的处理大致有以下几个过程

  • 在线程A(一般是UI线程)中创建handler,开启线程B用于执行耗时操作。
  • 当线程B需要与线程A进行通信时,在线程B中创建消息(Message或Runnable)。
  • 使用已经创建的handler向消息队列(MessageQueue)中插入(post/send)消息。
  • Looper开始循环,并从消息队列中取出消息,并将消息传给handler,这样这个消息就从线程B中来到了创建handler的线程A中。
  • 根据开发者的要求处理消息。

下面就分别讲解各个部分的工作原理

MessageQueue的工作原理

MessageQueue在Android称之为消息队列,虽然是队列,但实际上它的内部是通过单链表实现的,单链表在数据的插入和删除上比较好的性能优势。
MessageQueue中通过enqueueMessage向链表中插入数据,通过next方法取出数据。
需要注意的是next()是一个阻塞方法,当链表中没有消息时,next会一直阻塞在那里。

Looper的工作原理

Looper在Android的消息机制中主要用于循环消息队列,当它发现消息队列中有新的消息时就会立即处理,否则会一直阻塞在那里。
在上面的例子里我们已经说过了,使用handler必须要先创建一个Looper,创建Looper有两个方法

  • Looper.prepare();
    这个方法用于在子线程中开启一个Looper
  • Looper.prepareMainLooper();
    该方法用于在UI线程中开启一个Looper,因为主线程的Looper比较特殊,所以Looper单独提供了初始化方法。

如果你需要获取主线程的Looper,可以通过Looper.getMainLooper()在任何地方、任何线程中获得主线程的Looper。
创建完Looper后,你还需要开启循环,Looper才可以正常工作。
开启循环的方法主要是

  • Looper.loop()。

loop()的内部是一个死循环,它会不停的从消息队列取出消息,如果消息的队列的next()返回null,则会跳出循环,如下所示

  for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
  ……
  }

因为next()是一个阻塞方法,当消息队列中没有消息时,它并不会返回null,而是一直阻塞在那里。那消息队列什么时候会返回null呢?这个我们暂时不说,接着往下看。
如果msg不为空,Looper就会开始处理这条消息: msg.target.dispatchMessage(msg);msg.target就是发送这条消息的handler对象。这样Handler发送的消息就又交给它的dispatchMessage方法处理了。

Looper也提供了退出循环的方法。

  • quit()
  • quitSafely()

这两个方法区别在于quit会直接退出Looper,而quitSafely只是设定一个退出标记,它会把消息队列中已有的消息全部处理完,才会退出Looper。
Looper退出后,通过handler发送的消息都会失败,handler的send方法会返回false。
子线程中创建了Looper,在所有的事件处理完毕后需要退出Looper,否则该子线程会一直处于阻塞状态,继续占据系统资源,而Looper退出后,该线程就会终止。

Handler的工作原理

Handler的主要工作的就是消息的发送和接收。消息的发送主要使用send的一些方法(post内部也是send的实现)。
handler发送消息的过程仅仅是向消息队列中插入一条,然后消息队列的next就会返回这条消息给Looper,而Looper最终会把消息交给Handler处理,此时消息就转入到了创建handler所在线程中。
handler通过dispatchMessage(Message msg)来处理Message源码如下:

 /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        //handleCallback(msg) 实际上就是 message.callback.run();
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

首先检查Message的callback是否为空,不为空则交给handleCallback处理,其中callback就是一个runnable的对象,实际上就是handler.post(runnable)中runnable对象。
如果callbak为空,进入eles代码块,如果mCallback不为空,就调用mCallback的handleMessage来处理消息。
这里的Callback是一个接口,这个Callback允许我们在使用handler时,不需要派生一个子类,即可创建一个handler对象。
上面例子中我们在创建handler使用的无参构造方法中,默认就将callback设定为null。
如果mCallback为空,则调用handleMessage(msg)处理消息,handleMessage是一个空的方法,需要我们在派生Handler的子类实现,用于进一步对发送的消息做处理。源码如下

    public void handleMessage(Message msg) {
    }

为什么需要这样的消息机制呢?

首先,Android系统规定访问、修改UI只能在主线程中,如果在子线程中访问UI,那么程序就会抛出异常。而在Android中耗时操作是不能在主线程中操作的,所以访问网络、数据库等等这些耗时操作就不得不在子线程中执行了。如果在子线程中执行完毕的操作需要修改UI时?这时就轮到Android的消息机制出场了,再来看一个经典的使用场景:

    private TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = findViewById(R.id.textview);

        new Thread(new Runnable() {
            @Override
            public void run() {
                double d = 0;
                for (int i = 0; i < 99999; i++) {
                    d += i;    
                }
                Message message = new Message();
                message.what = 1;
                Bundle bundle = new Bundle();
                bundle.putDouble("key", d);
                message.setData(bundle);
                handler.sendMessage(message);
            }
        }).start();

       Handler handler=new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                if (msg.what == 1) {
                    mTextView.setText(msg.getData().getDouble("key") + "");
                }
                return true;
            }
        });
    }

上述的代码展示了在一个新的线程中做耗时计算后,将计算结果输出到主线程中并展示的过程。
注意:上面创建handler时使用了我们在Handler的原理中说到的使用Callback的方式创建。
这里延伸一下,Android为什么不允许在子线程中修改UI?原因在于Android中的各种UI控件不是线程安全的,在多线程并发操作时会发生不可预期的状态,那为什么UI控件不加锁呢?因为锁会让UI访问的逻辑变得复杂,同时也会降低UI的访问效率。所以Android选择了性能很高的但线程运行模式。

你可能感兴趣的:(深入了解Android多线程(三)Handler与多线程)