Android 消息机制源码分析

本篇文章涉及到的源码版本:Pie 9.0.0_r3

消息机制的由来

在开始之前,不知道大家有没有思考一个问题,就是大家都知道 Android 只能在主线程修改 UI,那么 Android 为什么要这样设计呢?为什么不可以在子线程中更新 UI 呢?

Android 和 Swing 等 UI 框架一样,都是采用单线程模型来处理 UI 事件,目的就是通过线程封闭性来避免线程并发问题

详细的说,如果子线程都可以修改 UI,那么势必就要让所以 UI 组件都要实现线程安全,要么加锁,要么通过其他方式,这样势必会增加很大的开发难度,并且锁机制也会降低 UI 访问效率,可以说得不偿失。

所以,Android 将访问 UI 操作限制在了一个主线程中操作,如果检测到非主程就会抛出异常。

既然如此,那么当我们在子线程操作完耗时任务后,就需要将更新 UI 的任务切换到主线程中执行。此刻消息机制就排上用场了,通过调用 Handler 的 sendXXX()、postXXX() 方法可以将任务切换到主线程中执行。所以消息机制主要就是 Handler 的运行机制。

Handler

Handler 在消息机制中扮演的角色,就是负责发送消息和接收消息。我们可以通过 sendXXX()、postXXX() 等方法发送消息,这两种方法无论是否是直接传入一个 Message,最终都会包装成一个 Message 对象,然后进入到 Looper 的消息队列中去,然后排队等待处理,Looper 收到消息后,就会再分发给 Handler 进行处理。

创建 Handler

Handler 在创建时默认会使用当前线程的 Looper,如果当前线程没有(例如我们新创建的子线程),那么就会抛出异常。因此想在子线程中使用 Handler,要么创建一个新的 Looper,要么使用已经创建好的 Looper(例如主线程中的)。

默认无参创建 Handler 时最后会走到该构造方法:

public Handler(@Nullable 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());
        }
    }
    // 使用当前调用该方法线程的 looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        // 当前线程没有 looper 就会抛出异常
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

Looper#myLooper:

public static @Nullable Looper myLooper() {
    return sThreadLocal.get(); // 通过 ThreadLocal 获取每个线程各自独享的 Looper(Thread: 这是我独享的 moment)
}

可以看到在该构造方法中,对当前是否有 Looper 进行了校验,从而也可以看出来 Looper 非常重要。

再来看看 callback 这个参数,Callback 是一个接口,这个参数的作用是,当我们不想通过继承 Handler 来使用 Handler 时,就可以实现接口并传入 Handler,例如下面的例子,使用效果和派生 Handler 子类一样:

private val mHandler2 = Handler(Handler.Callback {
  // 收到发送来的消息
  ......
  true // 返回一个 boolean 值,返回 true 表示不需要进一步的处理
})

第二个 async 参数,默认都是 false,如果为 true 时,当消息进入消息队列时,会将 Message 的 setAsynchronous() 方法设置为 true。

这就是创建 Handler 时发生的事情,其实很简单,就是获取了 Looper 和 Looper 中的消息队列,而 callback 和 async 默认都是 null 和 false,用处不大。

发送消息

Handler 中有很多发送消息的方法,还可以发送延时消息,这里就不一一列举了。不管是用哪一种发送消息的方法,即使不是直接传入一个 Message,最终也都会包装成一个 Message 对象,然后调用入消息队列的方法将该 Message 对象添加到消息队列中去:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

这里有个重点,就是 Message 对象持有了 Handler 的引用,这也就是为啥 Handler 可能会发生内存泄露的原因之一。

当我们创建非静态的 Handler 内部类时,会隐式持有外部类的引用,假如我们是在 Activity 中创建的 Handler,那么就会隐式持有 Activity 的引用,这时如果发送了一个延迟消息,那么这个消息也就是 Message 对象就会持有这个 Handler 的引用,这时如果退出了 Activity 就会导致内存泄露,因为 Handler 持有了 Activity 的引用,而 Message 又持有了 Handler 的引用,并且 Message 还在队列中未被处理。

解决方法很简单,只要将 Handler 定义为静态内部类,断开和 Activity 的引用即可。如果需要用到 Activity,那么在传入 Activity 的引用到 Handler 时,可以使用弱引用持有。

入队时调用的是 MessageQueue 的方法,这块在 MessageQueue 中进行讲解。

MessageQueue

MessageQueue 是一个存放 Message 对象的队列,在 Looper 创建时就会创建一个对应的 MessageQueue。MessageQueue 的构造参数如下:

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

quitAllowed 这个参数用于指定是否允许退出,有这个参数主要是因为主线程很特殊,主线程创建的 Looper 在创建 MessageQueue 时传入的就是 false,不允许调用 MessageQueue 的退出方法退出。而其他线程,都是 true,允许退出的。

