Android消息机制深入(一)

Android消息机制介绍

Android的消息机制主要是指Handler的运行机制, Handler需要底层的MessageQueue, 和 Looper 的支撑.

Handler 机制理解图
  1. MessageQueue : 消息队列, 它的内部可以存储一组消息,并且提供了添加和删除小的接口.虽然名为消息队列但是内部并不是一个队列,而是一个单链表.
  2. Looper : 中文翻译为循环, 由于 MessageQueue 只能保存消息并不能处理消息, Looper就是用来出来处理消息的. Looper会以无限循环的方式去查询MessageQueue中是否有消息.如果有消息就直接处理,否则就一直等待.

注意一 :ThreadLocal 可以在每一个线程中存储数据, 它可以在多个个线程中互不干扰得存储并提供数据. 通过ThreadLocal 可以轻松的获取每个线程的Looper.

注意二 : 新创建的线程默认是没有Looper的.如果需要关联Handler就必须为当期那线程创建Looper.

注意三 : 主线程(UI线程), 他就是ActivityThread, ActivityThread创建的时候就会初始化Looper.因此在主线程中可以直接使用Handler.

为什么要引入消息机制(Handler)

Handler的作用实质上就是将一个任务切换到指定的线程中去执行 . Android提供这一功能主要基于以下几点 :

  • Android规定智能在主线程中更新UI,如果在子线程中更新UI程序便会崩溃. ViewRootImpl类中的checkThread()方法进行了这个工作.源码如下 :
// 检查是否是在主线程中更新UI
void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}
  • Android 不建议在主线程中进行耗时操作, 否则会引发 ANR .

基于以上两点我们就需要在子线程中进行耗时任务,比如:联网获取数据等,然后再跳转到UI线程中更新UI.

  1. 其实Android不允许在子线程中更新UI的一个重要原因是Android的UI控件是线程不安全的.如果多个线程同时操作UI会造成不可预知的后果 .
  2. Android采用锁机制来保证UI控件的线程安全有有两个原因 :

    2.1 上锁会让UI访问的逻辑变的复杂.

    2.2 上锁会降低UI的访问效率.

ThreadLocal 类

ThreadLocal 类是一个线程内的数据存储类. 通过他可以在指定的线程存储数据, 存储以后只能在指定的线程中才可以获取到存储的数据,对于其他线程则无法获取到数据.

测试实例

/**
 * 定义一个Boolean类型的的ThreadLocal.
 */
private ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<>();
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // 1. 在主线程中设置为True
    mThreadLocal.set(true);
    Log.d(TAG, "MainThread : " + mThreadLocal.get());
    // 2. 在线程一种进行操作
    new Thread(new Runnable() {
        @Override
        public void run() {
            mThreadLocal.set(false);
            Log.d(TAG, "Thread-1 : " + mThreadLocal.get());
        }
    }).start();
    // 3. 在线程2中操作
    new Thread(new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "Thread-2 : " + mThreadLocal.get());
        }
    }).start();
}
// 输出
// D/MainActivity: MainThread : true
// D/MainActivity: Thread-1 : false
// D/MainActivity: Thread-2 : null

从上面的例子中可以看出,在不同线程中设置同一个ThreadLoacal但是结果不一样.这就实现了线程范围内的数据约束.

  • set()方法源码
public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取该线程中的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 如果已经有了则直接保存
        map.set(this, value);
    else
        createMap(t, value);// 创建当前线程的ThreadLocalMap对象
}
  • get()方法源码
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    // 创建当前线程的ThreadLocal并且,设置初始值,默认是null.
    return setInitialValue();
}

从ThreadLocal的set和get方法可以看出, 他们操作的对象都是当前线程绑定的那个ThreadLocal副本, 因此在不同的线程中访问同一个ThreadLocal的get和set方法,它们都是在操作当前线程中的ThreadLocal副本.

MessageQueue的工作原理

