Android Handler

多线程的应用在Android开发中是非常常见的,常用方法主要有:

1.继承Thread类(继承 Thread 类和实现 Runnable 接口的区别)

2.实现 Runnable 接口(继承 Thread 类和实现 Runnable 接口的区别)

3.Handler

4.AsyncTask(异步任务)

5.HandlerThread

下面就来看一下关于Handler 的理解和用法:

 

 

1.Handler是什么

答:Handler 是 Android 给我们提供来更新 UI 的一套机制,也是一套消息机制;我们可以通过它发送消息,也可以通过它处理消息。

下面是 Handler 官网的介绍(做了下翻译):

处理程序允许您发送和处理消息与线程的 MessageQueue 相关联的可运行对象。每个处理程序实例都与单个线程和该线程的消息队列相关联。当您创建一个新的处理程序时,它被绑定到创建它的线程/消息队列——从那时起,它将向该消息队列传递消息和可运行项,并在它们从消息队列中出来时执行它们。

2.为什么要用 Handler,它有什么用途,不用不可以吗

答:不用是不可以的。Android 在设计时,封装了一套消息的创建、传递和处理机制,如果不遵循这套机制就没有办法更新 UI ,并且会抛出异常。

下面是 Handler 用途官网的介绍(做了下翻译):
处理程序有两种主要用途: (1) 将消息和可运行项作为将来某个时间点执行; (2) 将要在不同线程上执行的操作加入队列。

知识:Handler 两个用途:1.可以定时发送 Message 或 Runnable 对象;2.在一个线程处理一个动作。

3. Handler 怎么用

下面你将会了解:1.如何发送延迟消息,2.如何发送循环消息,3.如何发送带参数的消息以及拦截消息。

先瞅瞅官方的文档对 Handler 如何使用的描述(下面是知识薄浅的我翻译过来的):

调度消息通过post(Runnable)、postAtTime(Runnable, long)、postdelay (Runnable, long)、sendEmptyMessage(int)、sendMessage(Message)、sendMessageAtTime(Message, long) 和 sendMessageDelayed(Message, long) 方法来完成。post 版本允许您将接收到的可运行对象加入队列,以便消息队列调用它们; sendMessage 版本允许您将包含一组数据的消息对象编入队列,这些数据将由处理程序的 handleMessage(Message) 方法处理(需要实现处理程序的子类)。

发送到处理程序时,您可以允许在消息队列准备就绪时立即处理项,或者指定处理前的延迟时间或处理项的绝对时间。后两种方法允许您实现超时、计时和其他基于时间的行为。

为应用程序创建进程时,其主线程专门用于运行一个消息队列,该队列负责管理顶级应用程序对象(活动、广播接收器等)和它们创建的任何窗口。您可以创建自己的线程,并通过处理程序与主应用程序线程进行通信。这是通过从新的线程调用相同的post或sendMessage方法来完成的。然后,将在处理程序的消息队列中调度给定的 Runnable 或消息,并在适当的时候进行处理。

文档说的很详细,但是很繁琐,其实我们用到的主要有四个方法:post(Runnable)、postdelay (Runnable, long)、sendMessage(Message)、sendMessageDelayed(Message, long) ;

要注意的是:Handler 发消息是分为了两种,一种是 Message 对象(sendMessage(Message)),另一种是 Runnable 对象(post(Runnable));而在代码最底层就会发现, Runnable 最后也是被封装成 Message 对象。

3.1 发送延迟消息(post(Runnable) 简单用法)

下面是创建子线程,延迟更新 UI (这是下面三个例子的源码,没币的私我发):

    private TextView textView;
    private Handler handler = new Handler();

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

        textView = findViewById(R.id.textView);

        //创建子线程,利用Handler更新UI
        new Thread() {
            @Override
            public void run() {
                try {
                    //0.5秒后更新UI
                    Thread.sleep(500);
                    //用 Handler 更新 TextView
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            textView.setText("更新成功");
                        }
                    });
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();//开始执行
    }

分析一下上面的代码:先创建了子线程 Thread 然后在里面延迟了0.5秒后,利用 handler.post(Runnable) 方法更新了 UI ,非常简单。