在调用 MessageQueue 的 quit(boolean safe) 方法时,需要传入一个 safe 参数,表示是否安全的退出,如果是 true,那么只会设置一个退出的标志,然后等队列中的消息都处理完毕后再退出,如果是 false,则直接遍历整个队列回收未处理的消息。退出就是将头结点 mMessages 置为了 null,然后 Looper 读取消息时如果读取到 null 时就会退出循环,这里利用了并发编程中的“毒丸”设计思想,mMessages 为 null 时就是一个“毒丸”,当 Looper “吞食”了“毒丸”后就死去了。

再来看看 MessageQueue 的入队方法 enqueueMessage():

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) {
        // 当退出的标志为 true 时不再加入新消息到队列,而是调用回收方法,并返回 false 表示入队失败
        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;
}

这个方法在入队时,主要是判断了是否设置了延迟的参数 when(当我们调用一些延迟的发送消息方法,最终延迟时间就会到该参数),如果没有延迟,那么直接作为新的头节点,这样在处理时就会优先处理。而如果设置了延迟时间,那么就会与队列中的 Message 的延迟时间进行一一比较,插在延迟时间刚好比自己小的消息后面。从而在消息处理的时候,是按照时间的顺序进行处理的。

Looper

Looper 的作用就是让当前线程循环起来,并在循环中从 MessageQueue 阻塞队列中不断尝试获取 Message,如果有,则将消息回调给 Handler 处理,如果没有,则阻塞直到下一条消息到来。

