当前位置: 首页 > news >正文

Android中的消息机制

提到Android中的消息机制,首先想到的必定是Handler,Looper和MesageQueue,我们在开发中接触到的最多的就是Handler,至于Looper和MessageQueue我们是知道的,但是并不会对其进行操作,最多的用法就是在子线程请求数据,然后在UI线程创建一个Handler,用创建的Handler在子线程发送消息,然后在UI线程的接受消息并更新UI。然而我们还是会碰到一些问题的,比如为什么不能再子线程创建Handler(其实是可以的),以及为什么多线程能够解决UI线程的阻塞问题。 Android应用在启动时,默认会有一个主线程(UI线程),在这个UI线程中会关联一个消息队列,所有的操作都会被封装成消息然后交给主线程来处理。一个完整的消息队列必须包含Handler ,Looper,MessageQueue,Handler是需要我们创建的,而UI线程的线程循环Looper是在ActivityThread.main()方法中创建的,贴出部分源码如下:

public static void main(String[] args) {
   //省略代码
      Looper.prepareMainLooper(); //1.创建消息队列

      ActivityThread thread = new ActivityThread();
      thread.attach(false);

      if (sMainThreadHandler == null) {
          sMainThreadHandler = thread.getHandler();//创建UI线程中的Handler
      }

      if (false) {
          Looper.myLooper().setMessageLogging(new
                  LogPrinter(Log.DEBUG, "ActivityThread"));
      }

      // End of event ActivityThreadMain.
      Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
      Looper.loop(); //2.执行消息循环

      throw new RuntimeException("Main thread loop unexpectedly exited");
  }
复制代码

执行过ActivityThread.main()方法后应用程序就启动了,Looper会一直在消息队列中拉取消息,然后处理消息,使得整个系统运行起来。那么消息又是如何产生的,又是如何从队列中获取消息并且处理消息的?答案就是Handler。例如在子线程发出消息到UI线程,并且在UI线程处理消息,其中更新UI的Handler是属于UI线程。然而更新UI的Handler为什么必须属于UI线程,我们打开Handler的相关的源代码看一下:

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());
          }
      }
//1.获取到Looper
      mLooper = Looper.myLooper(); 
      if (mLooper == null) {
          throw new RuntimeException(
              "Can't create handler inside thread that has not called Looper.prepare()");
      }
//2.获取到消息队列
      mQueue = mLooper.mQueue; 
      mCallback = callback;
      mAsynchronous = async;
  }
复制代码

从Handler的构造函数可以看到,通过Looper.myLooper()得到Looper,然后我们看一下myLooper这个方法:

/** * Return the Looper object associated with the current thread.  
Returns   null if the calling thread is not associated with a Looper. */
public static @Nullable Looper myLooper() {    
return sThreadLocal.get();
}
复制代码

可以看到这里返回了一个和当前线程关联的Looper,而这个Looper又是在什么地方和sThreadLocal关联起来的呢?

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

    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 static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

复制代码

可以看到在我们上面提到的prepareMainLooper() 方法中,调用了prepare()方法,在prepare()方法中创建了一个Looper并且和sThreadLocal关联起来( sThreadLocal.set(new Looper(quitAllowed));),通过这一个步骤消息就和线程关联了起来,不同线程之间就不能彼此访问对方的消息队列了。消息队列通过Looper和线程关联起来,而Handler又和Looper关联起来,而UI线程中的Looper是在ActivityThread.main()方法中创建的,当我们单独开启一个线程的时候,由于并没有创建Looper,所以在创建Handler的时候会抛出“Can't create handler inside thread that has not called Looper.prepare()”的异常,知道了这个异常的原因,我们就可以解决了这个异常了。

  new Thread(){
            Handler mHandler = null;

            @Override
            public void run() {
                super.run();
                //1.为当前线程传建一个Looper,并且和线程关联起来
                Looper.prepare();
                mHandler = new Handler();
                //2.进行消息的循环
                Looper.loop();
            }
        }.start();
复制代码

总结一下,每个Handler都会关联一个消息队列,每个消息队列都会被封装进Looper中,每个Looper都会关联一个线程,最终就相当于每个消息队列都会关联一个线程。Handler就是一个消息的处理器,将消息投递给消息队列,然后从消息队列中取出消息,并进行处理。默认情况下,只有UI线程中有一个Looper,是在ActivityThread.main()方法中创建的。这也就解释了为什么更新UI的Handler为什么要在UI线程创建,就是因为Handler要和UI线程的消息队列关联起来,这样handleMessage()方法才会在UI线程执行,此时更新UI才是线程安全的。至于Handler是在哪一步关联的Looper的,我们可以打开Handler的构造方法:

 /**
     * Default constructor associates this handler with the {@link Looper} for the
     * current thread.
     *
     * If this thread does not have a looper, this handler won't be able to receive messages
     * so an exception is thrown.
     */
     //默认的构造器,和当前的线程关联起来,若果当前线程没有Looper,则会出现异常
    public Handler() {
        this(null, false);
    }
