Android Handler

奋斗吧
奋斗吧
擅长邻域:未填写

标签: Android Handler JavaScript博客 51CTO博客

2023-05-15 18:24:06 92浏览

Android Handler,多线程的应用在Android开发中是非常常见的,常用方法主要有:1.继承Thread类(继


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

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

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

3.Handler

4.AsyncTask(异步任务)

5.HandlerThread

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

 

Android 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()

将消息移出消息队列

Looper

prepare()

创建 Looper对象和 MessageQueue 对象

loop()

轮询从 MessageQueue 取出消息

并发送给 Handler 处理;

无消息时阻塞

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

Android Handler_UI_02

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

Android Handler_消息队列_03

到这个地方,对 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 行,开始执行主线程向子线程发送消息。这样开始了无限的循环。

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

Android Handler_ide_04

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

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 的内容也就讲完了。。。


好博客就要一起分享哦!分享海报

此处可发布评论

评论(0展开评论

暂无评论,快来写一下吧

展开评论

您可能感兴趣的博客

客服QQ 1913284695