3.2 发送循环消息(postDelayed(Runnable long) 简单用法)

下面是发送循环消息,每一秒换一张图片(多用于图片定时轮换):

    private int images[] = {R.color.colorAccent, R.color.colorPrimary, R.color.colorPrimaryDark};
    private int index;//索引指定图片当前在哪个位置;

    private MyRunnable myRunable = new MyRunnable();
    private Handler handler = new Handler();

    class MyRunnable implements Runnable {
        @Override
        public void run() {
            index++;
            index = index % 3;
            //根据索引取出数组中的图片
            imageView.setImageResource(images[index]);
            //每隔一秒去切换一下图片
            handler.postDelayed(myRunable, 1000);
        }
    }

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

        //0.5秒后调用runnable切换图片
        handler.postDelayed(myRunable, 500);
    }

分析一下上面的代码:首先定义了一个装有三种颜色的数组和一个索引值;然后自定义了 MyRunnable 继承了 Runnable ,在 run() 方法中执行了为 ImageView 设置数组中的颜色,利用 handler.postDelayed() 方法设置每过一秒,再次执行该方法;最后在 onCreate() 方法中,调用 handler.postDelayed() 开始执行切换图片。这样便达到了每过一秒去更换一次图片的效果。

3.3 发送带参数的消息以及拦截消息(sendMessage() 简单用法)

下面是 sendMessage() 的简单用法(携带了数据):

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_send_message);
        
        //创建子线程,在里面往主线程发送消息,更新UI
        new Thread() {
            @Override
            public void run() {
                try {
                    //0.5秒后发送消息更新UI
                    Thread.sleep(500);
                    Message message = new Message();
                    message.arg1 = 88;
                    message.arg2 = 66;
                    //往下面的实体类添加一组数据
                    Person person = new Person();
                    person.age=18;
                    person.name="小王";
                    message.obj=person;
                    handler.sendMessage(message);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    //实体类,有人的姓名年龄等
    class Person {
        public int age;
        public String name;
        public String toString() {
            return "name=" + name + "age=" + age;
        }
    }
    //接收消息更新UI
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            textView.setText(msg.arg1 + "和" + msg.arg2+msg.obj);
        }
    };

分析一下上面的代码:首先创建了一个子线程,子线程休息0.5秒后,创建了 message 对象,message 携带了三组数据,前两组分别是整型的 arg1 和 arg2,第三组数据为 object 类型的一组个人信息,最后通过 handler.sendMessage() 方法,将携带数据的消息发送出去。上面代码38行,创建了一个 handler 对象,在里面接受了消息并且设置到 TextView 中。

下面是 handler 如何拦截消息的:

    private Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message message) {
            Toast.makeText(getApplicationContext(), "消息被拦截", Toast.LENGTH_SHORT).show();
            //下面的返回值为false时,不拦截消息;否则拦截消息,不会再走下面的handlerMessage()
            return true;
        }
    }) {
        @Override
        public void handleMessage(Message msg) {
            Toast.makeText(getApplicationContext(), "消息没有被拦截", Toast.LENGTH_SHORT).show();
        }
    };

分析一下上面的代码:在处理消息之前加入了 Handler.Callback() 方法,方法内返回值如果为 false不拦截消息,否则拦截消息(这是上面三个例子的源码,没币的私我发)。

4.为什么设计为只通过 Handler 更新 UI

答:最根本的目的是为了解决多线程并发问题。

比如:一个 Activity 多个线程更新 UI,而且没有加锁机制,那会怎么样?

        会出现更新界面混乱;因为没有加锁,引起了多线程并发问题。

        那么我们对更新 UI 的操作进行加锁处理,又会怎么样?

        会引起性能下降;每一个加锁的方法都会导致性能有一定的下降。

知识:Android 提供了一套更新 UI 的机制,只需要遵循它就可以,这样我们不用关心多线程并发的问题;所有更新 UI 的操作,都在主线程的消息队列中轮询处理。

5.Handler 原理,它与 Looper 、MessageQueue 有什么联系

5.1 Handler 、Looper 、MessageQueue 、Message 描述

5.1.1 Handler

Handler 中封装了消息的发送和处理(消息的发送:sendMessage();消息的处理:handleMessage())。