先来看看 Android 主线程是如何创建 Looper 的,主线程创建 Looper 是在 ActivityThread#main 方法中:


    public static void main(String[] args) {
        ......

        Looper.prepareMainLooper();
        ......
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        ......
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

通过上面的源码可以看到,主线程通过 prepareMainLooper() 方法创建了 Looper,并且在最后调用了 Looper 的 loop() 方法让线程开始进入“死”循环中,避免线程结束后进程也结束。

并且还可以看到主线程还初始化了一个 MainHandler,对应的是 H 这个类:

    class H extends Handler {
        public static final int BIND_APPLICATION        = 110;
        public static final int EXIT_APPLICATION        = 111;
        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 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 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 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 LOCAL_VOICE_INTERACTION_STARTED = 154;
        public static final int ATTACH_AGENT = 155;
        public static final int APPLICATION_INFO_CHANGED = 156;
        public static final int RUN_ISOLATED_ENTRY_POINT = 158;
        public static final int EXECUTE_TRANSACTION = 159;
        public static final int RELAUNCH_ACTIVITY = 160;
    ...

这个类很长,所以截取了一些,就是接收了和应用系统相关的一些消息,例如创建、停止服务等,然后做对应的处理。

所以,Handler 与 Looper 的关系,其实是一对多的关系,一个 Looper 对应多个 Handler。我们创建的 Handler 在处理消息时,一定不要耗时,否则就会阻塞后面的消息甚至系统相关的消息都无法进行处理。

接下来看看 prepareMainLooper() 方法的实现:

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

先调用了 prepare() 方法:

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

该方法就是往 ThreadLocal 中存储了一个 Looper 对象,并且只允许一个线程存储一次,如果一个线程两次调用该方法就会抛出异常。主线程中创建的 MainLooper,quitAllowed 参数是 false,表示不允许退出。而我们在子线程中调用 prepare() 方法创建 Looper,默认都是 true,允许退出的,这个在上面也讲到过:

public static void prepare() {
    prepare(true);
}

当调用 prepare() 方法创建好后,就赋值给了 sMainLooper 变量,这里又在同步代码块中进行了检验,MainLooper 只能赋值一次。

从上面的源码可以发现,当调用 prepareMainLooper() 后,MainLooper 就准备完毕了。接下来在最后调用了 loop() 方法:

public static void loop() {
    // 先校验当前线程是否有 Looper,如果没有则抛出异常,因此在调用 loop() 方法前必须先调用 prepare() 方法准备好 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;

    ......
    boolean slowDeliveryDetected = false;

    for (;;) {
        // 从消息队列中获取消息,这是一个阻塞方法,当没有消息时线程就会挂起并阻塞在这里
        Message msg = queue.next(); // might block
        if (msg == null) {
            // 当 Message 为 null 时就会退出循环,需要调用 MessageQueue 的 quit() 方法
            return;
        }

        ......
        try {
            // target 为关联的 Handler,调用 Handler 的 dispatchMessage() 方法
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            if (observer != null) {
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ......
        msg.recycleUnchecked();
    }
}

在 loop() 方法中开启了一个无限循环,并在其中不停的从阻塞队列中获取消息,当获取到消息后就交给 Message 对象关联的 Handler#dispatchMessage() 方法处理:

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

首先会将消息交给 msg.callback 处理,这个 callback 是一个 Runnable 对象,当我们调用 post() 方法发送消息时,传入的 Runnable 对象就会被赋值给 callback:

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

所以通过 post 发送的消息,接收消息是直接在 Runnable 中而不是 Handler 的 handleMessage 方法。

通过 send() 方法发送的消息,msg.callback 就为 null,这时会先交消息交给 Callback 处理,这个 Callback 在上面创建 Handler 时就说了。当 Callback 为空时,就会回调 handleMessage() 方法。

到这里,整个消息事件就完成了。

总结

最后总结一下,Handler、Looper、MessageQueue 和 Message 组成了消息机制。

Handler 负责发送消息和接收消息,在创建 Handler 时,当前线程必须有一个 Looper 对象,如果没有,则需要先调用 Looper#prepare() 方法创建一个当前线程的 Looper。在同一个线程中,我们可以创建多个 Handler,但是只能有一个 Looper。

Looper 负责处理发送来的消息,当调用 loop() 方法后就会将当前的线程进入无限循环中,然后在其中获取阻塞队列 MessageQueue 中的 Message 消息,队列中没有消息时就会挂起当前线程并阻塞,有消息时就会交给 Handler 进行处理。可以调用 Looper#quit() 方法退出无限循环,该方法最终会将 MessageQueue 中的头结点赋值为 Null,这样当 Looper 取到为 Null 的消息时就会退出循环。

MessageQueue 是一个消息队列,负责存储发送来的消息。而 Message 就是消息本身,其实现了 Parcelable 接口,可以将需要传递的数据存储到其中。

已标记关键词 清除标记
1、andbatdog电池监控 难度系数最小 Android Battery Dog 是 Android 平台上用来监控电池电量的服务软件,它生成电池记录文件:/sdcard/BatteryDog/battery.csv ,该文件包含时间、电量信息、温度和电压以及一些简单的图形。 项目就四个源码文件: BatteryDog_Service 继承了服务Service(后台运行和跨进程访问) 创建了一个线程负责输出信息到文件 注册了广播接收器ACTION_BATTERY_CHANGED BatteryDog 继承了Activity 布局battery_dog 文件 主要负责启动/关闭服务Service 和分析数据然后相关控件显示出来 BatteryGraph 继承了Activity 动态布局 显示相关画面 Log 负责输出显示信息 2、Droid Wall 手机防火墙 Droid Wall - Android Firewall 是一个类似于 Linux 下的 iptables 的 Android 手机防火墙软件,允许你限制某些应用访问数据网络,包括 2G/3G 以及 Wi-Fi。 项目共六个文件: Api 包含共享的编程接口。处理所有ip(可用)的“沟通”这个类别。 这是很重要的类。 BootBroadcast 广播接收机,设置在系统启动时的iptables规则。这是必要的,因为这些规则是不持久的。 HelpDialog对话框中显示的“帮助”菜单选项被选中时。 MainActivity 主界面 功能实现部分 PassDialog对话框中显示要求输入密码。 StatusWidget 构件实现的ON / OFF 部件状态 3、jchat4android手机聊天程序 (内含开发文档) Android jChat 是一个 Android 手机上基于位置的聊天软件,采用P2P通讯机制。 为了编译jChat,你要使用Eclipse创建一个新的Android项目,然后添加外部JAR和移动的jChat目录选择到libs目录JadeLeapAndroid.jar库。jChat使用了的MapView访问谷歌地图数据。 本项目有25个目标文件。项目里面有代码注释以及开发文档。 4、zz-doctor中医大夫助理信息系统 辅助中医大夫储备药方、药名药理备查。让大夫能腾出精力集中诊断分析, 而不必消耗精力去记忆琐碎的细节。平时有空方便时自己录入储备或完善经验方。 更有利于传承。 系统架构设计:1. PDA: GUI(Android) +Embedded DB (SQLite) ;2. (可配置)定期提示大夫备份数据到附加储存卡。 本项目有17个目标文件。 DbHelper 负责数据库的管理 功能有 执行sql语句 以及 升级等 ZZ 程序全局类 继承Application MedicineDetail 医药详情介绍 MedicineQuery 医药的查询 RxRecipeDetail 接收方详情 RxRecipeDetailEdit 接收方编辑详情 WelcomeRxRecipeQuery 欢迎接收方查询 IdentifiedString 标识结构类 IdentifiedAutoCompleteTextView 自动匹配标识的信息 IdentifiedStringAdapter 标识信息适配器 UnitAdapter 单位组适配器 ZzUtil 单位类 MedicineSQLBuilder 医药数据库管理类 RecipeMedicineMapSQLBuilder 接收方医药信息对应的数据库类 SQLBuilder 数据库编辑器 根据参数选择不同的编辑方式 含main方法 SubjectSQLBuilder 项目数据库的编辑器 由上分析可以得出,该软件实现了数据库的操作,界面布局不复杂。 5、一款查询软件(身份证号,号码归属等)源代码 (个人觉得这是一款开源软件) 此实例非常好,非常使用,在开发中可以借鉴啊。 本项目有24个目标文件。 BaseActivity 头部bar的布局 BaseLayout 头部bar的布局 DataListHolder 就两个成员 ImageView TextView 你说干嘛呢 ItemAdapter 继承适配器 QueryAddress 继承BaseActivity 实现地址查询 QueryIDCard 继承BaseActivity 实现身份证查询 QueryPhone 继承BaseActivity 实现手机号查询 SmallToolsActivity继承BaseActivity 实现主界面布局 Splash 继承Activity 实现闪烁延时效果 Update
相关推荐
©️2020 CSDN 皮肤主题: 黑客帝国 设计师:白松林 返回首页