从入坑到放弃——Android消息机制

从入坑到放弃是一个系列,讲述的是一个Android开发者在工作和学习时的悲惨命运,也希望读者在看文章 的时候能抱着同情的心情,自备纸巾。有钱的捧个钱场,没钱的借钱捧个钱场并双击666。

下面进入正题,Android消息机制,相信每个android开发者在平时工作中都会经常用到的。Handler是一个耳熟能详的词,也是在工作中需要交互的一个类。消息机制的存在就是为了解决Android系统中子线程中不能更新UI的矛盾,主线程(即UI线程)不能执行耗时的操作,比如经常提到的下载,否则会触发系统的ANR。所以耗时的操作就不得不放到主子线程去执行,如果想把下载的结果体现到View上,又不能直接更新,这时就需要Handler从子线程给主线程发送消息了。先来看一个简单的小“栗子”吧:

xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.transsion.zuochenzhang.testhandler.MainActivity">
    <ProgressBar
        android:id="@+id/progress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:progress="0"
        android:max="100"
        style="?android:attr/progressBarStyleHorizontal"/>

    <Button
        android:id="@+id/bt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="模拟进度"
        />    
</LinearLayout>     

MainActivity

public class MainActivity extends AppCompatActivity {

    private ProgressBar progressBar;
    private Button button;
    private int count;

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            progressBar.setProgress(msg.what);
        }
    };
        @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        progressBar = (ProgressBar)findViewById(R.id.progress);
        button = (Button)findViewById(R.id.bt);
        button.setOnClickListener(new TestProgressBar());

    }

    class TestProgressBar implements View.OnClickListener{

        @Override
        public void onClick(View v) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Timer timer = new Timer();
                    timer.schedule(new TimerTask() {
                        @Override
                        public void run() {
                            if(count == 100){
                                return;
                            }
                            count = count + 10;
                            Message msg = new Message();
                            msg.what = count;
                            handler.sendMessage(msg);
                        }
                    },0,1000);
                }
            }).start();
        }
    }
}

这里我搞了一个ProgressBar和一个Button,当我点击button的时候,使用计时器模拟了一个下载的动作,每隔一秒就去更新progressBar的进度,0-100每次更新10,这个10 就是通过handler从子线程传递给了主线程,然后更新UI。
在整个消息机制的体系下,是通过Handler,Looper以及MessageQueue它们之间相互协作来完成一整套工作的。首先请欣赏来自灵魂画手的一幅作品

工作原理

ThreadLocal与Looper
首先说一下ThreadLocal不是线程,它是保证线程之间互不干扰的保存数据。理论上在使用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;
    }

在handler的构造方法当中可以看到,会判断Looper.myLooper是不是空,如果空的话就会抛异常

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

这里能看到looper是从threadlocal中获取出来的,那么肯定会地方会set

    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));
    }
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

在looper的prepare方法里看到了set和具体的set实现,这里能看出来在set同时也获取了当前的线程,根据当前线程获取到了一个ThreadLocalMap,这样就能保证不同线程之间保存的数据互不干扰。最后map.set
时候key就是当前的threadlocal对象,values就是要保存的数据,这里指的就是looper对象。所以其实使用handler的正确姿势应该是这样的:

        Looper.prepare();
        Handler handler = new Handler();
        Looper.loop();

可是平时在使用handler的时候为什么没有这么做呢,这时候要去ActivityThread.java中去寻找答案了

    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();
        ...省略部分
        Looper.prepareMainLooper();
    }

在ActivityThread的main方法中已经准备了一个looper

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

到这里又看到了myLooper方法,那么它自然是从threadlocal中获取出来的,所以在UI线程android系统已经为我们准备了looper。如果是在子线程中,就要老老实实的自己准备looper

MessageQueue
MessageQueue即为消息队列,准备好了looper,looper的作用是不断轮询MessageQueue的,那下面就看下轮询的时候MQ(之后MessageQueue简称为MQ)都做了什么。MQ虽然叫作消息队列,但是它的实现其实是用链表来完成的。因为MQ主要就是两个功能,一个是插入数据,一个是删除数据,那么自然链表在这两个功能上效率是高的。

    boolean enqueueMessage(Message msg, long when) {
        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) {
             // 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;
              for (;;) {
                 prev = p;
                 p = p.next;
                 if (p == null || when < p.when) {
                     break;
                 }
                 if (needWake && p.isAsynchronous()) {
                     needWake = false;
                 }   
               }
                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;
    }                 

enqueueMessage即为消息插入到MQ中的操作

    Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        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();
            }  
            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) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        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;
                } 
            之后的省略...
          }
      }              

next方法里面的for循环是个无限循环,如果有新消息就return消息并从链表中删除此条消息,如果没有就阻塞

工作流程
了解完他们各自的工作原理,最后来梳理一下handler整个的流程。首先Looper.prepare去准备了一个looper,将创建的looper存到了threadlocal当中。然后我们可以创建Handler,在new Handler时候取出了looper,给MQ赋值等,参考最上面的源码。最后执行Loop.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
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

            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.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            省略之后代码...
          }
        }      

loop方法中,不断循环MQ,判断是否有新消息,如果有最后调用msg.target.dispatchMessage();这里要注意,这句代码就是真正调用发送消息的代码,是在Looper.java里执行的,而不是在Handler.java里,msg.target就是handler对象。当我们执行handler.sendMessage()的时候,MQ中就是有新消息,执行msg.target.dispatchMessage();

    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }
    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

从这里回调了handleMessage(msg),就可以接受发送的消息,然后处理自己的业务逻辑。再提一句就是无论以哪个方法来放松消息最后都会调用sendMessageAtTime方法来插入到MQ中,如果不相信读者可以自己点一点源码看看

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

以上说的就是Android消息机制的整个一套原理和工作流程,欢迎各位读者大神们多提意见。写的头晕眼花,要去吃饭恢复体能了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值