Android线程间通信-Handler消息机制

需要handler消息机制的原因

  • 在android中由于UI线程并不是线程安全的,如果有子线程更新UI容易导致数据错乱,如果UI线程设置为线程安全的话导致效率低下;
  • 而UI线程做耗时操作容易导致ANR发生。所以需要由子线程做耗时操作当子线程需要更新UI时通知主线程更新UI,而线程间的通信就由Handler消息机制完成。

Handler消息机制原理

在主线程创建一个handler的同时创建了looper和MessageQueue,子线程将需要更新UI的信息构建为message对象,调用入队方法添加到消息队列里,由looper调用loop方法无限循环取出消息并分发给对应的target(handler),由handler调用handlemessage方法处理消息。由此完成了子线程和主线程的通信。

流程图如下:

在这里插入图片描述

文字解释:
  1. 创建一个线程(一般是主线程),在线程内创建一个handler,创建handler的时候初始化了looper和MQ。
  2. 工作线程产生消息后,调用handler的sendMessageAtTime()方法,发送消息。
  3. MQ调用enqueueMessage方法将消息入队到MQ中
  4. Looper调用loop方法不断循环MQ的下一条消息
  5. 获取下一条消息后Handler调用dispatchMessage方法将消息分发给对应的tartget(Handler)
  6. 由Handler调用handlerMessage方法处理消息。

由于Handler和线程是绑定的,同一进程中,不同线程是可以公用资源的,所以在线程A中创建了handler,线程B可以调用来发送消息,经过上面步骤将线程B的消息分发到线程A处理,线程间的通信完成。

源码分析

基于Androidsdk28版本。

1、handler的创建(构造函数)

1.1、空构造函数
   //空构造,最终调用的是有参构造函数
   public Handler() {
        this(null, false);
   }
1.2、有参构造函数

空构造函数默认采用当前线程的looper,回调方法callback为null,消息为同步处理方式。

   //可以指定传入的looper,可以在子线程传入mainlooper
   public Handler(Looper looper) {
      this(looper, null, false);
   }
   
 
 public Handler(Callback callback, boolean async) {
 //匿名内部类如果不声明为static,会警告内存泄漏
        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());
            }
        }
		//初始化获取looper,具体获取方法见1.2.1
        mLooper = Looper.myLooper();
        //必须有一个looper,否则抛异常,这也就是为什么子线程直接使用handler会抛异常的原因,解决办法初始化一个looper
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        //初始化MQ,详细分析见1.2.2
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
1.2.1、Looper与MQ的初始化

looper通过Looper.myLooper()方法获取。是通过当前线程的threadlocal获取。

**ThreadLocal:**线程本地存储区(TLS),每一个线程都有自己的本地存储区,不同线程间彼此不能访问彼此的TLS。

public static @Nullable Looper myLooper() {
		//通过线程本地存储区获取
        return sThreadLocal.get();
}

 public T get() {
 		//获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程为key值的ThreadLocalMap:以threadlocal为key,Entry为value
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        	//获取当前线程存储区中的数据
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
}

一般线程的looper的初始化是在调用Looper.prepare()里初始化looper并初始化MQ。**注:**主线程的looper及MQ的初始化是在创建主线程的时候由ActivityThread的prepareMainLooper()方法自动初始化的。

//调用的方法
public static void prepare() {
        prepare(true);
    }

  private static void prepare(boolean quitAllowed) {
  		//一个线程只允许有一个looper
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //创建looper并将looper存储到当前线程为key的线程本地存储区里 Threadlocal中
        sThreadLocal.set(new Looper(quitAllowed));  //new looper的同时初始化了MQ
  }

主线程的looper的初始化,主线程不允许退出looper。

public static void prepareMainLooper() {
    prepare(false); //设置不允许退出的Looper
    synchronized (Looper.class) {
        //将当前的Looper保存为主Looper,每个线程只允许执行一次。
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

Threadlocal的存储方法:

 public void set(T value) {
 		//获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程为key值的ThreadlocalMap中
        ThreadLocalMap map = getMap(t);
        //如果已经存在,替换value
        if (map != null)
            map.set(this, value);
        else
        //不存在,创建map
            createMap(t, value);
    }

MessageQueue的初始化:

 private Looper(boolean quitAllowed) {
 		//初始化MQ
        mQueue = new MessageQueue(quitAllowed);
        //将looper绑定为当前线程
        mThread = Thread.currentThread();
    }

2、Handler的发送消息

2.1、handler发送消息

在工作线程中构建一个Message对象,调用handler的sentXXX进行发送消息,几个sentXXX方法最终都是调用sendMessageAtTime(Message msg, long uptimeMillis);将消息放入一个消息队列。

//获取到消息队列,并将发送的消息按时间入列,消息队列在loop的构造方法中创建
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;
        }
        //指定msg的target为handler并将消息入列
        return enqueueMessage(queue, msg, uptimeMillis);
    }
    //sendMessageAtFrontOfQueue  设置消息触发时间为0达到将消息放在队列头的目的
  private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //指定了msg的target是handler
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //调用MQ的入列操作
        return queue.enqueueMessage(msg, uptimeMillis);
    }

