关于android:2020Android面试重难点之Handler机制含字节京东腾讯经典面试真题解析

50次阅读

共计 28480 个字符,预计需要花费 72 分钟才能阅读完成。

Handler 在整个 Android 开发体系中占据着很重要的位置,对开发者来说起到的作用很明确,就是为了实现线程切换或者是执行延时工作,略微更高级一点的用法可能是为了保障多个工作在执行时的有序性。

因为 Android 零碎中的主线程有非凡位置,所以像 EventBus 和 Retrofit 这类并非 Android 独有的三方库,都是通过 Handler 来实现对 Android 零碎的非凡平台反对。大部分开发者都曾经对如何应用 Handler 很相熟了,这里就再来理解下其外部具体是如何实现的。

一、入手实现 Handler

本文不会一上来就间接介绍源码,而是会先依据咱们想要实现的成果来反推源码,一步步来本人入手实现一个简略的 Handler

1、Message

首先,咱们须要有个载体来示意要执行的工作,就叫它 Message 吧,Message 应该有什么参数呢?

  • 须要有一个惟一标识,因为要执行的工作可能有多个,咱们要分得清哪个是哪个,用个 Int 类型变量就足够示意了
  • 须要可能承载数据,须要发送的数据类型会有很多种可能,那就间接用一个 Object 类型变量来示意吧,由开发者本人在应用时再来强转类型
  • 须要有一个 long 类型变量来示意工作的执行工夫戳

所以,Message 类就应该至多蕴含以下几个字段:

/**
 * @Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc:
 * GitHub:https://github.com/leavesC
 */
public final class Message {
    // 惟一标识
    public int what;
    // 数据
    public Object obj;
    // 工夫戳
    public long when;
}

2、MessageQueue

因为 Message 并不是发送了就可能马上被生产掉,所以就必定要有个能够用来寄存的中央,就叫它 MessageQueue 吧,即音讯队列。Message 可能须要提早解决,那么 MessageQueue 在保留 Message 的时候就应该依照工夫戳的大小来程序寄存,工夫戳小的 Message 放在队列的头部,在生产 Message 的时候就间接从队列头取值即可

那么用什么数据结构来寄存 Message 比拟好呢?

  • 用数组?不太适合,数组尽管在遍历的时候会比拟快,但须要事后就申请固定的内存空间,导致在插入数据和移除数据时可能须要挪动大量数据。而 MessageQueue 可能随时会收到数量不定、工夫戳大小不定的 Message,生产完 Message 后还须要将该其移出队列,所以应用数组并不适合
  • 用链表?如同能够,链表在插入数据和移除数据时只须要扭转指针的援用即可,不须要挪动数据,内存空间也只须要按需申请即可。尽管链表在随机拜访的时候性能不高,然而对于 MessageQueue 而言无所谓,因为在生产 Message 的时候也只须要取队列头的值,并不需要随机拜访

好了,既然决定用链表构造,那么 Message 就须要减少一个字段用于指向下一条音讯才行

/**
 * @Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc:
 * GitHub:https://github.com/leavesC
 */
public final class Message {
    // 惟一标识
    public int what;
    // 数据
    public Object obj;
    // 工夫戳
    public long when;
    // 下一个节点
    public Message next;
}

MessageQueue 须要提供一个 enqueueMessage办法用来向链表插入 Message,因为存在多个线程同时向队列发送音讯的可能,所以办法外部还须要做下线程同步才行

/**
 * @Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc:
 * GitHub:https://github.com/leavesC
 */
public class MessageQueue {

    // 链表中的第一条音讯
    private Message mMessages;

    void enqueueMessage(Message msg, long when) {synchronized (this) {
            Message p = mMessages;
            // 如果链表是空的,或者处于队头的音讯的工夫戳比 msg 要大,则将 msg 作为链表头部
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
            } else {
                Message prev;
                // 从链表头向链表尾遍历,寻找链表中第一条工夫戳比 msg 大的音讯,将 msg 插到该音讯的后面
                for (; ;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {break;}
                }
                msg.next = p;
                prev.next = msg;
            }
        }
    }
}

