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 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Handler那些事儿(1) -> 正文阅读

[移动开发]Handler那些事儿(1)

文章转载自第一代码原创文章:https://www.diyidaima.com/android/126.html

1、一个线程有几个Handler?

你在MainActivity new一个Handler,在LoginActivity也可以new 一个Handler,甚至在Fragment也可以new一个Handler,而这些线程均是主线程,所以一个线程可以有多个Handler;

2、一个线程有几个Looper,如何保证?

一个线程只有一个Looper。那如何保证一个线程只有一个Looper呢?首先通过下面的Looper源码,我们看到Looper是通过调用静态方法prepare()进行初始化的,而prepare()又调用了prepare(true)方法。


其中prepare(true)的true表示创建的子线程的Looper是一定能被停止的。


在该方法里面,首先调用了通过sThreadLocal调用了get()。sThreadLocal是ThreadLocal的实例,内部维护了一个和HashMap类似的key-value键值对,只是略微有所不同,其内部是通过一个静态类Values的table数组来保存键值对的,如table[0]存储的是ThreadLocal实例本身,而table[1]存储的是Looper实例。所以sThreadLocal.get()的目的是判断当前线程是否已经创建了Looper了。如果没有创建,则通过Looper构造方法创建了一个新的实例,里面初始化了MessageQueue(消息队列),然后使用sThreadLocal.set(…)进行关联。

那Looper是如何和线程Thread关联上的呢?查看下方ThreadLocal的部分源码,上方sThreadLocal.set(…)方法内部通过Thread.currentThread()获取到当前的线程currentThread,也就是给当前线程加入了指向Looper的变量,然后通过values(currentThread)获取到Thread的变量localValues,其类型为ThreadLocal.Values,然后再赋值给values,如果为null,则进行初始化,最后将ThreadLocal和Looper put进values里。这样当前线程就和Looper关联到了一起。

那如何保证唯一性呢? 我们再翻看源码,在Looper源码里,只有一个地方调用了sThreadLocal.set(…),这也保证了Looper被初始化的唯一性。

3.Handler内存泄露原因?为什么其他的内部类没有说有过这个问题?

首先是因为内部类持有外部类的引用,导致即使Activity等执行了onDestory()也不会释放,因为jvm识别到该activity被引用了,所以不会对其进行回收,导致内存泄露。那为什么其他的内部类没有说有过这个问题呢?主要是Handler可以发送延时类消息,消息不到时间不会被销毁。同时MessageQueue持有Message,Message持有了Handler,而Handler持有了Activity,只有消息被销毁了,才会销毁Handler,Handler才会销毁Activity。所以Handler内部类持有外部类的引用才会发生内存泄露。那如何解决Handler的内存泄露问题呢?

4.为什么主线程可以new Handler()呢?如果想要在子线程中new Handler()需要做些什么准备?

在ActivityThread(主线程)里有一个main()函数,它是每一个Android应用最早执行的函数。如下源码:

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.
      Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
      Looper.loop();
 
      throw new RuntimeException("Main thread loop unexpectedly exited");
    }

可以看到,ActivityThread(主线程)的main函数已经执行了Looper.prepareMainLooper()和Looper.loop()。我们再来看看Looper.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(false)初始化了一个Looper。所以说主线程一启动,在main()函数中,由系统已经帮我们完成执行prepare() 和 loop()了,所以可以直接new Handler(),不需要再进行Looper初始化了。并且主线程中的所有代码,全都运行在这两个函数(prepareMainLooper()/prepare() 和 loop())之间。所有的线程都必须要prepare()和loop(),如果子线程中想要进行Handler操作,就必须在子线程中执行prepare() 和 loop()。

5.子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?

当MessageQueue没有Message时,loop()会阻塞状态,会一直卡在Message msg = queue.next()里。那此时我们需要调用Looper的quitSafely()或quit()函数,其内部均会调用MessageQueue的quit()把消息队列中的全部消息给移除掉,这样就释放了内存。而在quit()函数的最后一行,会执行一个nativeWake()函数,唤醒queue.next()里的nativePollOnce(ptr, nextPollTimeoutMillis),唤醒后,才能往下执行,发现 Message msg = mMessages是空的,就会执行dispose()和return null,此时Looper就跳出了死循环,释放线程。综上所述,子线程中维护的Looper,消息队列无消息的时,可以调用Looper的quitSafely()或quit()函数,使得Looper结束死循环,并且起到释放内存和线程的作用。

6.既然可以存在多个Handler往MessageQueue中添加数据(发消息时各个Handler可能处于不同线程中),那它内部是如何确保线程安全的?

在往MessageQueue添加数据时,使用syncchronized线程锁,在往MessageQueue取数据时,也使用syncchronized线程锁,保证消息不会混乱。

7.我们使用Message时应该如何创建它?

Message使用了享元设计模式,在销毁时并没有真正的销毁,只是将内部变量置为初始值或者说清零,在创建的时候,使用Message.obtain()进行创建,其内部会从队列头部取出Message对象复用。

8.使用Handler的postDelay后消息队列会有什么变化?

使用Handler的postDelayed(@NonNull Runnable r, long delayMillis)发送延时消息,该postDelayed内部会调用sendMessageDelayed(@NonNull Message msg, long delayMillis)方法,sendMessageDelayed又调用MessageQueue的enqueueMessage方法。如果MessageQueue为空,此时调用enqueueMessage会通过内部的nativeWake方法唤醒消息队列,消息队列被唤醒后,next方法会被执行,从而触发计算该Message执行需要等待的时间,计算完之后重新等待,直到时间到了才会触发相应操作。

9. Looper死循环为什么不会导致应用卡死?

我们先看主线程ActivityThread的main方法的源码:

public static void main(String[] args) {
        Looper.prepareMainLooper();//创建Looper和MessageQueue对象,用于处理主线程的消息
        ActivityThread thread = new ActivityThread();
        thread.attach(false);//建立Binder通道 (创建新线程)

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

        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        //如果能执行下面方法,说明应用崩溃或者是退出了...
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

我们在Launch桌面点击app的图标时,会先向AMS请求启动根Activity,AMS又会向zygote请求启动应用程序进程socket,zygote会创建并启动应用程序进程,应用程序进程准备就绪后返回给AMS,AMS再启动根Activity。最终进入到ActivityThread的main方法,在main方法里面创建Looper和MessageQueue处理主线程的消息,然后Looper.loop()方法进入死循环,我们的Activity的生命周期都是通过Handler机制处理的,包括 onCreate、onResume等方法。下面是loop方法循环:

    public static void loop() {
        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;
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                return;
            }
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
    
            msg.target.dispatchMessage(msg);
     
            msg.recycleUnchecked();
        }
    }

通过创建一个for死循环来处理消息 msg.target.dispatchMessage(msg);

真正卡死主线程操作的是在回调方法onCreate、onStart、onResume等操作时间过长,会导致掉帧甚至ANR,Looper.loop()本身不会导致应用卡死。主线程的死循环一直运行不会特别消耗CPU资源在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生。所以说主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-12-11 15:50:25  更:2021-12-11 15:51:13 
 
开发: 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 7:54:51-

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