需要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 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

更多相关文章

  1. Android(安卓)线程模型和 AsyncTask
  2. Android(安卓)消息机制(Handler Looper Message )理解
  3. Android信息推送—AndroidPN的学习(上)
  4. android线程、UI、AsyncTask
  5. Android中MotionEvent的来源和ViewRootImpl
  6. Android的线程详解(几种实现方法及区别)
  7. android log 分析(一)
  8. Android学习笔记——从源码看Handler的处理机制
  9. Android(安卓)远程图片获取和本地缓存策略

随机推荐

  1. android 短信通知和SQLITE 应用
  2. Android自动化测试
  3. Android(安卓)Cocos2d实现:一个图片围绕一
  4. phonegap sqlite (android)
  5. Android SDK开发 -- TitleBar重构 (代理模
  6. android音乐播放器(4)
  7. Android项目:proguard混淆第三方jar.
  8. Android 多个Module使用ButterKnife出现
  9. android studio 上junit4的使用
  10. android弹出消息框