Android 中消息传递那点事(上)

前言

2019年8月29日,以前都是在笔记上记录学习的知识,总感觉是一家之言,心血来潮,还是想写一点东西让大家看看,也是对自己能力的提高。

今天不是很忙,抽出时间来复习一下学习过的知识,又加深了印象,同时也学习了一些新的东西,在这里整理出来,有不足的地方,希望大家指出,文章仅供学习,记录,交流,转载请注明出处

这节说一下基本原理,下讲源码。

psvm

说Android消息传递之前,我们说一些相关的事情。

在刚刚接触android时,我们都会有一个问题,明明是java写的,为什么没有找到熟悉的psvm?

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

其实Android是有的,位于ActivityThread类中,我们先不看内容代码。

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

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

到了这里,可能也就明白,那我为什么会提出psvm这个问题呢,是因为我们在后面确实会用到。

明确前提

android中的线程

Android中的线程主要分为两种:

  • UIThread,即主线程,又称UI线程;
  • 另一类是WorkerThread,即自定义线程,或者称为工作线程。

一个Android进程运行起来后,JVM自动启动一个线程,即主线程(也就是UI线程),在这个线程里原则上可以进行任何合法操作。但在sdk13以后,规定不能再主线程中进行访问网络的操作,因此就需要我们新开一个线程来完成网络操作。

关于子线程能否更新UI

不可以,子线程访问UI并发访问,导致UI线程不安全,所以只能在主线程中更新UI

系统为什么不对UI控件加上访问锁?缺点有俩:

  • 锁的机制会让UI访问的逻辑变得复杂
  • 降低UI访问的效率,锁的机制会阻塞某些线程的执行

鉴于以上缺点,Android采用单线程模型来处理UI操作,然后通过Handler切换UI访问的执行线程

如果在其它线程访问UI线程,Android提供了以下的方式:

1.Activity.runOnUiThread(Runnable)
2.View.post(Runnable) / postDelayed(Runnable, long)
3.Handler

子线程更新UI错误提示

如果非要在子线程进行UI的更新,会出现

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6581) 
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:924)

如图也可以看出来,检测是由ViewRootImpl的checkThread方法

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

通过这个错误我们发现,自线程是可以更新UI的,因为他是说,只有创建该控件的线程可以更新他,而我们绝大部分创建控件都是在主线程,所以更新也就需要在主线程,也就有了只有主线程才能更新UI的说法了

涉及到的对象

Handler

  • 定义:Message的处理者,发送者
  • 作用:将一个任务切换到某个特定的线程中去执行(负责发送Message到消息队列 处理Looper分派过来的Message)

Message(消息)

  • 定义:Handler接收和处理的消息对象,数据的包装结构
  • 作用:通信时相关信息的存放和传递

MessageQueue(消息队列)

  • 定义:存储结构并不是真正的队列,而是采用单链表的数据结构存储消息列表
  • 作用:消息的存储单元,并不处理消息
    • 队列:先进先出,前段删除,后端进入
    • 单链表:单向链式存储的线性表

Looper(消息循环器)

  • 定义:扮演Message Queue和Handler之间桥梁的角色
  • 作用
    • 消息循环:循环取出Message Queue的Message 有消息就处理消息,没有就一直等待
    • 消息派发:将取出的Message交付给相应的Handler
  • 存储于:ThreadLocal
    • 定义:线程内部的数据存储类
    • 作用:并不是线程,作用域于线程,可以在每个线程中互不干扰的存储、提供数据,可以轻松的获得每个线程的Looper

handler和Looper的纠缠

  • 使用handler必须有Looper,否则会出错
    • 主线程默认创建了Looper
    • 子线程默认没有Looper
    • 没有Looper会出现错误
java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()

Looper类中给出了示例,正确的使用Handler需要进行如下操作。

class LooperThread extends Thread {
        public Handler mHandler;
  
        public void run() {
            Looper.prepare();
  
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
            };
  
            Looper.loop();
       }
}

有人说了,那为啥我不创建Looper也一样能使用,那是因为你在主线程中,我们之前在psvm时提到的代码这时就用到了,我剔除了一些其他代码,保留了相关代码。

 public static void main(String[] args) {

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

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

在精简一下,你会发现和上面的示例已经不能在像了

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

我的理解

在我们最开始关心线程的问题,肯定是需要根据数据去更新界面,但是又不是在主线程中,然后才意识到handler的存在,我们刚开始一般都会这样使用

public class XXXActivity {

   // 步骤1:匿名内部类方式创建handler实例
    private Handler mhandler = new mHandler(){

        // 步骤2:通过复写handlerMessage() 从而确定更新UI的操作
        @Override
        public void handleMessage(Message msg) {
         ...// 需执行的UI操作
            
        }
    };

    // 步骤3:创建所需的消息对象
        Message msg = Message.obtain(); // 实例化消息对象
        msg.what = 1; // 消息标识
        msg.obj = "AA"; // 消息内容存放

    // 步骤4:在工作线程中 通过Handler发送消息到消息队列中
        mHandler.sendMessage(msg);
}

看完涉及的对象,以及handler和Looper之间的关系,结合我们以前简单使用的handler的方式(先不考虑这样使用造成警告,导致内存泄漏的问题),我简单说一下我的理解

首先我们在工作线程获得了新的数据,但是不允许更新ui,我们只能去使用系统提供的handler,我们需要创建handler对象,但是使用handler需要先获得looper,我们目的是更新主线程,所以在主线程调用无参数的构造方法创建handler,这个构造函数默认调用当前线程的Looper。

我们做个比喻,handler只进行消息传递和处理,可以看成主审官员,数据就是一个个待处理的囚犯,Message是包装数据的,毕竟数据身份比较复杂,无论是大员还是乞丐,都要一致对待,也就是都要穿囚服,MessageQueue是一个存放消息的队列,就是审讯囚犯的通道,来的消息需要按顺序执行,大家不能插队是吧,Looper就是侍卫,毕竟handler有身份,需要有人来把囚犯带过来,有人说了,不是handler发送的消息么,主审官员需要提犯人,说我要审谁谁谁,毕竟这个数据是给当前handler处理。然后handler就可以坐在椅子上等待审讯了,Looper侍卫一遍一遍从MessageQueue中循环着拿出Message交给Handler进行处理。

消息传递流程

这里采用了网上一张图
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值