5.1.2 Looper

1.Looper 是消息盛装的载体,一个线程中只会有一个 Looper 实例;

2.其内部初始化了一个 MessageQueue (消息队列),所有的 Handler 发的消息都走向这个消息队列;

3.在 Looper 中,会调用一个 Looper.loop() 方法,调用这个方法后,进入了死循环,这样便起到了轮询的作用;不断从 MessageQueue 中取出消息,一有消息便处理(消息最后又交给 Handler 自己处理)。

4.要注意的是:主线程的 Looper 对象会自动生成,而子线程 Looper 对象要手动调用方法创建。

5.1.3 MessageQueue 描述

MessageQueue (消息队列)添加处理的消息,存储特点:先进先出;

5.1.4 Message 描述

Message 顾名思义消息;它可以携带少量的消息(也就是我们所说的参数),一般使用 arg1 和 arg2 字段来携带整型数据,使用 obj 字段携带一个 Object 对象。

三者的关系:Handler 会和 Looper 进行关联,也就是在 Handler 内部可以找到 Looper(如何关联要看下面源码),找到了 Looper 也就找到了 MessageQueue 。在 Handler 中发消息,其实就是向 MessgeQueue 发消息。

知识:Handler 负责发送处理消息,Looper负责接收 Handler 发送的消息并把消息回传给 Handler 自己,MessageQueue 是储存消息的容器(下面的源码解释了 Handler 如何发送消息, Looper 如何接收到消息后又如何回传给 Handler 的)。

看完上面的 Handler 、 Looper 与 MessageQueue 的理论知识,肯定对它们之间的关系有一定的理解;接下来看源码,加深理解它们的关系:

5.2 Handler、Looper源码

5.2.1 Looper 源码

Looper 中有两个重要的方法(翻译来为:准备和循环):prepare() 和 loop() ;先看准备方法 prepare() 。

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));
    }

从上面代码看出:代码中 prepare() 方法是一个 boolean 类型的,true 表示允许退出,false 则表示 Looper 不允许退出(ThreadLocal这里不再说了,想了解的可以去搜一下),先做一个是否为空的判断这个判断保证了一个线程中只有一个Looper实例;然后在将一个 Looper 的实例放入了 ThreadLocal ,之后调用了 new Looper(quitAllowed) ,下面看 Looper 的构造方法:

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

从上面代码看出:在里面创建了一个MessageQueue(消息队列),到此为止就能看出来 Looper 与 MessageQueue 关联起来了,也可以看出 Looper 与 Thread 关联起来了。上面是 prepare() 方法,下面继续看 Looper 的另一个循环方法 loop() :

    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

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

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

上面代码可以看出:先调用了 myLooper() 方法,看下 myLooper() 方法内有什么:

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

上面代码可以看出: myLooper() 内部返回了 sThreadLocal 存储的 Looper 实例(上面的 prepare() 方法内放入了一个 Looper 实例),如果 Looper 实例为空则抛出异常;也就是说 loop() 方法必须在 prepare() 方法之后运行,继续回到 loop() 方法:

第六行的 me.mQueue 获取当前 Looper 对应的消息队列;

剩下的内容就是主要的死循环方法:

第十三行的 for( ; ; ){ ...,说明进入了 loop() 进入了死循环,不停的处理消息队列中的消息;

第十四行的 queue.next() 方法是获取消息;

第十五行判断了如果没有消息则退出循环。

第二十七行的 msg.target.dispatchMessage(msg);  其中 target 就是 Handler ,dispatchMessage() 方法就是处理消息。这行代码的意思就是:用 Message 调用 Handler 来处理消息,最后处理消息的还是 Handler 。

第四十四行将 Message 放入消息池,释放消息占据的资源。

5.2.2 Handler 源码

Handler 源码主要看三个地方:1.Handler 如何与 Looper 关联起来。2.Handler 如何发消息。3.Handler如何接收并处理消息。

Handler 如何与 Looper 关联起来:

public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

从上面代码第十行可以看出:通过 Looper.myLooper() 方法取出了当前线程保存的 Looper 实例;

代码第十一行:判断 mLooper 对象是否为空,为空抛出异常信息。 mLooper 对象为空,说明没有初始化 Looper 对象,也就是说没有调用 looper.prepare() 方法;

代码十五行:获取了 Looper 实例中的 MessageQueue (消息队列);

到此为止,Handler 与 Looper 就关联起来了。

Handler 如何发送消息:

    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

上面代码可以看出,里面只调用了 sendMessageDelayed() 方法,下面看这个方法内有什么:
 

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

上面代码可以看出,里面又调用了 sendMessageAtTime() 方法:

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);
    }

