使用 Handler 切换线程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wanliguodu/article/details/83893990


   之前写过 android 的消息机制,对 Handler 源码进行了分析,但总是感觉对 handler 理解的不够深刻,所以再写一遍关于 handler 的使用和切换线程的文章。
  在android中通常有个说法:Handler 是用来实现子线程到主线程的切换去更新UI的。这个说法比较片面,只是 Handler 功能的一部分,比较全面的说法是: Handler 是将一个任务切换到指定的线程中去执行,这个切换线程指的是任何线程,不单单是主线程。

一、Handler 的使用

1. Handler 消息处理逻辑

   在 Looper.loop() 方法中,会调用 MessageQueue 的 next() 方法来获取新消息,然后会调用 msg.target.dispatchMessage(msg) 来处理消息。其中,msg 是 Message 类型,源码是:

public final class Message implements Parcelable {
		//其他字段
		Handler target; 	 //target 处理
		Runnable callback;  //Runnable 类型的 callback
		//代码省略
}

   从源码中可以看到,target 是 Handler 类型,实际上就是转了一圈,通过 Handler 将消息传递给消息队列,消息队列又将消息分发给 Handler 来处理。在 Handler 代码中,消息处理调用了 Handler 的 dispatchMessage 方法,相关代码如下:

    public void handleMessage(Message msg) {
    }

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

从上述程序中可以看到,dispatchMessage 只是一个分发的方法,如果 Runnable 类型的 callback 为空则执行handlerMessage 来处理消息,该方法为空,我们一般将更新 UI 的代码写在该函数中;如果 callback 不为空,则执行 handleCallback 来处理,该方法会调用 callback 的 run 方法。其实这是 Handler 分发的两种类型,比如我们 post(Runnable callback)就不为空,此时就会执行 Runnable 的 run 函数;当我们使用 Handler 来 sendMessage 时通常不会设置 callback ,因此,也就执行 handlerMessage 这个分支。下面我们来看看通过 Handler 来 post 一个 Runnable 对象的实现代码。

 
 public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
 private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
    
 public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
 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);//将消息插入到消息队列中
    }

从上述程序中可以看到,在 post(Runnable r)时,会将 Runnable 包装成 Message 对象,并且将 Runnable 对象设置给 Message 对象的 callback 字段,最后会将该 Message 对象插入消息队列。sendMessage 也是类似实现的:

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

因此不管是 post 一个 Runnable 还是 Message ,都会调用 sendMessageDelayed(msg,time)方法,然后改 Message 就会添加到消息队列中,当消息队列中取出该消息时就会调用 callback 的 run 方法或者 Handler 的 handlerMessage 来执行相应的操作。

2. Handler 的实例

更新主线程(UI线程)的 Handler的实例:

        //实例1
        final Handler handler1 = new Handler(){//在UI线程中创建
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //更新 UI 的操作
                Log.i("handleMessage","更新UI操作");
            }
        };
		//子线程
        new Thread(){
            @Override
            public void run() {
                super.run();
                 try {//模拟阻塞
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                handler1.sendEmptyMessage(123);
            }
        }.start();      

       
       //实例2
       Handler handler2 = new Handler();//在UI线程中创建
       //子线程
       new Thread(){
            @Override
            public void run() {
                super.run();

                try {//模拟阻塞
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                handler2.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.i("post 方法",""+currentThread().getName());
                        Toast.makeText(TestActivity.this,"post 方法:"+currentThread().getName()+"更新UI操作",Toast.LENGTH_LONG).show();
                    }
                });
            }
        }.start();
        

二、Handler 在线程之间进行切换

1.在线程中使用 Handler 的步骤

① 调用 Looperprepare() 方法为当前线程创建 Looper 对象,创建 Looper 对象时,它的构造器会创建与之配套的 MessageQueue
② 有了 Looper 之后,创建 Handler 子类的实例,重写 handlerMessage() 方法,该方法负责处理来自其他线程的消息。
③ 调用 Looperloop() 方法启动 Looper