复制代码

那我们能不能再分线程创建Handler,然后发送消息到主线程呢?继续看Handler的其他构造函数:


    /**
     * Use the provided {@link Looper} instead of the default one.
     *
     * @param looper The looper, must not be null.
     */
    public Handler(Looper looper) {
        this(looper, null, false);
    }
复制代码

我们可以传入一个非当前线程的Looper,来向传入的Looper所在的线程的消息队列发送消息:

public class MainActivity extends AppCompatActivity {

    private static final  String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        testForMianToMain();
    }

    
    private void testForMianToMain() {
        new Thread(){
            @Override
            public void run() {
                super.run();
                //这里不是在主线程创建的Handler,但是我传入的是主线程的Looper,既然是主线程的Looper,那么消息队列就一定是主线程的
                MainHandler mainHandler = new MainHandler(getMainLooper());
                Message mainMessage = new Message();
                mainMessage.obj="Main To Main";
                mainHandler.sendMessage(mainMessage);
            }
        }.start();
    }

    class MainHandler extends Handler{
        public MainHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.e(TAG,msg.obj.toString());
        }
    }
}

复制代码

补充:为什么只能在主线程更新UI?先看一下如果不在主线程更新UI会发生什么错误,会报出如下错误:"Only the original thread that created a view hierarchy can touch its views.",这个错误是在哪里定义的呢?打开源码,可以看到是在ViewRootImpl.java这个类中定义的,ViewRootImpl类是View内部类AttachInfo的一个成员变量,是连接Window和View的桥梁,View在更新页面的时候会调用 checkThread()这个方法检查是否是在主线程,若不在主线程(因为View更新只能在主线程),则会报出"Only the original thread that created a view hierarchy can touch its views."这个错误。

简书迁移过来的文章

转载于:https://juejin.im/post/5a4d71fa6fb9a044ff31dd84

相关文章:

  • Python爬虫入门之Urllib库的基本使用
  • SAML(Security assertion markUp language) 安全断言标记语言
  • Windows 系统变量
  • python学习笔记(四):函数
  • [THUWC 2017]在美妙的数学王国中畅游
  • Spring框架之我见(三)——IOC、AOP
  • 敏捷公关
  • js操作时间(持续更新)
  • 好文分享--java企业架构 spring mvc +mybatis + KafKa+Flume+Zookeeper分布式架构
  • MySQL 千万 级数据量根据(索引)优化 查询 速度
  • mariadb主从复制/半同步复制
  • 我的速读理解
  • Tengine 结合 lua 防御 cc ***
  • 常见高危安全漏洞
  • ajax发送post请求
  • $translatePartialLoader加载失败及解决方式
  • 《Java8实战》-第四章读书笔记(引入流Stream)
  • docker容器内的网络抓包
  • export和import的用法总结
  • LeetCode18.四数之和 JavaScript
  • Linux CTF 逆向入门
  • Median of Two Sorted Arrays
  • nginx 配置多 域名 + 多 https
  • Sass Day-01
  • Spring技术内幕笔记(2):Spring MVC 与 Web
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 搞机器学习要哪些技能
  • 树莓派 - 使用须知
  • 无服务器化是企业 IT 架构的未来吗?
  • 学习ES6 变量的解构赋值
  • 1.Ext JS 建立web开发工程
  • 阿里云ACE认证学习知识点梳理
  • 蚂蚁金服CTO程立:真正的技术革命才刚刚开始
  • ​LeetCode解法汇总2808. 使循环数组所有元素相等的最少秒数
  • # .NET Framework中使用命名管道进行进程间通信
  • (13)[Xamarin.Android] 不同分辨率下的图片使用概论
  • (Matalb回归预测)PSO-BP粒子群算法优化BP神经网络的多维回归预测
  • (翻译)Entity Framework技巧系列之七 - Tip 26 – 28
  • (附源码)spring boot北京冬奥会志愿者报名系统 毕业设计 150947
  • (附源码)ssm航空客运订票系统 毕业设计 141612
  • (三)终结任务
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (终章)[图像识别]13.OpenCV案例 自定义训练集分类器物体检测
  • (转)EOS中账户、钱包和密钥的关系
  • (转贴)用VML开发工作流设计器 UCML.NET工作流管理系统
  • (转载)hibernate缓存
  • .bashrc在哪里,alias妙用
  • .java 9 找不到符号_java找不到符号
  • .NET 3.0 Framework已经被添加到WindowUpdate
  • .net core webapi Startup 注入ConfigurePrimaryHttpMessageHandler
  • .net遍历html中全部的中文,ASP.NET中遍历页面的所有button控件
  • .NET开源的一个小而快并且功能强大的 Windows 动态桌面软件 - DreamScene2
  • .net连接MySQL的方法
  • /*在DataTable中更新、删除数据*/
  • @DataRedisTest测试redis从未如此丝滑