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

来源:互联网 发布:药品销售数据库 编辑:程序博客网 时间:2024/06/05 05:15

从入坑到放弃是一个系列,讲述的是一个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.loop();        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消息机制的整个一套原理和工作流程,欢迎各位读者大神们多提意见。写的头晕眼花,要去吃饭恢复体能了。