此外,MessageQueue 要有一个能够获取队头音讯的办法才行,就叫做 next() 吧。内部有可能会随时向 MessageQueue 发送 Message,next()办法外部就间接来开启一个有限循环来重复取值吧。如果以后队头的音讯能够间接解决的话(即音讯的工夫戳小于或等于以后工夫),那么就间接返回队头音讯。而如果队头音讯的工夫戳比以后工夫还要大(即队头音讯是一个延时音讯),那么就计算以后工夫和队头音讯的工夫戳的差值,计算 next() 办法须要阻塞期待的工夫,调用 nativePollOnce()办法来期待一段时间后再持续循环遍历

    // 用来标记 next() 办法是否正处于阻塞期待的状态
    private boolean mBlocked = false;

    Message next() {
        int nextPollTimeoutMillis = 0;
        for (; ;) {nativePollOnce(nextPollTimeoutMillis);
            synchronized (this) {
                // 以后工夫
                final long now = SystemClock.uptimeMillis();

                Message msg = mMessages;
                if (msg != null) {if (now < msg.when) {
                        // 如果以后工夫还未达到音讯的的解决工夫,那么就计算还须要期待的工夫
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 能够解决队头的音讯了,第二条音讯成为队头
                        mMessages = msg.next;
                        msg.next = null;
                        mBlocked = false;
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
                mBlocked = true;
            }
        }
    }

    // 将 next 办法的调用线程休眠指定工夫
    private void nativePollOnce(long nextPollTimeoutMillis) {}

此时就须要思考到一种情景:next()还处于阻塞状态的时候,内部向音讯队列插入了一个能够立刻解决或者是阻塞等待时间比拟短的 Message。此时就须要唤醒休眠的线程,因而 enqueueMessage还须要再改变下,减少判断是否须要唤醒 next() 办法的逻辑

    void enqueueMessage(Message msg, long when) {synchronized (this) {
            // 用于标记是否须要唤醒 next 办法
            boolean needWake = false;         
            Message p = mMessages;
            // 如果链表是空的,或者处于队头的音讯的工夫戳比 msg 要大,则将 msg 作为链表头部
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;     
                // 须要唤醒
                needWake = mBlocked;
            } else {
                Message prev;
                // 从链表头向链表尾遍历,寻找链表中第一条工夫戳比 msg 大的音讯,将 msg 插到该音讯的后面
                for (; ;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {break;}
                }
                msg.next = p;
                prev.next = msg;
            }  
            if (needWake) {// 唤醒 next() 办法
                nativeWake();}
        }
    }

    // 唤醒 next() 办法
    private void nativeWake() {}

3、Handler

既然寄存音讯的中央曾经确定就是 MessageQueue 了,那么天然就还须要有一个类能够用来向 MessageQueue 发送音讯了,就叫它 Handler 吧。Handler 能够实现哪些性能呢?

  • 心愿除了能够发送 Message 类型的音讯外还能够发送 Runnable 类型的音讯。这个简略,Handler 外部将 Runnable 包装为 Message 即可
  • 心愿能够发送延时音讯,以此来执行延时工作。这个也简略,用 Message 外部的 when 字段来标识心愿工作执行时的工夫戳即可
  • 心愿能够实现线程切换,即从子线程发送的 Message 能够在主线程被执行,反过来也一样。这个也不难,子线程能够向一个特定的 mainMessageQueue 发送音讯,而后让主线程负责循环从该队列中取音讯并执行即可,这样不就实现了线程切换了吗?

所以说,Message 的定义和发送是由 Handler 来实现的,但 Message 的散发则能够交由其余线程来实现

依据以上需要:Runnable 要可能包装为 Message 类型,Message 的解决逻辑要交由 Handler 来定义,所以 Message 就还须要减少两个字段才行

/**
 * @Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc:
 * GitHub:https://github.com/leavesC
 */
public final class Message {
    // 惟一标识
    public int what;
    // 数据
    public Object obj;
    // 工夫戳
    public long when;
    // 下一个节点
    public Message next;
    // 用于将 Runnable 包装为 Message
    public Runnable callback;
    // 指向 Message 的发送者,同时也是 Message 的最终解决者
    public Handler target;
}

Handler 至多须要蕴含几个办法:用于发送 Message 和 Runnable 的办法、用来解决音讯的 handleMessage 办法、用于散发音讯的 dispatchMessage办法

/**
 * @Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc:
 * GitHub:https://github.com/leavesC
 */
public class Handler {

    private MessageQueue mQueue;

    public Handler(MessageQueue mQueue) {this.mQueue = mQueue;}

    public final void post(Runnable r) {sendMessageDelayed(getPostMessage(r), 0);
    }

    public final void postDelayed(Runnable r, long delayMillis) {sendMessageDelayed(getPostMessage(r), delayMillis);
    }

    public final void sendMessage(Message r) {sendMessageDelayed(r, 0);
    }

    public final void sendMessageDelayed(Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}
        sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public void sendMessageAtTime(Message msg, long uptimeMillis) {
        msg.target = this;
        mQueue.enqueueMessage(msg, uptimeMillis);
    }

    private static Message getPostMessage(Runnable r) {Message m = new Message();
        m.callback = r;
        return m;
    }

    // 由内部来重写该办法,以此来生产 Message
    public void handleMessage(Message msg) { }

    // 用于散发音讯
    public void dispatchMessage(Message msg) {if (msg.callback != null) {msg.callback.run();
        } else {handleMessage(msg);
        }
    }

}

之后,子线程就能够像这样来应用 Handler 了:将子线程持有的 Handler 对象和主线程关联的 mainMessageQueue 绑定在一起,主线程负责循环从 mainMessageQueue 取出 Message 后再来调用 Handler 的 dispatchMessage 办法,以此实现线程切换的目标

        Handler handler = new Handler(mainThreadMessageQueue) {
            @Override
            public void handleMessage(Message msg) {switch (msg.what) {
                    case 1: {String ob = (String) msg.obj;
                        break;
                    }
                    case 2: {List<String> ob = (List<String>) msg.obj;
                        break;
                    }
                }
            }
        };
        Message messageA = new Message();
        messageA.what = 1;
        messageA.obj = "https://github.com/leavesC";
        Message messageB = new Message();
        messageB.what = 2;
        messageB.obj = new ArrayList<String>();
        handler.sendMessage(messageA);
        handler.sendMessage(messageB);

4、Looper

当初就再来想想怎么让 Handler 拿到和主线程关联的 MessageQueue,以及主线程怎么从 MessageQueue 获取 Message 并回调 Handler。这之间就肯定须要一个直达器,就叫它 Looper 吧。Looper 具体须要实现什么性能呢?

  • 每个 Looper 对象应该都是对应一个独有的 MessageQueue 实例和 Thread 实例,这样子线程和主线程才能够相互发送 Message 交由对方线程解决
  • Looper 外部须要开启一个有限循环,其关联的线程就负责从 MessageQueue 循环获取 Message 进行解决
  • 因为主线程较为非凡,所以和主线程关联的 Looper 对象要可能被子线程间接获取到,能够思考将其作为动态变量存着

这样,Looper 的大体框架就进去了。通过 ThreadLocal 来为不同的线程独自保护一个 Looper 实例,每个线程通过 prepare()办法来初始化本线程独有的 Looper 实例,再通过 myLooper()办法来获取和以后线程关联的 Looper 对象,和主线程关联的 sMainLooper 作为动态变量存在,不便子线程获取

/**
 * @Author: leavesC
 * @Date: 2020/12/1 13:31
 * @Desc:
 * GitHub:https://github.com/leavesC
 */
final class Looper {

    final MessageQueue mQueue;

    final Thread mThread;

    private static Looper sMainLooper;

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

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

    public static void prepare() {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }

    public static void prepareMainLooper() {prepare();
        synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();}
    }

    public static Looper getMainLooper() {synchronized (Looper.class) {return sMainLooper;}
    }

    public static Looper myLooper() {return sThreadLocal.get();
    }

}