这个是所有 Handler 使用的步骤,那么平常我们使用怎么 new Handler 就可以了呢?其实在android 主线程中在程序入口ActivityThread.main 方法中初始化了,这也就是在主线程中默认可以使用Handler的原因。ActivityThread.main的源码如下:

 public static void main(String[] args) {
		//代码省略
		
        Looper.prepareMainLooper();//Loope初始化r

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        AsyncTask.init();

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();//开启loop循环

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

所以在一般我们不使用主线程的Looper 的时候,就要按照上述的3个步骤进行编写就可以了。

2. 子线程和子线程之间的消息进行切换

android 中我们经常做的是在子线程中发消息给主线程(UI线程),然后在主线程中更新 UI 操作。那么我们可以从一个子线程到另一个子线程进行消息的传递吗?答案是肯定可以的,下面列出源码:

private Handler handler;//Handler 对象

//第一个子线程
class MyThread1 extends  Thread{
        public MyThread1(String name){super(name);}
        @Override
        public void run() {
            super.run();
            Looper.prepare();
            Looper looper = Looper.myLooper();//在子当前线程中初始化 Looper
            Log.i("当前子线程是----->","step 1:"+currentThread().getName());
            
            handler = new Handler(looper){//以当前 Looper构建 Handler 对象
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    Log.i("当前子线程是----->","step 2:"+currentThread().getName()+":接收到数据为:"+msg.what);
                }
            };
            Looper.loop();
        }
    }
    new MyThread1("thread1").start();//开启线程
    
//第二个子线程
class MyThread2 extends  Thread{
            public MyThread2(String name){super(name);}
            @Override
            public void run() {
                super.run();
                Log.i("当前子线程是----->","step 3:"+currentThread().getName()+" ");
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.i("当前子线程是----->","step 4:"+currentThread().getName()+" 10s后要发送数据");
                handler.sendEmptyMessage(1);
            }
        }
        new MyThread2("thread2").start();//开启线程

运行结果:

当前子线程是----->: step 1:thread1
当前子线程是----->: step 3:thread2
//10s 后
当前子线程是----->: step 4:thread2 10s后要发送数据
当前子线程是----->: step 2:thread1:接收到数据为:1

三、为啥Looper的死循环不能造成ANR现象

3.1 ANR发生的时机

官方文档中有如下:
An ANR will be triggered for your app when one of the following conditions occur:

  • While your activity is in the foreground, your app has not responded to an input event or BroadcastReceiver (such as key press or screen touch events) within 5 seconds.
  • While you do not have an activity in the foreground, your BroadcastReceiver hasn’t finished executing within a considerable amount of time.

大致意思为:

  • 虽然您的activity 在前台,但应用程序在5秒内没有对输入事件或BroadcastReceiver(如按键或屏幕触摸事件)作出响应。
  • 虽然在activity 中没有活动,但是BroadcastReceiver在相当长的时间内没有完成执行。

3.2 Looper.loop()的阻塞(死循环)

对于线程是一段可以执行的代码,当可执行代码执行完后,线程生命周期便该终止了,线程退出。对于android 中的主线程,我们是绝对不希望会被运行一段时间,自己就退出的,那么如何保证能一直存活呢?简单的做法就是可执行代码时能一直执行下去,死循环变能保证不会被退出。 那么既然是死循环又如何去处理其他事务呢?答案是创建新线程的方式。真正会卡死主线程的操作是在回调方法 onCreate/onStart/onDesotry/onResume等操作时间过长,会导致掉帧,甚至发生 ANR,而Looper.loop 本身不会导致应用卡死。
下面是ActivityThread.main()中:

public static void main(String[] args) {
        ....
        
        //创建Looper和MessageQueue对象,用于处理主线程的消息
        Looper.prepareMainLooper();

        //创建ActivityThread对象
        ActivityThread thread = new ActivityThread(); 

        //建立Binder通道 (创建新线程)
        thread.attach(false);

        Looper.loop(); //消息循环运行
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

thread.attach(false);便会创建一个Binder线程,该Binder线程通过 Handler 将Message 发送给主线程。 主线程死循环一直运行会导致消耗CPU资源严重吗?然而并不是,这里涉及到Linux pipe/epoll机制,简单来说,当MessageQueue中没有消息时,主线程便会释放CPU资源进入休眠状态,直到下一个消息的到达。

3.3 概括

其实抛开那些原理和底层代码,简单来说:
首先将这两个概念分开来看的话,其实 “Looper死循环”ANR 这两之间一点联系都没有,完全两码事。
其一:Looper上的阻塞,前提是没有输入事件,MessageQueue为空,Looper空闲状态,线程进入阻塞,释放CPU执行权,等待唤醒。
其二:UI耗时导致卡死,前提是要有输入事件,MessageQueue不为空,Looper正常轮询,线程并没有阻塞,但是该事件执行时间过长(5秒),而且与此期间其他的事件(按键按下,屏幕点击…)都没办法处理(卡死),然后就ANR异常了。

站在巨人的肩膀上:
Android源码设计模式解析与实战 ——何红辉 关爱民
Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

猜你喜欢

转载自blog.csdn.net/wanliguodu/article/details/83893990