MQ的消息入列操作:注:MQ的存储结构不是队列,而是单链表。

  boolean enqueueMessage(Message msg, long when) {
  	//msg必须有一个分发的目标
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        //消息正在使用抛异常
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
		//对消息队列加锁
        synchronized (this) {
        	//如果正在退出,将消息回收到消息池
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            //消息按时间排序
            msg.when = when;
            Message p = mMessages;   //当前待处理的消息
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
            //MQ中没有消息,或者当前待处理消息的时间是最早的
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;  //阻塞时需要唤醒
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                //将消息按时间顺序插入到MQ中
                for (;;) {
                    prev = p;
                    p = p.next;
                    //无当前处理消息,或者传进来的消息时间比当前处理消息时间早跳出循环
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //将当前消息放在P(待处理消息)前
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

3、轮询取出消息

3.1、loop方法
  • 获取looper
  • 不断的读取MQ里的下一条消息(没有消息时跳出死循环)
  • 将消息分发给相应的target
  • 把分发后的消息回收到消息池。
 /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
    //获取looper
        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 (;;) {
        //读取MQ里的下一条消息
            Message msg = queue.next(); // might block
            //没有消息的时候跳出死循环
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            // ....省略....
            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
            //msg.target 即Handler进行分发事件
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } 
            //...省略...
            //确保事件分发中identity不会被损坏
              final long newIdent = Binder.clearCallingIdentity();
               //...省略...
                msg.recycleUnchecked(); //将消息放入消息池以便重复利用。
    }
3.2、读取下一条消息
    Message next() {
        //当looper已经退出时,直接返回,这种情况出现在App试图在退出后重启looper,这是不允许的
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
		//循环迭代间隔的标记位
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        //循环取消息
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
			//阻塞操作,在native层完成
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // 当target为null 时,查找下一条消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // 当前时间比消息触发时间短,重新设置下一次轮询的超时时长
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                //当消息是消息队列的第一个消息或者MQ为null时执行Idle handle
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.只有第一次循环时
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }
3.3、循环获取到消息后分发给target----dispatchMessage
public void dispatchMessage(Message msg) {
		//msg回调方法不为null ,调用 message.callback.run();
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
        	//当Handler成员mCallback 不为null时,调用成员变量的callback handleMessage
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //否则调用Handler的自身的handleMessage方法。
            handleMessage(msg);
        }
    }

消息分发的优先级:

  • Message的回调方法:message.callback.run(),优先级最高。
  • Handler的回调方法:Handler.mCallback.handleMessage(msg)
  • Handler的默认方法:Handler.handleMessage(msg);

问题:

1、 HandlerLooperMessageMessageQueueThread 的对应关系?
  • Handler:主要发送消息(Handler.sentMessage())和处理消息(Handler.handleMessage())。Handler中有Looper和MessageQueue。
  • Looper:循环执行(Looper.loop()),按分发机制将消息分发给目标处理者。Looper中有一个MessageQueue.
  • Message:消息分为硬件(按钮、触摸)或软件生成的消息。Message中有一个Handler。
  • MessageQueue:消息队列主要功能是入队(MessageQueue.enqueueMessage())消息和取出(MessageQueue.next())消息。消息队列中有一组Message(待处理消息)。
  • Thread:线程,主要 是处理事务,一个Thread绑定一个Looper
2、主线程向子线程发消息如何发?

在子线程创建Handler,同时需要创建Looper,发送消息,在子线程中获取消息并处理消息。

3、在子线程 new 一个 Handler 需要注意什么?

在子线程中直接创建Handler会导致程序崩溃,报错:Can’t create handler inside thread that has not called Looper.prepare()。 需要手动创建一个looper。

4、Looper 死循环为什么不会导致应用卡死,会消耗大量资源吗?**
  • 当子线程运行结束时,线程退出,线程生命周期结束,(通过查看next方法,子线程开的looper对象的是否允许退出是true,所以在子线程执行next时循环到没有消息时,会执行dispose进行清理工作。)
  • 而对于主线程,我们不希望可运行期间退出,所以死循环保证了线程不被退出,当没有消息时,会通过nativePollOnce进行阻塞。
  • 主线程死循环不会消耗大量的资源,因为在主线程的MessageQueue没有消息时,便阻塞在loop()的next()里的nativePollOnce()方法里。此时主线程会释放CPU资源进入休眠状态,直到下个消息入队到消息队列,通过往管道写入字符唤醒loop线程(主线程)。
5、主线程的消息循环机制是什么(死循环如何处理其它事务)?
  • 主线程死循环 通过创建新线程处理其他事务。

  • 主线程的消息循环模型:AT(ActivityThread)通过ApplicationThread和AMS(ActivityManagerService)进行进程间通信。AMS完成AT的请求后会回调ApplicationThread中的Binder方法,ApplicationThread会向H发送消息,H接收到消息后会将ApplicationThread中的逻辑切换到AT中执行。
    在这里插入图片描述

6、ActivityThread 的动力是什么?(ATLooper中绑定的线程是什么?)

AT没有集成Thread,不是一个线程,那么在AT中Looper绑定的线程是zygote fork出来的进程,进程与线程的区别可能只是是否可以资源共享。

7、Handler如何能够切换线程?

同一进程间线程资源是共享的,Handler绑定的是在它关联的Looper绑定的线程处理消息的。

8、子线程有哪些更新UI的方法?
  • 主线程定义Handler,子线程发送消息,主线程更新UI。
  • runOnMainThread
  • 创建Handler,传入getMainLooper
  • View.Post(Runable r);

Handler绑定的是在它关联的Looper绑定的线程处理消息的,几种方法的源码归根结低都是使用Handler消息机制。

9、如何避免Handler造成的内存泄漏?

在子线程中如果创建Looper,那么在所有的事情完成后如果不将looper调用quit方法退出,子线程会一直等待,如果Looper退出,线程也就退出了。

另外如果在主线程Handler处理消息是有一个延时消息,会一直保存在 主线程的消息队列里,会影响系统对Activity的回收。

所以避免内存泄漏:

  • 在确定子线程不需要looper时将其退出。
  • 有延时消息时在Activity销毁时将Message移除。
  • 非静态内部类和匿名内部类会隐式持有外部类的引用,handler不被释放,持有的外部类也不能被释放,匿名内部类改成匿名静态内部类(一开始创建内存),对Activity的引用使用弱引用。

解决Handler内存泄漏例子如下:

1、内存泄漏的例子:
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
	//创建handler----非静态内部类
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

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

        //模拟异步操作
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "run: 模拟异步操作");
            }
        },1000*60*5);

    }
}
2、检测内存泄漏-Android profiler
  • 运行项目,点击Android profiler,选择设备及报名,选择MEMORY项查看内存。
  • 选择app package(Arrange by package) ,点击旁边 Jump Java heap按钮查看堆栈信息,在左边看到引用树。
  • 反复关闭页面操作,观察引用树,MainActivity一直未被回收,此时已经发生内存泄漏。
  • 点击左上角的垃圾桶(GC)内存也没有明显变化。