消息队列在Android中主要是指 MessageQueue ,主要操作如下:

  1. enqueueMessage() : 往消息队列中插入一条消息.就是单链表的插入操作
  2. next() 从消息队列中取出一条消息并将它从消息队列删除. next()方法是一个死循环,如果消息队列中没有消息则next() 方法会一直阻塞在这, 当有消息到来时, next() 方法就会返回这条消息将消息从单链表中删除.

具体实现而已参照源码

Looper的工作原理

Looper 会不停的从MessageQueue中查询是否有消息, 如果有消息就会立即处理,否则就会一直阻塞在那.

Looper构造方法

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

创建和启动Looper

new Thread(new Runnable() {
    @Override
    public void run() {
        // 1. 创建Looper
        Looper.prepare();
        // 2. 操作
        Handler handler = new Handler();
        // 3. 开启Looper, 此方法会阻塞.
        Looper.loop();
    }
}).start();

退出Looper
Looper提供了两个退出方法:

  1. quit : 直接退出Looper.
  2. quitSafely : 设置退出标志,一旦消息处理完则退出.

Looper 的 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.");
     }
     // 获取当前Looper的MessageQueue
     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的next() 方法,该方法是一个阻塞方法.
         Message msg = queue.next(); // might block
         if (msg == null) {
             // 当MessageQueue退出时会返回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 traceTag = me.mTraceTag;
         if (traceTag != 0) {
             Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
         }
         try {
             // msg.target : 是指定的Handler 对象.
             // dispatchMessage : 方法会到指定的线程中执行.讲逻辑切换到指定的线程中.
             msg.target.dispatchMessage(msg);
         } finally {
             if (traceTag != 0) {
                 Trace.traceEnd(traceTag);
             }
         }

         if (logging != null) {
             logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
         }

         // Make sure that during the course of dispatching the
         // identity of the thread wasn't corrupted.
         final long newIdent = Binder.clearCallingIdentity();
         if (ident != newIdent) {
             Log.wtf(TAG, "Thread identity changed from 0x"
                     + Long.toHexString(ident) + " to 0x"
                     + Long.toHexString(newIdent) + " while dispatching to "
                     + msg.target.getClass().getName() + " "
                     + msg.callback + " what=" + msg.what);
         }

         msg.recycleUnchecked();
     }
 }

Handler的工作原理

Handler的工作主要包含消息的发送和接收过程, 消息的发送可以通过postXXX和sendXXX方法, 其实postXXX 是通过 sendXXX 的一系列方法来实现的.

postXXX 是通过sendXXX 实现的,源码如下

public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

public final boolean postAtTime(Runnable r, long uptimeMillis)
{
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}

分析Handler消息发送部分的源码

public final boolean sendMessage(Message msg)
{
    // 调用sendDelayed
    return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

通过源码我们可以最终是调用了MessageQueue的enqueueMessage()也就是网消息队列中插入一个消息.

通过前面对Looper的loop()方法的分析我们知道最终会调用Handler的dispatchMessage,分析下源码

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    // 1. 判断msg 是否提供回调方法
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        // 2. 判断Handler是否有回调 
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 3. 执行Handler的handleMessage
        handleMessage(msg);
    }
}
  • 首先会检查Message的callback 是否为null.

    如果不为null , 就通过 handleCallback 来处理消息. Message的callback是一个Runnable对象,在Message中定义如下 :
Runnable callback;



handleCallback源码

private static void handleCallback(Message message) {
     // 调用run() 方法.
     message.callback.run();
}
  • 其次检查mCallBack是否为null,通过mCallBack的handleMessage(msg)来处理消息,CallBack是一个接口定义如下 :
public interface Callback {
    public boolean handleMessage(Message msg);
}

通过CallBack可以通过如下方式创建Handler对象, Handler handler = new Handler(callback);

Handler消息机制处理流程
  • 最后通过Handler的handleMessage(msg)方法处理消息.

主线程的消息循环