上面代码可以看出,首先获取到 MesageQueue(消息队列),并且判断是否为空,为空的话返回 false 发消息失败。否则调用 enqueueMessage() 方法;下面看 enqueueMessage() 方法:

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

上面代码可以看出,首先设置 msg 的 target 对象,并且指向了自己,也就是说把当前的 handler 作为 msg 的 target 属性。发消息最后也是说 Handler 发消息最后保存到了消息队列中。

Handler如何接收并处理消息:

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

上面代码可以看出,先判断 Message 的回调是否为空;然后再判断 Handler 的回调是否为空(一般拦截消息会利用 Handler 回调,上面有说如何拦截消息);最后进入方法 handlerMessgae() :

   public void handleMessage(Message msg) {
    }

上面代码可以看出,handlerMessgae() 这个方法为空的,因为一般我们自己接收到消息后去处理消息。

下面是对 Handler 、Lopper 和 MessgaeQueue 重要方法的总结:

名称重要方法作用
Handler

sendMessage(Message msg)

sendmessageDelayed(Message msg, long delayMillis)

1.发送消息

2.发送 Message 或 Runnable 两种对象

3.将消息发送到消息队列中

post(Runnable run)

postdelay(Runnable run , long delayMillis)

dispatchMessage(Message msg)分发消息,分发给对应的 Handler
handleMessage(Message msg)我们自己收到消息做出的处理(多为更新 UI)
MessageQueue

enqueueMessage(MessageQueue queue,

Message msg, long uptimeMillis)

将消息放入消息队列(先进先出的原则)
Message next()将消息移出消息队列
Looperprepare()创建 Looper对象和 MessageQueue 对象
loop()

轮询从 MessageQueue 取出消息

并发送给 Handler 处理;

无消息时阻塞

下面是主线程运行,Handler 与 Looper 关联的实例图:

关联完毕后就可以发消息了,如下图:

到这个地方,对 Handler 应该有一定的认识和了解以及简单的发消息。

6.使用 Handler 会遇到哪些问题

内容还在更新。。。

7.如何实现一个与线程相关的 Handler(手动写主线程给子线程发送消息并在子线程中处理消息)

上面全都是讲的子线程去给主线程发送消息,主线程用 Handler 处理消息执行更新 UI 的操作,那么如何实现在子线程中使用 Handler 呢(主线程给子线程发送消息并在子线程中处理消息)?

下面我来自定义一个子线程,并且在子线程创建 Looper 、Handler ;

再讲一下 Looper 与 Handler 关联的步骤(上面有图):

1.创建子线程;

2.手动调用 Looper.prepare() 方法(在 prepare() 内部创建了 MessgeQueue);

3.创建 Handler;

4.手动调用 Looper.loop() 方法,开始轮询;

要注意的是:只有在主线程下,Android 为我们封装了 Looper 的创建与轮询方法,如果自己写,一定要手动调用这两个方法;

这样我们就自定义 Handler 与 Looper 关联起来了。然后在主线程中发消息就好了,具体看下面代码:

    private Button button1;

    //1.创建子线程
    class Mythread extends Thread {
        public Handler handler;

        @Override
        public void run() {
            //2.手动调用 Looper.prepare 方法
            Looper.prepare();
            //3.创建 handler
            handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    Toast.makeText(ZidingyiHandlerActivity.this, "自定义", Toast.LENGTH_SHORT).show();
                }
            };
            //4.手动调用 Looper.loop() 方法,开始轮询 
            Looper.loop();
            //Handler 与 Looper 手动关联完毕
        }
    }

    private Mythread mythread;

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

        //在主线程初始化,并开始执行
        mythread = new Mythread();
        mythread.start();

        button1 = findViewById(R.id.button1);

        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //在主线程发送一个空消息
                mythread.handler.sendEmptyMessage(1);
            }
        });
    }

