从入坑到放弃——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消息机制的整个一套原理和工作流程,欢迎各位读者大神们多提意见。写的头晕眼花,要去吃饭恢复体能了。
- 从入坑到放弃——Android消息机制
- Android——消息机制
- Android——消息机制
- Android的消息机制—Handler机制
- 从手机来电分析android消息机制
- 从手机来电分析android消息机制
- 从源码角度解析Android消息机制
- [Android] 从源码分析 Handler 消息机制
- Android 从源码看Handler消息机制
- Android源码 从runOnUiThread聊聊消息机制
- Android 从源码分析Handler消息机制
- 从源码看Android消息机制
- android消息机制——Handler类
- Android——Handler异步消息机制
- Android消息机制解析——Handler
- Android的消息机制——概述
- kotlin——从入门到放弃
- Android消息机制 — Handler-Looper-MessageQueue
- 【牛客网】回文串
- CF 888E Maximum Subsequence 折半搜索.
- Redis与java结合使用
- Python3与OpenCV3.3 图像处理(四)--色彩空间
- Shiro (六) RememberMe
- 从入坑到放弃——Android消息机制
- 代理模式学习笔记
- maven+nexus的创建和常见操作
- 将数据以二进制和URL的方式存入数据库并且请求出json
- Flashtext:大规模数据清洗的利器
- 基于Spring MVC和Tomcat服务器的JavaWeb项目----入门篇
- 笔试题 shell
- 剑指offer——面试题14:调整数组顺序是奇数位于偶数前面
- 【JAVA基础】static关键字