Android的主线程就是ActivityThread, 主线程的入口方法是main如下.

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();
    // Set the reporter for event logging in libcore
    EventLogger.setReporter(new EventLoggingReporter());
    // Make sure TrustedCertificateStore looks in the right place for CA certificates
    final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
    TrustedCertificateStore.setDefaultUserDirectory(configDir);
    Process.setArgV0("<pre-initialized>");
    // 创建主线程的Looper
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }
    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    // 启动主线程的Looper.
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}
  • 通过源码发现有进行如下两步操作
    • 调用 Looper.prepareMainLooper();创建主运行循环.
    • 调动 Looper.loop();开启消息循环.

在ActivityThread中的Handler就是ActivityThread.H 在H中定义了很多消息.主要包括了四大组件的停止和运行等.

private class H extends Handler {
   public static final int LAUNCH_ACTIVITY         = 100;
   public static final int PAUSE_ACTIVITY          = 101;
   public static final int PAUSE_ACTIVITY_FINISHING= 102;
   public static final int STOP_ACTIVITY_SHOW      = 103;
   public static final int STOP_ACTIVITY_HIDE      = 104;
   public static final int SHOW_WINDOW             = 105;
   public static final int HIDE_WINDOW             = 106;
   public static final int RESUME_ACTIVITY         = 107;
   public static final int SEND_RESULT             = 108;
   public static final int DESTROY_ACTIVITY        = 109;
   public static final int BIND_APPLICATION        = 110;
   public static final int EXIT_APPLICATION        = 111;
   public static final int NEW_INTENT              = 112;
   public static final int RECEIVER                = 113;
   public static final int CREATE_SERVICE          = 114;
   public static final int SERVICE_ARGS            = 115;
   public static final int STOP_SERVICE            = 116;
   public static final int CONFIGURATION_CHANGED   = 118;
   public static final int CLEAN_UP_CONTEXT        = 119;
   public static final int GC_WHEN_IDLE            = 120;
   public static final int BIND_SERVICE            = 121;
   public static final int UNBIND_SERVICE          = 122;
   public static final int DUMP_SERVICE            = 123;
   public static final int LOW_MEMORY              = 124;
   public static final int ACTIVITY_CONFIGURATION_CHANGED = 125;
   public static final int RELAUNCH_ACTIVITY       = 126;
   public static final int PROFILER_CONTROL        = 127;
   public static final int CREATE_BACKUP_AGENT     = 128;
   public static final int DESTROY_BACKUP_AGENT    = 129;
   public static final int SUICIDE                 = 130;
   public static final int REMOVE_PROVIDER         = 131;
   public static final int ENABLE_JIT              = 132;
   public static final int DISPATCH_PACKAGE_BROADCAST = 133;
   public static final int SCHEDULE_CRASH          = 134;
   public static final int DUMP_HEAP               = 135;
   public static final int DUMP_ACTIVITY           = 136;
   public static final int SLEEPING                = 137;
   public static final int SET_CORE_SETTINGS       = 138;
   public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139;
   public static final int TRIM_MEMORY             = 140;
   public static final int DUMP_PROVIDER           = 141;
   public static final int UNSTABLE_PROVIDER_DIED  = 142;
   public static final int REQUEST_ASSIST_CONTEXT_EXTRAS = 143;
   public static final int TRANSLUCENT_CONVERSION_COMPLETE = 144;
   public static final int INSTALL_PROVIDER        = 145;
   public static final int ON_NEW_ACTIVITY_OPTIONS = 146;
   public static final int CANCEL_VISIBLE_BEHIND = 147;
   public static final int BACKGROUND_VISIBLE_BEHIND_CHANGED = 148;
   public static final int ENTER_ANIMATION_COMPLETE = 149;
   public static final int START_BINDER_TRACKING = 150;
   public static final int STOP_BINDER_TRACKING_AND_DUMP = 151;
   public static final int MULTI_WINDOW_MODE_CHANGED = 152;
   public static final int PICTURE_IN_PICTURE_MODE_CHANGED = 153;
   public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
}

ActivityThread通过ApplicationThread和AMS进行进程间通信, AMS以进程间通信的方式完成ActivityThread的请求后回调ApplicationThread的Binder方法, 然后ApplicationThread会想H发送消息,将逻辑从ApplicationThread切换到ActivityThread中.这就是主线程的消息循环类型

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容