从Glide源码里面学习到的对象池知识

    对象池的概念是我在Glide源码里面第一次接触到的。它的作用是回收那些被释放的对象资源然后达到重用的目的。为什么要重用被释放的对象资源呢?因为创建对象是一个需要消耗内存的事情,重复大量的这些操作,会造成内存的抖动,这一点是每个开发者不愿意看到的。

   来看源码:

先看Engine类的waitForExistingOrStartNewJob方法,这个方法里面有一个创建对象DecodeJob的操作。它就用到了对象池的概念。

1
2
3
4
// Engine
private <R> LoadStatus waitForExistingOrStartNewJob(......) {
DecodeJob<R> decodeJob = decodeJobFactory.build(......);
}

来直接看decodeJobFactory.build方法:

1
2
3
4
5
// Engine.DecodeJobFactory
DecodeJob<R> build(.......) {
DecodeJob<R> result = pool.acquire();
return result;
}

好重点来了:pool对象。从名字就能知道这是一个池子,是什么池子?当然是对象池子。

1
2
3
4
5
6
7
8
9
final Pools.Pool<EngineJob<?>> pool =
FactoryPools.threadSafe(
JOB_POOL_SIZE(150),
new FactoryPools.Factory<EngineJob<?>>() {
@Override
public EngineJob<?> create() {
return new EngineJob<>(....);
}
});

看下FactoryPools代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// FactoryPools
public static <T extends Poolable> Pool<T> threadSafe(int size, @NonNull Factory<T> factory) {
return build(new SynchronizedPool<T>(size), factory);
}
@NonNull
private static <T extends Poolable> Pool<T> build(
@NonNull Pool<T> pool, @NonNull Factory<T> factory) {
return build(pool, factory, FactoryPools.<T>emptyResetter());
}

@NonNull
private static <T> Pool<T> build(
@NonNull Pool<T> pool, @NonNull Factory<T> factory, @NonNull Resetter<T> resetter) {
return new FactoryPool<>(pool, factory, resetter);
}

这里就是它设计精妙的地方,创建对象的操作用一个接口回调出来:

1
2
3
public interface Factory<T> {
T create();
}

现在知道了pool对象的由来,它就是FactoryPool

继续回到代码pool.acquire(),看FactoryPoolacquire

1
2
3
4
5
6
7
8
9
10
// FactoryPools
public T acquire() {
// 先从SynchronizedPool这里取
T result = pool.acquire();
if (result == null) {
// 取不到在创建对象
result = factory.create();
}
return result;
}

可以看到在acquire里面有调用了一个pool:这个pool其实是SynchronizedPool,在创建FactoryPools的时候创建的。这里为啥会出现两个Pool。这个FactoryPools是从池子里做取创建得操作。SynchronizedPool这个池子做的是存删得操作。

1
2
3
4
5
6
7
8
9
10
11
12
//SynchronizedPool
//做取得操作,去完就从自己得队列里面删掉
public T acquire() {
if (mPoolSize > 0) {
final int lastPooledIndex = mPoolSize - 1;
T instance = (T) mPool[lastPooledIndex];
mPool[lastPooledIndex] = null;
mPoolSize--;
return instance;
}
return null;
}

总结:

    它的逻辑就是,我想获取对象的时候。FactoryPools执行acquire方法,然后这个方法调用SynchronizedPool里面的acquire方法从HashMap里面取。如果没取到对象那么FactoryPools就执行create创建对象。如果在SynchronizedPool里面取到了,就从它的HashMap里面取出这个对象,然后把HashMap的最后一个项删掉。到这里取得操作执行完了。现在执行释放得操作。当某个对象要被释放了,不用了,那么就执行FactoryPoolsrelease方法,这个方法会调用SynchronizedPoolrelease操作,将这个被释放的对象缓存起来,保存在HaseMap里面。好了释放的操作完成了。当然缓存的大小肯定是有限制的,不能不限缓存。

    如果在以后的项目里面你有出现会频繁创建对象的操作的时候,可以使用Glide的这部分知识,更高效的重用已经创建过的对象。代码可以直接抄:FactoryPoolsSynchronizedPool可以直接用。

BitMap图像缓存

博客:Android图片压缩必备基础知识

Android Bitmap 操作详解

   屏幕分辨率等于√(长的像素数量²+宽的像素数量²)/对角线长度。它的含义代表一英寸还有的像素个数。屏幕上的一个点代表一个像素(像素单位为px)。如果同样尺寸的手机屏幕一个是640*480(1号机) 一个是1600*1200(2号机)。那么展示一个100*100px的图像,那么在1号机展示的图像大小将要比2号机展示的更大。同时也要比2号机展示的更加粗糙。同样尺寸的手机显示的像素个数越多也就是px越多,那么也就是分辨率越高,那么显示效果一定就是越细腻。

img-0

   在Android里面获取一个Bitmap的内存大小的话可以通过Bitmap.getAllocationByCount()方法获取,当然这是在api>=19的时候才出来的新方法。以前是通过getByteCount()。(这部分知识是在看Glide源码的时候,看到LruBitmapPool类的时候,了解到这个类是用来存放我们加载进来的bitmap的,当然不是任何大小的bitmap他都做缓存,不然的话很大的图片也缓存那么手机的内存一定直接爆炸。在每次put的时候他会先调用类SizeStrategy.getSize(bitmap)方法获取这个bitmap的大小做判断)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 获取一个bitmap的占用的内存大小
public static int getBitmapByteSize(@NonNull Bitmap bitmap) {
if (bitmap.isRecycled()) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
return bitmap.getAllocationByteCount();
} catch (
@SuppressWarnings("PMD.AvoidCatchingNPE")
NullPointerException e) {
}
}
return bitmap.getHeight() * bitmap.getRowBytes();
}

更多案例可以看这篇博客:Android中一张图片占据的内存大小是如何计算的

实现一个高斯模糊:封装个 Android 的高斯模糊组件

Java之WeakReference学习

我是在什么情况下遇到了这部分知识?

在我看Glide源码学习它的缓存机制的时候,遇到类ActiveResources,这个类里面用一个Map<Key, ResourceWeakReference>的方式保存下缓存数据,然后这个ResourceWeakReference是继承自WeakReference,然后在代码里面还开了一个线程,里面有一个死循环一直在监听resourceReferenceQueue这个队列的数据。然后就很纳闷,然后去百度了下。发现这是弱引用的知识,当发生gc的时候,弱引用对象会被保存到resourceReferenceQueue这个队列里面。这个队列是线程阻塞的,只有当有数据的时候才会继续执行。这也是一个知识点。我们就可以利用这一点用来监听发生gc的时候移除的引用对象数据。完美

当一个对象仅仅被weak referencr指向的时候,而没有任何其他强引用指向的时候。如果这个时候触发了GC,那么这个对象就会被回收,不论当前的内存空间是否够用,这个对象都会被回收。

认识WeakReference

   WeakReference继承Reference

1
2
3
4
5
6
7
8
public class WeakReference<T> extends Reference<T> {
public WeakReference(T referent) {
super(referent);
}
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}

   这里有两个构造函数,我来分别讲下构造函数里面的参数信息:

  • referent:通过对他的翻译我们知道他是:所指对象的意思。就是我们弱引用的对象。(需要区分弱引用对象和被弱引用的对应),弱引用对象指的是WeakReference的实例或者其子类的实例。
  • q:ReferenceQueue:从名字也可以看出这是一个队列。他是在发生GC的时候,弱引用对象会被保存到这个队列里面。多以我们就可以通过这个监听这个队列的数据变化,来获取被gc的弱引用对象值。

案例

使用WeakReference

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package io.github.brightloong.lab.reference;
public class Apple {

private String name;

public Apple(String name) {
this.name = name;
}
/**
* 覆盖finalize,在回收的时候会执行。
* @throws Throwable
*/
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("Apple: " + name + " finalize。");
}

@Override
public String toString() {
return "Apple{" +
"name='" + name + '\'' +
'}' + ", hashCode:" + this.hashCode();
}
}

继承WeakReference的Salad:

1
2
3
4
5
6
7
package io.github.brightloong.lab.reference;
import java.lang.ref.WeakReference;
public class Salad extends WeakReference<Apple> {
public Salad(Apple apple) {
super(apple);
}
}

Client调用和输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package io.github.brightloong.lab.reference;
import java.lang.ref.WeakReference;
public class Client {
public static void main(String[] args) {
Salad salad = new Salad(new Apple("红富士"));
//通过WeakReference的get()方法获取Apple
System.out.println("Apple:" + salad.get());
System.gc();
try {
//休眠一下,在运行的时候加上虚拟机参数-XX:+PrintGCDetails,输出gc信息,确定gc发生了。
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}

//如果为空,代表被回收了
if (salad.get() == null) {
System.out.println("clear Apple。");
}
}
}
/**
Apple:Apple{name='红富士'}, hashCode:1846274136
[GC (System.gc()) [PSYoungGen: 3328K->496K(38400K)] 3328K->504K(125952K), 0.0035102 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (System.gc()) [PSYoungGen: 496K->0K(38400K)] [ParOldGen: 8K->359K(87552K)] 504K->359K(125952K), [Metaspace: 2877K->2877K(1056768K)], 0.0067965 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Apple: 红富士 finalize。
clear Apple。
*/

ReferenceQueue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package io.github.brightloong.lab.reference;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class Client2 {
public static void main(String[] args) {
ReferenceQueue<Apple> appleReferenceQueue = new ReferenceQueue<>();
WeakReference<Apple> appleWeakReference = new WeakReference<Apple>(new Apple("青苹果"), appleReferenceQueue);
WeakReference<Apple> appleWeakReference2 = new WeakReference<Apple>(new Apple("毒苹果"), appleReferenceQueue);

System.out.println("=====gc调用前=====");
Reference<? extends Apple> reference = null;
while ((reference = appleReferenceQueue.poll()) != null ) {
//不会输出,因为没有回收被弱引用的对象,并不会加入队列中
System.out.println(reference);
}
System.out.println(appleWeakReference);
System.out.println(appleWeakReference2);
System.out.println(appleWeakReference.get());
System.out.println(appleWeakReference2.get());

System.out.println("=====调用gc=====");
System.gc();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("=====gc调用后=====");

//下面两个输出为null,表示对象被回收了
System.out.println(appleWeakReference.get());
System.out.println(appleWeakReference2.get());

//输出结果,并且就是上面的appleWeakReference、appleWeakReference2,再次证明对象被回收了
Reference<? extends Apple> reference2 = null;
while ((reference2 = appleReferenceQueue.poll()) != null ) {
//如果使用继承的方式就可以包含其他信息了
System.out.println("appleReferenceQueue中:" + reference2);
}
}
}
/**
=====gc调用前=====
java.lang.ref.WeakReference@6e0be858
java.lang.ref.WeakReference@61bbe9ba
Apple{name='青苹果'}, hashCode:1627674070
Apple{name='毒苹果'}, hashCode:1360875712
=====调用gc=====
Apple: 毒苹果 finalize。
Apple: 青苹果 finalize。
=====gc调用后=====
null
null
appleReferenceQueue中:java.lang.ref.WeakReference@6e0be858
appleReferenceQueue中:java.lang.ref.WeakReference@61bbe9ba

Process finished with exit code 0
**/

在Android里面的使用案例

可以看Glide的源码里面的ActiveResources类。

多线程

ThreadPoolExecutor

线程池

好处:

  1. 降低资源的消耗。线程本身就是一种资源。创建和销毁线程会有CPU开销。创建的线程也会占用一定的内存。

  2. 提高任务执行的响应速度,执行任务时可以不必等到线程创建完之后在执行。

  3. 提高线程的可管理性,线程不能无限的创建,需要进行统一的分配调优监控。

​ 不适用线程池的话,频繁的线程创建和销毁会占用更多的cpu和内存。频繁的线程创建和销毁会对gc产生比较大的压力。线程太多,线程间切换带来的开销将不可忽视,线程太少,多核cpu得不到充分利用,是一种浪费。

​ 可以看出合理使用的线程池是多么重要的事情。