两个实例的depth都是3,不可以被GC,引用树里Reference有massage相关的,大概就是Handler发生了内存泄漏。

在这里插入图片描述

2.1、检测内存泄漏-LeakCanary
  • 添加依赖

      //内存泄漏检测
        debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
        releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
        // Optional, if you use support library fragments:
        debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.1'
    
  • 在Application中安装LeakCanary

    if (LeakCanary.isInAnalyzerProcess(this)){
         return;
    }
    LeakCanary.install(this);
    

    如果可能发生内存泄漏时会通知引用树。查看最后的引用情况就是MessageQueue.mMessages.

3、修复内存泄漏

**分析:**在Java中非静态内部类或匿名内部类会隐式持有外部类实例。修改为静态内部类和弱引用持有外部类。

  //修改为静态内部类
    private static class  MyHandler extends Handler{
        private final WeakReference<MainActivity> mActivity;

        public MyHandler(MainActivity activity) {
            mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity mainActivity = mActivity.get();
            super.handleMessage(msg);
            if (mainActivity!=null){
                Log.d(TAG, "handleMessage: 处理逻辑");
            }
        }
    }

    private static final Runnable mRunable = new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "run: 模拟耗时操作");
        }
    };
  private final  MyHandler handler = new MyHandler(MainActivity.this);

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

        //模拟异步操作
//        handler.postDelayed(new Runnable() {
//            @Override
//            public void run() {
//                Log.d(TAG, "run: 模拟异步操作");
//            }
//        },1000*60*5);
      handler.postDelayed(mRunable,1000*60*5);  
    }
    
  @Override
    protected void onDestroy() {
        super.onDestroy();

        handler.removeCallbacks(mRunable);
        handler.removeCallbacksAndMessages(null);
//        handler.removeMessages();
    }

再次使用Android profiler 查看内存,每次页面关闭时都会触发GC,内存有明显变化。

LeakCanary也没有内存泄漏的通知。

感谢前辈们的分享链接:
http://gityuan.com/2015/12/26/handler-message-framework/
http://www.10tiao.com/html/227/201711/2650241824/1.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章