8.HandlerThread 是什么

看完上面的 7.如何实现一个与线程相关的 Handler(主线程给子线程发送消息并在子线程中处理消息)是不是感觉很繁琐,要自己去调用 Looper 的两个方法,不能简单一点吗?所有就有了 HandlerThread。

HandlerThread 定义:

顾名思义:handler 处理者,Thread 线程。也就是创建一个线程去处理消息(在子线程处理消息);再看官网的定义:

它用于启动具有 looper 的新线程的便利类。然后,可以使用 looper 来创建处理程序类。注意,仍然必须调用 start()。

根据官网给的定义,可以看出 HandlerThread 中为我们创建了 Looper ,只需调用 start() 方法即可。

HandlerThread 源码:

下面先通过源码了解一下 HandlerThread ,先看一下它的构造方法 :

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

上面代码可以看出: HandlerThread 继承了 Thread,并且在里面定义了一个 Looper 对象,记住这些就够了,接下来看 HandlerThread 里面两个重要方法 run() 方法和 getLooper() 方法,下面先看 run() 方法:

@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();

        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

上面代码可以看出:HandlerThread 的 run() 方法中帮我们调用了 Looper.prepare() 初始化了 Looper 对象,最后有帮我们调用了 Looper.loop() 轮询的方法。这样我们通过 HandlerThread (子线程)使用 Handler 时,不用手动调用 Looper 的两个方法,方便了很多。下面是 getLooper() 方法:

public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

从上面代码可以看出: getLooper() 方法中经历了判断是否存活、Looper是否为空,最后返回了一个 Looper 对象。看完源码接下来通过代码了解使用方法。

HandlerThread 使用:

HandlerThread 一般用于主线程往子线程发消息,子线程去做一些耗时操作,下面通过 HandlerThread 来做一个每隔一秒去更新一次 UI 的耗时操作:  

​
    private Button button1;
    private TextView textView;
    private HandlerThread thread;//定义一个HandlerThread
    private Handler threadHandler;//子线程的Handler
    private Handler Mainhandler = new Handler();//主线程的Handler

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

        button1 = findViewById(R.id.button1);
        textView = findViewById(R.id.textView);

        //为HandlerThread指定一个名字,并开始执行
        thread = new HandlerThread("HandlerThread");
        thread.start();

        //子线程中的handler
        threadHandler = new Handler(thread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                try {
                    Thread.sleep(1000);
                    //每隔一秒发送一条消息
                    threadHandler.sendEmptyMessage(1);
                    //在主线程中更新UI
                    Mainhandler.post(new Runnable() {
                        @Override
                        public void run() {
                            int a = (int) (Math.random() * 100); //a是已经生成的随机数
                            textView.setText("1秒更新一次:" + a);
                        }
                    });
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.e("thread1", Thread.currentThread() + "==");//打印线程的id
            }
        };

        //发送空的消息
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                threadHandler.sendEmptyMessage(1);
            }
        });
    }

​

在上面代码 4.5.6 行可以看到,首先定义了 HandlerThread,子线程的 threadHandler和主线程的 MainHandler。

在代码 17.18 行可以看出,为 HandlerThread 指定了一个名字并且开始执行。

在代码 21 行,我们初始化了子线程中的 Handler ,并且指定了 Looper 为 HandlerThread 中的 Looper。(上面有讲:HandlerThread 的 getLooper() 方法返回了一个 Looper 对象)

在代码 25 行:模仿耗时操作,线程休息一秒后,发送一条消息。

在代码 29 行至 35 行:在上面子线程进行耗时操作完成后,我们又回到主线程去更新了 UI 视图。

在代码 47 行:设置点击事件,开始去每隔一秒去更新一次 UI 视图。

9.主线程向子线程发消息(HandlerThread 使用)