img-0

   先从线程池的构造函数看起(这里找了个最多参数的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public ThreadPoolExecutor(int corePoolSize, // 1
int maximumPoolSize, // 2
long keepAliveTime, // 3
TimeUnit unit, // 4
BlockingQueue<Runnable> workQueue, // 5
ThreadFactory threadFactory, // 6
RejectedExecutionHandler handler ) { //7
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
序号 名称 类型 含义
1 corePoolSize int 线程池里的核心线程数量
2 maximumPoolSize int 线程池里允许有的最大线程数量
3 keepAliveTime long 线程最大空闲等待时间
4 unit TimeUnit 时间单位分时秒
5 workQueue BlockingQueue Runable 线程等待队列
6 threadFactory ThreadFactory 线程创建工厂
7 handler RejectedExecutionHandler 拒绝策略

  corePoolSize线程池里面的核心线程池,这里面的线程是并行的,并行和并发是两个概念,并发是CPU切换的速度快到人们感受不到所以认为任务是在一起允许。而并行则是真正的在一起执行,这需要我们的CPU拥有多个核心才行。比如我的电脑是8核16线程,则代表我的这台电脑在同一时间可以并行允许16个线程。这里有个Demo可以自己跑起来看下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class ThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolDemo threadPoolDemo = new ThreadPoolDemo();
threadPoolDemo.doPricess();
}
//Runtime.getRuntime().availableProcessors() 获取的是你的电脑核心数比如8核16线程
void doPricess(){
ExecutorService executorService = null;
executorService = new ThreadPoolExecutor(16,
16,60, TimeUnit.SECONDS,new LinkedBlockingDeque<>());
for(int i=0;i<40;i++){
TestPoolThreadRunnable thread = new TestPoolThreadRunnable(i);
executorService.execute(thread);
}
executorService.shutdown();
}
class TestPoolThreadRunnable implements Runnable{
Integer count;
TestPoolThreadRunnable(Integer count){
this.count = count;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count++ +" "+ Thread.currentThread().getId());
}
}
}
// 上面我创建的线程池给的核心数是16,然后每个任务给了个延迟一秒的执行(这样可以更好的观察并行现象)
// 然后我循环创建了40个任务,可以看到当核心线程数给了16那么在一秒钟瞬间有16个线程同时执行,瞬间16个任务一起就完成了。然后你把核心线程数改成2,你会发现一秒钟里面同时创建了两个线程在执行任务,每次都是两个两个在运行。
// 为什么会出现这种现象?从线程池的工作原理可以看出。提交任务后会先判断核心线程数是不是满了没有满的话会创建线程执行任务,知道核心线程满了才会不在创建任务,然后满了后的任务会放到列表LinkedBlockingDeque里面等着。等到核心空下来了在下来了在执行。
// 通过文字要是没看懂的话 可以把上面代码跑起来看下。

触发问题:java.util.concurrent.RejectedExecutionException:这个错误的意思就是往线程池里面提交的任务被拒绝了。那什么情况下会出现线程被线程池拒绝呢?

  1. 当线程等待队列用的是有限的比如SynchronousQueue这个队列,当我们的核心线程满了,然后队列也满了然后运行的最大线程数量也就是(maximumPoolSize)也满了,这个时候就会报RejectedExecutionException奔溃。
  2. 当线程池显示的调用了shutDown方法后,线程池又去提交了任务,这个时候就会报这个错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 造成奔溃的例子
public class ThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolDemo threadPoolDemo = new ThreadPoolDemo();
try {
threadPoolDemo.doPricess();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
ExecutorService executorService = null;
//Runtime.getRuntime().availableProcessors() 获取的是你的电脑核心数比如8核16线程
void doPricess() throws InterruptedException {
executorService = new ThreadPoolExecutor(5,
8,60, TimeUnit.SECONDS,new LinkedBlockingDeque<>());
for(int i=0;i<40;i++){
TestPoolThreadRunnable thread = new TestPoolThreadRunnable(i);
executorService.execute(thread);
// 执行了shutdown然后又提交了任务会触发RejectedExecutionException奔溃
executorService.shutdown();
}
}
class TestPoolThreadRunnable implements Runnable{
Integer count;
TestPoolThreadRunnable(Integer count){
this.count = count;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count++ +" "+ Thread.currentThread().getId()+" "+System.currentTimeMillis());
}
}
}

Glide源码解析

1
Glide.with(this).load("https://wod.jpg").into(ig)

Glide通过with方法创建了对象Glide , RequestManagerRetrier ,RequestManager

然后通过load方法创建对象RequestBuilder

   先来看下Glide对象是怎么创建的:

   进入with方法:

1
2
3
4
public static RequestManager with(@NonNull Activity activity) {
return getRetriever(activity).get(activity);
}
// 可以看到调用with实际是创建了对象 RequestManager

   看下getRetriever方法干了什么:

1
2
3
4
5
6
7
8
9
10
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
Preconditions.checkNotNull(
context,
"一段描述信息");
return Glide.get(context).getRequestManagerRetriever();
}
// 可以看到通过getRetriever方法创建了对象 RequestManagerRetriever
// 创建RequestManagerRetrier对象又是通过Glide对象创建的。
// Glide对象又是通过Glide.get(context)方法,懒加载的方式创建的单例对象。
// 在调用Glide.get(context)方法创建对象Glide在里面又创建了对象RequestManagerRetriever

   总结:当我们调用Glide.with方法的时候,在内部先调用了Glide.get(context),方式创建了Glide对象。然后通过调用Glide.get(context).getRequestManagerRetriever方法创建了RequestManagerRetrier对象。然后在是通过ReqeustManagerRetrier对象调用了get(context)获取对象RequestManager

   Glide对象的创建用了工厂模式:创建了对象GlideBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static Glide get(@NonNull Context context) {
if (glide == null) {
synchronized (Glide.class) {
if (glide == null) {
checkAndInitializeGlide(context);
}
}
}
return glide;
}
private static void checkAndInitializeGlide(@NonNull Context context) {
initializeGlide(context);
}
private static void initializeGlide(@NonNull Context context) {
initializeGlide(context, new GlideBuilder());
}
private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) {
// 通过工厂模式创建了Glide对象。
Glide glide = builder.build(applicationContext);

Glide.glide = glide;
}

   来看下工厂里面创建了那些对象GlideBuilder.build

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
Glide build(@NonNull Context context) {
if (sourceExecutor == null) {
sourceExecutor = GlideExecutor.newSourceExecutor();
}

if (diskCacheExecutor == null) {
diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
}

if (animationExecutor == null) {
animationExecutor = GlideExecutor.newAnimationExecutor();
}

if (memorySizeCalculator == null) {
memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
}

if (connectivityMonitorFactory == null) {
connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
}

if (bitmapPool == null) {
// 获取手机内存大小。大的手机这个size一定是大于0的
int size = memorySizeCalculator.getBitmapPoolSize();
if (size > 0) {
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
}

if (arrayPool == null) {
arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
}

if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}

if (diskCacheFactory == null) {
// 这个创建的就是磁盘缓存,缓存的文件保存在 项目/cache/img_manager_disk_cache
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}

if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,//磁盘缓存
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
GlideExecutor.newAnimationExecutor(),
isActiveResourceRetentionAllowed);
}

if (defaultRequestListeners == null) {
defaultRequestListeners = Collections.emptyList();
} else {
defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners);
}

RequestManagerRetriever requestManagerRetriever =
new RequestManagerRetriever(requestManagerFactory);

return new Glide(
context,
engine,
memoryCache,
bitmapPool,
arrayPool,
requestManagerRetriever,
connectivityMonitorFactory,
logLevel,
defaultRequestOptions.lock(),
defaultTransitionOptions,
defaultRequestListeners,
isLoggingRequestOriginsEnabled);
}
// 可以看到创建了对象:三个都是线程池
// 这三个都继承自Executor接口。可想而知他们是和线程池有关系。
/**
* GlideExecutor sourceExecutor;// 源执行人
* GlideExecutor diskCacheExecutor;//磁盘高速缓存执行人
* GlideExecutor animationExecutor;// 动画线程池
* MemorySizeCalculator memorySizeCalculator; //内存大小的计算器
* DefaultConnectivityMonitorFactory connectivityMonitorFactory;//默认连接监视器工厂
* BitmaoPool bitmapPool;//如果内存大小计算器大于0 则bitmapPool = LruBitmapPool
* // 否则等于 BitmapPoolAdapter
* ArrayPool arrayPool;// 创建对象LruArrayPool
* MemoryCache memoryCache;// 创建对象LruResourceCache
* DiskCache.Factory diskCacheFactory;// 创建对象InternalCacheDiskCacheFactory 这是我们的磁盘缓存,也就是把下载的图片保存在本地项目/cache/image_manager_disk_cache文件里面
* Engine engine;//创建对象Engine
* RequestManagerRetriever requestManagerRetriever;//创建对象RequestManagerRetriever
*/
// 自此Glide对象创建完毕。

这里面我们看到了一个bitmapPool对象,它是个啥呢。怎么还通过了一个size去判断创建什么对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BitmapPool bitmapPool;
MemorySizeCalculator memorySizeCalculator;
if (memorySizeCalculator == null) {
memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
}
if (bitmapPool == null) {
int size = memorySizeCalculator.getBitmapPoolSize();
if (size > 0) {
//LruBitmapPool这也是一个缓存,不过我还不知道是在什么时候会调用它的put方法将图片塞到他的队列里面
//不过现在知道在put的时候会获取这个bitmap的大小是否大于size如果大于的话就不缓存,防止内存爆
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
}

这个MemorySizeCalculator从名字就可以知道是内存大小计算器。内存指的是手机的ram大小,ram是随机存储寄存器。我们看下他是怎么获取咱们手机的内存大小的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//MemorySizeCalculator.Builder(context):
//可以看到先通过Builder方法获取到了我们的 ActivityManager对象。
//然后调用我们系统方法 ActivityManager.isLowRamDevice方法判断是不是低内存的手机
public Builder(Context context) {
this.context = context;
activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
screenDimensions =
new DisplayMetricsScreenDimensions(context.getResources().getDisplayMetrics());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isLowMemoryDevice(activityManager)) {
bitmapPoolScreens = 0;
}
}
static boolean isLowMemoryDevice(ActivityManager activityManager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return activityManager.isLowRamDevice();
} else {
return true;
}
}
public MemorySizeCalculator build() {
return new MemorySizeCalculator(this);
}
// 在它的构造函数里面我们可以看到对内存进行了计算
MemorySizeCalculator(MemorySizeCalculator.Builder builder) {
this.context = builder.context;

arrayPoolSize =
isLowMemoryDevice(builder.activityManager)
? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR
: builder.arrayPoolSizeBytes;
int maxSize =
getMaxSize(
builder.activityManager, builder.maxSizeMultiplier, builder.lowMemoryMaxSizeMultiplier);

int widthPixels = builder.screenDimensions.getWidthPixels();
int heightPixels = builder.screenDimensions.getHeightPixels();
int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL;

int targetBitmapPoolSize = Math.round(screenSize * builder.bitmapPoolScreens);

int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);
int availableSize = maxSize - arrayPoolSize;

if (targetMemoryCacheSize + targetBitmapPoolSize <= availableSize) {
memoryCacheSize = targetMemoryCacheSize;
bitmapPoolSize = targetBitmapPoolSize;
} else {
float part = availableSize / (builder.bitmapPoolScreens + builder.memoryCacheScreens);
memoryCacheSize = Math.round(part * builder.memoryCacheScreens);
bitmapPoolSize = Math.round(part * builder.bitmapPoolScreens);
}
}

Glide里面使用到的线程池

在创建Glide对象的时候有一个参数叫Engine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Glide build(@NonNull Context context) {
if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
animationExecutor,
isActiveResourceRetentionAllowed);
}
new Glide(
context,
engine,
memoryCache,
bitmapPool,
arrayPool,
requestManagerRetriever,
connectivityMonitorFactory,
logLevel,
defaultRequestOptionsFactory,
defaultTransitionOptions,
defaultRequestListeners,
experiments);
}

   看下Engine这个类的构造函数:

1
2
3
4
5
6
7
8
9
10
public Engine(
MemoryCache memoryCache,
DiskCache.Factory diskCacheFactory,
GlideExecutor diskCacheExecutor,
GlideExecutor sourceExecutor,
GlideExecutor sourceUnlimitedExecutor,
GlideExecutor animationExecutor,
boolean isActiveResourceRetentionAllowed) {
//isActiveResourceRetentionAllowed 是否允许保留活动资源
}

   可以看Engine里面保存了我们会使用到的所有线程池(总共四个)。分别是:

  • diskCacheExecutor
  • sourceExecutor
  • sourceUnlimitedExecutor
  • animationExecutor

我们可以看下这四个线程池是什么时候创建的:

   这四个线程池都是在GlideBuilder.build方法里面创建的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// GlideBuilder
Glide build(@NonNull Context context) {
if (sourceExecutor == null) {
sourceExecutor = GlideExecutor.newSourceExecutor();
}

if (diskCacheExecutor == null) {
diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
}

if (animationExecutor == null) {
animationExecutor = GlideExecutor.newAnimationExecutor();
}

if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
animationExecutor,
isActiveResourceRetentionAllowed);
}
}

   可以看到diskCacheExecutor , sourceExecutor ,animationExecutor 的线程池的队列用的都是PriorityBlockingQueue。也就代表这个线程池永远不会满。也就不会触发RejectedExecutionException这个报错。这里唯一特殊的是sourceUnlimitedExecutor他的创建是GlideExecutor.newUnlimitedSourceExecutor()

