IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android的消息机制 Handler、MessageQueue、ThreadLocal、Looper -> 正文阅读

[移动开发]Android的消息机制 Handler、MessageQueue、ThreadLocal、Looper

handler的作用:
??具体来说,有时候需要执行一个比较耗时的工作,在主线程执行是非常不友好的,于是我们从子线程中去执行,可能是读取文件也可能是网络请求。当子线程中的工作完成以后可能需要在页面上显示信息,但在Android的规则中,不能在子线程中更新ui,这个时候就可以通过handler切换到主线程中进行ui操作。简单的说,将一个任务切换到某个指定的线程中去执行。

??Android的消息机制主要是指Handler的运行机制,Handler的运行需要MessageQueue和looper的支撑。MessageQueue的中文名称叫消息队列,但是MessageQueue的内部实现不是真正的队列,本质上是单链表(数据结构)。Looper中文名称叫循环,会循环的查找MessageQueue中是否有消息,有的话就去执行,没有则等待。Looper中有ThreadLocal,作用是在每个线程中存储数据。

??Handler创建时会采用当前线程的Looper来构造消息循环系统,问:Handler内部如何获取到当前线程的Looper?
??答:ThreadLocal可以在不同的线程中存储并提供数据,且各个线程的数据互不干扰。通过ThreadLocal获取每个线程looper。

??问:为什么可以在主线程中使用Handler?
??答:主线程是ActivityThread,ActivityThread被创建时就会初始化Looper。

ThreadLocal的工作原理

??ThreadLocal是一个线程内部的数据存储类,能够从指定的线程中获取相应的数据,对于其他线程来说无法获取不是自己的数据。
??使用场景,当某些数据是以线程为作用域并且不同线程具有不同的数据副本时,可考虑使用ThreadLocal。还有一种使用场景,复杂逻辑对象下的对象传递,如下。

??监听器传递的场景,一个线程中函数调用栈比较深以及代码入口的多样性,还要监听器能够贯穿整个线程。这情况使用ThreadLocal可以让监听器作为线程内的全局对象,通过get方法就可以获取到监听器。
??如果不使用ThreadLocal,第一种是将监听器通过参数的方式在函数调用中进行传递。第二种则是将监听器作为静态变量供线程访问。第一种方法的问题是当调佣栈很深的时候,通过参数来传递监听器对象是非常不友好的,不是一个好的程序设计。第二种方法的问题是不具有可扩充性,如同时有两个线程执行,就需要提供两个静态的监听器对象,多个线程就需要提供多个静态的监听器。

??下面通过代码来熟悉ThreadLocal。

//        booleanThreadLocal.set(true);
//        Log.d("ThreadLocal", Thread.currentThread().getName() + ",booleanThreadLocal:" + booleanThreadLocal.get() + "");

        new Thread("Thread#0") {
            @Override
            public void run() {

                booleanThreadLocal.set(true);
                Log.d("ThreadLocal", Thread.currentThread().getName() + ",booleanThreadLocal:" + booleanThreadLocal.get() + "");
            }
        }.start();

        new Thread("Thread#1") {
            @Override
            public void run() {

                booleanThreadLocal.set(false);
                Log.d("ThreadLocal", Thread.currentThread().getName() + ",booleanThreadLocal:" + booleanThreadLocal.get() + "");
            }
        }.start();

        new Thread("Thread#2") {
            @Override
            public void run() {

                Log.d("ThreadLocal", Thread.currentThread().getName() + ",booleanThreadLocal:" + booleanThreadLocal.get() + "");
            }
        }.start();

结果如下

2022-02-27 18:15:15.417 6191-6230/cn.study.study20220109 D/ThreadLocal: Thread#0,booleanThreadLocal:true
2022-02-27 18:15:15.418 6191-6231/cn.study.study20220109 D/ThreadLocal: Thread#1,booleanThreadLocal:false
2022-02-27 18:15:15.421 6191-6232/cn.study.study20220109 D/ThreadLocal: Thread#2,booleanThreadLocal:null

