Android消息机制与类加载

2020-10-12  本文已影响0人  老北瓜

Android消息机制原理
Android类加载
Android热修复

Android消息机制

   Handler、Looper、MessageQueue三者的关系.


image.png

一个Handler 有一个Looper,
一个Looper有一个MessageQueue,
一个MessageQueue对应多个Message,
一个MessageQueue对应多个Handler.

Message 中
when :被执行的时间戳,在队列中排序的唯一依据。
target:对应了Message是由哪一条Handler发送的
next: 单向链表中每一条消息都要持有下一条的引用关系

消息传递的优先级

        // #1, 直接在Runnable中处理任务。
        Handler handler = new Handler();
        handler.post(new Runnable() {
            @Override
            public void run() {

            }
        });

       // #2, 使用Handler.Callback 来处理任务.
        Handler cHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(@NonNull Message msg) {

                return false;
            }
        });

        //#3, 使用HandleMessage 接收消息
        Handler mHandler = new Handler(){
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);

            }
        };

Looper无限循环会不会占用过多资源 。 - 不会,
Handler 是怎么和Looper绑定的,
主线程中的Handler 是通过ActivityThread.main()中 Looper.prepareLooper()创建了主线程的Looper, 然后loop()开启无限轮询.

========================  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.prepare(), looper是为空的。
        #  问题是,怎么保证Looper.myLooper()是本线程的looper.
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            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 =============================

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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

 private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

以上

Looper 的创建

-- prepare() 中判断 Looper是否已经创建了,如果已经创建过了会报错,没有创建会新建保存在 ThreadLocal 中(一个类似map的容器- key=线程, value = looper),所以也就解释了一个Handler对应一个Looper. Thread是一个静态的变量,只需要在当前线程中获取到当前线程的ThreadLocal,就可以获得当前线程的 Looper . 在Looper的构造函数中,与之相关联的 MessageQueue 就创建了,(解释了一个Looper有一个MessageQueue)

如何让子线程获得消息分发的能力

像ActivityThread 一样,在入口创建Looper, looper.prepare() 就创建了Looper, looper.loop()就有了无限循环的动力.
默认情况下,线程是没有Looper的,需要我们显示的调用Looper.prepare() 和 looper,loop().                   //但是一旦这样 就需要我们在必要的时候主动去quite() 主动退出线程,否则这个线程就会一直循环下去。

消息入队

使用插入和删除, 用链表更快
查询 用ArrayList或者HashMap更快


image.png

消息发送不论是使用handler.send 还是handler.post,最后都是通过Handler.enqueueMessage将消息插入队列。

消息分发

 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;

        ....
        for (;;) {
           // 官方注释,可能会造成线程阻塞.
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
        ...
         msg.target.dispatchMessage(msg);
        ...
         msg.recycleUnchecked();

以上,表示了消息分发的过程,在loop()的无限循环中通过MessageQueue 消息队列中去取可执行的Message,然后通过 msg.target.dispatchMessage()将消息分发出去,当消息发送完成 再把Message回收重复利用。

msg.target 就是 Handler 。

public final class Message implements Parcelable {
    @UnsupportedAppUsage
    /*package*/ Handler target;
}

ThreadLocal

looper 是使用了ThreadLocal 来存储的。
ThreadLocal发生内存泄漏的情况
...

image.png

obtain()可以从全局消息池中获取一个空的Message对象,这样可以节省系统资源。同时还可以得到一些Message的copy, 对Message做一些初始化。

通常都是通过postDelayed() 和sendMessageDelayed() 来发送延迟消息,其实并不是延迟发送,而是将延时计算后确定为被执行的时间,经过 sendMessageAtTime() --> enqueueMessage() --> queue.enqueueMessage() 将消息插入到MessageQueue消息队列中,然后在MessageQueue.next()中将线程阻塞一定时间,到达执行时间后取出分发出去。

在子线程中添加Looper.prepare() 和 Looper.loop();

 Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Looper.prepare();

                Toast.makeText(MainActivity.this, "子线程", Toast.LENGTH_SHORT).show();
                
                Looper.loop();
            }
        };
        
        Thread t = new Thread(runnable);
        t.start();

任务完成之后要手动 添加 Looper.quiteSafety(); 否则线程不会结束。

类加载机制

双亲委派
ClassLoader 加载 .class 文件

image.png

双亲委派的作用

dex文件被加载到内存中

Class文件加载
Class.forName()
ClassLoader.loadClass()
为什么在静态方法里不能访问非静态变量

类加载的步骤
装载 -> 链接 -> 初始化
装载: 查找和导入Class文件
   通过一个类的全限命命名来获取其定义的二进制字节流
   将这个字节流转化为方法区中的运行时结构
   在Java堆中生成一个代表这个类的Class对象,只有通过这个Class对象才能执行方法区中的运行时结构。
链接:
   验证:确保被加载类的正确性
   准备:为类的静态变量分配内存,并将其初始化为默认值。
     - 这时候的初始化仅仅包括 类变量static,而不包括成员变量,成员变量只有在对象实例化时被初始化。
     - 这里所设置的初始化值通常情况下是基本数据类型的零值,而不是Java代码中的显示赋值。 在准备阶段都是 0 , 而初始化是在类的clinit中执行的。
   解析:把类中的符号转换为直接引用。

image.png
cliinit 是在类被加载时为static 赋值,
init 是对象呗初始化时为成员变量赋值。
静态变量初始化的时机是要比非静态变量初始化的时机早
静态方法不能执行非静态成员的原因就是 在类被加载时,因为类还没有被实例化,非静态变量也就没有被初始化,静态方法中就不能调用非静态的变量了

初始化 :clinit 执行类的方法,对类中非静态变量 非静态代码块进行初始化 (非必须)。
类的初始化:
-创建类的实例,也就是new一个对象 会触发类的初始化
-访问某个类或接口的静态变量,或者对该静态变量赋值。
-调用类的静态方法
-反射Class.forName("android.app.xxx")
-初始化一个类的子类(会先初始化子类的父类)
-jvm启动时标明的启动类。

Class.forName() 和 ClassLoader.loadClass()的不同 和不同点

.... 类的初始化

Android热修复

dex被加载到内存当中

Tinker

root build.gradle 的依赖中 添加 tinker-support 依赖包
.... 待续 ...

上一篇下一篇

猜你喜欢

热点阅读