开发用到的容器
Arraylist
- 底层是数组
- 扩容机制:默认扩容一半。如果扩容一半不够,就用目标的size(原数组长度+1)作为扩容后的容量
- 增删改查中, 增导致扩容,则会修改modCount,删一定会修改。 改和查一定不会修改modCount。
- 扩容操作会导致数组复制,批量删除会导致找出两个集合的交集,以及数组复制操作,因此,增、删都相对低效。 而 改、查都是很高效的操作。
- 结合特点,在使用中,以Android中最常用的展示列表为例,列表滑动时需要展示每一个Item(element)的数组,所以 查 操作是最高频的。相对来说,增操作 只有在列表加载更多时才会用到 ,而且是在列表尾部插入,所以也不需要移动数据的操作。而删操作则更低频。 故选用ArrayList作为保存数据的结构
LinkedList
- LinkedList和ArrayList是List接口的两种不同的实现,ArrayList的增删效率低,但是改查效率高。 而LinkedList正好相反,增删由于不需要移动底层数组数据,其底层是链表实现的,只需要修改链表节点指针,所以效率较高。
- LinkedList 是双向列表。通过下标获取某个node 的时候,(add select),会根据index处于前半段还是后半段 进行一个折半,以提升查询效率
HashMap
-
HashMap 是一个关联数组、哈希表,它是线程不安全的,允许key为null,value为null。遍历时无序。1.8之前是 数组+链表 1.8之后当链表长度达到8,会转化成红黑树,以提升它的查询、插入效率。
-
它是线程不安全的,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap,Map map=Collections.synchronizedMap(new HashMap())
-
当HashMap的容量达到threshold域值时,就会触发扩容。扩容前后,哈希桶的长度一定会是2的次方。这样在根据key的hash值寻找对应的哈希桶时,可以用位运算替代取余操作,更加高效。因为扩容是容量翻倍,所以原链表上的每个节点,现在可能存放在原来的下标,即low位, 或者扩容后的下标,即high位。 high位= low位+原哈希桶容量
-
由于HashMap的哈希桶的长度远比hash取值范围小,默认是16,所以当对hash值以桶的长度取余,以找到存放该key的桶的下标时,由于取余是通过与操作完成的,会忽略hash值的高位。因此只有hashCode()的低位参加运算,发生不同的hash值,但是得到的index相同的情况的几率会大大增加,这种情况称之为hash碰撞。 即,碰撞率会增大。
-
扰动函数就是为了解决hash碰撞的。它会综合hash值高位和低位的特征,并存放在低位,因此在与运算时,相当于高低位一起参与了运算,以减少hash碰撞的概率。
-
与运算替代模运算。用 hash & (table.length-1) 替代 hash % (table.length)
HashMap的put方法
HashMap与HashTable的区别
- 与之相比HashTable是线程安全的,且不允许key、value是null。
- HashTable默认容量是11。
- HashTable是直接使用key的hashCode(key.hashCode())作为hash值,不像HashMap内部使用static final int hash(Object key)扰动函数对key的hashCode进行扰动后作为hash值。
- HashTable取哈希桶下标是直接用模运算%.(因为其默认容量也不是2的n次方。所以也无法用位运算替代模运算)
- 扩容时,新容量是原来的2倍+1。int newCapacity = (oldCapacity << 1) + 1;
- Hashtable是Dictionary的子类同时也实现了Map接口,HashMap是Map接口的一个实现类;
LinkedHashMap
- LinkedHashMap 是一个关联数组、哈希表,它是线程不安全的,允许key为null,value为null。
- 它继承自HashMap,实现了Map<K,V>接口。其内部还维护了一个双向链表,在每次插入数据,或者访问、修改数据时,会增加节点、或调整链表的节点顺序。以决定迭代时输出的顺序。
- 遍历时的顺序是按照插入节点的顺序。这也是其与HashMap最大的区别。也可以在构造时传入accessOrder参数,使得其遍历顺序按照访问的顺序输出。accessOrder ,默认是false,则迭代时输出的顺序是插入节点的顺序。若为true,则输出的顺序是按照访问节点的顺序
Map<String, String> map = new LinkedHashMap<>();
map.put("1", "a");
map.put("2", "b");
map.put("3", "c");
map.put("4", "d");
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println("以下是accessOrder=true的情况:");
map = new LinkedHashMap<String, String>(10, 0.75f, true);
map.put("1", "a");
map.put("2", "b");
map.put("3", "c");
map.put("4", "d");
map.get("2");//2移动到了内部的链表末尾
map.get("4");//4调整至末尾
map.put("3", "e");//3调整至末尾
map.put(null, null);//插入两个新的节点 null
map.put("5", null);//5
iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
结果:
1=a
2=b
3=c
4=d
以下是accessOrder=true的情况:会将最近访问的数据放置队尾
1=a
2=b
4=d
3=e
null=null
5=null
- 它与HashMap比,还有一个小小的优化,重写了containsValue()方法,直接遍历内部链表去比对value值是否相等。为什么它不重写containsKey()方法,也去循环比对内部链表的key是否相等呢? 是否因为HashMap中根据key找到index,再定位到元素,需要O(1)的时间。如果用链表则需要o(n)的时间。
ArrayMap(安卓中的容器)
-
ArrayMap 实现了implements Map<K, V>接口,所以它也是一个关联数组、哈希表。存储以key->value 结构形式的数据。它也是线程不安全的,允许key为null,value为null。
-
它的内部实现是基于两个数组。一个int[]数组,用于保存每个item的hashCode.一个Object[]数组,保存key/value键值对。容量是上一个数组的两倍。
-
但是它不适合大容量的数据存储。存储大量数据时,它的性能将退化至少50%。比传统的HashMap时间效率低。因为其会对key使用二分法进行从小到大排序,
-
当你想获取某个value的时候,ArrayMap会计算输入key转换过后的hash值,然后对hash数组使用二分查找法寻找到对应的index,然后我们可以通过这个index在另外一个数组中直接访问到需要的键值对。如果在第二个数组键值对中的key和前面输入的查询key不一致,那么就认为是发生了碰撞冲突。为了解决这个问题,我们会以该key为中心点,分别上下展开,逐个去对比查找,直到找到匹配的值。
SparseArray
-
SparseArray是用于在Android平台上替代HashMap的数据结构,更具体的说,是用于替代key为int类型,value为Object类型的HashMap。
-
它也是线程不安全的,允许value为null。
-
从原理上说,
- 它的内部实现也是基于两个数组。一个int[]数组mKeys,用于保存每个item的key,key本身就是int类型,所以可以理解hashCode值就是key的值.一个Object[]数组mValues,保存value。容量和key数组的一样。
-
同样它不适合大容量的数据存储。存储大量数据时,它的性能将退化至少50%。
-
比传统的HashMap时间效率低。因为其会对key从小到大排序,使用二分法查询key对应在数组中的下标
-
适用场景:
- 数据量不大(千以内)
- 空间比时间重要
- 需要使用Map,且key为int类型。
Java容器类
从图中可以知道分为:Collection和Map两大类
Collection
Collection是List和Set抽象出来的接口,它包含了这些集合的基本操作。是集合的顶层接口
- List
List接口通常表示一个列表(数组、队列、链表,栈等),其中的元素可以重复,常用的实现类为ArrayList、LinkedList和Vector。
- Set
Set接口通常表示一个集合,集合中的元素不允许重复(通过hashCode和equals函数保证),常用的实现类有HashSet和TreeSet,HashSet是通过Map中的HashMap来实现的,而TreeSet则是通过Map中的TreeMap实现的,另外TreeSet还实现了SortedSet接口,因此是有序的集合。
- List 和 Set 的区别
Set接口存储的是无序的、不重复的数据 List接口存储的是有序的、可以重复的数据Set检索效率低,删除和插入效率高,插入和删除不会引起元素位置改变。List查找元素效率高,删除和插入效率低,List和数组类似,可以动态增长,根据实际存储的长度自动增长List的长度
Map
Map是一个映射接口,其中的每个元素都是一个Key-Value键值对,同样抽象类AbstractMap通过适配器模式实现了Map接口的大部分函数,TreeMap、HashMap和WeakHashMap等实现类都通过继承AbstractMap来实现。
Iterator
Iterator是遍历集合的迭代器,它可以用来遍历Collection,但是不能用来遍历Map。Collection的实现类都实现了iterator()函数,它返回一个Iterator对象,用来遍历集合,ListIterator则专门用来遍历List。而Enumeration则是JDK 1.0时引入的,作用与Iterator相同,但它的功能比Iterator要少,它只能在Hashtable、Vector和Stack中使用。
Arrays 和 Collections
Arrays和Collections是用来操作数组、集合的两个工具类,例如在ArrayList和Vector中大量调用了Arrays.Copyof()方法,而Collections中有很多静态方法可以返回各集合类的synchronized版本,即线程安全的版本,当然了,如果要用线程安全的集合类,首选concurrent并发包下的对应的集合类。
Java中哪些是线程安全的容器?
同步容器类:使用了synchronized:
- 1.Vector
- 2.HashTable 并发容器:
- 3.ConcurrentHashMap:分段
- 4.CopyOnWriteArrayList:写时复制
- 5.CopyOnWriteArraySet:写时复制 Queue:
- 6.ConcurrentLinkedQueue:是使用非阻塞的方式实现的基于链接节点的无界的线程安全队列,性能非常好。
- 7.ArrayBlockingQueue:基于数组的有界阻塞队列
- 8.LinkedBlockingQueue:基于链表的有界阻塞队列。
- 9.PriorityBlockingQueue:支持优先级的无界阻塞队列,即该阻塞队列中的元素可自动排序。默认情况下,元素采取自然升序排列
- 10.DelayQueue:一种延时获取元素的无界阻塞队列。
- 11.SynchronousQueue:不存储元素的阻塞队列。每个put操作必须等待一个take操作,否则不能继续添加元素。内部其实没有任何一个元素,容量是0
Deque: (Deque接口定义了双向队列。双向队列允许在队列头和尾部进行入队出队操作。)
- 12.ArrayDeque:基于数组的双向非阻塞队列。
- 13.LinkedBlockingDeque:基于链表的双向阻塞队列。 Sorted容器:
- 14.ConcurrentSkipListMap:是TreeMap的线程安全版本
- 15.ConcurrentSkipListSet:是TreeSet的线程安全版本
java常见问题
问题1:多线程中wait和sleep的区别
- 1、sleep()是Thread类的方法,wait()是Object类中定义的方法。
- 2、sleep()方法可以在任何地方使用,wait()方法只能在synchronized方法或synchronized中使用。
- 3、Thread.sleep()只会让出CPU,不会导致锁行为的改变;Object.wait()不仅让出CPU,还会释放已经占有的同步资源锁 blog.csdn.net/u012050154/…
线程的几种状态
- 初始(NEW) :新创建了一个线程对象,但还没有调用start()方法。
- 运行(RUNNABLE) :Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
3. 阻塞(BLOCKED) :表示线程阻塞于锁。
4. 等待(WAITING) :进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
5. 超时等待(TIMED_WAITING) :该状态不同于WAITING,它可以在指定的时间后自行返回。
- 终止(TERMINATED) :表示该线程已经执行完毕。
问题2: java 中==和 equals 和 hashCode 的区别
- 1.==若是基本数据类型比较,是比较值,若是引用类型,则比较的是他们在内存中的存 放地址。对象是存放在堆中,栈中存放的对象的引用,所以==是对栈中的值进行比较,若 返回 true 代表变量的内存地址相等;
- 2.equals 是 Object 类中的方法,Object 类的 equals 方法用于判断对象的内存地址引用 是不是同一个地址(是不是同一个对象)。若是类中覆盖了 equals 方法,就要根据具体代 码来确定,一般覆盖后都是通过对象的内容是否相等来判断对象是否相等。
- 3.hashCode()计算出对象实例的哈希码,在对象进行散列时作为 key 存入.
- 4.equals 与 hashCode 方法关系:
- hashCode()是一个本地方法,实现是根据本地机器上关的。equals()相等的对象, hashCode()也一定相等;hashCode()不等,equals()一定也不等;hashCode()相等,equals() 可能相等,也可能不等。
- 所以在重写 equals(Object obj)方法,有必要重写 hashCode()方法,确保通过 equals(Object obj)方法判断结果为 true 的两个对象具备相等的 hashCode()返回值。
- 5.equals 与==的关系:
- Integer b1 = 127;在 java 编译时被编译成 Integer b1 = Integer.valueOf(127);对于-128 到 127 之间的 Integer 值,用的是原生数据类型 int,会在内存里供重用,也就是这之间的 Integer 值进行==比较时,只是进行 int 原生数据类型的数值进行比较。而超出-128〜127 的范围,进行==比较时是进行地址及数值比较。
问题3:int 与 integer 的区别
Integer 是 int 的包装类,int 则是 java 的一种基本数据类型,Integer 变量必须实例化才能 使用,当 new 一个 Integer 时,实际是生成一个指向此对象的引用,而 int 是直接存储数 据的值,Integer 默认值是 null,而 int 默认值是 0
问题4:谈谈对 java 多态的理解
- 同一个消息可以根据发送对象的不同而采用多种不同的行为方式,在执行期间判断所引用的 对象的实际类型,根据其实际的类型调用其相应的方法。
- 作用:消除类型之间的耦合关系。实现多态的必要条件:继承、重写(因为必须调用父类中 存在的方法)、父类引用指向子类对象
问题5:String、StringBuffer、StringBuilder 区别
- 都是字符串类,String 类中使用字符数组保存字符串,因有 final 修饰符,String 对象是不 可变的,每次对 String 操作都会生成新的 String 对象,这样效率低,且浪费内存空间。但 线程全。
- StringBuilder 和 StringBuffer 也是使用字符数组保存字符,但这两种对象都是可变的,即对字符串进行 append 操作,不会产生新的对象
- 它们的区别是:StringBuffer 对方法加 了同步锁,是线程安全的,StringBuilder 非线程安全。
问题6:序列化 Serializable 和 Parcelable 的区别
Serializable
serialVersionUID
如果不手动指定 serialVersionUID 的值,反序列化时当前类有些改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的hash值并把它赋值给serialVersionUID,这个时候当前类的 serialVersionUID 就和反序列化数据中的 serialVersionUID 不一致,就会造成反序列化失败的结果。
对于使用序列化还有两点需要注意:
- 静态成员变量属于类不属于对象,所以不参与序列化过程
- 用transient关键字标记的成员变量不参与序列化过程
Parcelable
Parcelable接口是Android SDK提供的一种专门用于Android应用中对象的序列化和反序列化的方式,相比于Seriablizable具有更好的性能。实现Parcelable接口的对象就可以实现序列化并可以通过 Intent 和 Binder 传递。
实现Parcelable接口主要可以分为一下几步:
- 1)implements Parcelable。
- 2)重写writeToParcel方法,将你的对象序列化为一个Parcel对象,即:将类的数据写入外部提供的Parcel中,打包需要传递的数据到Parcel容器保存,以便从Parcel容器获取数据。
- 3)重写describeContents方法,内容接口描述,默认返回0即可。
- 4)实例化静态内部对象CREATOR实现接口Parcelable.Creator 。
注意 : 若将Parcel看成是一个流,则先通过writeToParcel把对象写到流里面,再通过createFromParcel从流里读取对象,因此类实现的写入顺序和读出顺序必须一致。
Serializable 和 Serializable 区别
Android的Parcelable的设计初衷是因为Serializable效率过慢,为了在程序内不同组件间以及不同Android程序间(AIDL)高效的传输数据而设计,这些数据仅在内存中存在,Parcelable是通过IBinder通信的消息的载体。
常见的几种夸进程通信方式(IPC)
- 序列化
- aidl
- messenger
- binder连接池
android常见问题
ThreadLocal
- 1:使每个线程都可以独立的改变自己空间的资源(即存储的值)而不会和其他线程的资源冲突
- 2:若两个线程同时执行一段含有一个Threadlocal变量引用的代码,它们也无法访问到对方的ThreadLocal变量 原理:存值的时候是先获取到当前线程的ThreadLocalMap对象,ThreadLocalMap key是当前ThreadLocal,值是存储的value。即一个线程对应一个ThreadLocalMap,这样不会和其他线程的资源冲突
实际开发中应用的例子
- 主线程跟子线程通过spliteHelper获取数据的问题
Handler消息机制
子线程中需要更新UI的消息传递到UI线程中,从而实现子线程对UI的更新处理,最终实现异步消息的处理,从而避免线程操作不安全的问题
相关概念:
- Message:线程之间通讯的数据单元
- MessageQueue:消息存储的队列,是一种数据结构(先进先出)
- Handler:
- 1:主线程与子线程的通信媒介(将消息添加到消息队列)
- 2:线程消息的主要处理者(处理循环器Looper分派过来的消息)
- Looper:消息队列与处理者的通信媒介
- 1:消息获取:循环取出消息队列的消息
- 2:消息分发:将取出的消息发送给对应的处理者
源码分析:
主线程的死循环一直运行是不是特别消耗CPU资源呢?
主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
线程池
线程池就是事先将多个线程对象放到一个容器中,使用的时候就不用 new 线程而是直接去池中拿线程即可,节 省了开辟子线程的时间,提高了代码执行 效率。
作用
- 1:复用线程
- 2:管理线程(统一分配,调优和监控;控制线程池的最大并发数
优点
- 1:降低因线程的创建/销毁所带来的性能开销
- 2:提高线程响应速度/执行效率
- 3:提高对线程的管理
核心参数:
- corePoolSize:核心线程数
- maximumPoolSize:线程池所能容纳的最大线程数
- keepAliveTime:非核心线程闲置超时时长
- TimeUnit unit:指定keepAliveTime参数的时间单位
- workQueue:任务队列
- threadFactory:线程工程,为线程池创建新线程
常见的4类功能线程池:
- 定长线程池(FixedThreadPool) 它是一种线程数量固定的线程池,当线程处于空闲状态时,它们并不会被收回,除非线程池关闭了。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来
// 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run(){
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务:execute()
fixedThreadPool.execute(task);
// 4. 关闭线程池
fixedThreadPool.shutdown();
- 定时线程池(ScheduledThreadPool ) 它的核心线程数量是固定的,而非核心线程数量是没有限制的,并且当核心线程闲置时会被立即收回。ScheduledThreadPoll这类线程主要用于执行定时任务和具有固定周期的重复任务
// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run(){
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务:schedule()
scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务
// 4. 关闭线程池
scheduledThreadPool.shutdown();
- 可缓存线程池(CachedThreadPool) 它是一种线程数量不定的线程池,它只有非核心线程,并且最大线程数为Integer.MAX_VALUE。由于Integer.MAX_VALUE是一个很大的数,实际上就相当于最大线程数可以任意大。当线程池中的线程都处于活动状态时,线程池会创建新的线程来处理新任务,否则就会利用空闲的线程来处理新的任务。线程池中的空闲线程都有超时机制,这个超时时长为60秒,超过60秒闲置线程就会被回收。和FixedThreadPool不同的是,CachedThreadPool的任务队列其实相当于一个空集合,这将导致任何任务都会立即被执行,因为这种情况下SynchronousQueue是无法插入任务的。SynchronousQueue是一个非常特殊的队列,很多情况下可以理解为一个无法存储元素的队列(实际中很少使用)。从CachedThreadPool的特性来看这类线程池比较适合执行大量的耗时较少的任务。当整个线程池都处于闲置状态时,线程池中的线程都会超时而被停止,这个时候CachedThreadPool之中是没有任何线程的,它几乎不占用任何系统资源的。
// 1. 创建可缓存线程池对象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run(){
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务:execute()
cachedThreadPool.execute(task);
// 4. 关闭线程池
cachedThreadPool.shutdown();
//当执行第二个任务时第一个任务已经完成
//那么会复用执行第一个任务的线程,而不用每次新建线程。
- 单线程化线程池(SingleThreadExecutor) 这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中顺序执行。SingleThreadExecutor的意义在于统一所有的外界任务到一个线程中,这使得在这些任务之间不需要处理线程同步的问题
// 1. 创建单线程化线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run(){
System.out.println("执行任务啦");
}
};
// 3. 向线程池提交任务:execute()
singleThreadExecutor.execute(task);
// 4. 关闭线程池
singleThreadExecutor.shutdown();
线程池的线程复用:
这里就需要深入到源码 addWorker():它是创建新线程的关键,也是线程复用的 关键入口。最终会执行到 runWoker,它取任务有两个方式:
- firstTask:这是指定的第一个 runnable 可执行任务,它会在 Woker 这个 工作线程中运行执行任务 run。并且置空表示这个任务已经被执行。
- getTask():这首先是一个死循环过程,工作线程循环直到能够取出 Runnable 对象或超时返回,这里的取的目标就是任务队列 workQueue, 对应刚才入队的操作,有入有出。 其实就是任务在并不只执行创建时指定的 firstTask 第一任务,还会从任务队列 的中通过 getTask()方法自己主动去取任务执行,而且是有/无时间限定的阻塞等 待,保证线程的存活。
线程池都有哪几种工作队列?
1、ArrayBlockingQueue
是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素 进行排序。
2、LinkedBlockingQueue
一个基于链表结构的阻塞队列,此队列按 FIFO (先进先出) 排序元素,吞吐 量通常要高于 ArrayBlockingQueue。静态工厂方法 Executors.newFixedThreadPool()和 Executors.newSingleThreadExecutor 使用了 这个队列。
3、 SynchronousQueue
一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作, 否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQueue,静 态工厂方法 Executors.newCachedThreadPool 使用了这个队列。
4、PriorityBlockingQueue
一个具有优先级的无限阻塞队列。
怎么理解无界队列和有界队列?
- 有界队列
1.初始的 poolSize < corePoolSize,提交的 runnable 任务,会直接做为 new 一 个 Thread 的参数,立马执行 。 2.当提交的任务数超过了 corePoolSize,会将 当前的 runable 提交到一个 block queue 中。3.有界队列满了之后,如果 poolSize < maximumPoolsize 时,会尝试 new 一个 Thread 的进行救急处理,立马执行 对应的 runnable 任务。 4.如果 3 中也无法处理了,就会走到第四步执行 reject 操作。
- 无界队列
与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败 的情况。当有新的任务到来,系统的线程数小于 corePoolSize 时,则新建线程 执行任务。当达到 corePoolSize 后,就不会继续增加,若后续仍有新的任务加 入,而没有空闲的线程资源,则任务直接进入队列等待。若任务创建和处理的速 度差异很大,无界队列会保持快速增长,直到耗尽系统内存。 当线程池的任务缓存队列已满并且线程池中的线程数目达到 maximumPoolSize,如果还有任务 到来就会采取任务拒绝策略。
Android中多线程开发
-
asyncTask 主要是调用线程池执行耗时任务,然后用handler跟新UI,定义了最大线程数是128,超过这个数,执行会报错
-
HandlerThread HandlerThread继承了Thread,它是一种可以使用Handler的Thread,它的实现也很简单,就是在run方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环,这样在实际的使用中就允许HandlerThread中创建Handler
-
intentService 复写onHandleIntent()方法处理耗时任务
事件分发机制
- 对于 dispatchTouchEvent,onTouchEvent,return true是终结事件传递。return false 是回溯到父View的onTouchEvent方法。
- ViewGroup 想把自己分发给自己的onTouchEvent,需要拦截器onInterceptTouchEvent方法return true 把事件拦截下来。
- ViewGroup 的拦截器onInterceptTouchEvent 默认是不拦截的,所以return super.onInterceptTouchEvent()=return false;
- View 没有拦截器,为了让View可以把事件分发给自己的onTouchEvent,View的dispatchTouchEvent默认实现(super)就是把事件分发给自己的onTouchEvent。
事件分发的一点思考
给一个按钮同时设置onTouch和onClick事件谁先执行的? onTouch是优先于onClick执行的,并且onTouch执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(你还可能会有多次ACTION_MOVE的执行,如果你手抖了一下)。因此事件传递的顺序是先经过onTouch,再传递到onClick。
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "onClick execute");
}
});
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "onTouch execute, action " + event.getAction());
return false;
}
});
如果我们尝试把onTouch方法里的返回值改成true会出现什么问题?
onClick方法不再执行了
原因:
这是view的dispatchTouchEvent源码:
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
当dispatchTouchEvent三个条件都成立后就会执行retrun true方法,我们知道dispatchTouchEvent retrun true后事件是消费掉的。 这三个条件是:
- 第一个条件 mOnTouchListener != null,也就是说只要我们给控件注册了touch事件,mOnTouchListener就一定被赋值了。
- 第二个条件是(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true。
- 第三个条件就比较关键了,mOnTouchListener.onTouch(this, event),其实也就是去回调控件注册touch事件时的onTouch方法。也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,从而整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
return true;
}
return false;
}
这里我们知道是onClick的调用肯定是在onTouchEvent(event)方法中的!
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
View的绘制流程
主要分为:measure、layout、draw 过程
measure
对于View的测量,肯定会和MeasureSpec接触翻译过来“测量规格”或者“测量参数”,“一个MeasureSpec封装了从父容器传递给子容器的布局要求”“传递” 两个字很重要,更精确的说法应该这个MeasureSpec是由父View的MeasureSpec和子View的LayoutParams通过简单的计算得出一个针对子View的测量要求,这个测量要求就是MeasureSpec。
MeasureSpec是一个大小跟模式组合的值。 MeasureSpec是一个32位的int值,其中高2位为测量模式,低30位为测量大小。
测量模式分类
- EXACTLY 精确模式,即控件的宽高为具体的数值,例如Android:lanout_width=100dp,或者指定为match_parent属性时(占据父view的大小)
- AT_MOST 最大值模式,即控件的宽高属性为android:lanout_width=warp_content时,这时候控件的大小随着内容的大小决定,只要不超过父控件的大小
- UPSPECIFIED 父容器对于子容器没有任何限制,子容器想要多大就多大
测量过程:
父View的measure的过程会先测量子View,等子View测量结果出来后,再来测量自己,
根据父View的测量规格和父View自己的Padding, 还有子View的Margin和已经用掉的空间大小(widthUsed),就能算出子View的MeasureSpec,
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
// 子View的LayoutParams,你在xml的layout_width和layout_height,
// layout_xxx的值最后都会封装到这个个LayoutParams。
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//根据父View的测量规格和父View自己的Padding,
//还有子View的Margin和已经用掉的空间大小(widthUsed),就能算出子View的MeasureSpec,具体计算过程看getChildMeasureSpec方法。
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height);
//通过父View的MeasureSpec和子View的自己LayoutParams的计算,算出子View的MeasureSpec,然后父容器传递给子容器的
// 然后让子View用这个MeasureSpec(一个测量要求,比如不能超过多大)去测量自己,如果子View是ViewGroup 那还会递归往下测量。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// spec参数 表示父View的MeasureSpec
// padding参数 父View的Padding+子View的Margin,父View的大小减去这些边距,才能精确算出
// 子View的MeasureSpec的size
// childDimension参数 表示该子View内部LayoutParams属性的值(lp.width或者lp.height)
// 可以是wrap_content、match_parent、一个精确指(an exactly size),
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec); //获得父View的mode
int specSize = MeasureSpec.getSize(spec); //获得父View的大小
//父View的大小-自己的Padding+子View的Margin,得到值才是子View的大小。
int size = Math.max(0, specSize - padding);
int resultSize = 0; //初始化值,最后通过这个两个值生成子View的MeasureSpec
int resultMode = 0; //初始化值,最后通过这个两个值生成子View的MeasureSpec
switch (specMode) {
// Parent has imposed an exact size on us
//1、父View是EXACTLY的 !
case MeasureSpec.EXACTLY:
//1.1、子View的width或height是个精确值 (an exactly size)
if (childDimension >= 0) {
resultSize = childDimension; //size为精确值
resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY 。
}
//1.2、子View的width或height为 MATCH_PARENT/FILL_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size; //size为父视图大小
resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY 。
}
//1.3、子View的width或height为 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size; //size为父视图大小
resultMode = MeasureSpec.AT_MOST; //mode为AT_MOST 。
}
break;
// Parent has imposed a maximum size on us
//2、父View是AT_MOST的 !
case MeasureSpec.AT_MOST:
//2.1、子View的width或height是个精确值 (an exactly size)
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension; //size为精确值
resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY 。
}
//2.2、子View的width或height为 MATCH_PARENT/FILL_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size; //size为父视图大小
resultMode = MeasureSpec.AT_MOST; //mode为AT_MOST
}
//2.3、子View的width或height为 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size; //size为父视图大小
resultMode = MeasureSpec.AT_MOST; //mode为AT_MOST
}
break;
// Parent asked to see how big we want to be
//3、父View是UNSPECIFIED的 !
case MeasureSpec.UNSPECIFIED:
//3.1、子View的width或height是个精确值 (an exactly size)
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension; //size为精确值
resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY
}
//3.2、子View的width或height为 MATCH_PARENT/FILL_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0; //size为0! ,其值未定
resultMode = MeasureSpec.UNSPECIFIED; //mode为 UNSPECIFIED
}
//3.3、子View的width或height为 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0; //size为0! ,其值未定
resultMode = MeasureSpec.UNSPECIFIED; //mode为 UNSPECIFIED
}
break;
}
//根据上面逻辑条件获取的mode和size构建MeasureSpec对象。
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
子view的测量过程
View的测量过程主要是在onMeasure()方法,如果想自定义View的测量,你应该去重写onMeasure()方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//获取的是android:minHeight属性的值或者View背景图片的大小值
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
//@param size参数一般表示设置了android:minHeight属性或者该View背景图片的大小值
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED: //表示该View的大小父视图未定,设置为默认值
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
ViewGroup的Measure过程
//FrameLayout 的测量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
....
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 遍历自己的子View,只要不是GONE的都会参与测量,measureChildWithMargins方法在最上面
// 的源码已经讲过了,如果忘了回头去看看,基本思想就是父View把自己的MeasureSpec
// 传给子View结合子View自己的LayoutParams 算出子View 的MeasureSpec,然后继续往下传,
// 传递叶子节点,叶子节点没有子View,根据传下来的这个MeasureSpec测量自己就好了。
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
....
....
}
}
.....
.....
//所有的孩子测量之后,经过一系类的计算之后通过setMeasuredDimension设置自己的宽高,
//对于FrameLayout 可能用最大的字View的大小,对于LinearLayout,可能是高度的累加,
//具体测量的原理去看看源码。总的来说,父View是等所有的子View测量结束之后,再来测量自己。
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
....
}
简述下自定义FlowLayout
要做FlowLayout,首先涉及下面几个问题:
-
(1)何时换行 (2)、如何得到FlowLayout的宽度 (3)、如何得到FlowLayout的高度 步骤:
-
重写onMeasure()——计算当前FlowLayout所占的宽高
- 首先,刚进来的时候是利用MeasureSpec获取系统建议的数值的模式
- 然后,是计算FlowLayout所占用的空间大小:
- 遍历子view的宽(涉及到子view的margin)>测量控件的宽 如果大于换行
- 最后,通过setMeasuredDimension()设置到系统中:
-
重写onLayout()——布局所有子控件
- 重写onLayout()——布局所有子控件
- 然后根据是否要换行来计算当行控件的top坐标和left坐标:
java动态代理实现与原理详细分析
静态代理
- 被代理类跟代理类都实现同一个接口
- 代理类持有被代理类的实力对象
- 调用代理类的方法实际就是执行被代理类的方法
动态代理
代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类(studentProxy)是自己定义好的,在程序运行之前就已经编译完成。
相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。 动态代理简单实现
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
//创建一个代理对象stuProxy,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
创建StuInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。
再再invoke方法中执行被代理对象target的相应方法。
public class StuInvocationHandler<T> implements InvocationHandler {
//invocationHandler持有的被代理对象
T target;
public StuInvocationHandler(T target) {
this.target = target;
}
/**
* proxy:代表动态代理对象
* method:代表正在执行的方法
* args:代表调用目标方法时传入的实参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理执行" +method.getName() + "方法");
*/
//代理过程中插入监测方法,计算该方法耗时
MonitorUtil.start();
Object result = method.invoke(target, args);
MonitorUtil.finish(method.getName());
return result;
}
}
执行操作:
public class ProxyTest {
public static void main(String[] args) {
//创建一个实例对象,这个对象是被代理的对象
Person zhangsan = new Student("张三");
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);
//创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
//代理执行上交班费的方法
stuProxy.giveMoney();
}
}
原理分析
- 上面我们利用Proxy类的newProxyInstance方法创建了一个动态代理对象,它只是封装了创建动态代理类的步骤(红色标准部分):
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
其实,我们最应该关注的是 Class<?> cl = getProxyClass0(loader, intfs);这句,这里产生了代理类这个类文件是缓存在java虚拟机中的
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;
public final class $Proxy0 extends Proxy implements Person
{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
/**
*注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白
*为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个
*被代理对象的实例,不禁会想难道是....? 没错,就是你想的那样。
*
*super(paramInvocationHandler),是调用父类Proxy的构造方法。
*父类持有:protected InvocationHandler h;
*Proxy构造方法:
* protected Proxy(InvocationHandler h) {
* Objects.requireNonNull(h);
* this.h = h;
* }
*
*/
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
//这个静态块本来是在最后的,我把它拿到前面来,方便描述
static
{
try
{
//看看这儿静态块儿里面有什么,是不是找到了giveMoney方法。请记住giveMoney通过反射得到的名字m3,其他的先不管
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
/**
*
*这里调用代理对象的giveMoney方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。
*this.h.invoke(this, m3, null);这里简单,明了。
*来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象,
*再联系到InvacationHandler中的invoke方法。嗯,就是这样。
*/
public final void giveMoney()
throws
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
//注意,这里为了节省篇幅,省去了toString,hashCode、equals方法的内容。原理和giveMoney方法一毛一样。
}
我们可以对InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
okhttp源码分析
Dispatcher:
- 同步 Dispatcher在执行同步的Call:直接加入到runningSyncCall队列中,实际上并没有执行该Call,而是交给外部执行
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
- 异步 将Call加入队列:如果当前正在执行的call的数量大于maxRequest(64),或者该call的Host上的call超过maxRequestsPerHos(5),则加入readyAsyncCall排队等待,否则加入runningAsyncCalls并执行
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
异步队列是如何从ready到running?,在每个call结束的时候都会调用finished
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
//每次remove完后,执行promoteCalls来轮转。
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
//线程池为空时,执行回调
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
private void promoteCalls() {
//如果当前执行的线程大于maxRequests(64),则不操作
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
大家可以知道finished先执行calls.remove(call)删除call,然后执行promoteCalls(),在promoteCalls()方法里面:如果当前线程大于maxRequest则不操作,如果小于maxRequest则遍历readyAsyncCalls,取出一个call,并把这个call放入runningAsyncCalls,然后执行execute。在遍历过程中如果runningAsyncCalls超过maxRequest则不再添加,否则一直添加。所以可以这样说: promoteCalls()负责ready的Call到running的Call的转化 具体的执行请求则在RealCall里面实现的,同步的在RealCall的execute里面实现的,而异步的则在AsyncCall的execute里面实现的。里面都是调用RealCall的getResponseWithInterceptorChain的方法来实现责任链的调用
5大拦截器的作用
RetryAndFollowUpInterceptor
重定向拦截器的主要作用是负责请求的重定向操作以及请求失败后像路由错误、IO异常等的失败的重试 该方法内部执行过程大概是这么几件事:
- 开启一个循环,因为重定向可能不止一次;
- 执行Transmitter的prepareToConnec方法进行连接准备,如判断是否相同连接、是否释放连接等;
- 执行Chain的proceed方法触发下一个拦截器;
- 执行followUpRequest方法处理重定向,方法内会做30X(300~399是重定向相关返回码)的返回码* 判断从而进行重定向请求;
- 判断重定向是否大于最大限制。
BridgeInterceptor
该拦截器主要是添加一些必要的请求头信息
缓存处理拦截器CacheInterceptor
- 获得缓存对象;
- 通过CacheStrategy.Factory的get方法计算缓存是否可用,然后获得网络请求对象
- networkRequest和缓存对象cacheResponse;
- 判断networkRequest为null,表示缓存有效,直接返回缓存;
- 触发下一个拦截器;
- 判断cacheResponse不为null,表示网络请求和缓存请求同时存在进一步判断缓存是否可用;
- 如果上述中缓存不可用,便使用网络请;
- 缓存响应结果。
连接拦截器ConnectInterceptor
如果前面缓存拦截器中,缓存无效,则会触发到连接拦截器。 主要作用是:创建与服务端建立Socket连接、发起与服务端的TCP连接、服务端建立TCP连接、TLS握手等的逻辑处理,这里就不重复了。
CallServerInterceptor 该拦截器是拦截器列表中,最后执选择拦截器,它用于向服务器发起网络请求。
该方法内部忽略100和101的返回码逻辑,过程大概是这么几件事:
- 写入请求头信息;
- 写入请求主体信息;
- 结束请求;
- 获得响应头信息;
- 获得响应主体信息;
- 关闭连接。
okhttp缓存机制
CacheControl对应HTTP里面的CacheControl
public final class CacheControl {
private final boolean noCache;
private final boolean noStore;
private final int maxAgeSeconds;
private final int sMaxAgeSeconds;
private final boolean isPrivate;
private final boolean isPublic;
private final boolean mustRevalidate;
private final int maxStaleSeconds;
private final int minFreshSeconds;
private final boolean onlyIfCached;
private final boolean noTransform;
/**
* Cache control request directives that require network validation of responses. Note that such
* requests may be assisted by the cache via conditional GET requests.
*/
public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();
/**
* Cache control request directives that uses the cache only, even if the cached response is
* stale. If the response isn't available in the cache or requires server validation, the call
* will fail with a {@code 504 Unsatisfiable Request}.
*/
public static final CacheControl FORCE_CACHE = new Builder()
.onlyIfCached()
.maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
.build();
}
- noCache() 对应于“no-cache”,如果出现在 响应 的头部,不是表示不允许对响应进行缓存,而是表示客户端需要与服务器进行再次验证,进行一个额外的GET请求得到最新的响应;如果出现请求头部,则表示不适用缓存响应,即记性网络请求获取响应。
- noStore() 对应于"no-store",如果出现在响应头部,则表明该响应不能被缓存
- maxAge(int maxAge,TimeUnit timeUnit) 对应"max-age",设置缓存响应的最大存货时间。如果缓存响满足了到了最大存活时间,那么将不会再进行网络请求
- maxStale(int maxStale,TimeUnit timeUnit) 对应“max-stale”,缓存响应可以接受的最大过期时间,如果没有指定该参数,那么过期缓存响应将不会被使用
- minFresh(int minFresh,TimeUnit timeUnit) 对应"min-fresh",设置一个响应将会持续刷新最小秒数,如果一个响应当minFresh过去后过期了,那么缓存响应不能被使用,需要重新进行网络请求
- onlyIfCached() 对应“onlyIfCached”,用于请求头部,表明该请求只接受缓存中的响应。如果缓存中没有响应,那么返回一个状态码为504的响应。
CacheStrategy类详解
策略原理
根据输出的networkRequest和cacheResponse的值是否为null给出不同的策略,如下:
| networkRequest | cacheResponse | result 结果 |
|---|---|---|
| null | null | only-if-cached (表明不进行网络请求,且缓存不存在或者过期,一定会返回503错误) |
| null | nulnon-nulll | 不进行网络请求,直接返回缓存,不请求网络 |
| non-null | null | 需要进行网络请求,而且缓存不存在或者过去,直接访问网络 |
| non-null | non-null | Header中包含ETag/Last-Modified标签,需要在满足条件下请求,还是需要访问网络 |
CacheStrategy类的构造
public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;
if (cacheResponse != null) {
this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
Headers headers = cacheResponse.headers();
//获取cacheReposne中的header中值
for (int i = 0, size = headers.size(); i < size; i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
if ("Date".equalsIgnoreCase(fieldName)) {
servedDate = HttpDate.parse(value);
servedDateString = value;
} else if ("Expires".equalsIgnoreCase(fieldName)) {
expires = HttpDate.parse(value);
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
lastModified = HttpDate.parse(value);
lastModifiedString = value;
} else if ("ETag".equalsIgnoreCase(fieldName)) {
etag = value;
} else if ("Age".equalsIgnoreCase(fieldName)) {
ageSeconds = HttpHeaders.parseSeconds(value, -1);
}
}
}
}
/**
* Returns a strategy to satisfy {@code request} using the a cached response {@code response}.
*/
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
return new CacheStrategy(null, null);
}
return candidate;
}
/**
* Returns a strategy to satisfy {@code request} using the a cached response {@code response}.
*/
public CacheStrategy get() {
//获取当前的缓存策略
CacheStrategy candidate = getCandidate();
//如果是网络请求不为null并且请求里面的cacheControl是只用缓存
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
//使用只用缓存的策略
return new CacheStrategy(null, null);
}
return candidate;
}
/** Returns a strategy to use assuming the request can use the network. */
private CacheStrategy getCandidate() {
// No cached response.
//如果没有缓存响应,返回一个没有响应的策略
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
//如果是https,丢失了握手,返回一个没有响应的策略
// Drop the cached response if it's missing a required handshake.
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
// 响应不能被缓存
// If this response shouldn't have been stored, it should never be used
// as a response source. This check should be redundant as long as the
// persistence store is well-behaved and the rules are constant.
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//获取请求头里面的CacheControl
CacheControl requestCaching = request.cacheControl();
//如果请求里面设置了不缓存,则不缓存
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
//获取响应的年龄
long ageMillis = cacheResponseAge();
//获取上次响应刷新的时间
long freshMillis = computeFreshnessLifetime();
//如果请求里面有最大持久时间要求,则两者选择最短时间的要求
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
long minFreshMillis = 0;
//如果请求里面有最小刷新时间的限制
if (requestCaching.minFreshSeconds() != -1) {
//用请求中的最小更新时间来更新最小时间限制
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
//最大验证时间
long maxStaleMillis = 0;
//响应缓存控制器
CacheControl responseCaching = cacheResponse.cacheControl();
//如果响应(服务器)那边不是必须验证并且存在最大验证秒数
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
//更新最大验证时间
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
//响应支持缓存
//持续时间+最短刷新时间<上次刷新时间+最大验证时间 则可以缓存
//现在时间(now)-已经过去的时间(sent)+可以存活的时间<最大存活时间(max-age)
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
//缓存响应
return new CacheStrategy(null, builder.build());
}
//如果想缓存request,必须要满足一定的条件
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
String conditionName;
String conditionValue;
if (etag != null) {
conditionName = "If-None-Match";
conditionValue = etag;
} else if (lastModified != null) {
conditionName = "If-Modified-Since";
conditionValue = lastModifiedString;
} else if (servedDate != null) {
conditionName = "If-Modified-Since";
conditionValue = servedDateString;
} else {
//没有条件则返回一个定期的request
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
//返回有条件的缓存request策略
return new CacheStrategy(conditionalRequest, cacheResponse);
}
通过上面分析,我们可以发现,OKHTTP实现的缓存策略实质上就是大量的if/else判断,这些其实都是和RFC标准文档里面写死的。 上面说了这么多,那么咱们要开始今天的主题了----CacheInterceptor类
CacheInterceptor类
//CacheInterceptor.java
@Override
public Response intercept(Chain chain) throws IOException {
//如果存在缓存,则从缓存中取出,有可能为null
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//获取缓存策略对象
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
//策略中的请求
Request networkRequest = strategy.networkRequest;
//策略中的响应
Response cacheResponse = strategy.cacheResponse;
//缓存非空判断,
if (cache != null) {
cache.trackResponse(strategy);
}
//缓存策略不为null并且缓存响应是null
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
//禁止使用网络(根据缓存策略),缓存又无效,直接返回
// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
//缓存有效,不使用网络
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
//缓存无效,执行下一个拦截器
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
//本地有缓存,根据条件选择使用哪个响应
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
//使用网络响应
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
//缓存到本地
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
简单的说下上述流程:
- 如果配置缓存,则从缓存中取一次,不保证存在
- 缓存策略
- 缓存监测
- 禁止使用网络(根据缓存策略),缓存又无效,直接返回
- 缓存有效,不使用网络
- 缓存无效,执行下一个拦截器
- 本地有缓存,根具条件选择使用哪个响应
- 使用网络响应
- 缓存到本地
Arouter源码分析
- 当使用@Route注解时 程序在编译期间会使用对应的注解处理器()
- 调用ARouter.init(Context),通过反射把生成的文件全限定名取到加入routermap中然后通过反射实例化对应的类调用loadinto方法将group添加进如下集合之中
- Warehouse 类中主要有两个重要的map,一个分组表的map(key=组名,value=路由表group信息),两外一个是路由表的map(key=path,value=RouteMeta及对应的class信息)
//Warehouse 精简源码
class Warehouse {
// group节点集合
static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
// group下子节点集合
static Map<String, RouteMeta> routes = new HashMap<>();
}
- 程序调用navigation()的时候方法的时候会将路由表的map设置对应的值,通过path获取对应的RouteMeta对象,最终intent 方法跳转到对应的activity
//_ARouter 中navigation()方法精简版
final class _ARouter {
protected Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
switch (postcard.getType()) {
case ACTIVITY:
// Build intent
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// Set flags.
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if (!(currentContext instanceof Activity)) { // Non activity, need less one flag.
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
// Navigation in main looper.
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
if (requestCode > 0) { // Need start for result
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
} else {
ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
}
if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version.
((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
}
if (null != callback) { // Navigation over.
callback.onArrival(postcard);
}
}
});
break;
case PROVIDER:
return postcard.getProvider();
case BOARDCAST:
case CONTENT_PROVIDER:
case FRAGMENT:
Class fragmentMeta = postcard.getDestination();
try {
Object instance = fragmentMeta.getConstructor().newInstance();
if (instance instanceof Fragment) {
((Fragment) instance).setArguments(postcard.getExtras());
} else if (instance instanceof android.support.v4.app.Fragment) {
((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
}
return instance;
} catch (Exception ex) {
logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
}
case METHOD:
case SERVICE:
default:
return null;
}
return null;
}
}
路由拦截器的使用--如果要跳转一个页面需要登陆,可以使用拦截器,通过拦截器的统一处理,登陆完成后跳转到对应界面,有点像aop原理
retrofit源码分析
-
第1 创建Retrofit对象,内部先会根据不同的运行平台来创建不同的Platform对象。然后通过构造器模式进行参数的初始化,如使用:
- 使用baseUrl方法设置要访问的URL;
- 选择性使用callFactory方法创建“网络请求执行器工厂”callFactory,若不使用该方法,则会创建一个OkHttpClient对象;
- 选择性使用callbackExecutor方法创建“回调方法执行器”callbackExecutor,若不使用该方法,则创建一个默认的defaultCallbackExecutor,它是一个带Looper的Handler的MainThreadExecutor对象;
- 选择性使用addCallAdapterFactory方法添加“网络请求适配器工厂”,以及追加一个默认的网络请求器DefaultCallAdapterFactory;
- 选择性使用addConverterFactory方法添加“数据转换器工厂”中;
- 最后通过上述创建的对象进行实例化Retrofit。
-
第2: 通过Retrofit的泛型方法create并会传入自定义的接口类型来创建自定义的请求接口对象,该对象是使用了Proxy.newProxyInstance的动态代理对象。
-
第3:通过调用自定义的请求接口方法创建Call对象,
- 内部先获取定义方法中的注解值创建一个“请求工厂”RequestFactory,然后再获取前面Retrofit对象中的“网络请求适配器”callAdapter、“数据转换器”responseConverter、“网络请求执行器工厂”callFactory 以及 刚创建的“请求工厂”RequestFactory,根据类型创建出继承于HttpServiceMethod的CallAdapted对象;
- 因为使用了动态代理,所以最后会调用到CallAdapted对象的adapt方法去,又因为默认的“网络请求适配器”就是DefaultCallAdapterFactory对象,所以最后adapt方法内部创建的Call对象就是ExecutorCallbackCall对象
-
第4:发起网络请求,就是通过DefaultCallAdapterFactory.ExecutorCallbackCall的enqueue和execute方法来执行异步和同步。这两个方法都是通过一个OkHttpCall对象来完成,OkHttpCall内部就是封装了okhttp3的网络请求。待请求完成后更是将结果回调回到ExecutorCallbackCall里,再通过带Looper的Handler的MainThreadExecutor回调方法执行器将结果回调到调用方的主线程中去。
eventbus源码解析
主要分析三个部分:
1、注册: EventBus.getDefault().register(obj)
public void register(Object subscriber) {
// 通过反射拿到传入的obj的Class对象,如果是在MainActivity里做的注册操作,
// 那subscriber就是MainActivity对象
Class<?> subscriberClass = subscriber.getClass();
// 步骤1
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
// 步骤2
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
步骤1 的是去获取当前注册的对象里所有的被@Subscribe注解的方法集合
- SubscriberMethod 定义了Method方法名,ThreadMode 线程模型,eventType 事件的class对象,priority是指接收事件的优先级,sticky是指是否是粘性事件,SubscriberMethod 对这些信息做了一个封装
- 首先是从缓存中获取,如果缓存中存在直接返回,缓存中没有如果缓存里没有数据,就会根据ignoreGeneratedIndex 这个boolean值去调用不同的方法,
- 如果此时,获取到的方法集合还是空的,程序就会抛出异常,提醒用户被注册的对象以及他的父类没有被@Subscribe注解的public方法(这里插一句,很多时候,如果打正式包的时候EventBus没有做混淆处理,就会抛出该异常,因为方法名被混淆处理了,EventBus会找不到),把获取到的方法集合存入到缓存中去,并且把方法集合return出去。
步骤2 处理以下问题
- 这些方法是不是已经注册过该事件了要不要考虑方法名是不是相同的问题
- 一个注册对象中有多个方法注册了该事件,我们该怎么保存这些方法
EventBus.getDefault().post(xxx);
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
// 将Event添加到List集合中去
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
// 遍历这个list集合,条件是集合是否是空的
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
- 当前的 Event添加到eventQueue中去,并且while循环,处理post每一个Event事件
- 获取事件类型
- 根据事件类型获取订阅者和订阅方法
- 根据当前发送方是否是主线程跟订阅方法的线程模型处理订阅事件
- 订阅方法是主线程
- 发送方是主线程,直接反射执行订阅方法
- 发送方是子线程,通过handler消息机制反射执行订阅方法
- 订阅方法是子线程
- 发送方是主线程,通过线程池执行反射订阅方法
- 发送方是子线程,直接反射执行订阅方法
- 订阅方法是主线程
glide执行流程跟三级缓存
glide执行流
- Glide.with(this),获取一个requestManager对象
- 传入Application参数,此时Application对象的生命周期即应用程序的生命周期,因此Glide并不需要做什么特殊的处理,它自动就是和应用程序的生命周期是同步的,如果应用程序关闭的话,Glide的加载也会同时终止。
- 传入非Application参数不管你在Glide.with()方法中传入的是Activity、FragmentActivity、v4包下的Fragment、还是app包下的Fragment,最终的流程都是一样的,那就是会向当前的Activity当中添加一个隐藏的Fragment。那么这里为什么要添加一个隐藏的Fragment呢?因为Glide需要知道加载的生命周期。因为Fragment的生命周期和Activity是同步的,如果Activity被销毁了,Fragment是可以监听到的,这样Glide就可以捕获这个事件并停止图片加载了。
缓存策略
内存缓存-->磁盘缓存-->网络加载
流程
缓存key是实现内存和磁盘缓存的唯一标识
//创建EngineKey对象
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
这里会调用keyFactory的buildkey方法来创建EngineKey对象,并将load传入的数据(String,URL等),图片的宽高,签名,设置参数等传进去。
EngineKey中重写equals和hashcode方法,这样就能确保只有传入EngineKey的所有参数都相同的情况下才认为是同一个EngineKey对象。
内存缓存
缓存原理:弱引用机制和LruCache算法
- 弱引用机制:当JVM进行垃圾回收时,无论当前的内存是否足够,都会回收掉弱引用关联的对象
- LruCache算法:内部采用LinkedHashMap以强引用的方式存储外界的缓存对象,当缓存满时,LruCache会移除较早使用的缓存对象,然后添加新的缓存对象
缓存实现:正在使用的图片使用弱引用机制进行缓存,不在使用中的图片使用LruCache来进行缓存。
为啥要用弱引用 www.it610.com/article/128…
- 这样可以保护当前使用的资源不会被 LruCache 算法回收
- 使用弱引用,即可以缓存正在使用的强引用资源,又不阻碍系统需要回收的无引用资源。
磁盘缓存
glide与Picasso对比
缓存比较
- Glide 是会先下载图片,然后改变图片的大小,以适应 imageView 的要求,然后缓存到本地。 所以如果你是下载同一张图片,但是设定两个不一样大小的 imageView, 那么Glide 实际上是会缓存两份。
- Picasso是下载图片然后缓存完整的大小到本地,比如说图片的大小是1080p的,之后如果我需要同一张图片,就会返回这张 full size 的,如果我需要resize,也是对这种 full size 的做 resize
- Glide 默认的 Bitmap 格式是 RGB_565 格式,这个内存开销要小一半。
- Picasso默认的是 ARGB_8888 格式
加载图片速度 当内存中不存在时,Picasso会比Glide快一点,可能的原因是缓存机制导致,因为Picasso是直接把图加载到内存中,而Glid 则需要改变图片大小再加载到内存中去,这个应该是会耗费一定的时间。
但是,当加载的图片在内存中时,Glide 则比 Picasso 要快。其原理还是因为缓存机制的区别,因为Picasso 从内存中拿到的图片,还要先去 resize 后,然后设定给 imageView,但是 Glide 则不需要这样。
生命周期 Glide 相比 Picasso 的一大优势是它可以和 Activity 以及 Fragment 的生命周期相互协作,我们在调用 Glide.with() 函数时可以将 Activity 或者 Fragment 的实例传进去,这样 Glide 就会自动将图片加载等操作和组件的生命周期关联起来。
glide其他优势
-
Glide可以加载视频缩略图
-
Glide 支持 GIF动态图
RxJava2 源码解析
我们的目的:
- 知道源头(Observable)是如何将数据发送出去的。
- 知道终点(Observer)是如何接收到数据的。
- 何时将源头和终点关联起来的
- 知道线程调度是怎么实现的
- 知道操作符是怎么实现的
总结:
- 在subscribeActual()方法中,源头和终点关联起来
- source.subscribe(parent);这句代码执行时,才开始从发送ObservableOnSubscribe中利用ObservableEmitter发送数据给Observer。即数据是从源头push给终点的。
- CreateEmitter 中,只有Observable和Observer的关系没有被dispose,才会回调Observer的onXXXX()方法
- Observer的onComplete()和onError() 互斥只能执行一次,因为CreateEmitter在回调他们两中任意一个后,都会自动dispose()。根据上一点,验证此结论
- 先error后complete,complete不显示。 反之会crash
- 还有一点要注意的是onSubscribe()是在我们执行subscribe()这句代码的那个线程回调的,并不受线程调度影响。
线程调度subscribeOn
- 返回一个ObservableSubscribeOn包装类对象
- 上一步返回的对象被订阅时,回调该类中的subscribeActual()方法,在其中会立刻将线程切换到对应的Schedulers.xxx()线程。
- 在切换后的线程中,执行source.subscribe(parent);,对上游(终点)Observable订阅
- 上游(终点)Observable开始发送数据,上游发送数据仅仅是调用下游观察者对应的onXXX()方法而已,所以此时操作是在切换后的线程中进行。
插件化原理
我们如何实现一个插件化呢?
首先一个apk组成部分是有代码跟资源组成,所以我们主要从下面几个方面来实现插件化:
- 如何加载插件的类?
- 如何加载插件的资源?
- 如何调用插件的类?
加载插件的类
我们知道在Java中,Java源码文件编译后会生成一个class文件,而在安卓中,将代码编译后生成一个apk文件,将apk文件解压后会看到有一个或多个classes.dex文件,它就是安卓把所有class文件进行合并,优化后生成的。
android中classloder是如何加载dex文件的?
Classloder classloder是一个抽象类,主要分成两种类型:系统类加载器和自定义加载器。 其中系统类加载器包括三种:
- bootClassLoader 主要加载Android Framework层class文件
- PathClassLoader 用于加载Android应用程序类加载器。可以加指定定的dex,以及jar,zip,apk中的classes.dex
- DexClassLoader 用于加载指定的dex,以及jar,zip,apk中的classes.dex
private void printClassLoader() {
ClassLoader classLoader = getClassLoader();
while (classLoader != null) {
Log.e("leo", "classLoader: " + classLoader);
classLoader = classLoader.getParent();
}
// pathClassLoader 和 BootClassLoader 分别加载什么类
Log.e("leo", "Activity的classLoader: " + Activity.class.getClassLoader());
// 1 Path 2 boot
Log.e("leo", "Activity的classLoader: " + AppCompatActivity.class.getClassLoader());
}
通过打印结果可知,应用程序类是由PathClassLoader加载的,Activity 类是BootClassLoader加载的,并且BootClassLoader是PathClassLoader的parent,这里要注意parent与父类的区别。这个打印结果我们下面还会提到。
我们如何将插件的dex文件加载到宿主里面?
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
首先检测这个类是否已经被加载了,如果已经加载了,直接获取并返回。如果没有被加载,parent 不为null,则调用parent的loadClass进行加载,依次递归,如果找到了或者加载了就返回,如果即没找到也加载不了,才自己去加载。这个过程就是我们常说的双亲委托机制。
为什么要用双亲委托机制?
-
1、避免重复加载,当父加载器已经加载了该类的 时候,就没有必要子ClassLoader再加载一次。
-
2、安全性考虑,防止核心API库被随意篡改。
接着我们再来看下,在所有parent都没加载成功的情况下,DexClassLoader 是如何加载的。通过查找我们发现 在它的父类BaseDexClassLoader中,重写了findClass 方法。
我们发现Class对象就是从Element中获得的,而每-个Element就对应一个dex文件,因为我们的dex文件可 能有多个,所以这儿使用数组Element[]。到这儿我们的思路就出来了,分为以下几步:
- 1.创建插件的DexClassLoader类加载器,然后通过反射获取插件的dexElements值。
- 2.获取宿主的PathClassLoader类加载器,然后通过反射获取宿主的dexElements值。
- 3.合并宿主的dexElements与插件的dexElements,生成新的Element[]。
- 4.最后通过反射将新的Element[]赋值给宿主的dexElements。
具体代码如下:
public static void loadClass(Context context) {
/**
* 宿主dexElements = 宿主dexElements + 插件dexElements
*
* 1.获取宿主dexElements
* 2.获取插件dexElements
* 3.合并两个dexElements
* 4.将新的dexElements 赋值到 宿主dexElements
*
* 目标:dexElements -- DexPathList类的对象 -- BaseDexClassLoader的对象,类加载器
*
* 获取的是宿主的类加载器 --- 反射 dexElements 宿主
*
* 获取的是插件的类加载器 --- 反射 dexElements 插件
*/
try {
Class<?> clazz = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = clazz.getDeclaredField("pathList");
pathListField.setAccessible(true);
Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
// 宿主的 类加载器
ClassLoader pathClassLoader = context.getClassLoader();
// DexPathList类的对象
Object hostPathList = pathListField.get(pathClassLoader);
// 宿主的 dexElements
Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathList);
// 插件的 类加载器
ClassLoader dexClassLoader = new DexClassLoader(apkPath, context.getCacheDir().getAbsolutePath(),
null, pathClassLoader);
// DexPathList类的对象
Object pluginPathList = pathListField.get(dexClassLoader);
// 插件的 dexElements
Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);
// 宿主dexElements = 宿主dexElements + 插件dexElements
// Object[] obj = new Object[]; // 不行
// 创建一个新数组
Object[] newDexElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(),
hostDexElements.length + pluginDexElements.length);
System.arraycopy(hostDexElements, 0, newDexElements,
0, hostDexElements.length);
System.arraycopy(pluginDexElements, 0, newDexElements,
hostDexElements.length, pluginDexElements.length);
// 赋值
// hostDexElements = newDexElements
dexElementsField.set(hostPathList, newDexElements);
} catch (Exception e) {
e.printStackTrace();
}
}
启动插件的activity
Activity是需要在清单文件中注册的,显然,插件的Activity没有在宿主的清单文件中注册,那我们如何来启动它呢?
这里我们就需要使用Hook技术,来绕开系统的检测。可能有些同学不知道Hook是什么,所以我们先简单的介绍下Hook技术。
Hook
Hook可以是一个方法或者一个对象,它就像一个钩子一样挂在对象B 上面,当对象A调用对象B之前, Hook可以做一些处理,起到欺上瞒下”的作用。而对象B就是我们常说的Hook点。为了保证Hook的稳定性,Hook点-般选择 容易找到并且不易变化的对象,如静态变量和单例。
那么思路就来了,首先我们在宿主里面创建一个 ProxyActivity继承自Activity, 并且在清单中注册。当启动插件Activity的时候,在系统检测前,找到一个Hook点,然后通过Hook将插件Activity 替换成ProxyActivity,等到检测完了后,再找一个Hook点, 使用Hook将它们换回来,这样就实现了插件Activity的启动。思路是不是非常的简单。
如何查找Hook点呢?这就需要我们了解Activity的启动流程了。
通过这张图我们可以确定Hook点的大致位置。
- 1.在进入AMS之前,找到一个Hook点,用来将插件Activity替换为ProxyActivity。
- 2.从AMS出来后,再找一个Hook点,用来将ProxyActivity替换为插件Actity。
在看源码之前,我们再想一个问题,看源码是要找什么东西作为Hook点呢? 我们在项目中一般通过l startActivity(new Intent(this ,PluginActivity. class);启动PluginActivity,如果我想换成启动ProxyActity,调用方法startActivity(new Intent(this , ProxyActivity. class));这样就可以了。 是不是已经知道答案了! ! !没错,我们只要找到能够修改Intent的地方,就可以作为Hook点,从这儿也可以看出Hook点并不是唯一的。
具体代码:
package com.enjoy.leo_plugin;
import android.content.Intent;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import androidx.annotation.NonNull;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
public class HookUtil {
private static final String TARGET_INTENT = "target_intent";
public static void hookAMS() {
try {
// 获取 singleton 对象
Field singletonField = null;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { // 小于8.0
Class<?> clazz = Class.forName("android.app.ActivityManagerNative");
singletonField = clazz.getDeclaredField("gDefault");
} else {
Class<?> clazz = Class.forName("android.app.ActivityManager");
singletonField = clazz.getDeclaredField("IActivityManagerSingleton");
}
singletonField.setAccessible(true);
Object singleton = singletonField.get(null);
// 获取 系统的 IActivityManager 对象
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
final Object mInstance = mInstanceField.get(singleton);
Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");
// 创建动态代理对象
Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{iActivityManagerClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// do something
// Intent的修改 -- 过滤
/**
* IActivityManager类的方法
* startActivity(whoThread, who.getBasePackageName(), intent,
* intent.resolveTypeIfNeeded(who.getContentResolver()),
* token, target != null ? target.mEmbeddedID : null,
* requestCode, 0, null, options)
*/
// 过滤
if ("startActivity".equals(method.getName())) {
int index = -1;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
// 启动插件的
Intent intent = (Intent) args[index];
Intent proxyIntent = new Intent();
proxyIntent.setClassName("com.enjoy.leo_plugin",
"com.enjoy.leo_plugin.ProxyActivity");
proxyIntent.putExtra(TARGET_INTENT, intent);
args[index] = proxyIntent;
}
// args method需要的参数 --- 不改变原有的执行流程
// mInstance 系统的 IActivityManager 对象
return method.invoke(mInstance, args);
}
});
// ActivityManager.getService() 替换成 proxyInstance
mInstanceField.set(singleton, proxyInstance);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void hookHandler() {
try {
// 获取 ActivityThread 类的 Class 对象
Class<?> clazz = Class.forName("android.app.ActivityThread");
// 获取 ActivityThread 对象
Field activityThreadField = clazz.getDeclaredField("sCurrentActivityThread");
activityThreadField.setAccessible(true);
Object activityThread = activityThreadField.get(null);
// 获取 mH 对象
Field mHField = clazz.getDeclaredField("mH");
mHField.setAccessible(true);
final Handler mH = (Handler) mHField.get(activityThread);
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
// 创建的 callback
Handler.Callback callback = new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
// 通过msg 可以拿到 Intent,可以换回执行插件的Intent
// 找到 Intent的方便替换的地方 --- 在这个类里面 ActivityClientRecord --- Intent intent 非静态
// msg.obj == ActivityClientRecord
switch (msg.what) {
case 100:
try {
Field intentField = msg.obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
// 启动代理Intent
Intent proxyIntent = (Intent) intentField.get(msg.obj);
// 启动插件的 Intent
Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
if (intent != null) {
intentField.set(msg.obj, intent);
}
} catch (Exception e) {
e.printStackTrace();
}
break;
case 159:
try {
// 获取 mActivityCallbacks 对象
Field mActivityCallbacksField = msg.obj.getClass()
.getDeclaredField("mActivityCallbacks");
mActivityCallbacksField.setAccessible(true);
List mActivityCallbacks = (List) mActivityCallbacksField.get(msg.obj);
for (int i = 0; i < mActivityCallbacks.size(); i++) {
if (mActivityCallbacks.get(i).getClass().getName()
.equals("android.app.servertransaction.LaunchActivityItem")) {
Object launchActivityItem = mActivityCallbacks.get(i);
// 获取启动代理的 Intent
Field mIntentField = launchActivityItem.getClass()
.getDeclaredField("mIntent");
mIntentField.setAccessible(true);
Intent proxyIntent = (Intent) mIntentField.get(launchActivityItem);
// 目标 intent 替换 proxyIntent
Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
if (intent != null) {
mIntentField.set(launchActivityItem, intent);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
// 必须 return false
return false;
}
};
// 替换系统的 callBack
mCallbackField.set(mH, callback);
} catch (Exception e) {
e.printStackTrace();
}
}
}
加载资源
在项目中,我们一般通过Resources去访问res中的资源,使用AssetManager访问assets里面的资源。
实现步骤:
- 1.创建一个AssetManager对象,并调用addAssetPath方法,将插件apk的路径作为参数传入。
- 2.将第一步创建的 AssetManager对象作为参数,创建一个新的Resources对象,并返回给插件使用。
为了防止资源冲突: 我们的做法是在插件activity中创建一个context,将resource资源跟context对应绑定: 具体代码:
public class LoadUtil {
private final static String apkPath = "/sdcard/plugin-debug.apk";
private static Resources mResources;
public static Resources getResources(Context context) {
if (mResources == null) {
mResources = loadResource(context);
}
return mResources;
}
private static Resources loadResource(Context context) {
// assets.addAssetPath(key.mResDir)
try {
AssetManager assetManager = AssetManager.class.newInstance();
// 让 这个 AssetManager对象 加载的 资源为插件的
Method addAssetPathMethod = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPathMethod.invoke(assetManager, apkPath);
// 如果传入的是Activity的 context 会不断循环,导致崩溃
Resources resources = context.getResources();
// 加载插件的资源的 resources
return new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
在插件activity中
public class BaseActivity extends AppCompatActivity {
protected Context mContext;
// @Override
// public Resources getResources() {
// if (getApplication() != null && getApplication().getResources() != null) {
// return getApplication().getResources();
// }
// return super.getResources();
// Resources resources = LoadUtil.getResources(getApplication());
// // 如果插件作为一个单独的app,返回 super.getResources()
// return resources == null ? super.getResources() : resources;
// }
// 不会影响到宿主
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Resources resources = LoadUtil.getResources(getApplication());
mContext = new ContextThemeWrapper(getBaseContext(), 0);
Class<? extends Context> clazz = mContext.getClass();
try {
Field mResourcesField = clazz.getDeclaredField("mResources");
mResourcesField.setAccessible(true);
mResourcesField.set(mContext, resources);
} catch (Exception e) {
e.printStackTrace();
}
}
}
apk打包流程
- 1:将资源文件通过aapt生成的R.java文件、代码文件、aidl生成的java文件通过Java c编译生成.class文件
- 2:将.class对应的dex文件跟第三方库等的.class文件 通过apkbuilder生产apk文件
- 3:将apk文件签名
- 4:将apk文件压缩对齐,生成最终的apk文件
activity启动流程
Activity的启动过程,我们可以从Context的startActivity说起,其实现是ContextImpl的startActivity,然后内部会通过Instrumentation来尝试启动Activity,这是一个跨进程过程,它会调用ams的startActivity方法,当ams校验完activity的合法性后,会通过ApplicationThread回调到我们的进程,这也是一次跨进程过程,而applicationThread就是一个binder,回调逻辑是在binder线程池中完成的,所以需要通过Handler H将其切换到ui线程,第一个消息是LAUNCH_ACTIVITY,它对应handleLaunchActivity,在这个方法里完成了Activity的创建和启动,接着,在activity的onResume中,activity的内容将开始渲染到window上,然后开始绘制直到我们看见。
MVC MVP MVVM
MVC
- model 主要获取数据
- view UI视图
- controller 成为了Model和View的桥梁 Model将结果告知Controller,Controller再去更新 View。
缺点:
- Android 的 Activity 同时担任 Controller 和部分 View 的角色。导致已经很重的 Controller 变得更臃肿了
MVP
MVP结构跟上面MVC虽然架构上一样,但是这里利用接口的方式将M层跟V层彻底剥离开了。
优点:
-
- MVP 之间的交互通过接口来进行的,那就便于进行单元测试了,维护性和扩展性也提高了。
-
- M 和 V 之间彻底分离了,降低了耦合性,修改 V 层也不会影响 M 层。
-
- V和P之间的职责更加明确
缺点:
- 由于增加了很多接口的定义,需要编写的代码量暴增,增加了项目的复杂度。
- 页面Activity退出,如果Model在子线程上执行耗时任务,还没有结束,Model持有Presenter的引用,Presenter持有Activity的引用。那么这个Activity对象就没有办法被回收。
MVVM
- M层获取数据,交给VM
- VM通过livedate将数据传给V层
- V通过datebinding实现数据双向绑定
livedata问题
- 1:数据倒灌 juejin.cn/post/698689…
- 2:数据丢失
发布订阅者跟观察者模式区别
观察者模式与发布订阅模式还有一些区别,观察者模式之间的发布者与订阅者是双向关联的
发布订阅模式中的发布者与订阅者的关系如下图所示:
- 1.观察者模式中,发布者与订阅者直接关联,发布者维护观察者列表,发布者状态变更通知订阅者;在发布-订阅模式中,订阅者与发布者相互不了解,通过事件中心进行通信;
- 2.耦合性方面,发布订阅者中发布者与订阅者完全松耦合;
- 3.观察者模式主要是以同步的方式来发送消息,发布订阅者一般以异步的方式来实现。
怎么判断一个链表是环形
快指针(fast)和慢指针(slow),初始时,慢指针在位置 head,而快指针在位置 head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表
为什么我们要规定初始时慢指针在位置 head,快指针在位置 head.next,而不是两个指针都在位置 head(即与「乌龟」和「兔子」中的叙述相同)?
观察下面的代码,我们使用的是 while 循环,循环条件先于循环体。由于循环条件一定是判断快慢指针是否重合,如果我们将两个指针初始都置于 head,那么 while 循环就不会执行。因此,我们可以假想一个在 head 之前的虚拟节点,慢指针从虚拟节点移动一步到达 head,快指针从虚拟节点移动两步到达 head.next,这样我们就可以使用 while 循环了。 当然,我们也可以使用 do-while 循环。此时,我们就可以把快慢指针的初始值都置为 head。
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}