1
2
3
4
5
6
7
8
9
10
11
public static GlideExecutor newUnlimitedSourceExecutor() {
return new GlideExecutor(
new ThreadPoolExecutor(
0,
Integer.MAX_VALUE,
KEEP_ALIVE_TIME_MS,
TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>(),
new DefaultThreadFactory(
DEFAULT_SOURCE_UNLIMITED_EXECUTOR_NAME, UncaughtThrowableStrategy.DEFAULT, false)));
}

   可以看到它的缓存队列是SynchronousQueue,这个队列是有界的,所以当我们触发大量网络请求得时候,很可能会触发RejectedExecutionException这个报错。

   在回过头来看下public static RequestManager with(@NonNull Activity activity) {
return getRetriever(activity).get(activity);
}
这个get(activity)干了什么:

进入类RequestManagerRetriver:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 public RequestManager get(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
} else if (Util.isOnMainThread() && !(context instanceof Application)) {
if (context instanceof FragmentActivity) {
return get((FragmentActivity) context);
} else if (context instanceof Activity) {
return get((Activity) context);
} else if (context instanceof ContextWrapper) {
return get(((ContextWrapper) context).getBaseContext());
}
}
return getApplicationManager(context);
}
// 我们可以通过RequestManager这个类名就能猜到是一个请求管理类。他管理了请求的生命周期。
// 通过我们的context去创建对应的生命周期管理类。

   看完Glide.with(context)方法可以看出,我们app的全局Glide对象是唯一的,它的创建用的是单例模式。所以可以这么说Glide对象里面创建过的所有对象也都只有一份,不会重复创建。唯一的是每次调用Glide.with方法都会重新创建RequestManager对象。


   with方法看完了,现在来看下load方法:

1
2
3
4
public RequestBuilder<Drawable> load(@Nullable String string) {
return asDrawable().load(string);
}
// 他创建了对象RequestBuilder<T>

   看下asDrawable

1
2
3
4
5
6
7
8
public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class);
}
// 可以看到这个resourceClass就是 Drawable.class
public <ResourceType> RequestBuilder<ResourceType> as(
@NonNull Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass, context);
}

可以看到通过asDrawable方法创建了对象RequestBuilder

  看下into方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
switch (view.getScaleType()) {
case FIT_XY:
requestOptions = requestOptions.clone().optionalCenterInside();
break;
default:
}
return into(
// 返回的是ViewTarget<ImageView,X>
glideContext.buildImageViewTarget(view, transcodeClass),
null,
requestOptions,// 这个是图片展示模式什么得
Executors.mainThreadExecutor());
}

   看下glideContext.buildImageViewTarget(view, transcodeClass)方法是怎么创建ViewTarget对象的:

首先需要知道这个glideContext:GlideContext对象是什么,这个GlideContext对象是在RequestBuilder的构造函数里面进行赋值的,获取的是Glide构造函数里面创建的GlideContext对象:

1
2
3
4
5
6
7
8
9
protected RequestBuilder(
@NonNull Glide glide,
RequestManager requestManager,
Class<TranscodeType> transcodeClass,
Context context) {
this.glide = glide;
this.glideContext = glide.getGlideContext();
this.transcodeClass = transcodeClass;
}

   在看buildImageViewTarget方法前还需要知道这个transcodeClass是什么?可以知道这个对象也是在RequestBuilder的构造函数里面传递的。见上面的构造函数。现在回到RequestBuilder创建的地方:可以看上面讲了RequestBuilder的创建过程。看完上面的讲解,知道了这个transcodeClass就是Drawable.class

  现在在回到主线glideContext.buildImageViewTarger方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public <X> ViewTarget<ImageView, X> buildImageViewTarget(
@NonNull ImageView imageView, @NonNull Class<X> transcodeClass) {
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
}
public <Z> ViewTarget<ImageView, Z> buildTarget(@NonNull ImageView view,
@NonNull Class<Z> clazz) {
if (Bitmap.class.equals(clazz)) {
return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
} else if (Drawable.class.isAssignableFrom(clazz)) {
return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
} else {
}
}

通过上面我们知道了我们传进来的transcodeClassDrawable.class。所以可以知道最终创建出来的ViewTarget是BitmapImageViewTarget对象。

   分析完ViewTarget的创建过程,现在继续回到主线RequestBuilder.into方法

   接着看into方法:into方法完成了请求的创建Request然后将请求发送出去Request.begin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// RequestBuilder
// 这里知道了target是BitmapImageViewTarget
// targetListener=null
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> options,
Executor callbackExecutor) {

Request request = buildRequest(target, targetListener, options, callbackExecutor);
Request previous = target.getRequest();
if (request.isEquivalentTo(previous)
&& !isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) {
request.recycle();
if (!Preconditions.checkNotNull(previous).isRunning()) {
previous.begin();
}
return target;
}
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
// 这里创建了请求类Request,然后还判断了之前有没有发送过,如果发送了就不在进行新的请求。

   看下Request的创建过程:可以看到先调用buildRequest方法,然后内部调用了buildRequestRecursive从名字可以知道构建递归调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
Request request = buildRequest(target, targetListener, options, callbackExecutor);
private Request buildRequest(
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> requestOptions,
Executor callbackExecutor) {
//构建请求递归
return buildRequestRecursive(
target,
targetListener,
null,
transitionOptions,
requestOptions.getPriority(),
requestOptions.getOverrideWidth(),
requestOptions.getOverrideHeight(),
requestOptions,
callbackExecutor);
}
//构建请求递归
private Request buildRequestRecursive(
Target<TranscodeType> target,
@Nullable RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
BaseRequestOptions<?> requestOptions,
Executor callbackExecutor) {
//这个errorBuilder是我们通过RequetBuilder调用error方法传入了RequestBuilder对象。这是在我们请求失败图后才会走这个error的请求。
ErrorRequestCoordinator errorRequestCoordinator = null;
if (errorBuilder != null) {
// 可以看到如果用户设置了创建失败的errorBuilder那么就会去创建一个错误拦截器:ErrorRequestCoordinator对象。
errorRequestCoordinator = new ErrorRequestCoordinator(parentCoordinator);
parentCoordinator = errorRequestCoordinator;
}

Request mainRequest =
//构建缩略图请求递归
buildThumbnailRequestRecursive(
target,
targetListener,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
requestOptions,
callbackExecutor);
// 如果没有失败拦截器就直接返回mainRequest
if (errorRequestCoordinator == null) {
return mainRequest;
}

int errorOverrideWidth = errorBuilder.getOverrideWidth();
int errorOverrideHeight = errorBuilder.getOverrideHeight();
if (Util.isValidDimensions(overrideWidth, overrideHeight)
&& !errorBuilder.isValidOverride()) {
errorOverrideWidth = requestOptions.getOverrideWidth();
errorOverrideHeight = requestOptions.getOverrideHeight();
}

Request errorRequest =
//构建请求递归
errorBuilder.buildRequestRecursive(
target,
targetListener,
errorRequestCoordinator,
errorBuilder.transitionOptions,
errorBuilder.getPriority(),
errorOverrideWidth,
errorOverrideHeight,
errorBuilder,
callbackExecutor);
errorRequestCoordinator.setRequests(mainRequest, errorRequest);
return errorRequestCoordinator;
}

   在构建Request对象的时候,当我们没有设置error情况的时候就直接去创建mainRqeust,这里为了尽快分析完整个请求过程我就只分析了mainRequest的创建过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// 创建mainRequest方法调用的方法
private Request buildThumbnailRequestRecursive(
Object requestLock,
Target<TranscodeType> target,
RequestListener<TranscodeType> targetListener,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
BaseRequestOptions<?> requestOptions,
Executor callbackExecutor) {
// 这个是缩略图的展示。我们如果没有调用 Glide.with(context).load("http://..").thumbnail()
// 方法来创建缩略图的话,那么这个thumbnailBuilder的值就是null。
// 这里默认没调用thumbnail方法。
if (thumbnailBuilder != null) {
// 所以这里不会进来
if (isThumbnailBuilt) {
throw new IllegalStateException(
"报错。。。。。");
}

TransitionOptions<?, ? super TranscodeType> thumbTransitionOptions =
thumbnailBuilder.transitionOptions;

// Apply our transition by default to thumbnail requests but avoid overriding custom options
// that may have been applied on the thumbnail request explicitly.
if (thumbnailBuilder.isDefaultTransitionOptionsSet) {
thumbTransitionOptions = transitionOptions;
}

Priority thumbPriority =
thumbnailBuilder.isPrioritySet()
? thumbnailBuilder.getPriority()
: getThumbnailPriority(priority);

int thumbOverrideWidth = thumbnailBuilder.getOverrideWidth();
int thumbOverrideHeight = thumbnailBuilder.getOverrideHeight();
if (Util.isValidDimensions(overrideWidth, overrideHeight)
&& !thumbnailBuilder.isValidOverride()) {
thumbOverrideWidth = requestOptions.getOverrideWidth();
thumbOverrideHeight = requestOptions.getOverrideHeight();
}

ThumbnailRequestCoordinator coordinator =
new ThumbnailRequestCoordinator(requestLock, parentCoordinator);
Request fullRequest =
obtainRequest(
requestLock,
target,
targetListener,
requestOptions,
coordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
callbackExecutor);
isThumbnailBuilt = true;
// Recursively generate thumbnail requests.
Request thumbRequest =
thumbnailBuilder.buildRequestRecursive(
requestLock,
target,
targetListener,
coordinator,
thumbTransitionOptions,
thumbPriority,
thumbOverrideWidth,
thumbOverrideHeight,
thumbnailBuilder,
callbackExecutor);
isThumbnailBuilt = false;
coordinator.setRequests(fullRequest, thumbRequest);
return coordinator;
} else if (thumbSizeMultiplier != null) {
// 这也同理没调用thumbnail这个方法这个值就不会动态设值进来
ThumbnailRequestCoordinator coordinator =
new ThumbnailRequestCoordinator(requestLock, parentCoordinator);
Request fullRequest =
obtainRequest(
requestLock,
target,
targetListener,
requestOptions,
coordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
callbackExecutor);
BaseRequestOptions<?> thumbnailOptions =
requestOptions.clone().sizeMultiplier(thumbSizeMultiplier);

Request thumbnailRequest =
obtainRequest(
requestLock,
target,
targetListener,
thumbnailOptions,
coordinator,
transitionOptions,
getThumbnailPriority(priority),
overrideWidth,
overrideHeight,
callbackExecutor);

coordinator.setRequests(fullRequest, thumbnailRequest);
return coordinator;
} else {
// 没有缩略图的情况下 Glide默认会进来的地方。
return obtainRequest(
requestLock,
target,
targetListener,
requestOptions,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
callbackExecutor);
}
}

   进入obtainRequest方法,这是没有创建缩略图的时候,默认创建Request的地方。可以看到在没有配置缩略图的时候,默认的RequestSingleRequest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private Request obtainRequest(
Object requestLock,
Target<TranscodeType> target,
RequestListener<TranscodeType> targetListener,
BaseRequestOptions<?> requestOptions,
RequestCoordinator requestCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority,
int overrideWidth,
int overrideHeight,
Executor callbackExecutor) {
return SingleRequest.obtain(
context,
glideContext,
requestLock,
model,
transcodeClass,
requestOptions,
overrideWidth,
overrideHeight,
priority,
target,
targetListener,
requestListeners,
requestCoordinator,
glideContext.getEngine(),
transitionOptions.getTransitionFactory(),
callbackExecutor);
}
}

   至此Request就创建完毕了。

   既然已经知道Request对象是SingleRequest了。那就直接进入SingleRequestbegin方法。

1
2
3
4
5
6
7
8
public RequestBuilder<TranscodeType> load(@Nullable String string) {
return loadGeneric(string);
}
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// SingleRequest
public synchronized void begin() {
assertNotCallingCallbacks();
stateVerifier.throwIfRecycled();
startTime = LogTime.getLogTime();
// 这个model就是我们调用Glide.whit().load("http:...")
// load里面的String.class 可以看上面我贴出来了model对象的赋值过程。
if (model == null) {
return;
}

if (status == Status.RUNNING) {
throw new IllegalArgumentException("Cannot restart a running request");
}

if (status == Status.COMPLETE) {
onResourceReady(resource, DataSource.MEMORY_CACHE);
return;
}
//对于既不完整也不运行的请求重新启动可以被视为新请求,并可以重新开始运行。
status = Status.WAITING_FOR_SIZE;
//isValidDimensions 是否是有效的。
// 当宽高不是有效值 默认的值为-1 所以第一次进行请求的时候会进入onSizeReady方法。
// 看过onSizeReady源码你就会知道,他会回调到ViewTarget里面去获取我们ImageView的宽高,获取完毕的后,又会通过回调的方式调用SingleRequest里面的onSizeReady方法。在onSizeReady方法里面回去执行engine.load方法正正开启往了请求。
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
// 如果知道了大小数据则直接进行网络请求 调用了engind.load方法。
onSizeReady(overrideWidth, overrideHeight);
} else {
// 这个target就是我们求前面讲的在调用into方法的时候去创建了一个ViewTarget对象。然后也分析了这个ViewTarget对象就是BitmaoImageViewTarget
// 去获取大小,大小获取完回回调出来 调用了onSizeReady方法。onSizeReady里面调用了正正的网络请求 engine.load方法。
target.getSize(this);
}