Looper 还须要有一个用于循环从 MessageQueue 获取音讯并解决的办法,就叫它 loop() 吧。其作用也能简略,就是循环从 MessageQueue 中取出 Message,而后将 Message 再反过来分发给 Handler 即可

    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();// 可能会阻塞
            msg.target.dispatchMessage(msg);
        }
    }

这样,主线程就先通过调用 prepareMainLooper() 来实现 sMainLooper 的初始化,而后调用 loop() 开始向 mainMessageQueue 循环取值并进行解决,没有音讯的话主线程就临时休眠着。子线程拿到 sMainLooper 后就以此来初始化 Handler,这样子线程向 Handler 发送的音讯就都会被存到 mainMessageQueue 中,最终在主线程被生产掉

5、做一个总结

这样一步步走下来后,读者对于 Message、MessageQueue、Handler、Looper 这四个类的定位就应该都很清晰了吧?不同线程之间就能够依附拿到对方的 Looper 来实现音讯的跨线程解决了

例如,对于以下代码,即便 Handler 是在 otherThread 中进行初始化,但 handleMessage 办法最终是会在 mainThread 被调用执行的,

        Thread mainThread = new Thread() {
            @Override
            public void run() {
                // 初始化 mainLooper
                Looper.prepareMainLooper();
                // 开启循环
                Looper.loop();}
        };

        Thread otherThread = new Thread() {
            @Override
            public void run() {Looper mainLooper = Looper.getMainLooper();
                Handler handler = new Handler(mainLooper.mQueue) {
                    @Override
                    public void handleMessage(Message msg) {switch (msg.what) {
                            case 1: {String ob = (String) msg.obj;
                                break;
                            }
                            case 2: {List<String> ob = (List<String>) msg.obj;
                                break;
                            }
                        }
                    }
                };
                Message messageA = new Message();
                messageA.what = 1;
                messageA.obj = "https://github.com/leavesC";
                Message messageB = new Message();
                messageB.what = 2;
                messageB.obj = new ArrayList<String>();
                handler.sendMessage(messageA);
                handler.sendMessage(messageB);
            }
        };

再来做个简略的总结:

  • Message:用来示意要执行的工作
  • Handler:子线程持有的 Handler 如果绑定到的是主线程的 MessageQueue 的话,那么子线程发送的 Message 就能够由主线程来生产,以此来实现线程切换,执行 UI 更新操作等目标
  • MessageQueue:即音讯队列,通过 Handler 发送的音讯并非都是立刻执行的,须要先依照 Message 的优先级高下(延时工夫的长短)保留到 MessageQueue 中,之后再来顺次执行
  • Looper:Looper 用于从 MessageQueue 中循环获取 Message 并将之传递给音讯解决者(即音讯发送者 Handler 自身)来进行生产,每条 Message 都有个 target 变量用来指向 Handler,以此把 Message 和其解决者关联起来。不同线程之间通过相互拿到对方的 Looper 对象,以此来实现跨线程发送音讯

有了以上的认知根底后,上面就来看看理论的源码实现 ~ ~

二、Handler 源码

1、Handler 如何初始化