??上面的代码主要体现了在不同的线程中,取到的值也是不同的。重点说明一下,看到上面的注释代码了吗,如果去掉注释运行,得到的日志与我所在书籍上的日志有差异,Thread#2不会是null,而是会与主线程中的booleanThreadLocal的值保持一致。去掉注释后,日志如下:

2022-02-27 18:22:39.019 10297-10297/cn.study.study20220109 D/ThreadLocal: main,booleanThreadLocal:true
2022-02-27 18:22:39.020 10297-10335/cn.study.study20220109 D/ThreadLocal: Thread#0,booleanThreadLocal:true
2022-02-27 18:22:39.021 10297-10336/cn.study.study20220109 D/ThreadLocal: Thread#1,booleanThreadLocal:false
2022-02-27 18:22:39.021 10297-10337/cn.study.study20220109 D/ThreadLocal: Thread#2,booleanThreadLocal:true

??ThreadLocal为何能够在不同的线程中获取的值不一样,因为不同的线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,从数组中根据当前ThreadLocal的索引去查找对应的value值。由于Android系统的升级,部分代码可能发生了改变,在新的代码中通过get方法获取数据,取出的是ThreadLocalMap,ThreadLocalMap有Entry数组,从数组中获取Entry,在Entry中获取value。

消息队列的了解

??MessageQueue主要包含两个操作:插入和读取。插入和读取对应的方法是enqueueMessage和next,enqueueMessage的作用是往消息队列中插入一条消息,next的作用是从消息队列中取出一条消息并将其从消息队列中移除。说明一下next方法是一个无限循环的方法,如果MessageQueue中有消息则执行,没有则处于等待状态,会阻塞。

Looper的工作原理

??Looper会不停地从MessageQueue中查看是否有新消息,如有新的消息会立即处理,没有消息则会一直阻塞。

??如何为一个线程提供Looper,代码如下:

  new Thread("thread#3") {

            @Override
            public void run() {
                super.run();
                Looper.prepare();
                Handler handler = new Handler();
                Looper.loop();
            }
        }.start();

??说明一下Looper中的几个方法,quit与quitSafely方法的作用是退出Looper,二者的区别是:quit会直接退出Looper,quitSafely是提出退出的意图,直到消息队列中的已有消息全部处理完毕才退出。就上面的在一个线程中提供Looper也是需要通过quit来退出的,否则这个线程会一直处于等待的状态。

Handler的工作原理

??Handler的工作主要包含消息的发送和接收过程。其中post的一系列方法最终通过send的一系列方法来实现。具体代码:

public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }

    public final boolean sendEmptyMessage(int what)
    {
        return sendEmptyMessageDelayed(what, 0);
    }

    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

    public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageAtTime(msg, uptimeMillis);
    }

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public boolean sendMessageAtTime(@NonNull 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(@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);
    }

??通过源码发现,Handler发送消息的过程只是向消息队列中插入一条消息。消息会由Looper传递给Handler进行处理,Handler的dispatchMessage方法被调用。dispatchMessage源码如下:

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

??上面的代码很简单,只说一下关键点,Message 的callback是一个Runnable对象,实际上就是Handler的post方法所传递Runnable参数。mCallback 是一个接口。

Handler handler1 = new Handler(callback);

??Callback的意义是创建一个Handler的实例但不需要派生Handler的子类。

主线程的消息循环

??Android的主线程是ActivityThread,主线程的入口方法为main,通过Looper.prepareMainLooper()来创建主线程的Looper和Message,通过Looper.loop()来开启主线程的消息循环。看到这里是你是否会想起给一个线程中的handler提供looper的例子,非常相似。

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

        // Install selective syscall interception
        AndroidOs.install();

        // 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();

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        // Call per-process mainline module initialization.
        initializeMainlineModules();

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        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.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-03-03 16:26:11  更:2022-03-03 16:29:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 16:41:05-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码