// 这个就是回调到viewTarget告诉使用者,这次的网络求准备就绪。马上开始调用engine.load方法去获取数据,。
if ((status == Status.RUNNING || status == Status.WAITING_FOR_SIZE)
&& canNotifyStatusChanged()) {
target.onLoadStarted(getPlaceholderDrawable());
}
}

   看下onSizeReady,它就意味着Glide准备就绪,现在可以去调用网络请求了。engine.load

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// SingleRequest
public synchronized void onSizeReady(int width, int height) {
stateVerifier.throwIfRecycled(){
// 判断状态已经在请求得话不重复进行
if (status != Status.WAITING_FOR_SIZE) {
return;
}
status = Status.RUNNING;

float sizeMultiplier = requestOptions.getSizeMultiplier();
this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
this.height = maybeApplySizeMultiplier(height, sizeMultiplier);
// engine是在创建Glide对象的时候创建好的。glide是要给单例,整个程序就只会创建一次。engine也一样。
loadStatus =
engine.load(
glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),
transcodeClass,
priority,
requestOptions.getDiskCacheStrategy(),
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.isScaleOnlyOrNoTransform(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
requestOptions.getUseUnlimitedSourceGeneratorsPool(),
requestOptions.getUseAnimationPool(),
requestOptions.getOnlyRetrieveFromCache(),
this,
callbackExecutor);
if (status != Status.RUNNING) {
loadStatus = null;
}
if (IS_VERBOSE_LOGGABLE) {
logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));
}
}

   开始真正得网络请求:load

  • 开始加载给定参数。
    必须在主线程上调用。
    任何请求的流程如下:
    检查当前使用的资源集,返回活动资源(如果存在),并将任何新的非活动资源移动到内存缓存中。
    检查内存缓存并提供缓存资源(如果存在)。
    检查当前的一组正在进行的加载并将 cb 添加到进行中的加载(如果存在)。
    开始新的加载。
    活动资源是那些已提供给至少一个请求但尚未释放的资源。 一旦资源的所有消费者都释放了该资源,该资源就会进入缓存。 如果资源从缓存中返回给新的使用者,它会重新添加到活动资源中。 如果资源从缓存中被逐出,它的资源将被回收并在可能的情况下重新使用,并丢弃该资源。 没有严格要求消费者释放他们的资源,因此活跃的资源被弱持有。

参数:

  • width – 所需资源的目标宽度(以像素为单位)。
    height – 所需资源的目标高度(以像素为单位)。
    cb – 加载完成时将调用的回调
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public synchronized <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, Transformation<?>> transformations,
boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb,
Executor callbackExecutor) {
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);

EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}

EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}

EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
current.addCallback(cb, callbackExecutor);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Added to existing load", startTime, key);
}
return new LoadStatus(cb, current);
}

EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);

DecodeJob<R> decodeJob =
decodeJobFactory.build(
glideContext,
model,
key,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
onlyRetrieveFromCache,
options,
engineJob);
jobs.put(key, engineJob);
engineJob.addCallback(cb, callbackExecutor);
engineJob.start(decodeJob);
return new LoadStatus(cb, engineJob);
}

   首先从缓存里面进行查找,如果缓存没有的话就创建新的网络请求。

   在使用Glide得时候,我们可以使用Glide.with(this).load(mUrl).skipMemoryCache(false)方法设置需不需要走缓存。设置为false就是不走缓存,那么每次都是重新请求。默认是true。走缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
// 为false得时候也就不会走缓存查找了
if (!isMemoryCacheable) {
return null;
}
// 这是活动缓存 通过弱引用的方式保存数据 实现类是ActiveResources
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return active;
}
//从LruCache获取缓存图片 实现类 LruResourceCache
EngineResource<?> cached = loadFromCache(key);
if (cached != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return cached;
}

return null;
}

   先从活动缓存里面找(活动缓存ActiveCache:正在使用的图片),不使用了后就从活动缓存里面删掉,删掉的数据放到MemoryCache

   内存缓存:回收机制:LRU最少使用算法来管理的。被使用的时候会将数据提到磁盘缓存。如果最少使用,内部算法会回收。活动缓存被回收的数据又会被丢回到内存缓存

   你正在使用的图片—-[活动缓存]。如果不用了,才会扫描回收。[存入 移除 非常快]。

   在Glide里面内存缓存的类是LruResourceCache

缓存策略

组合策略

和其他三级缓存一样,Glide的缓存读取顺序是 内存->磁盘->网络

需要注意的是Glide的内存缓存和磁盘缓存的配置是相互没有直接影响,所以可以同时进行配置:

例:

  1. 内存不缓存,磁盘缓存所有图片

    1
    Glide.with(this).load(url).skipMemoryCache(true).disCacheStratrgy(DiskCachestrat.ALL).into(imageview)
  2. 内存缓存处理图,磁盘缓存原图

    1
    Glide.with(this).load(mUrl).skipMemoryCache(false).diskCacheStrategy(DiskCacheStrategy.SOURCE).into(mIv);

缓存大小及路径

2.1 内存缓存最大空间

Glide的内存缓存其实涉及到表较多的计算,这里就介绍最重要的一个参数。就是内存缓存最大空间内存缓存最大空间(maxSize)=每个进程可用的最大内存0.4。(低配手机的话就是:每个进程可用的最大内存0.33)

2.2 磁盘缓存大小

磁盘缓存大小:250*1024*1024(250MB)

1
2
/**250MB of cache**/
int DEFULT_DISK_CACHE_SIZE = 250*1024*1024

2.3 磁盘缓存目录

磁盘缓存目录:项目/cache/image_manager_disk_cache

1
String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache";

清理缓存

3.1 清理所有缓存

清理所有内存缓存(需要在ui线程操作)

1
Glide.get(this).clearMemory();

清理所有磁盘缓存(需要在子线程操作)

1
Glide.get(MainActivity.this).clearDiskCache();

注:在使用中的资源不会被清除。

Recycleview基于滑动分析

既然要研究滑动那就要看RecycleviewonTouchEvent方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public boolean onTouchEvent(MotionEvent e) {
switch (action) {
case MotionEvent.ACTION_MOVE: {
// scrollByInternal 实现recycleview滚动
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
e)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
// 实现预缓存,如果要预取的viewHolder调用过onBinderViewHolder方法,那么预取的viewHolder就保存到mCachedViews里面。如果没有调用过onBindViewHolder方法,则把这个viewHolder添加到mRecycleviewPool里面。
if (mGapWorker != null && (dx != 0 || dy != 0)) {
mGapWorker.postFromTraversal(this, dx, dy);
}
}
} break;
case MotionEvent.ACTION_UP: {
mVelocityTracker.addMovement(vtev);
eventAddedToVelocityTracker = true;
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
final float xvel = canScrollHorizontally
? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;
final float yvel = canScrollVertically
? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;
// fling实现惯性滑动
if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
resetScroll();
} break;
}
return true;
}

    看方法scrollByInternal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
boolean scrollByInternal(int x, int y, MotionEvent ev) {
if (mAdapter != null) {
// 实现recycleview的滑动
scrollStep(x, y, mReusableIntPair);
}
if (!mItemDecorations.isEmpty()) {
invalidate();
}
if (!awakenScrollBars()) {
invalidate();
}
return consumedNestedScroll || consumedX != 0 || consumedY != 0;
}

void scrollStep(int dx, int dy, @Nullable int[] consumed) {
int consumedX = 0;
int consumedY = 0;
if (dx != 0) {
consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
}
if (dy != 0) {
consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
}
if (consumed != null) {
consumed[0] = consumedX;
consumed[1] = consumedY;
}
}

    可以看到滑动事件交给了LayoutManagerscrollVerticallyByh和scrollHorizontallyBy

所以自定义LayoutManager的话如果支持滑动那么就需要重写这两个方法。现在来看下scrollVertically方法。

1
2
3
4
5
6
7
8
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 这个方法的每一行代码都是那么的重要。。。。
// 经过九九八十一难,我才看懂。
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
// delta就是每次的滚动值
mLayoutState.mRecycle = true;
final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDelta = Math.abs(delta);
// 这个方法里面每个参数的赋值都很重要。看下面我的分析
// 这个方法最重要的事情就是给 mAvailable 和 mScrollingOffset赋值。
// 我们的mScrollingOffset的值也就是在这时候被改变了。
updateLayoutState(layoutDirection, absDelta, true, state);
// fill方法 是不是很眼熟。。。这个方法实现父视图对孩子的添加
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
if (consumed < 0) {
return 0;
}
final int scrolled = absDelta > consumed ? layoutDirection * consumed : delta;
// 上面fill完成了布局
// offsetChildren实现将每个view进行平移。太棒了。
// 以后我也要这么干,太会了。
mOrientationHelper.offsetChildren(-scrolled);
mLayoutState.mLastScrollDelta = scrolled;
return scrolled;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 用来保存最后一个view全部展示出来需要滑动的距离
// 或者是最上面一个view全部展示出来需要的距离。
// 以及这次滑动值requireSpace - 最后一个view全部展示出来需要滑动的距离 他们的值的状态
// 如果大于0代表这次滑动最后一个view可以全部展示出来,需要去添加一个新view进来了。
private void updateLayoutState(int layoutDirection, int requiredSpace,
boolean canUseExistingSpace, RecyclerView.State state) {
// 这里假设手指运动方向是从下往上 则 layoutDirection = LayoutState.LAYOUT_END
mLayoutState.mInfinite = resolveIsInfinite(); //fasle
mLayoutState.mLayoutDirection = layoutDirection; // 1
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
calculateExtraLayoutSpace(state, mReusableIntPair);// 现在看来啥也没干
int extraForStart = Math.max(0, mReusableIntPair[0]);
int extraForEnd = Math.max(0, mReusableIntPair[1]);
boolean layoutToEnd = layoutDirection == LayoutState.LAYOUT_END;
mLayoutState.mExtraFillSpace = layoutToEnd ? extraForEnd : extraForStart;
mLayoutState.mNoRecycleSpace = layoutToEnd ? extraForStart : extraForEnd;
int scrollingOffset;
if (layoutToEnd) {//这里为true
// 我没搞懂这里为啥要叠加recycleview的paddingBottom的值 咋叠加都是0
mLayoutState.mExtraFillSpace += mOrientationHelper.getEndPadding();
// 找到最底下的view
final View child = getChildClosestToEnd();
// 由于不是反向布局 这里的mItemDirection = 1
mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
: LayoutState.ITEM_DIRECTION_TAIL;
// 比如我们页面已经添加了12个子view,那么这个getPosition(child)的值就是11 然后 由于是下上滑动的那么mItemDirection的值就是1 结果显而易见 mLayoutState.mCurrentPositiond = 11 + 1
mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
// mOffset 的值 等于 当前这个view距离父视图底部的距离。bottom的值。
mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
// calculate how much we can scroll without adding new children (independent of layout)
// mOrientationHelper.getDecoratedEnd(child) 由于这个child是最底部的这个子视图。所以这个获取的值就是最底下的view距离recycleview顶部的距离。
//mOrientationHelper.getEndAfterPadding()获取的是recycleview的高度-paddingBottom值
// 这两个一减代表最后一个字view全部出现到屏幕上需要移动的距离
scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
- mOrientationHelper.getEndAfterPadding();

} else {
final View child = getChildClosestToStart();
mLayoutState.mExtraFillSpace += mOrientationHelper.getStartAfterPadding();
mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
: LayoutState.ITEM_DIRECTION_HEAD;
mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child);
// 最上面一个view全部出现到屏幕上需要移动的距离
scrollingOffset = -mOrientationHelper.getDecoratedStart(child)
+ mOrientationHelper.getStartAfterPadding();
}
// requiredSpace的值就是滚动的值
mLayoutState.mAvailable = requiredSpace;
// 这个canUseExistingSpace的值等于true
if (canUseExistingSpace) {
// scrollingOffset代表最后一个view全部划出屏幕的距离
// mLayoutState.mAvailable 代表此次滑动需要的距离
// 两者一减: 如果结果>0 代表最后一个view已经全部出现在屏幕了,这个时候需要去添加一个新的view
// 如果减的<0,则代表最后一个视图还不足以全部出现,也就不用添加新的页面了
mLayoutState.mAvailable -= scrollingOffset;
}
// 设置为最后一个view全部展示出来需要的距离
mLayoutState.mScrollingOffset = scrollingOffset;
}
//比如这次滑动距离为 10 。 最后一个view全部展示出来需要的距离为
//那么:mAvailable = 10 - 20 = -10 mScrollingOffset = 20。

    进入fill方法来看下:

滑动的时候mScrollingOffset = 最后一个view全部展示出来的距离

mAvailable = 滑动距离 - mScrollingOffset

这个mAvailable > 0 代表这次的滑动需要去增加新的页面。

​ mAvailable < 0 代表这次的滑动不需要去增加新的页面。

如果小于0,他里面执行了 mScrollingOffset = mScrollingOffset - mAvailable 也就等于这次的滑动距离。然后根据这次的滑动距离,去判断需不需要对不可见的view进行回收。4

fill方法不仅执行了添加视图的操作也执行了回收的操作。回收的操作我放在另外一篇博客来讲。《Recycleview回收原理分析》

img-0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
final int start = layoutState.mAvailable;
// 这个mScrollingOffset的值在触发滑动的时候会被赋值为
// 最后一个view全部展示在屏幕上需要的距离
// mScrollingOffset的值只有在滑动的时候才会赋值
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// 这里面只会在滚动的时候才会调用。
// 小于0代表这次的滑动不足以让最下面的视图全部展示出来,
// 也就说明,我们不需要添加新的view到父视图里面来。
if (layoutState.mAvailable < 0) {
// 当小于0的时候这一步骤的操作无异于就是让
// mScrollingOffset的值 等于 这次滑动的值。
layoutState.mScrollingOffset += layoutState.mAvailable;
}
// 这是回收那些不可见的视图。这个方法很重要,回收的操作就在这里进行的。
// 回收的条件就是上面的mScrollingOffset这个方法,在回收的时候有一个基准线limit 他的值等于 mScrollingOffset
// 然后会循环遍历recycleview所有的子view,找到孩子view底部距离顶部的值 > limit 的所有符合条件的view。然后将他们
// 从父视图进行移除。 具体讲解参考我的博客: 《Recycleview回收原理分析》
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
// remainSpace > 0 表示 有可用空间可以使用,那么为了填补这个可用空间,我们就需要执行addVie操作,不然怎么填补这部分空间,
// 你说是吧。 添加结束的标志就是 可用空间被填满。
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
// 这个方法就是加载一个子view
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (layoutChunkResult.mFinished) {
break;
}
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
// mIgnoreConsumed看名之意:忽略消耗。一般都不会忽略。
if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
|| !state.isPreLayout()) {
// layoutChunkResult.mConsumed 的值就等于 一个itemView所需要消耗的高度。
layoutState.mAvailable -= layoutChunkResult.mConsumed;
remainingSpace -= layoutChunkResult.mConsumed;
}
// 这一段和上面那一段代码一样。
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
// 这一步的操作无疑就是让mScrollingOffset的值 = 这次移动的值。
layoutState.mScrollingOffset += layoutState.mAvailable;
}
// 回收那些view底部距离recycleview顶部的值 大于 这次滑动的值的页面。
recycleByLayoutState(recycler, layoutState);
}
if (stopOnFocusable && layoutChunkResult.mFocusable) {
break;
}
}
// 当我们还有可以展示的更过的孩子的时候这个mAvailable的值一定是负数的。
return start - layoutState.mAvailable;
}

    现在关于onTouchevent里面的scrollByInternal方法讲解完了,现在看onTouchevent下的mGapWorker.postFromTraversal:

1
2
3
4
5
6
7
8
9
10
11
12

// GapWorker是一个继承子Runnable的类,也就是说通过post方法我们可以把这条消息发送出去
void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) {
if (recyclerView.isAttachedToWindow()) {
if (mPostTimeNs == 0) {
mPostTimeNs = recyclerView.getNanoTime();
// 通过post的方法把消息发送出去了。就需要看run方法。
recyclerView.post(this);
}
}
recyclerView.mPrefetchRegistry.setPrefetchVector(prefetchDx, prefetchDy);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// GapWorker.java
@Override
public void run() {
try {
if (mRecyclerViews.isEmpty()) {
return;
}
// 看名字就知道 预取
// 为啥预取。就是为了创建下一个viewHolder然后保存到缓存里面。
prefetch(nextFrameNs);
} finally {
mPostTimeNs = 0;
TraceCompat.endSection();
}
}
void prefetch(long deadlineNs) {
buildTaskList();
flushTasksWithDeadline(deadlineNs);
}
private void flushTasksWithDeadline(long deadlineNs) {
for (int i = 0; i < mTasks.size(); i++) {
final Task task = mTasks.get(i);
if (task.view == null) {
break;
}
flushTaskWithDeadline(task, deadlineNs);
task.clear();
}
}
//用最后期限刷新任务
private void flushTaskWithDeadline(Task task, long deadlineNs) {
long taskDeadlineNs = task.immediate ? RecyclerView.FOREVER_NS : deadlineNs;
//prefetchPositionWithDeadline 通过名字可以知道 是通过指定位置去预取
// 这个方法的内部就调用了我们在fill里面看到的next方法获取view的方法。就是tryGetViewHolderForPositionByDeadline方法。从recycer的四重缓存里面,寻找可以使用的viewHolder。
RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view,
task.position, taskDeadlineNs);
if (holder != null
&& holder.mNestedRecyclerView != null
&& holder.isBound()
&& !holder.isInvalid()) {
prefetchInnerRecyclerViewWithDeadline(holder.mNestedRecyclerView.get(), deadlineNs);
}
}

private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view,
int position, long deadlineNs) {
if (isPrefetchPositionAttached(view, position)) {
return null;
}

RecyclerView.Recycler recycler = view.mRecycler;
RecyclerView.ViewHolder holder;
try {
view.onEnterLayoutOrScroll();
// 这个方法就是从缓存里面获取多应得viewHolder
holder = recycler.tryGetViewHolderForPositionByDeadline(
if (holder != null) {
//isBound得意思就是判断这个viewHolder是否调用过onBinderViewHolder方法。
if (holder.isBound() && !holder.isInvalid()) {
recycler.recycleView(holder.itemView);
} else {
recycler.addViewHolderToRecycledViewPool(holder, false);
}
}
} finally {
view.onExitLayoutOrScroll(false);
}
return holder;
}

        看下prefetchPositionWithDeadline方法,里面判断viewHolder是否调用过onBinderViewHolder方法,如果调用过了,就把这个viewHolder添加到mCachedViews数组里面,如果没有添加过的话就添加到mRecycleviewPool里面。并且mRecycleviewPool里面的这种类型的数组还没有超过数组上线默认一种类型的数组大小是5:

1
2
3
4
5
6
7
8
9
10
11
12
// 这里先判断这个viewHolder是否调用过onBindViewHolder方法。onBindViewHolder方法就是把数据绑定到了view上面。
// recycle得缓存机制里面。mCachedViews这个数组是用来缓存那些调用过onBinderViewHolder方法得viewHolder。而mRecycleviwPool里面得mScrap数组保存的是从mCachedViews数组里面踢出来得viewHolder。也是那些没有调用过onBindViewHolder方法的视图。
if (holder != null) {
if (holder.isBound() && !holder.isInvalid()) {
// 如果绑定过视图了,就保存到mCachedViews数组里面,这样的话下次就可以直接展示了
recycler.recycleView(holder.itemView);
} else {
// 添加到mRecycleviewPoold的mScrap里面。
// 不过如果对应的veiwType的数组大于了最大限制默认是5.那么也不会往里面加了。
recycler.addViewHolderToRecycledViewPool(holder, false);
}
}

     关于滑动的时候的预取操作讲完了。


    在来看下在onTouchEvent里面的scrollByInternal方法。是怎么实现滑动 的。

     当手指从下往上的时候mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END这个mLayoutState.mLayoutDirection的值是根据每次滑动的值来判断的。滑动改变值>0等于LAYOUT_END ;<0 等于LAYOUT_START。mLayoutState.mItemDirection是我们初始化的时候就已经设置好的值。不会变。


监听viewHolder移除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21


// 这个方法是在fill方法里面,移除缓存里面的数据的时候调用。这个方法里面最终还是会调用下面的dispatchChildDetached方法。
public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
final View view = getChildAt(index);
removeViewAt(index);
recycler.recycleView(view);
}

void dispatchChildDetached(View child) {
final ViewHolder viewHolder = getChildViewHolderInt(child);
onChildDetachedFromWindow(child);
if (mAdapter != null && viewHolder != null) {
mAdapter.onViewDetachedFromWindow(viewHolder);
}
if (mOnChildAttachStateListeners != null) {
final int cnt = mOnChildAttachStateListeners.size();
for (int i = cnt - 1; i >= 0; i--) { mOnChildAttachStateListeners.get(i).onChildViewDetachedFromWindow(child);
}
}
}

方式一:

调用Recycleview.setRecyclerListener注册回收监听,这个回调里面可以拿到对应的viewHolder

调用Recycleveiw.addOnChildAttachStateChangeListener注册监听。这个方法只能拿到view.

重写Recycleview.onChildDetachedFromWindow,这个方法只能拿到view.

方式二:

重写Adapter.onViewRecycled可以监听到视图的回收,这个回调里面可以拿到对应的viewHolder

重写Adapter.onViewDetachedFromWindow

监听viewHolder添加

Recycleview:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 当一个viewHolder被添加的时候这个方法会被调用
void dispatchChildAttached(View child) {
final ViewHolder viewHolder = getChildViewHolderInt(child);
onChildAttachedToWindow(child);
if (mAdapter != null && viewHolder != null) {
mAdapter.onViewAttachedToWindow(viewHolder);
}
if (mOnChildAttachStateListeners != null) {
final int cnt = mOnChildAttachStateListeners.size();
for (int i = cnt - 1; i >= 0; i--) {
mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child);
}
}
}

方式一:

重写Adapter.onViewAttachedToWindow>方法。

方式二:

调用Recycleveiw.addOnChildAttachStateChangeListener注册监听。

可以发现addOnChildAttachStateChangeListener方法可以监听移除和添加

1
2
3
4
5
6
public interface OnChildAttachStateChangeListener {
// 这个方法是在viewGroup执行完 addView操作之后调用
void onChildViewAttachedToWindow(@NonNull View view);
// 这个方式是在ViewGroup.removeViewAt之前执行的。所以你在这个方法里面调用getChildCOunt的值还是
void onChildViewDetachedFromWindow(@NonNull View view);
}

如果要实现像抖音那样选中的视频要进行播放的话,该怎么做。

要实现一页一页滑动的话,当然用的是PagerSnapHelper工具类。

可以重写RecycleviewonScrollStateChanged方法监听滑动状态,当状态为SCROLL_STATE_IDLE的时候表示滑动停止。这样我们就可以在这个状态下获取当前展示在屏幕上的view。

让Recycleview滚动到指定位置

在看PagerSnapHelper的源码发现。

在实现惯性滑动的时候,recycleview的底层是通过这样的方法让recycleview滚动起来的。:

  1. 首先我们需要new一个Recycleview.SmoothScroller对象
  2. 给Recycleview.SmoothScroller对象设置一个目标滚动位置
  3. 调用layoutManager的startSmoothScroll(RecycleView.SmaoothScroller)方法
1
2
3
4
5
6
7
8
9
10
11
// 这部分全部代码在 SnaperHelper的 snapFromFling()方法里面。
// 创建Recycleview.SmoothScroller对象
RecyclerView.SmoothScroller smoothScroller = createScroller(layoutManager);
if (smoothScroller == null) {
return false;
}
int targetPosition = 10
// 设置滚动到的位置
smoothScroller.setTargetPosition(targetPosition);
// 然recycleview滚动起来
layoutManager.startSmoothScroll(smoothScroller);

Recycleview的缓存过程

首先先从mAttachedScrap里面获取,他代表的是展示在手机屏幕上面的viewHolder。mAttachesScrop在我们第一次布局页面的时候,虽然调用了他的add操作。但是那个时候recycleview是空的,所以mAttachedScrap数组也是空的。在我看来,当旋转屏幕的时候,系统会自动触发requestLayout。这个时候,我们的mAttachedScrop的数组就不在是空的。

在当mAttachedScrap为空的时候,就从mHiddenViews数组里面获取。