Handler 的构造函数一共有七个,除去 两个曾经废除的和三个暗藏的,实际上开发者能够应用的只有两个。而不论是应用哪个构造函数,最终的目标都是为了实现 mLooper、mQueue、mCallback、mAsynchronous 这四个常量的初始化,同时也能够看进去 MessageQueue 是由 Looper 来实现初始化的,而且 Handler 对于 Looper 和 MessageQueue 都是一对一的关系,一旦初始化后就不可扭转

大部分开发者应用的应该都是 Handler 的无参构造函数,而在 Android 11 中 Handler 的无参构造函数曾经被标记为废除的了。Google 官网更举荐的做法是通过显式传入 Looper 对象来实现初始化,而非隐式应用以后线程关联的 Looper

Handler 对于 Looper 和 MessageQueue 都是一对一的关系,然而 Looper 和 MessageQueue 对于 Handler 能够是一对多的关系,这个前面会讲到

    @UnsupportedAppUsage
    final Looper mLooper;
    final MessageQueue mQueue;
    @UnsupportedAppUsage
    final Callback mCallback;
    final boolean mAsynchronous;

    // 省略其它构造函数

    /**
     * @hide
     */
    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());
            }
        }

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

2、Looper 如何初始化

在初始化 Handler 时,如果内部调用的构造函数没有传入 Looper,那就会调用 Looper.myLooper() 来获取和以后线程关联的 Looper 对象,再从 Looper 中取 MessageQueue。如果获取到的 Looper 对象为 null 就会抛出异样。依据异样信息 Can't create handler inside thread that has not called Looper.prepare() 能够看进去,在初始化 Handler 之前须要先调用 Looper.prepare()实现 Looper 的初始化

走进 Looper 类中能够看到,myLooper()办法是 Looper 类的静态方法,其只是单纯地从 sThreadLocal 变量中取值并返回而已。sThreadLocal 又是通过 prepare(boolean) 办法来进行初始化赋值的,且只能赋值一次,反复调用将抛出异样

咱们晓得,ThreadLocal 的个性就是能够为不同的线程别离保护独自的一个变量实例,所以,不同的线程就会别离对应着不同的 Looper 对象,是一一对应的关系

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

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

    /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    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));
    }

此外,Looper 类的构造函数也是公有的,会初始化两个常量值:mQueue 和 mThread,这阐明了 Looper 对于 MessageQueue 和 Thread 都是一一对应的关系,关联之后不能扭转

    @UnsupportedAppUsage
    final MessageQueue mQueue;

    final Thread mThread;    

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

在日常开发中,咱们在通过 Handler 来执行 UI 刷新操作时,常常应用的是 Handler 的无参构造函数,那么此时必定就是应用了和主线程关联的 Looper 对象,对应 Looper 类中的动态变量 sMainLooper

    @UnsupportedAppUsage
    private static Looper sMainLooper;  // guarded by Looper.class

    // 被标记为废除的起因是因为 sMainLooper 会交由 Android 零碎主动来实现初始化,内部不应该被动来初始化
    @Deprecated
    public static void prepareMainLooper() {prepare(false);
        synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();}
    }

    /**
     * Returns the application's main looper, which lives in the main thread of the application.
     */
    public static Looper getMainLooper() {synchronized (Looper.class) {return sMainLooper;}
    }

prepareMainLooper()就用于为主线程初始化 Looper 对象,该办法又是由 ActivityThread 类的 main() 办法来调用的。该 main() 办法即 Java 程序的运行起始点,所以当利用启动时零碎就主动为咱们在主线程做好了 mainLooper 的初始化,而且曾经调用了 Looper.loop() 办法开启了音讯的循环解决,利用在应用过程中的各种交互逻辑(例如:屏幕的触摸事件、列表的滑动等)就都是在这个循环里实现散发的

正是因为 Android 零碎曾经主动实现了主线程 Looper 的初始化,所以咱们在主线程中才能够间接应用 Handler 的无参构造函数来实现 UI 相干事件的解决

public final class ActivityThread extends ClientTransactionHandler {public static void main(String[] args) {
        ···
        Looper.prepareMainLooper();
        ···
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}

3、Handler 发送音讯

Handler 用于发送音讯的办法十分多,有十几个,其中大部分最终调用到的都是 sendMessageAtTime() 办法。uptimeMillis 即 Message 具体要执行的工夫戳,如果该工夫戳比以后工夫大,那么就意味着要执行的是提早工作。如果为 mQueue 为 null,就会打印异样信息并间接返回,因为 Message 是须要交由 MessageQueue 来解决的

     public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(this + "sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

须要留神 msg.target = this 这句代码,target 指向了 发送音讯的主体,即 Handler 对象自身,即由 Handler 对象发给 MessageQueue 的音讯最初还是要交由 Handler 对象自身来解决

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {msg.setAsynchronous(true);
        }
        // 将音讯交由 MessageQueue 解决
        return queue.enqueueMessage(msg, uptimeMillis);
    }

4、MessageQueue

MessageQueue 通过 enqueueMessage 办法来接管音讯