了解完上面的 8.HandlerThread 是什么之后,应该对主线程向子线程发消息会有简单了解,因为主线程向子线程发消息要通过 HandlerThread ,而上面的例子就是一个主线程向子线程发消息的例子;下面再通过一个简单的例子去加深,这个一个1秒后主线程向子线程发消息,子线程收到消息后再向主线程发消息,这样不停去轮换。下面看代码:

    private Button button1;
    private HandlerThread thread;
    private Handler threadHandler;//子线程的Handler
    //主线程的Handler
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            Message message = new Message();
            //主线程收到消息子线程发的消息后,向子线程发消息;
            threadHandler.sendMessageDelayed(message,1000);
            Log.e("=====", "MainHandler" );
        }
    };

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

        button1 = findViewById(R.id.button1);

        //为HandlerThread指定一个名字,并开始执行
        thread = new HandlerThread("HandlerThread");
        thread.start();

        //子线程中的handler
        threadHandler = new Handler(thread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                Message message = new Message();
                //子线程收到主线程发的消息后,向主线程发消息;
                handler.sendMessageDelayed(message,1000);
                Log.e("=====", "ThreadHandler" );
            }
        };

        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //开始执行,主线程向子线程发消息
                handler.sendEmptyMessage(1);
            }
        });
    }

从上面代码 2 至 5 行可以看出,先创建一个子线程 threadHandler ,然后创建子线程的 Handler,最后创建并初始化主线程的 Handler 。

从代码 7 至 12 行可以看出,主线程收到了子线程发来的消息并且又向子线程发消息。

从代码 22 至 24 行可以看出,我们为HandlerThread指定一个名字,并开始执行。

从代码 26 至 35 行可以看出,子线程收到了主线程发来的消息并且又向主线程发消息。

这样一来一回,就实现了无限的循环。

代码 41 行,开始执行主线程向子线程发送消息。这样开始了无限的循环。

下面是执行结果的部分代码:

到此,主线程向子线程发消息便讲完了。

10.Android 更新 UI 的几种方式

Android 提供给我们更新 UI 的方法一共有四种:

1. runOnUiThreead() 方法

2. view post() 方法

3. handler post() 方法 (上面有详细讲解,不再讲)

4. handler message() 方法(上面有详细讲解,不再讲)

下面来看第一种方法 runOnUiThreead() :

    private TextView textView;   

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

        textView = findViewById(R.id.textView);

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                textView.setText("123");
            }
        });
    }

方法很简单,下面看第二种方法 view post() :

    private TextView textView;

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

        textView = findViewById(R.id.textView);

        textView.post(new Runnable() {
            @Override
            public void run() {
                textView.setText("1234");
            }
        });
    }

上面的两种方法都很简单,但不常用,所以也不过多去说。比较常用的是第四种,没有很明白的可以再看看上面。

11.非 UI 线程真的不能更新 UI 吗

答案是:非 UI 线程是可以更新 UI ,但不推荐。

先看下面代码:

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

        textView = findViewById(R.id.textView);

        new Thread(){
            @Override
            public void run() {
                textView.setText("123");
            }
        }.start();
    }

从上面代码可以看出,代码很简单,创建了一个子线程去更新 TextView 。然后运行会发现,TextView 更新成功,并且没有报任何错误。。再看下面代码,将子线程休眠一秒后更新 UI :

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

        textView = findViewById(R.id.textView);

        new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    textView.setText("123");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

运行后,可以发现程序崩溃并报出异常。这是为什么,就休眠一秒就崩溃了。

都知道 Activity 会执行生命周期方法 onCreate() 和 onResume() ;如果在 onResume() 方法之前更新 UI ,程序不会报错,休眠一秒后,程序走了 onResume() 方法,程序便崩溃。

因为在 onResume() 方法中,会创建 ViewRootImpl (不清楚的话,点这里了解它);

ViewRootlmpl 会执行方法 invalidateChild();

而 invalidateChild() 方法会调用 checkThread() 方法来检查当前线程是否为主线程,不是在主线程会崩溃,并报出异常。

这样大家应该了解了:

非 UI 线程是可以更新 UI,但是只能在 onResume() 方法调用之前更新才会成功,因为ViewRootlmpl 会在 onResume() 方法中创建,而 ViewRootlmpl 会执行检查当前线程是否为主线程。

所以我们直接在 onCreate() 方法中创建子线程并更新 UI 会成功。

讲到这里,关于 Handler 的内容也就讲完了。。。

 

更多内容戳我&

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值