如果mHiddenViews为空,就从mCachedViews里面获取。这个mCachedViews的数组默认大小为2。意味着最大缓存为2,当超过2的时候,mCachedViews会移除超出的数据把他添加到RecycledViewPool里面。RecyclePool里面的数组是根据viewHolder的类型来保存的。每个类型的最大缓存数量为5。

mCachedViews为空的话就去RecycledViewPool里找,再为空就调用onCreateViewHolder去创建。

那mCachedViews里面的数据是在什么时候添加进去的呢?是在移动Recycleview的时候,然触发滑动的时候,如果有的页面超过了我们的限定线limit的时候,就会执行移除操作被添加到mCachedViews数组里面。

那RecycleredViewPool里的数据什么时候加的呢?再手势滑动的时候,执行move操作会调用recycleview的预取操作。预取操作里面判断想获取的那个view有没有执行过onBindViewHolder操作。执行过的那么就把预取得viewHolder加到mCachedViews里面。没有执行过onBindViewHolder操作得就放到RecycledViewPool里面。

通过这里可以知道,从mCachedViews里面获取得viewHolder可以直接拿来展示,而从RecycledViewPool里面获取得viewHolder需要再次执行onBindViewHodler操作。

Recycleview缓存过程知道了,那么他Recycleview从缓存里面取的条件是什么呢?

条件是:先遍历mCachedViews数组通过position找到和mCachedViews里面对应的那个viewHolder,mCachedViews里面保存的是最近移除屏幕的view,所以可以通过position找到那个最近移除的viewholder然后直接就可以展示出来,不需要走onBindViewHolder操作。如果在mCachedViews里面找不到就去RecycleredViewPool里面找,遍历RecycleredViewPool是不根据position来找的,而是根据viewHolder的type值来找,找到的话就把RecycleredViewPool数组里面的这个viewHolder删掉,然后在把删掉的这个返回回去。然后则个viewHolder是需要执行onBindViewHolder操作的。然后才能进行展示。

RecycleView更新

需要跟新得时候,会把数据加到:mPendingUpdates里面。

mAdapterUpdateDuringMeasure = true

Recycleview基于LinearLayoutManager的初次布局

看了Recycleview代码后你会发现Recycleview真的牛逼,他在onLayout里面通过detachViewFromParent将全部的子view全部从recycleview里面移除,用这个方法会不触发requestLayout。然后添加view用的是addView,用addView会触发requestLayout呀。但是为什么每次addView。Recycleview都没有执行onLayout呢。原因是recycleview重写了requetLayout方法。绝绝子。。

    讲到布局那么就要先进入到RecycleviewonLayout方法。

1
2
3
4
5
6
7
8
// Recycleview
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}

    在布局的时候调用了dispatchLayout方法。在看源码的时候可以发现,dispatchLayout方法又被分成了dispatchLayoutStep1dispatchLayoutStep2dispatchLayoutStep3。在首次布局完成后Recycleview会将mFirstLayoutComplete设置为true。这里最终的完成布局的操作都在dispatchLayoutStep2方法。

    来看下dispatchLayoutStep2:最主要是调用了我们的LayoutManager的onLayoutChildren方法。完成页面的布局。

1
2
3
private void dispatchLayoutStep2() {
mLayout.onLayoutChildren(mRecycler, mState);
}
1
2
3
4
5
6
7
// LinearLayoutManager
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
detachAndScrapAttachedViews(recycler);
updateLayoutStateToFillEnd(mAnchorInfo);
fill(recycler, mLayoutState, state, false);
}

    代码一删,关键代码就这些。

    先来看下这个detechAndScrapAttachedViews方法。这个方法看名之意就是从父视图分离掉全部的子view。内部调用了ViewGroup.detachViewFromParent方法。这是一个轻量级的移除操作。不会触发页面的requestLayout操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Recycleview
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
// isInvalid:这是视图是否还有用,没用的话直接走remove操作,彻底父视图删掉
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
// 一般都走这里
// 最终走的是RecyclerView.this.detachViewFromParent(offset);
// 这是轻量级的移除,不会触发requestLayout操作
detachViewAt(index);
// 将view添加到Recycler的mAttachedScrap数组里面。
// mAttachedScrap执行add操作是在detachAndScrapAttachedViews方法里面。而这个方法又只会在LinearLayoutManager的
//onLayoutChildren方法里才会被调用。而onLayoutChildren方法又只会在Recycleview的onLayout方法被调用。而要触发
//onLayout方法就要触发requestLayout方法。不然这个mAttachedScrap的值就一直为空。
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
public void detachViewAt(int index) {
detachViewInternal(index, getChildAt(index));
}
private void detachViewInternal(int index, @NonNull View view) {
if (DISPATCH_TEMP_DETACH) {
ViewCompat.dispatchStartTemporaryDetach(view);
}
//最终走的是RecyclerView.this.detachViewFromParent(offset);不会触发requestLayout
mChildHelper.detachViewFromParent(index);
}

        现在来看下updateLayoutStateToFillEnd方法。看名知意就是更新LayoutState的信息。

1
2
3
4
5
6
7
8
9
10
11
12
private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo) {
updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate);
}
private void updateLayoutStateToFillEnd(int itemPosition, int offset) {
mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset;
mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
LayoutState.ITEM_DIRECTION_TAIL;
mLayoutState.mCurrentPosition = itemPosition;
mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END;
mLayoutState.mOffset = offset;
mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
}

    这个里面出现了很重要的几个参数信息:

  • LayoutState.mAvailable:官方解释是:在布局放向上,我们应该填充的像素数。这个参数在第一次布局的时候被赋值为recycleview的高度。然后在滑动的时候这个值 = 滑动量 - 最后一个view全部展示出来需要的量。
  • LayoutState.mScrollingOffset:官方解释是:在滚动的时候,在不创建新视图的情况下的滚动量。这个值在第一次布局的时候等于一个固定值。然后在滚动的时候,这个值起到了确定哪些view应该被回收的作用。
img-0

    现在进入fill方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
final int start = layoutState.mAvailable;
// 这个mScrollingOffset的值在触发滑动的时候会被赋值为
// 最后一个view全部展示在屏幕上需要的距离
// mScrollingOffset的值只有在滑动的时候才会赋值
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// 这里面只会在滚动的时候才会调用。
// 小于0代表,这次的滑动不足以让最下面的视图全部展示出来,
也就说明,我们不需要添加新的view到父视图里面来。
if (layoutState.mAvailable < 0) {
// 当小于0的时候这一步骤的操作无异于就是让 mScrollingOffset的值 等于 这次滑动的值。
layoutState.mScrollingOffset += layoutState.mAvailable;
}
// 这是回收那些不可见的视图。这个方法很重要,回收的操作就在这里进行的。
// 回收的条件就是上面的mScrollingOffset这个方法,在回收的时候有一个基准线limit 他的值等于 mScrollingOffset
// 然后会循环遍历recycleview所有的子view,找到孩子view底部距离顶部的值 > limit 的所有符合条件的view。然后将他们
// 从父视图进行移除。 具体讲解参考我的博客: 《Recycleview回收原理分析》
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
// remainSpace > 0 表示 有可用空间可以使用,那么为了填补这个可用空间,我们就需要执行addVie操作,不然怎么填补这部分空间,
// 你说是吧。 添加结束的标志就是 可用空间被填满。
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
// 这个方法就是加载一个子view
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (layoutChunkResult.mFinished) {
break;
}
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
// mIgnoreConsumed看名之意:忽略消耗。一般都不会忽略。
if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
|| !state.isPreLayout()) {
// layoutChunkResult.mConsumed 的值就等于 一个itemView所需要消耗的高度。
layoutState.mAvailable -= layoutChunkResult.mConsumed;
remainingSpace -= layoutChunkResult.mConsumed;
}
// 这一段和上面那一段代码一样。
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
// 这一步的操作无疑就是让mScrollingOffset的值 = 这次移动的值。
layoutState.mScrollingOffset += layoutState.mAvailable;
}
// 回收那些view底部距离recycleview顶部的值 大于 这次滑动的值的页面。
recycleByLayoutState(recycler, layoutState);
}
if (stopOnFocusable && layoutChunkResult.mFocusable) {
break;
}
}
// 当我们还有可以展示的更过的孩子的时候这个mAvailable的值一定是负数的。
return start - layoutState.mAvailable;
}

    现在来看下layoutChunk方法,看他是怎么添加一个孩子进去的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// LinearLayoutManager
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
// next:就是从RecycleView的缓存存里面取得。这个看我得博客:《Recycleview回收原理分析》
// 这里我就分析已经拿出来了。
View view = layoutState.next(recycler);
if (view == null) {
result.mFinished = true;
return;
}
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
measureChildWithMargins(view, 0, 0);
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
int left, top, right, bottom;
if (mOrientation == VERTICAL) {
if (isLayoutRTL()) {
right = getWidth() - getPaddingRight();
left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
} else {
left = getPaddingLeft();
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
bottom = layoutState.mOffset;
top = layoutState.mOffset - result.mConsumed;
} else {
top = layoutState.mOffset;
bottom = layoutState.mOffset + result.mConsumed;
}
} else {
top = getPaddingTop();
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);

if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
right = layoutState.mOffset;
left = layoutState.mOffset - result.mConsumed;
} else {
left = layoutState.mOffset;
right = layoutState.mOffset + result.mConsumed;
}
}
layoutDecoratedWithMargins(view, left, top, right, bottom);
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable = view.hasFocusable();
}
public void addView(View child) {
addView(child, -1);
}
public void addView(View child, int index) {
addViewInt(child, index, false);
}
private void addViewInt(View child, int index, boolean disappearing) {
final ViewHolder holder = getChildViewHolderInt(child);
if (disappearing || holder.isRemoved()) {
mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder);
} else {
mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder);
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (holder.wasReturnedFromScrap() || holder.isScrap()) {
if (holder.isScrap()) {
holder.unScrap();
} else {
holder.clearReturnedFromScrapFlag();
}
mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);
if (DISPATCH_TEMP_DETACH) {
ViewCompat.dispatchFinishTemporaryDetach(child);
}
} else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child
// ensure in correct position
int currentIndex = mChildHelper.indexOfChild(child);
if (index == -1) {
index = mChildHelper.getChildCount();
}
if (currentIndex != index) {
mRecyclerView.mLayout.moveView(currentIndex, index);
}
} else {
// 执行了Recycleview.addView操作。
mChildHelper.addView(child, index, false);
lp.mInsetsDirty = true;
if (mSmoothScroller != null && mSmoothScroller.isRunning()) {
mSmoothScroller.onChildAttachedToWindow(child);
}
}
if (lp.mPendingInvalidate) {
holder.itemView.invalidate();
lp.mPendingInvalidate = false;
}
}

    在layoutChunk里面通过addView操作将子view添加进去了。

操作系统

ROM存储器是一次写入。

Flash存储器可以进行多次写入。

在电脑系统里电源启动会先执行ROM里面的程序,也就是常见的bios程序,他用来选择进入哪个具体系统。(BOIS是一个固化在ROM芯片中的程序)

graph LR;
  计算机运行模式-->内核态
  计算机运行模式-->用户态

操作系统运行在内核态上,内核态也称管态和核心态。

操作系用拥有硬件的访问权,可以执行机器能够运行的任何指令。

软件的其余部分运行在用户态。用户接口程序(Shell 或者 GUI) 处于用户态。

graph LR;
  操作系统对外的接口-->面向外部应用程序-->外壳Shell
  操作系统对外的接口-->面向内部-->内核kernel
  外壳Shell-->Linux
  外壳Shell-->Windows
  外壳Shell-->Android
  内核kernel

操作系统管理硬件资源:cpu 内存 磁盘 网卡 声卡 显卡

graph LR;
  cpu管理-->cpu调用  
  cpu管理-->进程线程管理
  内存管理-->物理内存
  内存管理-->虚拟内存
  • cpu管理:cpu调用 进程线程管理

  • 内存管理:物理内存管理,虚拟内存管理(为上层应用提供尽量大的内存空间)

  • 文件管理:

底层相关:中断处理,io,设备驱动

操作系统实现在一段时间同时运行多个程序的实现原理:

  • 多道程序设计:提高了计算机系统的使用效率。缺点:交互性不好。他的过程不需要人的参与,自动执行了。比如你想debug代码,但是程序的执行由系统自动执行了,那么我们就没办法在想要停止的位置终止我们的程序。
  • 分时系统:千分之一秒产生一个分时,也就是说一个程序只执行千分之一秒,人感受不到他的切换。分时调用,可以让我们有更好的交互。那他为什么可以打断程序的执行呢?时钟定期可以产生中断,帮助操作系统实现分时调用。

操作系统有自己的堆栈。