  • 因为存在多个线程同时往一个 MessageQueue 发送音讯的可能,所以 enqueueMessage 外部必定须要进行线程同步
  • 能够看出 MessageQueue 外部是以链表的构造来存储 Message 的(Message.next),依据 Message 的工夫戳大小来决定其在音讯队列中的地位
  • mMessages 代表的是音讯队列中的第一条音讯。如果 mMessages 为空(音讯队列是空的),或者 mMessages 的工夫戳要比新音讯的工夫戳大,则将新音讯插入到音讯队列的头部;如果 mMessages 不为空,则寻找音讯列队中第一条触发工夫比新音讯晚的非空音讯,将新音讯插到该音讯的后面

到此,一个依照工夫戳大小进行排序的音讯队列就实现了,后边要做的就是从音讯队列中顺次取出音讯进行解决了

    boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            ···
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            // 用于标记是否须要唤醒线程
            boolean needWake;
            // 如果链表是空的,或者处于队头的音讯的工夫戳比 msg 要大,则将 msg 作为链表头部
            //when == 0 阐明 Handler 调用的是 sendMessageAtFrontOfQueue 办法,间接将 msg 插到队列头部 
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // 如果以后线程处于休眠状态 + 队头音讯是屏障音讯 + msg 是异步音讯
                // 那么就须要唤醒线程
                needWake = mBlocked && p.target == null && msg.isAsynchronous();

                Message prev;
                // 从链表头向链表尾遍历,寻找链表中第一条工夫戳比 msg 大的音讯,将 msg 插到该音讯的后面
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {break;}
                    if (needWake && p.isAsynchronous()) {
                        // 如果在 msg 之前队列中还有异步音讯那么就不须要被动唤醒
                        // 因为曾经设定唤醒工夫了
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {nativeWake(mPtr);
            }
        }
        return true;
    }

晓得了 Message 是如何保留的了,再来看下 MessageQueue 是如何取出 Message 并回调给 Handler 的。在 MessageQueue 中读取音讯的操作对应的是next() 办法。next() 办法外部开启了一个有限循环,如果音讯队列中没有音讯或者是队头音讯还没到能够解决的工夫,该办法就会导致 Loop 线程休眠挂起,直到条件满足后再从新遍历音讯

    @UnsupportedAppUsage
    Message next() {
        ···
        for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();
            }

            // 将 Loop 线程休眠挂起
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {if (now < msg.when) {
                        // 队头音讯还未到解决工夫,计算须要期待的工夫
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message:" + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
                ···
            }
            ···
            }
            ···
        }
    }

next() 办法又是通过 Looper 类的 loop() 办法来循环调用的,loop() 办法内也是一个有限循环,惟一跳出循环的条件就是 queue.next()办法返回为 null。因为 next() 办法可能会触发阻塞操作,所以没有音讯须要解决时也会导致 loop() 办法被阻塞着,而当 MessageQueue 有了新的音讯,Looper 就会及时地解决这条音讯并调用 msg.target.dispatchMessage(msg) 办法将音讯回传给 Handler 进行解决

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the 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.");
        }
        ···    
        for (;;) {Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ···
            msg.target.dispatchMessage(msg);
            ···
        }
    }

Handler 的 dispatchMessage 办法就是在向外局部发 Message 了。至此,Message 的整个散发流程就完结了

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {handleCallback(msg);
        } else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}
            }
            handleMessage(msg);
        }
    }

5、音讯屏障

Android 零碎为了保障某些高优先级的 Message(异步音讯)可能被尽快执行,采纳了一种音讯屏障(Barrier)机制。其大抵流程是:先发送一个屏障音讯到 MessageQueue 中,当 MessageQueue 遍历到该屏障音讯时,就会判断以后队列中是否存在异步音讯,有的话则先跳过同步音讯(开发者被动发送的都属于同步音讯),优先执行异步音讯。这种机制就会使得在异步音讯被执行完之前,同步音讯都不会失去解决

Handler 的构造函数中的 async 参数就用于管制发送的 Message 是否属于异步音讯

    public class Handler {

        final boolean mAsynchronous;

        public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {mAsynchronous = async;}

        private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                long uptimeMillis) {
            msg.target = this;
            msg.workSourceUid = ThreadLocalWorkSource.getUid();
            if (mAsynchronous) {
                // 设为异步音讯
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }

    }

MessageQueue 在取队头音讯的时候,如果判断到队头音讯就是屏障音讯的话,那么就会向后遍历找到第一条异步音讯优先进行解决

    @UnsupportedAppUsage
    Message next() {for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) { //target 为 null 即属于屏障音讯
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    // 循环遍历,找到屏障音讯前面的第一条异步音讯进行解决
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
            }
        }
    }

6、退出 Looper 循环

Looper 类自身做了办法限度,除了主线程外,子线程关联的 MessageQueue 都反对退出 Loop 循环,即 quitAllowed 只有主线程能力是 false

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

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

}

