美文网首页
Handler的使用

Handler的使用

作者: 三年级一班亚索 | 来源:发表于2018-11-09 19:57 被阅读1次

参考:
Android面试常客之Handler全解 > https://blog.csdn.net/fnhfire_7030/article/details/79518819
《第一行代码》P345
《Java多线程编程核心技术》P191

Handler的作用

先从Handler的作用开始。我们都知道Android的主线程不能处理耗时的任务,否者会导致ANR的出现,但是界面的更新又必须要在主线程中进行,这样,我们就必须在子线程中处理耗时的任务,然后在主线程中更新UI。但是,我们怎么知道子线程中的任务何时完成,主线程如何和子线程同步工作呢?为了解决这个问题,Android为我们提供了一个消息机制即Handler。

什么是ANR
Application Not Respinding
在一个activity当中,最长的执行时间是5秒。如果超出了5秒没有做出相应,它就会出现anr的弹框,而在broadcastReceiver当中,最长的执行时间是10秒。如果超出了10秒同样会造成anr。

通过Handler更新UI实例

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button mStartTask;

     // 定义一个handler
     // 关闭Handler的内存泄露提示,关于内存泄露,我的下一篇文章会讲
    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1) {
                Toast.makeText(MainActivity.this, "刷新UI、", Toast.LENGTH_SHORT).show();
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        mStartTask = findViewById(R.id.btn_start_task);
        mStartTask.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_start_task:
                // 创建一个线程
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 模拟耗时操作,注意:如果在主线程中进行耗时操作时会出现ARN
                            Thread.sleep(1000);
                            mHandler.sendEmptyMessage(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
                break;
        }
    }
}

通过上面的过程,我们就可以 “ 在子线程中更新UI ” 了,不过,我想这里有一个很严重的问题!

  • Handler明明是在子线程中发的消息怎么会跑到主线程中了呢?
  • Handler的发送消息handleMessage又是怎么接收到的呢?

当时我在学这点有一个疑惑,为什么子线程可以使用mhandler,需要注意的是,实例变量可以由多个线程访问,只有方法内的变量是私用的。

handler源代码解析

按住ctrl点击查看Handler类源代码,发现无参构造函数最终调用下面这个构造函数



前面那个代码块我们不用管,关键代码是第一个if代码块后面的这些代码


mLooper = Looper.myLooper();

我们查看这个函数的源代码,发可以知道,它是sThreadLocal.get()的一个返回值,如果以前不知道ThreadLocal类的话,这一个肯定很费解,接下来,解释一下,什么是ThreadLocal。我们是怎么通过ThreadLocal创建Looper的!


ThreadLocal类

变量值的共享可以使用public static变量的形式,所以的线程都使用同一个public static变量。如果想实现一个线程都有自己的共享变量应该如何解决呢?

例子

这里有一个静态变量a,每过100ms打印一次,没有毛病吧。


这很正常,那么问题来了,如果我还有一个线程对a进行访问呢?



凉了,哈哈哈哈哈哈哈哈



为了防止别的线程访问我们的静态变量,这个时候我们引入ThreadLocal,就把问题解决了
public class Run {

    public static ThreadLocal t1 = new ThreadLocal();
    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                t1.set(0);
                for (int i = 0; i < 100; i++){
                    System.out.println(t1.get());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    t1.set(i);
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    t1.set(0);
                }
            }
        }).start();

    }
}

所以ThreadLocal的实质是用来解决线程安全问题的,它可以让线程绑定自己的public static变量。

sThreadLocal.set(new Looper(quitAllowed));

Looper类

文章到了这里我们可以知道以下几点信息了。

  • 在对Handler进行实例化的时候,会对一些变量进行赋值。
  • 对Looper进行赋值是通过Looper.myLooper方法

查看Looper方法可以知道,Looper.prepare()方法,用来创建一个Looper(),在Handler类中的Looper.myLooper()来拿到我们的Looper,可是,Looper.prepare()是什么时候被调用的呢?系统的源码我就不深入了。我们只要知道,在我们的MainThread类中,我们的安卓系统已经给我们调用好了Looper.prepare方法。所以在一开始,就已经创建了Looper。

Handler的sendMessage方法都做了什么?

我们再来看我们之前讲的问题

  • Handler明明是在子线程中发的消息怎么会跑到主线程中了呢?
  • Handler的发送消息handleMessage又是怎么接收到的呢?

我们在发送消息时调用了sendMessage方法,而这个方法最终会调用enqueueMessage()方法

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

这个this也就是指当然的Handler实例,所以每个调用sendMessage()方法的Handler与Message进行了绑定。

注意: Message类的作用是在线程之间传递信息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。

所以这个过程,我们把我们的消息发送到了之前 的mQueue队列中,mQueue是Looper类的一个内部类,主线程Hander的一个属性的引用Looper,自然消息就传到了主线程,哈哈哈哈哈?为什么不直接通过一个静态变量收发消息呢?还搞的这么麻烦,因为Looper还可以用于绑定子线程啊!!!

怎样从MessageQueue中获取Message

使用Looper.loop(),而在MainThread中已经帮助我们调用了这个方法

Looper.loop();

我们看一下loop()的源代码,它是不断的从MessageQueue中拿消息,有消息后,拿到Message之后调用这个函数

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                // 就是这玩意儿
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

所以,使用Handler的sendMessage方法,最后在handleMessage(Message msg)方法中来处理消息。使用Handler的post方法,最后在Runnable的run方法中来处理。
不过,在第一行代码中,我们只使用过sendMessager方法给handler发送数据,那么怎么使用post方法呢?

mRunnable = new Runnable() {
            @Override
            public void run() {
                Toast.makeText(MainActivity.this, "正在循环!!!", Toast.LENGTH_SHORT).show();
                mHandler.postDelayed(mRunnable, 1000);
            }
        };

mHandler.post(mRunnable);
mHandler.removeCallbacks(mRunnable);

post的方法的实质是将mRunnable封装成消息。

总结

整个过程如下所示


我们在子线程中使用handler对象实例的sendMessage()方法,或者通过post将runnable对象,不过他最终也会被封装成Messager然后放到到MessagerQueue中,当然这个MessageQueue是Looper中的一个实例,而Looper一开始是在MainThread中实例化的,最后在MainThread中的最后一行代码,会将我们的handler的handlerMessage调用,当然,如果msg对像是runnable,则将它执行。

扩展

在子线程中实现handler

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare();
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };
        Looper.loop();
    }
}

相关文章

网友评论

      本文标题:Handler的使用

      本文链接:https://www.haomeiwen.com/subject/aucoxqtx.html