计算机体系结构/内存分层体系

  • 计算机体系结构
    • Cpu
    • 内存
    • 外设
  • 内存分层模型
  • 在操作系统的内存管理范例

地址空间/地址生成

地址空间:逻辑地址和物理地址。

逻辑地址到物理地址的映射关系是通过我们的操作系统来完成的。

程序逻辑地址空间。每个程序可以访问的地址空间。每个程序的运行就需要一段连续的内存空间。

因为需要连续的内存空间,所以很容易产生内存碎片。这时候需要使用更好的算法将内存空间进行更好的管理。

连续内存分配

  • 内存碎片化问题
    • 外部碎片
      • 在分配单元间的未使用内存
    • 内部碎片
      • 在分配单元中的未使用内存
  • 分区的动态分配
    • 第一适配
    • 第二适配
  • 压缩式碎片整理
  • 交换式碎片整理

最关心的是:内存碎片化问题。

一个程序需要运行的时候,操作系统要将程序从硬盘加载到内存中去。这个时候,需要为程序分配一个连续的内存空间用来存放这个程序。应用程序在运行的时候,需要为这个程序去提供一个连续的内存空间。

所以操作系统需要管理这些内存空间。他需要知道哪些内存空间是占用的,哪些是空闲下来的。

内存分配的几种方式:

  • 首次适配

    • 为了分配n字节,使用第一个可用的空闲块。如果现在5 10 200 那么我们需要4字节的内存空间,那么就选择5。
      • 优势:简单,易于产生更大的空闲块。缺点:容易产生外部碎片
  • 最优适配

    • 寻找整个空闲块里面,寻找那个最适合满足要求的空闲块。大白话就是找到那个更适合的。
      • 好处:避免把打的空闲块进行拆分。缺点: 容易产生外部碎片,充分配慢,容易产生很多的微小碎片
  • 最差匹配分配

    • 找到差距最大的空闲块进行分配
      • 好处:假如分配是中等尺寸效果最好。坏处:重分配,外部碎片,易于破坏空闲块以至于大分区无法被分配。
  • 压缩式碎片整理

    • 重置程序以合并某些内存块。要求所有程序是动态可重置的。
      • 何时重置,何时分销?在程序运行的时候进行挪动内存空间显然是不行的。
    img-0
  • 交换式碎片整理

    • 运行程序需要更多的内存
    • 抢占等待的程序&回收他们的内存
      • p1p2p4已经把内存占满了,然后p4现在在等待事件产生,那么操作系统就把p4的内存块移到了硬盘里面,那么p4占的内存空间就被空出来了。正在执行的p3就分配到了他需要的内存空间。

物理内存的管理第二部分

如何更好的管理非连续内存?

为什么需要非连续内存分配?基于连续内存分配带来的问题。

基于连续内存分配的缺点

  • 分配给一个程序的物理内存是连续的
  • 内存利用率低
  • 有外部碎片,内部碎片的问题

为了改善这些问题,提出了非连续内存。

非连续分配的优点:

  • 一个程序的物理地址空间是非连续的
  • 更好的内存利用和管理
  • 允许共享代码和数据
  • 支持动态加载和动态链接

缺点:

  • 如何建立虚拟地址和物理地址之间的转换
    • 软件方案
    • 硬件方案
      • 分段机制
      • 分页机制
img-0

进程管理

  • 什么是进程
  • 进程状态
  • 线程
  • 进程间通信
  • 进程互斥与同步
  • 死锁

进程描述

  • 进程定义
    • 代表整个程序运行过程的描述
    • 为什么要有进程?因为在之前只能跑一个程序,现在需要跑多个程序,那么一个程序的实例在系统里面的表诉就是一个个进程。
    • 进程是一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程。
  • 进程的组成
    • 程序的代码需要放到内存中。
    • 程序处理的数据。数据需要放到内存中
    • 程序计数器中的值,指示下一条将运行的指令。
    • 一组通用的寄存器的当前的值,堆,栈
    • 一组系统资源(如打开文件)
    • 总之:进程包含了正在运行的一个程序的所有状态信息。
    • 进程与程序的联系:
      • 程序是产生进程的基础。
      • 程序的每次运行构成不同的进程。
      • 进程是程序功能的体现。
      • 通过多次执行,一个程序可对应多个进程;通过调用关系,一个进程可包含多个程序。
    • 进程与程序的区别
      • 进程是动态的,程序是静态的。程序是有序代码的集合。进程是程序的执行,进程有核心态/用户态
  • 进程的特点
    • 动态性:可创建,结束;
    • 并发性:进程可以被独立调度并占用处理机运行。并发并行。多个cpu才能实现并行。;
    • 独立性:不同进程的工作不相互影响;
      • 怎么保障的呢?前面讲了内存管理,通过页表实现不同的不同的程序访问不同的地址空间,他不可能越过对应的地址空间。
    • 制约性:因访问共享数据/资源数据或进程间同步而产生制约。;
  • 进程控制块
    • 操作系统管理控制进程运行所用的信息集合,操作系统用PCB来描述进程的基本情况以及运行变化的过程,pcb是进程存在的唯一标识。
    • 使用进程控制块
      • 进程的创建:为该进程创建一个pcb
      • 进程的终止:回收pcb
      • 进程的组织管理:通过对pcb的组织管理来实现。

进程控制块PCB具体含有什么信息,如何组织,进程的状态转换?

  • 进程的标识信息
    • 如本进程的标识,本进程的产生者标识(父进程标识);用户标识
  • 处理机状态信息保存区:保存进程的运行现场信息
    • 用户可见寄存器:用户程序可以使用的数据,地址等寄存器
    • 控制和状态寄存器:如程序计数器(PC),程序状态字(PSW)
    • 栈指针:过程调用/系统调用/中断处理和返回时需要用到它。
  • 进程控制信息
    • 调度和状态信息:用于操作系统调度进程并占用处理机使用
    • 进程间通信信息:为支持进程间的与通信相关的各种标识,信号,信件等。这些信息存在接收对方的进程控制块中。
    • 存储管理信息:包含有指向本进程映像存储空间的数据结构。
    • 进程所用资源:说明由进程打开,使用的系统资源,如打开文件等。
    • 有关数据结构链接信息:进程可以连接到一个进程队列中,或连接到相关的其他进程的pcb
  • pcb的组织方式
    • 链表:同一状态的进程其pcb成一链表,多个状态对应多个不同链表。各个状态的进程形成不同的链表:就绪链表,阻塞链表。
    • 索引表:同一状态的进程归入一个index表,由index指向pcb,多个状态对应多个不同的index表。

进程状态

  • 进程的生命周期

    • 进程创建

      • 引起进程创建的三个主要事件:
        • 系统初始化;
        • 用户请求创建一个新进程;
        • 正在运行的进程执行了创建进程的系统调用;
    • 进程运行

      • 内核选择一个就绪的进程,让他占用处理机并执行。
        • 为何选择,如何选择?
    • 进程等待

      • 请求并等待系统服务,无法马上完成。
      • 启动某种操作,无法马上完成。
      • 需要的数据没有到达。
      • 进程只能自己阻塞自己,因为只有进程自身才能知道何时需要等待某种事件的发生。
    • 进程唤醒

      • 被阻塞进程需要的资源可被满足
      • 被阻塞进程等待的事件到达
      • 将该进程的pcb插入到就绪队列
      • 进程只能被别的进程或操作系统唤醒。
    • 进程结束

      img-0
  • 进程的三种基本状态

  • 运行状态
  • 就绪状态
  • 等待状态
  stateDiagram-v2
  new --> ready: 进入就绪队列
  ready-->running: 被调度
  running-->ready: 时间片完
  running-->blocked: 等待事件
  blocked-->ready: 事件发生
  running-->exit: 结束

线程管理

为什么使用线程,什么是线程,线程的实现,多线程编程接口实现。

  • 什么是线程:

    • 进程当中的一条执行流程

      • 从资源组合的角度:进程把一组相关的资源整合起来,构成了一个资源平台,包括地址空间(代码段,数据段),打开文件等各种资源。
      • 从运行角度:代码在这个资源平台上的一条执行流程。
      img-0
    • 线程的缺点:一个线程崩溃,会导致其所属的进程的所有线程奔溃。

      • 这就出现了一个问题:什么时候使用线程 什么时候使用进程?
        • 在需要安全性的时候使用进程。防止一个线程的奔溃导致进程崩溃了。
    • 线程所需要的资源:需要有独立的寄存器,堆栈。但是共享代码段,数据段。

    img-0

线程与进程的区别

  • 进程是资源分配的单位,线程是cpu调度的单位
  • 进程拥有一个完整的资源平台,而线程只独享必不可少的资源,如寄存器和栈。
  • 线程同样具有就绪,阻塞和执行三种基本状态,同样具有状态之间的转换关系。
  • 线程能减少并发执行的时间和空间开销。
    • 线程的创建时间比进程短
    • 线程的终止时间比进程短
    • 同一进程内的线程切换时间比进程短。
    • 由于同一进程的各线程共享和文件资源,可直接执行不通过内核通信。

手机屏幕背后的技术

手机屏幕的显示技术分为两种:

  • LCD:液晶显示
  • OLED:有机发光二极管,有机电激光显示

LCD:

    液晶并不能发光,需要一块由LED(发光二极管)组成的背板来提供白色光源,也叫做背光。在在每个像素的背光基础上,再加上一层红绿蓝三种颜色的薄膜,白色透过这些薄膜就形成; 有色光。如果三种光的强度一样的话,混合起来就会又变成白光或者灰光。因此必须灵活控制每种光的强度,才能混合出多种颜色来。

OLED

    OLED屏的结构比较简单,不需要背光,也没有液晶和彩色滤光膜,其内部的有机材料涂层就像有小灯泡一样,通电就会亮。

    AMOLED就是驱动,有源矩阵。通常使用TFT作为开关,来控制有机材料的电流来实现不同颜色显示/目前所有的屏幕的OLED都是AMOLED。

    SUPER AMOLED是三星对AMOLED的改进,取消了中间层触摸感应面板,将AMOLED感应层做在了屏幕里面。因此参控更加灵敏,更加浅薄。亮度也更加高,在阳光下演示也就更加好。

    Dynamic AMOLED是三星推出的AMOLED改进技术,目前主要用于高端机,改变了OLED中的有机材料,据称可以实现更加广的动态范围,在图像明暗对比上更加好。

优劣势:

跟OLED屏相比,LED的劣势:

  1. 无法显示黑色:由于液晶层无法完全闭合,总会有些光透出来, 因此LCDw无法显示纯黑的东西,而OLED则是通过控制每个像素的开关实现纯黑色显示。
  2. 容易漏光:LCD屏幕由于有背光,所以屏幕四周容易有光露出来。
  3. 屏幕厚度更大:
  4. 难以实现曲面屏
  5. 耗电量大
  6. 响应时间长:比起OLED,在快速拖动的时候LED屏幕容易产生拖影

OLED屏幕的缺点:

  1. 烧屏:由于OLED屏幕所采用的是有机材料,那么就存在材料的老化现象,如果有的像素点长时间工作负荷大,有的比较空闲,那么就会出现颜色不一致的情况。
  2. 频闪:对于LCD来说,直接控制背光的亮度就可以了。但OLED就比较麻烦,需要通过高频的开关屏幕来实现调光。想要调亮就打开屏幕的次数多一些,想要调暗,就关闭的次数多一些。

参考文档:

  1. 一文看懂:手机屏幕背后的原理技术

View测量流程深度分析

  在看View源码的时候,有一个算数的知识我们一定要掌握,就是& | ^ 这三个运算符的叠加使用。

  • a&~b: 清除标志位b
  • a|b :增加标志位b
  • a&b:取出标志位b
  • a^b:取出a与b的不同部分

setFlag(int flags,int mask)

在View的构造函数里面有两个变量:viewFlagValuesviewFlagMasks。他们通过|运算符把元素添加到里面,这些元素有:

  • fitsSystamWindow:viewFlagValues|=FITS_SYSTEM_WINDOWS
  • view_focusable:(viewFlagValues & ~FOCUSABLE_MASK)|getFoucusableAttribute(a)
  • …..