MessageQueue 反对两种形式来退出 Loop:

  • safe 为 true,只移除所有尚未执行的音讯,不移除工夫戳等于以后工夫的音讯
  • safe 为 false,移除所有音讯
    void quit(boolean safe) {if (!mQuitAllowed) {
            //MessageQueue 设置了不容许退出循环,间接抛出异样
            throw new IllegalStateException("Main thread not allowed to quit.");
        }
        synchronized (this) {if (mQuitting) {
                // 防止反复调用
                return;
            }
            mQuitting = true;
            if (safe) {
                // 只移除所有尚未执行的音讯,不移除工夫戳等于以后工夫的音讯
                removeAllFutureMessagesLocked();} else {
                // 移除所有音讯
                removeAllMessagesLocked();}
            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

7、IdleHandler

IdleHandler 是 MessageQueue 的一个外部接口,能够用于在 Loop 线程处于闲暇状态的时候执行一些优先级不高的操作

    public static interface IdleHandler {boolean queueIdle();
    }

MessageQueue 在获取队头音讯时,如果发现以后没有须要执行的 Message 的话,那么就会去遍历 mIdleHandlers,顺次执行 IdleHandler

    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();

    @UnsupportedAppUsage
    Message next() {
        ···
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            ···
            synchronized (this) {
                ···
                // 如果队头音讯 mMessages 为 null 或者 mMessages 须要提早解决
                // 那么就来执行 IdleHandler
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
                if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
            for (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler
                boolean keep = false;
                try {
                    // 执行 IdleHandler
                    // 如果返回 false 的话阐明之后不再须要执行,那就将其移除
                    keep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);
                    }
                }
            }
        }
    }

例如,ActivityThread 就向主线程 MessageQueue 增加了一个 GcIdler,用于在主线程闲暇时尝试去执行 GC 操作

public final class ActivityThread extends ClientTransactionHandler {

    @UnsupportedAppUsage
    void scheduleGcIdler() {if (!mGcIdlerScheduled) {
            mGcIdlerScheduled = true;
            // 增加 IdleHandler
            Looper.myQueue().addIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }

    final class GcIdler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            // 尝试 GC
            doGcIfNeeded();
            purgePendingResources();
            return false;
        }
    }

}

8、做一个总结

再来总结下以上的所有内容

  1. 每个 Handler 都会和一个 Looper 实例关联在一起,能够在初始化 Handler 时通过构造函数被动传入实例,否则就会默认应用和以后线程关联的 Looper 对象
  2. 每个 Looper 都会和一个 MessageQueue 实例关联在一起,每个线程都须要通过调用 Looper.prepare()办法来初始化本线程独有的 Looper 实例,并通过调用 Looper.loop() 办法来使得本线程循环向 MessageQueue 取出音讯并执行。Android 零碎默认会为每个利用初始化和主线程关联的 Looper 对象,并且默认就开启了 loop 循环来解决主线程音讯
  3. MessageQueue 依照链接构造来保留 Message,执行工夫早(即工夫戳小)的 Message 会排在链表的头部,Looper 会循环从链表中取出 Message 并回调给 Handler,取值的过程可能会蕴含阻塞操作
  4. Message、Handler、Looper、MessageQueue 这四者就形成了一个生产者和消费者模式。Message 相当于产品,MessageQueue 相当于传输管道,Handler 相当于生产者,Looper 相当于消费者
  5. Handler 对于 Looper、Handler 对于 MessageQueue、Looper 对于 MessageQueue、Looper 对于 Thread,这几个之间都是一一对应的关系,在关联后无奈更改,但 Looper 对于 Handler、MessageQueue 对于 Handler 能够是一对多的关系
  6. Handler 能用于更新 UI 蕴含了一个隐性的前提条件:Handler 与主线程 Looper 关联在了一起。在主线程中初始化的 Handler 会默认与主线程 Looper 关联在一起,所以其 handleMessage(Message msg) 办法就会由主线程来调用。在子线程初始化的 Handler 如果也想执行 UI 更新操作的话,则须要被动获取 mainLooper 来初始化 Handler
  7. 对于咱们本人在子线程中创立的 Looper,当不再须要的时候咱们应该被动退出循环,否则子线程将始终无奈失去开释。对于主线程 Loop 咱们则不应该去被动退出,否则将导致利用解体
  8. 咱们能够通过向 MessageQueue 增加 IdleHandler 的形式,来实现在 Loop 线程处于闲暇状态的时候执行一些优先级不高的工作。例如,假如咱们有个需要是心愿当主线程实现界面绘制等事件后再执行一些 UI 操作,那么就能够通过 IdleHandler 来实现,这能够防止拖慢用户看到首屏页面的速度

三、Handler 在零碎中的利用

1、HandlerThread

HandlerThread 是 Android SDK 中和 Handler 在同个包下的一个类,从其名字就可以看进去它是一个线程,而且应用到了 Handler

其用法相似于以下代码。通过 HandlerThread 外部的 Looper 对象来初始化 Handler,同时在 Handler 中申明须要执行的耗时工作,主线程通过向 Handler 发送音讯来触发 HandlerThread 去执行耗时工作

class MainActivity : AppCompatActivity() {private val handlerThread = HandlerThread("I am HandlerThread")

