Android消息机制与类加载

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 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 sThreadLocal = new ThreadLocal();

 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发生内存泄漏的情况
...

  • 为什么主线程不会因为Looper.loop()里面的死循环卡死?

    主线程也是通过Looper.loop()进入无限循环的,这样就保证了主线程不会像一般线程一样执行完任务之后结束生命周期。 当主线程处理消息 没有可处理的消息时,会阻塞在MessageQueue.next()方法中,此时释放CPU资源进入休眠状态,知道下一个消息过来,所以主线程多半都是休眠状态,并不会占用大量CPU资源。
  • post和sendMessage发送消息有什么区别?

image.png
  • 为什么要使用Message.obtain()获取Messsage?

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

  • Handler延迟发送消息的原理

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

  • 为什么非静态的Handler会造成内存泄漏,咋解决?

    首先,非静态的类,匿名内部类和局部内部类都会持有其外部类的引用,也就是在Activity中创建Handler会持有Activity的引用。 如果MessageQueue队列中的Message在这个Activity的生命周期内还未被处理,因为Message 关联Handler, Handler关联Activity, Handler无法回收,Activity也无法被回收。
    使用静态内部类 + 弱引用。

  • 在子线程中弹出Toast

在子线程中添加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

双亲委派的作用

  • 防同一个.class文件被重复加载
  • 对于任意一个类在虚拟机中的唯一性。类的唯一性是由他的类加载器和类的全类名来确定的
  • 防止系统类被篡改。通过委托到顶级类加载器

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 依赖包

你可能感兴趣的:(Android消息机制与类加载)