(还有很多比如可不可见,自动获取焦点,可不可点击都是通过|运算符添加到viewFlagValues里面的。具体可以看View的构造函数。)

  上面的属性全部设置完成后,在调用setFlags(viewFlagValues,viewFlagMasks)将值全部设置到mViewFlags里面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
  void setFlags(int flags, int mask) {
// mView
int old = mViewFlags;
// 看似一通计算,实则就是将flags的值赋值给了mViewFlags
mViewFlags = (mViewFlags & ~mask) | (flags & mask);
// 接着通过^运算取出他们的不同部分。。
int changed = mViewFlags ^ old;
if (changed == 0) {
// 为0代表并没有变化
return;
}
// 这个flag就是传进来的flag标志
final int newVisibility = flags & VISIBILITY_MASK;
if (newVisibility == VISIBLE) {
// 这个判断的原理是 上面进行了^运算取出了他们的不同部分,如果之前是可见的,现在还是可见的,那么^结果后的changed & VISIBILITY_MASK的结果一定是0。这样做是为了防止设置了同样的可见属性导致无用的页面刷新。
if ((changed & VISIBILITY_MASK) != 0) {
mPrivateFlags |= PFLAG_DRAWN;
invalidate(true);
needGlobalAttributesUpdate(true);
shouldNotifyFocusableAvailable = hasSize();
}
}
//从这里可以看到当视图变成不可见的时候,会去主动触发一个requestLayout
if ((changed & GONE) != 0) {
needGlobalAttributesUpdate(false);
requestLayout();

if (((mViewFlags & VISIBILITY_MASK) == GONE)) {
if (hasFocus()) {
clearFocus();
if (mParent instanceof ViewGroup) {
((ViewGroup) mParent).clearFocusedInCluster();
}
}
clearAccessibilityFocus();
destroyDrawingCache();
if (mParent instanceof View) {
// GONE views noop invalidation, so invalidate the parent
((View) mParent).invalidate(true);
}
mPrivateFlags |= PFLAG_DRAWN;
}
if (mAttachInfo != null) {
mAttachInfo.mViewVisibilityChanged = true;
}
}
// 在View的构造函数里面触发setFlags不会触发这个里面的方法
if ((changed & DRAW_MASK) != 0) {
requestLayout();
invalidate(true);
}
}

通过对setFlags()方法分析他的里面有两个地方会主动触发requestLayout:

  1. 当页面设置成了GONE
  2. 当changed标志里面加上了DRAW_MASK

View里面的参数mAttachInfo是什么时候设置上值得:

     在dispatchAttachedToWindow(AttachInfo info,int visibility)。

1
2
3
 void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
}

     在dispatchDetachedFromWindow里面将mAttacInfo设置为了null。

现在问题来了,这个dispatchAttachedToWindow()方法是什么时候被调用的。

  在ViewRootImpl里面主动触发requestLayout得时候会去一步步走到ViewRootImpl.performTraversals()方法。在performTraversals方法里面调用了host.dispatchAttachedToWindow方法这个host也就是DevorViewDecorview是一个ViewGroup,所以现在进到ViewGroup得dispatchAttachedToWindow方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// ViewGroup中的实现:
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
// 先调用自己的
super.dispatchAttachedToWindow(info, visibility);
mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;

final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
// 递归调用每个child的dispatchAttachedToWindow方法
// 典型的深度优先遍历
child.dispatchAttachedToWindow(info,
combineVisibility(visibility, child.getVisibility()));
}
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View view = mTransientViews.get(i);
view.dispatchAttachedToWindow(info,
combineVisibility(visibility, view.getVisibility()));
}
}

// View中的实现:
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
//System.out.println("Attached! " + this);
mAttachInfo = info;
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
}
mWindowAttachCount++;
// We will need to evaluate the drawable state at least once.
mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
if (mFloatingTreeObserver != null) {
info.mTreeObserver.merge(mFloatingTreeObserver);
mFloatingTreeObserver = null;
}
if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
mAttachInfo.mScrollContainers.add(this);
mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();

ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewAttachedToWindow(this);
}
}

int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(vis);
}

// Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
// As all views in the subtree will already receive dispatchAttachedToWindow
// traversing the subtree again here is not desired.
onVisibilityChanged(this, visibility);

if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
// If nobody has evaluated the drawable state yet, then do it now.
refreshDrawableState();
}
needGlobalAttributesUpdate(false);
}

  可以看到执行了一个循环,遍历了全部的子view的dispatchAttachedToWindow方法。

  所以view的dispatchAttachedToWindow是在onResume阶段,最早由ViewRootImpl触发,然后交给了ViewGroup。遍历调用了所有的子view的dispatchedToWindow。

   在ViewRootImpl的构造函数里面会创建对象mAttachInfo:View.AttachInfo,在AttachInfo的构造函数里面,有一个Handler的参数,他是在ViewRootImpl类里面就已经创建好了ViewRootHandler。这个mAttachInfo也是一层层的传递到ViewRootImpl里面的所有子view里面。那他是通过什么传递的呢,就是通过

dispatchAttachedToWindow方法传递下去的。

  既然现在分析到了这里,那我就看下子view的dispatchAttachedToWindow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;

// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();

ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewAttachedToWindow(this);
}
}
}

    在view的dispatchAttachedToWindow方法里面,可以看到有这么一行代码:mRunQueue.executeActions(info.mHandler)>。通过名字execute意思是执行的意思,他想表达的意思就是执行mRunQueue里面的全部Message事件。

    那我们就来看下这个mRunQueue:HandlerActionQueue的数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
public class HandlerActionQueue {
private HandlerAction[] mActions;//报错所有的待执行的message
private int mCount;
// 添加一个message
public void post(Runnable action) {
postDelayed(action, 0);
}

public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
// 删除一个message
public void removeCallbacks(Runnable action) {
synchronized (this) {
final int count = mCount;
int j = 0;

final HandlerAction[] actions = mActions;
for (int i = 0; i < count; i++) {
if (actions[i].matches(action)) {
continue;
}

if (j != i) {
actions[j] = actions[i];
}

j++;
}
mCount = j;
for (; j < count; j++) {
actions[j] = null;
}
}
}
// 执行一个message。通过看源码,这个executeActions是在View的dispatchAttachedToWindow方法
//里面被主动调用了。dispatchAttachedToWindow方法又是在onResume里面被ViewRootImpl触发的。
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}

mActions = null;
mCount = 0;
}
}

public int size() {
return mCount;
}

public Runnable getRunnable(int index) {
if (index >= mCount) {
throw new IndexOutOfBoundsException();
}
return mActions[index].action;
}

public long getDelay(int index) {
if (index >= mCount) {
throw new IndexOutOfBoundsException();
}
return mActions[index].delay;
}

private static class HandlerAction {
final Runnable action;
final long delay;

public HandlerAction(Runnable action, long delay) {
this.action = action;
this.delay = delay;
}

public boolean matches(Runnable otherAction) {
return otherAction == null && action == null
|| action != null && action.equals(otherAction);
}
}
}

  通过这次的代码分析可以知道,我们为什么在onCreate里面调用了view.post方法,而不是在onResume里面了。因为在onCreate里面调用view.post就会往view的mRunQueue数组里面我们的Message消息。然后在页面走到onResume的生命周期的时候,由ViewRootImpl触发了dispatchAttachedToWindow方法层层调用到子view的dispatchAttachedToWindow方法,然后触发了mRunQueue.executeAcions()方法,执行我们在onCreate里面通过view.post添加的所有Message消息

    继续看回我们的performTraversals()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//ViewRootImpl.java
private void performTraversals() {
...
// 执行ViewRootImpl里面添加的所有message。目前我在源码看到ViewRootImpl只会在
//ViewRootImpl.performLayout方法里面会添加一条message。这个message的内容就是调用子view的
//requestLayout方法:
getRunQueue().executeActions(mAttachInfo.mHandler);
performMeasure()
...
}

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
// 唯一在ViewRootImpl里面添加的message
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
view.requestLayout();
}
}
});
}
}
}

     继续看ViewRootImpl.performTraversals()可以看到perFormMeasure方法:

1
2
3
4
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

  继续看下mView.measure(),分析这个代码可以发现**:**

1. 如果页面需要强制重新布局,那么不管页面的长宽有没有改变都不会调用onMeasure方法.

2. 如果强制更新布局为false,但是needsLayout是true.然后如果长宽没有改变,那么不会执行onMeasure方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 如果是强制布局或者需要重新布局 那么就进入
if (forceLayout || needsLayout) {
// 在Activity创建完成第一次走到这里面的时候mMesureCache这个数组里面是没数据的,所以
// 这个判断条件一定是符合要求的.回进入调用onMeasure方法.
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
}

mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

从这个代码可以

invalidate

核心思想:对于下面这个View树而言,如果A视图没有阿尔法通道,即完全不透明,那么理论上讲,如果只是A视图需要重绘,则不会导致A视图所在的父视图也进行重绘,只有当A视图隐藏时,才会通知父视图B绘制之前A所占领的区域即可。然而,对于包含阿尔法通道的视图系统而言,屏幕的效果是由A和B及底层所有父视图共同产生的,因此,每次当A视图需要重新绘制的时候,其底层的父视图B及所有父视图都需要进行重绘该区域。所以当A需要重新绘制的时候,必须通知父视图也绘制该区域。

img4

invalidate的意思就是使失效:

  1. 使什么失效?使当前的状态失效。

  2. 为什么要失效?因为只有标记为失效,系统才会在下一次屏幕刷新的时候执行重绘

  3. 什么时候失效?在例如点击效果,背景颜色,高度,长度等等当前屏幕元素需要更新的时候。

  4. 那么失效做了什么?为什么等下一次刷新呢?而不是直接出发重新绘制呢?这样做啥好处?

    invalidate三部曲之始于invalidate

  invalidate的调用原理

img1

  View.invalidateInternal

img1

打上DIRTY标记
  为后面的Draw做准备。

mPrivateFlags |= PFLAG_DIRTY;

调用父组件的invalidateChild
并将自身的left,top,right,bottom通知给父组件

ViewGroup.invalidateChild

  循环执行父组件的invalidateChildInPatent,直到执行到ViewRootImpl完成后返回null。也就是尽头了。

img1

ViewGroup.invalidateChildParent

img1

ViewRootImpl.invalidateRectOnScreen

img1

加入重绘区域合集

localDirty.union(dirty.left,dirty.top,dirty.right,dirty.bottom)

本次invalidate的dirty区域同之前的dirty区域合并。

1
2
3
4
5
 final boolean intersected = localDirty.intersect(0, 0,
(int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
if (!intersected) {
localDirty.setEmpty();
}

判断要重绘的区域是不是在屏幕内,也就是判断dirty和屏幕区域是否有交叉,屏幕外的就不用绘制了。

scheduleTraversals()

开始遍历绘制。

invalidate()使用场景

  1. 直接调用invalidate方法,请求重新draw()。但只会绘制调用者本身。
  2. setSelection()方法:请求重新draw,但只会绘制调用者本身
  3. setVisibilify():当View可视状态在INVISIBLE转换到VISIBILE时候,会间接调用invalidate()继而绘制该view
  4. setEnabled():请求重新draw(),但不会重新绘制任何视图包括视图调用者本身。

 

测量Measture过程

首先在ViewRootImpl里面触发了performTraversals()方法,在performTraversals()方法里面触发了performMeasure()方法。

1
2
3
4
5
6
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

可以看到调用了mView的measure方法,在mVIew的measure方法里面调用了onMeasure方法。这个mView我们都知道是DecorView,然后DecorView的父类是Framelayout。进入DecorView的onMeasure方法,首先调用了父类的onMeasure方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// FrameLayout.java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();

final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();

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) {
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);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}

// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}

setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));

count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}

final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}

看下ViewGroup的measureChildWidthMargins方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

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

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

如果父亲是MeasureSpec.EXACTLY:

  • 如果孩子的测量值是大于等于0的,那么这个孩子的结果值就是设置的值,他的模式就是MeasureSpec.EXACTLY。

  • 如果孩子的测量值等于 MATCH_PARENT:那么这个孩子的结果值等于父宽度的最大值-padding-margin值,他的测量模式设置为MeasureSpec.EXACTLY。

  • 如果孩子的测量模式等于WRAP_CONTENT:那么这个孩子的结果值等于父宽度的最大值-padding-margin值,他的测量模式设置为MeasureSpec.AT_MOST。

如果父亲是MeasureSpecc.AT_MOST:

  • 如果孩子的测量值是确定值那么结果孩子的值就是他自己设置上去的值,测量模式设置为MeasureSpec.EXACTLY。
  • 如果孩子的测量值是MATCH_PARENT,那么结果值是父宽度-padding-margin。测量模式 = AT_MOST
  • 如果孩子的测量模式WRAP_CONTENT,那么结果值是父宽度-padding-magin。测量模式设置为AT_MOST

如果父亲设置的是UNSPECIFIED:

  • 如果孩子的测量值是确定值那么结果孩子的值就是他自己设置上去的值,测量模式设置为MeasureSpec.EXACTLY。
  • 如果孩子的测量值是MATCH_PARENT,那么结果值是父宽度-padding-margin。测量模式 = UNSPECIFIED
  • 如果孩子的测量值是WRAP_CONTENT,那么结果值是父宽度-padding-margin。测量模式 = UNSPECIFIED