    private val handler by lazy {object : Handler(handlerThread.looper) {override fun handleMessage(msg: Message) {Thread.sleep(2000)
                Log.e("MainActivity", "这里是子线程,能够用来执行耗时工作:" + Thread.currentThread().name)
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btn_test.setOnClickListener {handler.sendEmptyMessage(1)
        }
        handlerThread.start()}

}

HandlerThread 的源码还是挺简略的,只有一百多行

HandlerThread 是 Thread 的子类,其作用就是为了用来执行耗时工作,其 run()办法会主动为本人创立一个 Looper 对象并保留到 mLooper,之后就被动开启音讯循环,这样 HandlerThread 就会来循环解决 Message 了

public class HandlerThread extends Thread {

    // 线程优先级
    int mPriority;
    // 线程 ID
    int mTid = -1;
    // 以后线程持有的 Looper 对象
    Looper mLooper;

    private @Nullable Handler mHandler;

    public HandlerThread(String name) {super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    public HandlerThread(String name, int priority) {super(name);
        mPriority = priority;
    }

    @Override
    public void run() {mTid = Process.myTid();
        // 触发以后线程创立 Looper 对象
        Looper.prepare();
        synchronized (this) {
            // 获取 Looper 对象
            mLooper = Looper.myLooper();
            // 唤醒所有处于期待状态的线程
            notifyAll();}
        // 设置线程优先级
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        // 开启音讯循环
        Looper.loop();
        mTid = -1;
    }

}

此外,HandlerThread 还蕴含一个 getLooper() 办法用于获取 Looper。当咱们在内部调用 handlerThread.start() 启动线程后,因为其 run() 办法的执行机会仍然是不确定的,所以 getLooper()办法就必须等到 Looper 初始化结束后能力返回,否则就会因为 wait() 办法而始终阻塞期待。当 run() 办法初始化 Looper 实现后,就会调用 notifyAll() 来唤醒所有处于期待状态的线程。所以内部在应用 HandlerThread 前就记得必须先调用 start() 办法来启动 HandlerThread

    // 获取与 HandlerThread 关联的 Looper 对象
    // 因为 getLooper() 可能先于 run() 被执行
    // 所以当 mLooper 为 null 时调用者线程就须要阻塞期待 Looper 对象创立结束
    public Looper getLooper() {if (!isAlive()) {return null;}

        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {while (isAlive() && mLooper == null) {
                try {wait();
                } catch (InterruptedException e) {}}
        }
        return mLooper;
    }

HandlerThread 起到的作用就是不便了主线程和子线程之间的交互,主线程能够间接通过 Handler 来申明耗时工作并交由子线程来执行。应用 HandlerThread 也不便在多个线程间共享,主线程和其它子线程都能够向 HandlerThread 下发工作,且 HandlerThread 能够保障多个工作执行时的有序性

2、IntentService

IntentService 是零碎提供的 Service 子类,用于在后盾串行执行耗时工作,在解决完所有工作后会主动进行,不用来手动调用 stopSelf() 办法。而且因为 IntentService 是四大组件之一,领有较高的优先级,不易被零碎杀死,因而适宜用于执行一些高优先级的异步工作

Google 官网以前也举荐开发者应用 IntentService,然而在 Android 11 中曾经被标记为废除状态了,但这也不障碍咱们来理解下其实现原理

IntentService 外部依附 HandlerThread 来实现,其 onCreate()办法会创立一个 HandlerThread,拿到 Looper 对象来初始化 ServiceHandler。ServiceHandler 会将其承受到的每个 Message 都转交由形象办法 onHandleIntent来解决,子类就通过实现该办法来申明耗时工作

public abstract class IntentService extends Service {

    private volatile Looper mServiceLooper;
    @UnsupportedAppUsage
    private volatile ServiceHandler mServiceHandler;

    private final class ServiceHandler extends Handler {public ServiceHandler(Looper looper) {super(looper);
        }

        @Override
        public void handleMessage(Message msg) {onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

    @Override
    public void onCreate() {super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        // 触发 HandlerThread 创立 Looper 对象
        thread.start();
        // 获取 Looper 对象,构建能够向 HandlerThread 发送 Message 的 Handler
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);

}

每次 start IntentService 时,onStart()办法就会被调用,将 intentstartId 包装为一个 Message 对象后发送给 mServiceHandler。须要特地留神的是 startId 这个参数,它用于惟一标识每次对 IntentService 发动的工作申请,每次回调 onStart() 办法时,startId 的值都是主动递增的。IntentService 不应该在解决完一个 Message 之后就立刻进行 IntentService,因为此时 MessageQueue 中可能还有待处理的工作还未取出来,所以如果当调用 stopSelf(int) 办法时传入的参数不等于以后最新的 startId 值的话,那么stopSelf(int) 办法就不会导致 IntentService 被进行,从而防止了将尚未解决的 Message 给脱漏了

    @Override
    public void onStart(@Nullable Intent intent, int startId) {Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

四、Handler 在三方库中的利用

1、EventBus

EventBus 的 Github 上有这么一句介绍:EventBus is a publish/subscribe event bus for Android and Java. 这阐明了 EventBus 是广泛实用于 Java 环境的,只是对 Android 零碎做了非凡的平台反对而已。EventBus 的四种音讯发送策略蕴含了ThreadMode.MAIN 用于指定在主线程进行音讯回调,其外部就是通过 Handler 来实现的

EventBusBuilder 会去尝试获取 MainLooper,如果拿失去的话就能够用来初始化 HandlerPoster,从而实现主线程回调

    MainThreadSupport getMainThreadSupport() {if (mainThreadSupport != null) {return mainThreadSupport;} else if (AndroidLogger.isAndroidLogAvailable()) {Object looperOrNull = getAndroidMainLooperOrNull();
            return looperOrNull == null ? null :
                    new MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) looperOrNull);
        } else {return null;}
    }

    static Object getAndroidMainLooperOrNull() {
        try {return Looper.getMainLooper();
        } catch (RuntimeException e) {// Not really a functional Android (e.g. "Stub!" maven dependencies)
            return null;
        }
    }
public class HandlerPoster extends Handler implements Poster {protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {super(looper);
    }
    @Override
    public void handleMessage(Message msg) {}}

2、Retrofit

和 EventBus 一样,Retrofit 的外部实现也不须要依赖于 Android 平台,而是能够用于任意的 Java 客户端,Retrofit 只是对 Android 平台进行了非凡实现而已

在构建 Retrofit 对象的时候,咱们能够抉择传递一个 Platform 对象用于标记调用方所处的平台

public static final class Builder {

    private final Platform platform;

    Builder(Platform platform) {this.platform = platform;}
}

Platform 类只具备一个惟一子类,即 Android 类。其次要逻辑就是重写了父类的 defaultCallbackExecutor()办法,通过 Handler 来实现在主线程回调网络申请后果

static final class Android extends Platform {

    @Override
    public Executor defaultCallbackExecutor() {return new MainThreadExecutor();
    }
    static final class MainThreadExecutor implements Executor {private final Handler handler = new Handler(Looper.getMainLooper());
      @Override
      public void execute(Runnable r) {handler.post(r);
      }
    }
  }

五、面试环节

1.Handler

  • Handler Looper Message 关系是什么?
  • Messagequeue 的数据结构是什么?为什么要用这个数据结构?
  • 如何在子线程中创立 Handler?
  • Handler post 办法原理?
  • Android 音讯机制的原理及源码解析
  • Android Handler 音讯机制

因为篇幅无限,仅展现局部内容,所有的知识点 整顿的具体内容都放在了我的【GitHub】, 有须要的敌人自取。

2.Activity 相干

  • 启动模式以及应用场景?
  • onNewIntent()与 onConfigurationChanged()
  • onSaveInstanceState()与 onRestoreInstanceState()
  • Activity 到底是如何启动的
  • 启动模式以及应用场景
  • onSaveInstanceState 及 onRestoreInstanceState 应用
  • onConfigurationChanged 应用以及问题解决
  • Activity 启动流程解析

3.Fragment

  • Fragment 生命周期和 Activity 比照
  • Fragment 之间如何进行通信
  • Fragment 的 startActivityForResult
  • Fragment 重叠问题
  • Fragment 初探
  • Fragment 重叠,如何通信
  • Fragment 生命周期

因为篇幅无限,仅展现局部内容,所有的知识点 整顿的具体内容都放在了我的【GitHub】, 有须要的敌人自取。

4.Service 相干

  • 过程保活
  • Service 的运行线程(生命周期办法全副在主线程)
  • Service 启动形式以及如何进行
  • ServiceConnection 外面的回调办法运行在哪个线程?
  • startService 和 bingService 区别
  • 过程保活个别套路
  • 对于过程保活你须要晓得的所有

5.Android 布局优化

  • 什么状况下应用 ViewStub、include、merge?
  • 他们的原理是什么?
  • ViewStub、include、merge 概念解析
  • Android 布局优化之 ViewStub、include、merge 应用与源码剖析

6.BroadcastReceiver 相干

  • 注册形式,优先级
  • 播送类型,区别
  • 播送的应用场景,原理
  • Android 播送动静动态注册
  • 常见应用以及流程解析
  • 播送源码解析

#### 7.AsyncTask 相干

  • AsyncTask 是串行还是并行执行?
  • AsyncTask 随着安卓版本的变迁
  • AsyncTask 齐全解析
  • 串行还是并行

8.Android 事件散发机制

  • onTouch 和 onTouchEvent 区别,调用程序
  • dispatchTouchEvent,onTouchEvent,onInterceptTouchEvent 办法程序以及应用场景
  • 滑动抵触,如何解决
  • 事件散发机制
  • 事件散发解析
  • dispatchTouchEvent,onTouchEvent,onInterceptTouchEvent 办法的应用场景解析

因为篇幅无限,仅展现局部内容,所有的知识点 整顿的具体内容都放在了我的【GitHub】, 有须要的敌人自取。

对于 Android 开发的敌人来说应该是十分残缺的面试材料了,为了更好地整顿每个模块,我参考了很多网上的优质博文和我的项目,力求不漏掉每一个知识点。很多敌人靠着这些内容进行温习,拿到了 BATJ 等大厂的 offer,这个材料也曾经帮忙了很多的安卓开发者,心愿也能帮忙到你。

正文完
 0