Glide 源码解析 一

前言

在众多的图片加载框架中,Glide 是 Google 推荐的,并在自家的项目中大量使用的一个非常强大的框架,专注于平滑滚动,并且还提供 Gif,本地 Vedio 首帧的解码和显示。Glide 提供了非常便捷的链式调用接口,以及丰富的拓展和自定义功能,开发者可以非常简单地对框架进行配置和图片再加工。

如今 Gilde 已经更新到4.x,了解其源码对更好的使用 Glide ,以及学习相关的图片处理技术,学习更优雅的编码会有很大的帮助。

不得不说,Glide 整个框架的极其复杂的,特别是在对资源的转换和解码过程中,涉及了许多的嵌套循环,同时也使用了大量的工厂模式用于生产转换模块,编码模块,解码模块等,笔者在阅读过程中,多次迷失在茫茫的代码流中。

为此,萌生了将对 Glide 的理解记录成文的想法,借以理清思路。

那么接下来,我们就先看看 Glide 是如何进行框架初始化的。

探究

Glide.with发生了什么?

1. Glide单例的加载

使用过 Glide 的都知道,调用 Glide 加载一张图片时,第一句代码便是 Glide.with(this),这里肯定就是 Glide 的入口了,通过这句代码,Glide 开始了“漫漫的”初始化之路。

Glide 重载了多个 with 的方法,分别用于不同的情境下使用,我们看其中最常用的在 Activity 中调用的方法,即

1
2
3
4
@NonNull
public static RequestManager with(@NonNull Activity activity) {
return getRetriever(activity).get(activity);
}

首先,跟进 getRetriever(activity)

1
2
3
4
5
6
7
8
9
10
11
@NonNull
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
// Context could be null for other reasons (ie the user passes in null), but in practice it will
// only occur due to errors with the Fragment lifecycle.
Preconditions.checkNotNull(
context,
"You cannot start a load on a not yet attached View or a Fragment where getActivity() "
+ "returns null (which usually occurs when getActivity() is called before the Fragment "
+ "is attached or after the Fragment is destroyed).");
return Glide.get(context).getRequestManagerRetriever();
}

这里首先检查了 context 是否为空,如果为 null,抛出异常。

我们重点来看 Glide.get(context)

1
2
3
4
5
6
7
8
9
10
11
12
@NonNull
public static Glide get(@NonNull Context context) {
if (glide == null) {
synchronized (Glide.class) {
if (glide == null) {
checkAndInitializeGlide(context);
}
}
}

return glide;
}

这里是一个典型的双检锁单例模式。

继续跟进 checkAndInitialzeGlide(context)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static void checkAndInitializeGlide(@NonNull Context context) {
// In the thread running initGlide(), one or more classes may call Glide.get(context).
// Without this check, those calls could trigger infinite recursion.
if (isInitializing) {
throw new IllegalStateException("You cannot call Glide.get() in registerComponents(),"
+ " use the provided Glide instance instead");
}
isInitializing = true;
initializeGlide(context);
isInitializing = false;
}

private static void initializeGlide(@NonNull Context context) {
initializeGlide(context, new GlideBuilder());
}
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
@SuppressWarnings("deprecation")
private static void initializeGlide(@NonNull Context context, @NonNull GlideBuilder builder) {
Context applicationContext = context.getApplicationContext();
// 获取注解的 module
GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules();
List<com.bumptech.glide.module.GlideModule> manifestModules = Collections.emptyList();
if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) {
// AndroidMenifest.xml 中注册的 module
manifestModules = new ManifestParser(applicationContext).parse();
}

// 移除相同的GlideModule
if (annotationGeneratedModule != null
&& !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {
Set<Class<?>> excludedModuleClasses =
annotationGeneratedModule.getExcludedModuleClasses();
Iterator<com.bumptech.glide.module.GlideModule> iterator = manifestModules.iterator();
while (iterator.hasNext()) {
com.bumptech.glide.module.GlideModule current = iterator.next();
if (!excludedModuleClasses.contains(current.getClass())) {
continue;
}
iterator.remove();
}
}

if (Log.isLoggable(TAG, Log.DEBUG)) {
for (com.bumptech.glide.module.GlideModule glideModule : manifestModules) {
Log.d(TAG, "Discovered GlideModule from manifest: " + glideModule.getClass());
}
}

RequestManagerRetriever.RequestManagerFactory factory =
annotationGeneratedModule != null
? annotationGeneratedModule.getRequestManagerFactory() : null;
builder.setRequestManagerFactory(factory);
for (com.bumptech.glide.module.GlideModule module : manifestModules) {
module.applyOptions(applicationContext, builder);
}
if (annotationGeneratedModule != null) {
annotationGeneratedModule.applyOptions(applicationContext, builder);
}
Glide glide = builder.build(applicationContext);
for (com.bumptech.glide.module.GlideModule module : manifestModules) {
module.registerComponents(applicationContext, glide, glide.registry);
}
if (annotationGeneratedModule != null) {
annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry);
}
applicationContext.registerComponentCallbacks(glide);
Glide.glide = glide;
}

留意最后将初始化得到的glide赋值给了Glide.glide的单例。

接下里就来看看在这初始化方法中,Glide都加载了哪些配置。

2. GlideModule配置加载

在使用 Glide 的时候,我们都会有一些想要设置的系统级配置,如设置缓存的存储位置,缓存区的大小,网络加载模块等等,那么我们通常就是使用 GldieModule 进行配置。在 Glide3.x 中,我们首先会定义一个继承于 GlideModule 的类,然后在项目的 AndroidMenifest.xml 中进行指定:

1
2
<meta-data android:name="com.test.GlideConfiguration"
android:value="GlideModule"/>

而在 Glide4 中,提供另外一个配置的模式,那就是注解,并且不再继承 GlideModule ,而是继承 AppGlideModule 和 LibraryGlideModule,分别对应 Application 和 Library,使用 @GlideModule 注解进行标记。而 Glide3.x 中的配置方式已经建议放弃使用。示例如下:

1
2
3
4
5
6
7
8
@GlideModule
public class GlideConfiguration extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
//设置缓存到外部存储器
builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context));
}
}

Glide 是如何对 GlideModule 的配置进行初始化的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Context applicationContext = context.getApplicationContext();
GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules();

List<com.bumptech.glide.module.GlideModule> manifestModules = Collections.emptyList();
if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) {
manifestModules = new ManifestParser(applicationContext).parse();
}

if (annotationGeneratedModule != null
&& !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {
Set<Class<?>> excludedModuleClasses =
annotationGeneratedModule.getExcludedModuleClasses();
Iterator<com.bumptech.glide.module.GlideModule> iterator = manifestModules.iterator();
while (iterator.hasNext()) {
com.bumptech.glide.module.GlideModule current = iterator.next();
if (!excludedModuleClasses.contains(current.getClass())) {
continue;
}
iterator.remove();
}
}

第二行代码中,getAnnotationGeneratedGlideModules() 会获取 Glide 注解自动生产的一个 Glide 的 Module 配置器。如下:

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
@Nullable
@SuppressWarnings({"unchecked", "deprecation", "TryWithIdenticalCatches"})
private static GeneratedAppGlideModule getAnnotationGeneratedGlideModules() {
GeneratedAppGlideModule result = null;
try {
Class<GeneratedAppGlideModule> clazz =
(Class<GeneratedAppGlideModule>)
Class.forName("com.bumptech.glide.GeneratedAppGlideModuleImpl");
result = clazz.getDeclaredConstructor().newInstance();
} catch (ClassNotFoundException e) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Failed to find GeneratedAppGlideModule. You should include an"
+ " annotationProcessor compile dependency on com.github.bumptech.glide:compiler"
+ " in your application and a @GlideModule annotated AppGlideModule implementation or"
+ " LibraryGlideModules will be silently ignored");
}
// These exceptions can't be squashed across all versions of Android.
} catch (InstantiationException e) {
throwIncorrectGlideModule(e);
} catch (IllegalAccessException e) {
throwIncorrectGlideModule(e);
} catch (NoSuchMethodException e) {
throwIncorrectGlideModule(e);
} catch (InvocationTargetException e) {
throwIncorrectGlideModule(e);
}
return result;
}

其中 com.bumptech.glide.GeneratedAppGlideModuleImpl 是在编译时由 Glide 生成的一个类,主要用于过滤不必要的 GlideModule ,以及提供一个请求检索器工厂,这个后面会讲到。

接下生成一个 Manifest 解析器 ManifestParser ,用于获取配置的 GlideModule ,并存放在 manifestModules 中。然后是一个判断

1
2
3
4
if (annotationGeneratedModule != null
&& !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {
......
}

如果条件成立,即编译时自动生成的类中,包含了需要排除的 GlideModule ,逐个将其移除。

接着以上代码,Glide 将逐个调用剩下的 GlideModule ,并回调 applyOptionsregisterComponents 接口,这时,用户配置的 GlideModule 就会被调用,同时用户设置的参数也就被配置到 Glide 中。

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
RequestManagerRetriever.RequestManagerFactory factory =
annotationGeneratedModule != null
? annotationGeneratedModule.getRequestManagerFactory() : null;
builder.setRequestManagerFactory(factory);

// 逐个回调用户配置的 GlideModule
for (com.bumptech.glide.module.GlideModule module : manifestModules) {
module.applyOptions(applicationContext, builder);
}

// 回调注释标记的 AppGlideModule
if (annotationGeneratedModule != null) {
annotationGeneratedModule.applyOptions(applicationContext, builder);
}

// 构造 Glide 实例
Glide glide = builder.build(applicationContext);
for (com.bumptech.glide.module.GlideModule module : manifestModules) {
module.registerComponents(applicationContext, glide, glide.registry);
}
if (annotationGeneratedModule != null) {
annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry);
}
applicationContext.registerComponentCallbacks(glide);
Glide.glide = glide;

在以上代码中,发现一句代码,在回调 registerComponents 前,首先构建了 glide 的实例。

1
Glide glide = builder.build(applicationContext);

3. GlideBuilder构建Glide单例

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
@NonNull
Glide build(@NonNull Context context) {

/*
* 建立资源加载请求器,实际上是一个线程池,
* 用于加载 URL 或者本地资源,即加载源数据
*/
if (sourceExecutor == null) {
sourceExecutor = GlideExecutor.newSourceExecutor();
}

/*
* 建立本地缓存资源加载请求器,也是一个线程池
* 用于加载缓存在磁盘中的数据
* 并且这个线程池不能用于加载 url 网络数据
*/
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();
}

/*
* 建立 bitmap 资源缓存池,
* 该缓存主要用于 bitmap 资源的缓存和回收,
* 避免由于大量创建和回收 bitmap 导致内存抖动
*/
if (bitmapPool == null) {
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());
}

/*
* 本地磁盘缓存器,默认为存储在 app 内部私密目录
*/
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}


/*
* 创建图片加载引擎,用于执行图片加载请求驱动
*/
if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
GlideExecutor.newAnimationExecutor(),
isActiveResourceRetentionAllowed);
}

/*
* 建立请求索引器
*/
RequestManagerRetriever requestManagerRetriever =
new RequestManagerRetriever(requestManagerFactory);

/*
* 创建 Glide
*/
return new Glide(
context,
engine,
memoryCache,
bitmapPool,
arrayPool,
requestManagerRetriever,
connectivityMonitorFactory,
logLevel,
defaultRequestOptions.lock(),
defaultTransitionOptions);
}

通过以上一系列工具的新建,Glide 建立了资源请求线程池,本地缓存加载线程池,动画线程池,内存缓存器,磁盘缓存工具等等,接着构造了 Engine 数据加载引擎,最后再将 Engine 注入 Glide ,构建 Glide 。

其中还建立了一个请求器索引器,用于索引 RequestManger ,后面我们再详细讲。

我们进入最后, 构建 Glide 。

4. 构建Glide,配置数据转换器/解码器/转码器/编码器

回到 Glide 中,看看 Glide 的构造函数,这是一个长得变态的构造函数(有200行),但是不必被它吓倒(好吧,其实第一次看到这里,我是被吓倒了,直接略过去了,限于文章篇幅,这里只截取了部分源码,仔细的话可以直接看源码),仔细分析一下,其实整个构造过程并没那么复杂。

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
Glide(
@NonNull Context context,
@NonNull Engine engine,
@NonNull MemoryCache memoryCache,
@NonNull BitmapPool bitmapPool,
@NonNull ArrayPool arrayPool,
@NonNull RequestManagerRetriever requestManagerRetriever,
@NonNull ConnectivityMonitorFactory connectivityMonitorFactory,
int logLevel,
@NonNull RequestOptions defaultRequestOptions,
@NonNull Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions) {

// 步骤1:赋值 GlideBuilder 注入的工具
this.engine = engine;
this.bitmapPool = bitmapPool;
this.arrayPool = arrayPool;
this.memoryCache = memoryCache;
this.requestManagerRetriever = requestManagerRetriever;
this.connectivityMonitorFactory = connectivityMonitorFactory;

DecodeFormat decodeFormat = defaultRequestOptions.getOptions().get(Downsampler.DECODE_FORMAT);
bitmapPreFiller = new BitmapPreFiller(memoryCache, bitmapPool, decodeFormat);

final Resources resources = context.getResources();

// 步骤2:新建注册器
registry = new Registry();
registry.register(new DefaultImageHeaderParser());

// 步骤3:构建编码器和解码器
Downsampler downsampler = new Downsampler(registry.getImageHeaderParsers(),
resources.getDisplayMetrics(), bitmapPool, arrayPool);
ByteBufferGifDecoder byteBufferGifDecoder =
new ByteBufferGifDecoder(context, registry.getImageHeaderParsers(), bitmapPool, arrayPool);
ResourceDecoder<ParcelFileDescriptor, Bitmap> parcelFileDescriptorVideoDecoder =
VideoDecoder.parcel(bitmapPool);
ByteBufferBitmapDecoder byteBufferBitmapDecoder = new ByteBufferBitmapDecoder(downsampler);
StreamBitmapDecoder streamBitmapDecoder = new StreamBitmapDecoder(downsampler, arrayPool);
ResourceDrawableDecoder resourceDrawableDecoder =
new ResourceDrawableDecoder(context);
ResourceLoader.StreamFactory resourceLoaderStreamFactory =
new ResourceLoader.StreamFactory(resources);
ResourceLoader.UriFactory resourceLoaderUriFactory =
new ResourceLoader.UriFactory(resources);
ResourceLoader.FileDescriptorFactory resourceLoaderFileDescriptorFactory =
new ResourceLoader.FileDescriptorFactory(resources);
ResourceLoader.AssetFileDescriptorFactory resourceLoaderAssetFileDescriptorFactory =
new ResourceLoader.AssetFileDescriptorFactory(resources);
BitmapEncoder bitmapEncoder = new BitmapEncoder(arrayPool);

BitmapBytesTranscoder bitmapBytesTranscoder = new BitmapBytesTranscoder();
GifDrawableBytesTranscoder gifDrawableBytesTranscoder = new GifDrawableBytesTranscoder();

ContentResolver contentResolver = context.getContentResolver();

// 步骤4:开始注册各个类型对应的解码器和编码器 省略部分代码
registry
.append(ByteBuffer.class, new ByteBufferEncoder())
.append(InputStream.class, new StreamEncoder(arrayPool))
/* Bitmaps */
.append(Registry.BUCKET_BITMAP, ByteBuffer.class, Bitmap.class, byteBufferBitmapDecoder)
.append(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class, streamBitmapDecoder)
/* BitmapDrawables */
.append(
Registry.BUCKET_BITMAP_DRAWABLE,
ByteBuffer.class,
BitmapDrawable.class,
new BitmapDrawableDecoder<>(resources, byteBufferBitmapDecoder))
/* GIFs */
.append(
Registry.BUCKET_GIF,
InputStream.class,
GifDrawable.class,
new StreamGifDecoder(registry.getImageHeaderParsers(), byteBufferGifDecoder, arrayPool))
.append(Registry.BUCKET_GIF, ByteBuffer.class, GifDrawable.class, byteBufferGifDecoder)
.append(GifDrawable.class, new GifDrawableEncoder())
/* GIF Frames */
// Compilation with Gradle requires the type to be specified for UnitModelLoader here.
.append(
GifDecoder.class, GifDecoder.class, UnitModelLoader.Factory.<GifDecoder>getInstance())
.append(
Registry.BUCKET_BITMAP,
GifDecoder.class,
Bitmap.class,
new GifFrameResourceDecoder(bitmapPool))
/* Drawables */
.append(Uri.class, Drawable.class, resourceDrawableDecoder)
.append(
Uri.class, Bitmap.class, new ResourceBitmapDecoder(resourceDrawableDecoder, bitmapPool))
/* Files */
.register(new ByteBufferRewinder.Factory())
.append(File.class, ByteBuffer.class, new ByteBufferFileLoader.Factory())

// Compilation with Gradle requires the type to be specified for UnitModelLoader here.
.append(File.class, File.class, UnitModelLoader.Factory.<File>getInstance())
/* Models */
.register(new InputStreamRewinder.Factory(arrayPool))
.append(int.class, InputStream.class, resourceLoaderStreamFactory)

/* Transcoders */
.register(
Bitmap.class,
BitmapDrawable.class,
new BitmapDrawableTranscoder(resources))
.register(Bitmap.class, byte[].class, bitmapBytesTranscoder);


// 步骤5:新建图片显示目标对象工厂,并构建 Glide 上下文
ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory();
glideContext =
new GlideContext(
context,
arrayPool,
registry,
imageViewTargetFactory,
defaultRequestOptions,
defaultTransitionOptions,
engine,
logLevel);
}

其中最重要的是步骤3和步骤4,分别为 Glide 初始化了模型转换加载器,解码器,转码器,编码器,并将对各种类型进行一一注册,将其列成表格如下:

  • 模型转换器
转换器 功能
ResourceLoader.StreamFactory 将Android资源ID转换为Uri,在加载成为InputStream
ResourceLoader.UriFactory 将资源ID转换为Uri
ResourceLoader.FileDescriptorFactory 将资源ID转化为ParcelFileDescriptor
ResourceLoader.AssetFileDescriptorFactory 将资源ID转化为AssetFileDescriptor
UnitModelLoader.Factory 不做任何转换,返回源数据
ByteBufferFileLoader.Factory 将File转换为ByteBuffer
FileLoader.StreamFactory 将File转换为InputStream
FileLoader.FileDescriptorFactory 将File转化为ParcelFileDescriptor
DataUrlLoader.StreamFactory 将Url转化为InputStream
  • 解码器
解码器 功能
ByteBufferGifDecoder 将ByteBuffer解码为GifDrawable
ByteBufferBitmapDecoder 将ByteBuffer解码为Bitmap
ResourceDrawableDecoder 将资源Uri解码为Drawable
ResourceBitmapDecoder 将资源ID解码为Bitmap
BitmapDrawableDecoder 将数据解码为BitmapDrawable
StreamBitmapDecoder 将InputStreams解码为Bitmap
StreamGifDecoder 将InputStream数据转换为BtyeBuffer,再解码为GifDrawable
GifFrameResourceDecoder 解码gif帧
FileDecoder 包装File成为FileResource
  • 转码器
转码器 功能
BitmapDrawableTranscoder 将Bitmap转码为BitmapDrawable
BitmapBytesTranscoder 将Bitmap转码为Byte arrays
DrawableBytesTranscoder 将BitmapDrawable转码为Byte arrays
GifDrawableBytesTranscoder 将GifDrawable转码为Byte arrays
  • 编码器
编码器 功能
ByteBufferEncoder 将Byte数据缓存为File
StreamEncoder InputStream缓存为File
BitmapEncoder 将Bitmap数据缓存为File
BitmapDrawableEncoder 将BitmapDrawable数据缓存为File
GifDrawableEncoder 将GifDrawable数据缓存为File
  • 模型转换注册表(实在太多,只列出了部分)
源数据 转换数据 转换器
Integer.class InputStream.class ResourceLoader.StreamFactory
String.class InputStream.class DataUrlLoader.StreamFactory
Uri.class InputStream.class DataUrlLoader.StreamFactory

以上模型转换注册表非常重要,在Glide进入解码流程时,将会遍历这里注册的所有可能转换的情形,尝试进行数据转换。

这里只列出部分情形,其它还包括 File/Bitmap/Drawable/Byte 等等几乎涵括了日常使用的情况。

Glide的加载流程可以概括为以下流程:

model(数据源)-->data(转换数据)-->decode(解码)-->transformed(缩放)-->transcoded(转码)-->encoded(编码保存到本地)

其中,transformed 为对解码得到的图片数据进行缩放,如 FitCenter、CropCenter 等。

到这里,Glide 单例就构建完成了,让我们返回到 Glide#with 中

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

@NonNull
public static RequestManager with(@NonNull FragmentActivity activity) {
return getRetriever(activity).get(activity);
}

@NonNull
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
// Context could be null for other reasons (ie the user passes in null), but in practice it will
// only occur due to errors with the Fragment lifecycle.
Preconditions.checkNotNull(
context,
"You cannot start a load on a not yet attached View or a Fragment where getActivity() "
+ "returns null (which usually occurs when getActivity() is called before the Fragment "
+ "is attached or after the Fragment is destroyed).");
return Glide.get(context).getRequestManagerRetriever();
}

在构建好 Glide 后,通过 getRequestManagerRetriever() 将会得到一个 RequestManagerRetriever ,即 RequestManager 的检索器,RequestManagerRetriever#get() 将为每个请求页面创建一个 RequestManager。

还记得 GlideBuilder#build 提到的一句代码吗?

1
2
RequestManagerRetriever requestManagerRetriever =
new RequestManagerRetriever(requestManagerFactory);

没错,这里获取的就是它。这里就必须要讲到 Glide 数据请求的生命周期了。

我们都知道 Glide 会根据页面的生命周期来自动的开启和结束数据的请求,那么 Glide 是怎么做到的呢?

5. 生命周期管理

我们进入 RequestManagerRetriever#get(Activity) 方法中。

1
2
3
4
5
6
7
8
9
10
11
12
@SuppressWarnings("deprecation")
@NonNull
public RequestManager get(@NonNull Activity activity) {
if (Util.isOnBackgroundThread()) {
return get(activity.getApplicationContext());
} else {
assertNotDestroyed(activity);
android.app.FragmentManager fm = activity.getFragmentManager();
return fragmentGet(
activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
}
}

首先,判断是否为后台线程,如果是,则使用 ApplicationContext 重新获取。
重点来看 else 代码块。先断言请求的页面是否已经销毁。否则获取当前页面的 FragmentManager,并传给 fragmentGet 方法。

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
@SuppressWarnings({"deprecation", "DeprecatedIsStillUsed"})
@Deprecated
@NonNull
private RequestManager fragmentGet(@NonNull Context context,
@NonNull android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}

@NonNull
SupportRequestManagerFragment getSupportRequestManagerFragment(FragmentActivity activity) {
return getSupportRequestManagerFragment(
activity.getSupportFragmentManager(), /*parentHint=*/ null, isActivityVisible(activity));
}

在 fragmentGet 中首先通过 getRequestManagerFragment() 来获取一个命名为 FRAGMENT_TAG 的 fragment,如不存在,则新建一个 RequestManagerFragment,并添加到当前页面中。

这里我们就可以猜到了,Glide 是通过在页面中添加一个 Fragment 来动态监听页面的创建和销毁,从而达到依赖页面生命周期,动态管理请求的目的。

在 RequestManagerFragment 构造函数中,注入了一个生命周期监听器 ActivityFragmentLifecycle,并在 Fragment 各个生命周期回调中,调用了对应的方法。

而 ActivityFragmentLifecycle 也紧接着会调用 lifecycleListener 监听器,而这个监听器其实就是 RequestManger。如下:

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
class ActivityFragmentLifecycle implements Lifecycle {
private final Set<LifecycleListener> lifecycleListeners =
Collections.newSetFromMap(new WeakHashMap<LifecycleListener, Boolean>());
private boolean isStarted;
private boolean isDestroyed;

@Override
public void addListener(@NonNull LifecycleListener listener) {
lifecycleListeners.add(listener);

if (isDestroyed) {
listener.onDestroy();
} else if (isStarted) {
listener.onStart();
} else {
listener.onStop();
}
}

@Override
public void removeListener(@NonNull LifecycleListener listener) {
lifecycleListeners.remove(listener);
}

void onStart() {
isStarted = true;
for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
lifecycleListener.onStart();
}
}

void onStop() {
isStarted = false;
for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
lifecycleListener.onStop();
}
}

void onDestroy() {
isDestroyed = true;
for (LifecycleListener lifecycleListener : Util.getSnapshot(lifecycleListeners)) {
lifecycleListener.onDestroy();
}
}
}

最后,RequestManagerRetriever#fragmentGet,判断这个Fragment的RequestManager是否存在,否则创建一个RequestManager,并将生命周期注入,同时RquestManager构建时,将会通过addListener注入生命周期回调(具体可以查看RequestManger构造函数)。

最后,Glide#with终将得到一个RequestManager。

至此,Glide的加载过程就解析完毕了。总结一下整个流程:

  • 通过AndroidManifest和@GlideModule注解获取用户自定义配置GlideModule,并调用其对应的方法
  • 通过GlideBuilder构建Glide:
    1. 新建线程池
    2. 新建图片缓存池和缓存池
    3. 新建内存缓存管理器
    4. 新建默认本地缓存管理器
    5. 新建请求引擎Engine
    6. 新建RequestManger检索器
    7. 新建Glide
  • Glide构造方法中,新建模型转换器,解码器,转码器,编码器,以及生成Glide上下文GlideContext
  • 通过RequestManager检索器,建立生命周期监听,并建立一个RequestManager

Glide与GlideApp

如果在项目中已经使用了 Glide3.x ,并且想要升级到 Glide4.x ,那么你会发现,原来使用链式调用进行参数配置的方法已经被修改了,同一个封装到了 RequesOptions 中,如下:

1
2
3
4
5
6
7
8
9
10
RequestOptions options = new RequestOptions()
.centerCrop()
.placeholder(R.mipmap.ic_launcher_round)
.error(R.mipmap.ic_launcher)
.priority(Priority.HIGH)
.diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this)
.load(ImageConfig.URL_GIF)
.apply(options)
.into(iv);

这样的话升级后将导致大量的修改,当然你也可以自己封装一下,但是 Glide 已经为我们做好了兼容方案。

还记得初始化是通过 @GlideModule 注解来注册自定义配置吗?只要在项目中定义这么一个配置,那么 Glide 将会自动帮我们生成一个 GlideApp 模块,封装了 Glide3.x 中的调用方式。

1
2
3
4
5
6
public class GlideConfiguration extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {

}
}

调用如下,还是原来的配方,还是熟悉的味道~

1
2
3
4
5
6
7
GlideApp.with(this)
.load(ImageConfig.URL_WEBP)
.sizeMultiplier(0.5f)
.centerCrop()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.error(R.mipmap.ic_launcher)
.into(iv);

如果你还觉得不爽,那么你甚至可以把GlideApp直接修改为Glide,实现几乎“无缝对接”。当然,你还是要修改引用路径的。

1
2
3
4
5
6
7
@GlideModule(glideName="Glide")
public class GlideConfiguration extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {

}
}

来源:

收藏

AIDL

Book.java

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
package com.honszeal.processdemo;

import android.os.Parcel;
import android.os.Parcelable;

/**
* date:2018/3/13 on 16:07
* desc:
* author: liuch
*/
public class Book implements Parcelable{

private int bookId;
private String bookName;

protected Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}

public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}

@Override
public Book[] newArray(int size) {
return new Book[size];
}
};

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
}

IDE 自动完成

Book.aidl

1
2
3
package com.honszeal.processdemo;

parcelable Book;

ILibraryManager.aidl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.honszeal.processdemo;

import com.honszeal.processdemo.Book;


interface ILibraryManager {

List<Book> getNewBookList();

void donateBook(in Book book);

int add(int a, int b);

void log(String message);

}

编译一下

切换到 project 选项卡,在 app -> build -> generated -> source -> aidl -> debug -> 包名 下 找到系统生成的ILibraryManager.java

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
package com.honszeal.processdemo;

public interface ILibraryManager extends android.os.IInterface {

public static abstract class Stub extends android.os.Binder implements com.honszeal.processdemo.ILibraryManager {

private static final java.lang.String DESCRIPTOR = "com.honszeal.processdemo.ILibraryManager";

public Stub() {
this.attachInterface(this, DESCRIPTOR);
}

public static com.honszeal.processdemo.ILibraryManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.honszeal.processdemo.ILibraryManager))) {
return ((com.honszeal.processdemo.ILibraryManager) iin);
}
return new com.honszeal.processdemo.ILibraryManager.Stub.Proxy(obj);
}

@Override
public android.os.IBinder asBinder() {
return this;
}

@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getNewBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.honszeal.processdemo.Book> _result = this.getNewBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_donateBook: {
data.enforceInterface(DESCRIPTOR);
com.honszeal.processdemo.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.honszeal.processdemo.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.donateBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

private static class Proxy implements com.honszeal.processdemo.ILibraryManager {
private android.os.IBinder mRemote;

Proxy(android.os.IBinder remote) {
mRemote = remote;
}

@Override
public android.os.IBinder asBinder() {
return mRemote;
}

public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}

@Override
public java.util.List<com.honszeal.processdemo.Book> getNewBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.honszeal.processdemo.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getNewBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.honszeal.processdemo.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}

@Override
public void donateBook(com.honszeal.processdemo.Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_donateBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}

static final int TRANSACTION_getNewBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_donateBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}

public java.util.List<com.honszeal.processdemo.Book> getNewBookList() throws android.os.RemoteException;

public void donateBook(com.honszeal.processdemo.Book book) throws android.os.RemoteException;

}

BookService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BookService extends Service {

private Binder binder = new ILibraryManager.Stub() {
@Override
public List<Book> getNewBookList() throws RemoteException {
return null;
}

@Override
public void donateBook(Book book) throws RemoteException {

}
};

@Override
public void onCreate() {
super.onCreate();
}

@Override
public IBinder onBind(Intent intent) {
return binder;
}
}

手写

IBookManager.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.honszeal.processdemo;

import android.os.IInterface;
import android.os.RemoteException;

import java.util.List;

public interface IBookManager extends IInterface {

String DESCRIPTOR = "com.honszeal.processdemo.ILibraryManager";
int TRANSACTION_getNewBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
int TRANSACTION_donateBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

List<Book> getNewBookList() throws RemoteException;

void donateBook(Book book) throws RemoteException;
}

BookManagerImpl

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
package com.honszeal.processdemo;

import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.RemoteException;

import java.util.List;

public class BookManagerImpl extends Binder implements IBookManager {

public BookManagerImpl() {
this.attachInterface(this, DESCRIPTOR);
}

public static IBookManager asInterface(IBinder obj) {
if (obj == null) {
return null;
}

IInterface iin = obj.queryLocalInterface(DESCRIPTOR);

if (iin != null && iin instanceof IBookManager) {
return ((IBookManager) iin);
}
return new BookManagerImpl.Proxy(obj);
}

@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case INTERFACE_TRANSACTION:
reply.writeString(DESCRIPTOR);
return true;
case TRANSACTION_getNewBookList:
data.enforceInterface(DESCRIPTOR);
List<Book> bookList = this.getNewBookList();
reply.writeNoException();
reply.writeTypedList(bookList);
return true;
case TRANSACTION_donateBook:
data.enforceInterface(DESCRIPTOR);
Book arg0;
if (0 != data.readInt()) {
arg0 = Book.CREATOR.createFromParcel(data);
} else {
arg0 = null;
}
this.donateBook(arg0);
reply.writeNoException();
return true;
}
return super.onTransact(code, data, reply, flags);
}

@Override
public List<Book> getNewBookList() throws RemoteException {
return null;
}

@Override
public void donateBook(Book book) throws RemoteException {

}

@Override
public IBinder asBinder() {
return this;
}

private static class Proxy implements IBookManager {

private IBinder mRemote;

public Proxy(IBinder mRemote) {
this.mRemote = mRemote;
}

public String getInterfaceDescriptor() {
return DESCRIPTOR;
}

@Override
public List<Book> getNewBookList() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();

List<Book> result;

data.writeInterfaceToken(getInterfaceDescriptor());
mRemote.transact(TRANSACTION_getNewBookList, data, reply, 0);
reply.readException();
result = reply.createTypedArrayList(Book.CREATOR);
return result;
}

@Override
public void donateBook(Book book) throws RemoteException {
//
}

@Override
public IBinder asBinder() {
return mRemote;
}
}
}

感觉,就是具体的实现在服务端写,在客户端调用。实际上就是调用了 Proxy 的对应方法,然后 Bindertransact 方法 再就是 onTransact 方法。

权限验证

  1. onBind 中进行验证,验证不通过就直接返回 null, 这样验证失败的客户端直接无法绑定服务。
  2. onTransact 中进行验证,验证不通过就直接返回 false

Android 性能优化

布局优化

  • 删除布局中无用的控件和层级,其次有选择的使用性能较低的 ViewGroup,比如 RelativeLayout, ConstraintLayout 。同样层级的话更优先选择使用 LinearLayout,因为RelativeLayout 的布局过程需要花费更多的 CPU 时间。如果需要嵌套的方式来做,更建议采用 RelativeLayout ,因为 ViewGroup 的嵌套就相当于增加了布局的层级,同样会降低程序的性能。
  • 采用 <include> 标签,如果多个布局中需要一个相同的 layout ,可以使用<include> 来避免写重复的布局文件。
  • 采用 <merge> 标签。一般和 <include> 标签一起使用而减少布局的层级。
  • ViewStub。它非常轻量级,而且高/宽都是 0,因此它本身不参与任何的布局和绘制过程。ViewStub 的意义在于按需加载所需的布局文件,在实际开发中,有很多布局文件在正常情况下不会显示,这个时候就没有必要在整个界面初始化的时候将其加载进来,通过 ViewStub 就可以做到在使用的时候再加载。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <ViewStub
    android:id="@+id/stub_import"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center"
    android:inflatedId="@+id/id_login"
    android:layout="@layout/refresh_layout" />

    其中 stub_import 是 ViewStub 的 id,id_login 是 refresh_layout 这个布局根元素的 id, 需要加载 ViewStub 的布局时,有两种方式:

    ((ViewStub)findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);

    或者

    View layout = ((ViewStub)findViewById(R.id.stub_import)).inflate();

绘制优化

绘制优化是指 ViewonDraw 方法要避免执行大量的操作。

onDraw 里不要创建新的局部对象 不要做耗时的操作。

内存泄露优化

内存泄露优化分为两个方面,一方面是在开发过程中避免写出有内存泄漏的代码,另一方面是通过一些分析工具找出潜在的内存泄漏继而解决。

场景1 静态变量导致的内存泄漏

下面的代码将导致 Activity 无法正常销毁,因为静态变量 sContext 引用了它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MainActivity extends Activity{

private static final String TAG = "MainActivity";

private static Context sContext;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

sContext = this;
}
}

解决方案:

  1. onDestroy 中释放 sContext = null
  2. 变成虚引用 private static WeakReference<Context> context

场景2 单例模式导致的内存泄漏

如下所示,提供一个单例模式的 TestManagerTestManager可以接受外部的注册并将外部的监听器存储起来。

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
public class TestManager{
private List<OnDataArrivedListener> mOnDataArrivedListeners = new ArrayList<>();

private static class SingletonHolder {
public static final TestManager INSTANCE = new TestManager();
}

private TestManager(){

}

public static TestManager getInstance(){
return SingletonHolder.INSTANCE;
}

public synchronized void registerListener(OnDataArrivedListener listener){
if(!mOnDataArrivedListeners.contains(listener)){
mOnDataArrivedListeners.add(listener);
}
}

public synchronized void unregisterListener(OnDataArrivedListener listener){
mOnDataArrivedListeners.remove(listener);
}

public interface OnDataArrivedListener{
void onDataArrived(Object data);
}
}

Activity 实现 OnDataArrivedListener 并向 TestManager 注册监听,如下所示。下面的代码由于缺少解注册的操作而引起内存泄漏,泄露的原因是 Activity 的对象被单例模式的 TestManager 所持有,而单例模式的特点是其生命周期和 Application 保持一致,因此 Activity 无法被及时释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MainActivity extends Activity{

private static final String TAG = "MainActivity";

private static Context sContext;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

TestManager.getInstance().registerListener(this);
}
}

解决方案:

onDestroyTestManager.getInstance().unregisterListener(this);

场景3 属性动画导致的内存泄漏

属性动画中有一类无限循环的动画,如果在 Activity 中播放此类动画并且没有在 onDestroy 中去停止动画,那么动画会一直播放下去,尽管已经无法在页面上看到动画效果了,并且这个时候 ActivityView 会被动画持有,而 View 又持有了 Activity,最终 Activity 无法释放。

解决方法

是在 onDestroy 中停止动画。

场景4 非静态内部类导致的内存泄漏

比如 Handler

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 class MainActivity extends AppCompatActivity {

private MyHandler handler;

private static class MyHandler extends Handler {

private WeakReference<MainActivity> reference;

public MyHandler(MainActivity activity) {
reference = new WeakReference<>(activity);
}

@Override
public void handleMessage(Message msg) {
//TODO: do something
reference.get().doSomeThing();
}
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

handler = new MyHandler(this);
}

@Override
protected void onDestroy() {
handler.removeMessages(1);
super.onDestroy();
}
}

响应速度优化和 ANR 日志分析

响应速度优化的核心思想是避免在主线程中做耗时操作。响应速度过慢更多的体现在 Activity 的启动速度上,如果在主线程中做太多事情,会导致 Activity 启动时出现黑屏现象,甚至出现 ANR(Application not response)Android 规定,Activity 如果 5s 内无法响应屏幕触摸事件或者键盘输入事件就会出现 ANRBroadcastReceiver 如果 10s 之内还未执行完操作也会出现 ANRService 如果 20s 之内还未执行完操作也会出现 ANR

当一个进程发生了 ANR 以后,系统会在data/anr 目录下创建一个 traces.txt,通过分析这个文件就能定位出 ANR 的原因。

ListView 优化 和 Bitmap 优化

ListView 优化方式

  • 布局复用
  • 使用 ViewHolder,减小 findViewById 的使用,避免在 getView 中执行耗时操作
  • 在列表快速滑动时不适合开启大量的异步任务的。
  • 数据可以分页加载。
  • 可以尝试开启硬件加速来使 ListView 的滑动更加流畅。
  • 尽量让 ItemView 的 Layout 层次结构简单。
  • ItemView 元素避免半透明。
  • 尽量能保证 Adapter 的 hasStableIds() 返回 true,这样在 notifyDataSetChanged() 的时候,如果 id 不变,ListView 将不会重新绘制这个 View,达到优化的目的。
  • 每个 ItemView 不能太高,特别是不要超过屏幕的高度。

Bitmap 优化方式

  • 加载合适尺寸的图片(二次采样,inSampleSize)。

线程优化

线程优化的思想是使用线程池,避免程序中存在大量的 Thread。线程池可以重用内部的线程,从而避免了线程的创建和销毁所带来的性能开销,同时线程池还可以有效的控制线程池的最大并发数,避免大量的线程因互相抢占系统资源从而导致堵塞现象的发生。

一些性能优化建议

  • 避免创建过多的对象。
  • 不要过多使用枚举,枚举占用的内存空间要比整形大。(不过枚举要是能影响性能的话,就太差了)
  • 常量请使用 static final 来修饰。
  • 使用 Android 特有的数据结构,比如 SparseArrayPair 等。
  • 适当使用软引用和弱引用。
  • 尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄漏。

Android View事件分发

前段时间看了《Android开发艺术探索》,关于 Android 事件分发,写了一些笔记。
事件分发机制的伪代码。

1
2
3
4
5
6
7
8
9
10
public boolean dispatchTouchEvent(MotionEvent event){
boolean consume = false;
if(onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev);
} else{
consume = child.dispatchTouchEvent(ev);
}

return consume;
}

对于一个根 ViewGroup 来说,点击事件产生后,首先会传递给它,它的 dispatchTouchEvent 方法会被调用,如果这个 ViewGrouponInterceptTouchEvent 方法返回 true 则表示它要拦截此事件,事件就会交给它自己处理,即调用它的 onTouchEvent 方法;如果它的 onInterceptTouchEvent 方法返回 false 表示不会拦截这个事件,当前事件就会传递给它的子元素,子元素的 dispatchTouchEvent 方法会被调用,如此循环知道事件被处理。

当一个 View 需要处理事件时,如果它已经设置了 OnTouchListener,那么 OnTouchListener 中的 onTouch 方法会被回调,这是事件如何处理还要看 onTouch 的返回值,如果返回 true 则当前 ViewonTouchEvent 方法会被调用;如果返回 false ,那么 onTouchEvent 方法不会被调用。由此可见方法的优先级 onTouch > onTouchEvent > onClick

点击事件的传递过程遵循以下顺序:Activity -> Window -> View,即事件总是先传递给 ActivityActivity 再传递给 Window,最后再传递给顶级 View,顶级 View 接收到事件后,就会按照事件分发机制分发事件。

如果一个 ViewOnTouchEvent 方法返回 false,那么他的父容器的 OnTouchEvent 会被调用,以此类推。如果所有的元素都不处理这个事件,那么这个事件最终会传递给 Activity 处理。

注意:

  1. 同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中产生的一系列事件,这个事件序列以 down 事件开始,中间含有数量不定的 move 事件,最终以 up事件结束。
  2. 正常情况下,一个事件序列只能被一个 View 拦截并消耗。因为一旦一个元素拦截了某事件,那么同一个序列内的所有事件都会直接交给他处理,因此同一个序列中的事件不能分别有两个 View 同时处理。但是通过特殊手段可以做到,比如一个 View 将本该有它处理的事件通过 OnTouchEvent 强行传递给其他 View 处理。
  3. 某个 View 一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的 onInterceptTouchEvent 方法不会再被调用。
  4. 某个 View 一旦开始处理事件,如果它不消耗 ACTION_DOWN 事件,那么同一个事件序列里的其他事件都不会再交给他处理,并且事件将重新交由它的父元素去处理,即父元素的 onTouchEvent 方法会被调用。
  5. 如果 View 不消除掉 ACTION_DOWN 之外的其他事件,那么这个点击事件就会消失,此时父元素的 onTouchEvent 并不会被调用,并且当前 View 可以持续收到后续的事件,最终这些消失的点击事件会传递给 Activity 处理。
  6. ViewGroup 方法默认不拦截任何事件。Android 源码中 ViewGrouponInterceptTouchEvent 方法默认返回 false
  7. View 没有 onInterceptTouchEvent 方法,只有有子元素的元素才有。所以只要有点击事件传递给 View,就会调用它的 onTouchEvent 方法。
  8. ViewonTouchEvent 默认都会消耗事件,除非它是不可点击的。ViewlongClickable 属性默认都为 false
  9. Viewenable 属性不影响 onTouchEvent 的默认返回值。哪怕一个 Viewdisable 状态的,只要它的 clickablelongClickable 有一个为 true,那么它的 onTouchEvent 就返回 true
  10. onClick 发生的前提是当前 View 是可点击的。并且它收到了 downup 的事件。
  11. 事件传递过程是由外到内的,即事件总是先传递给父元素,然后再由父元素下发到子元素。

CrashHandler

源码

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
public class CrashHandler implements Thread.UncaughtExceptionHandler {

private static final String TAG = "CrashHandler";
private static final boolean DEBUG = true;
private static final String PATH = Environment.getExternalStorageDirectory().getPath() + "/CrashTest/log/";
private static final String FILE_NAME = "crash";
private static final String FILE_NAME_SUFFIX = ".trace";

private static CrashHandler sInstance = new CrashHandler();
private Thread.UncaughtExceptionHandler mDefaultCrashHandler;
private Context mContext;

private CrashHandler() {
}

public static CrashHandler getInstance() {
return sInstance;
}

public void init(Context context) {
mContext = context.getApplicationContext();
mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}

@Override
public void uncaughtException(Thread t, Throwable e) {
try {
dumpExceptionToSDCard(e);
uploadExceptionToServer();
} catch (IOException e1) {
e1.printStackTrace();
}

if (mDefaultCrashHandler != null) {
mDefaultCrashHandler.uncaughtException(t, e);
} else {
Process.killProcess(Process.myPid());
}

}

private void dumpExceptionToSDCard(Throwable t) throws IOException {
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
if (DEBUG) {
Log.w(TAG, "sdcard unmounted, skip dump exception");
return;
}
}

File dir = new File(PATH);
if (!dir.exists()) {
dir.mkdirs();
}

long current = System.currentTimeMillis();
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(current));
File file = new File(PATH + FILE_NAME + time + FILE_NAME_SUFFIX);

try {
PrintWriter printWriter = new PrintWriter(new BufferedWriter(new FileWriter(file)));
printWriter.println(time);
dumpPhoneInfo(printWriter);
printWriter.println();
t.printStackTrace(printWriter);
printWriter.close();
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "dump crash info failed ");
e.printStackTrace();
}
}

private void dumpPhoneInfo(PrintWriter pw) throws PackageManager.NameNotFoundException {
PackageManager manager = mContext.getPackageManager();
PackageInfo pi = manager.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
pw.print("App Version: ");
pw.print(pi.versionName);
pw.print("_");
pw.println(pi.versionCode);

//Android版本号
pw.print("OS Version: ");
pw.print(Build.VERSION.RELEASE);
pw.print("_");
pw.println(Build.VERSION.SDK_INT);

//手机制造商
pw.print("Vendor: ");
pw.println(Build.MANUFACTURER);

//手机型号
pw.print("Model: ");
pw.println(Build.MODEL);

//CPU架构
pw.print("CPU ABI: ");
pw.println(Build.CPU_ABI);
}

private void uploadExceptionToServer() {
//TODO: Upload Exception Message To Web Server
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class BaseApplication extends Application {

private static BaseApplication sInstance;

@Override
public void onCreate() {
super.onCreate();
sInstance = this;
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(this);
}

public static BaseApplication getInstance() {
return sInstance;
}

}

RxJava详解

RxJava

RxJava 到底是什么

一个词:异步。

RxJava 在 GitHub 主页上的自我介绍是 “a library for composing asynchronous and event-based programs using observable sequences for the Java VM”(一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库)。这就是 RxJava ,概括得非常精准。

然而,对于初学者来说,这太难看懂了。因为它是一个『总结』,而初学者更需要一个『引言』。

其实, RxJava 的本质可以压缩为异步这一个词。说到根上,它就是一个实现异步操作的库,而别的定语都是基于这之上的。

RxJava 好在哪

换句话说,『同样是做异步,为什么人们用它,而不用现成的 AsyncTask / Handler / XXX / … ?』

一个词:简洁。

异步操作很关键的一点是程序的简洁性,因为在调度过程比较复杂的情况下,异步代码经常会既难写也难被读懂。 Android 创造的 AsyncTask 和Handler ,其实都是为了让异步代码更加简洁。RxJava 的优势也是简洁,但它的简洁的与众不同之处在于,随着程序逻辑变得越来越复杂,它依然能够保持简洁。

API 介绍和原理简析

1.概念:扩展的观察者模式

RxJava 的异步实现,是通过一种扩展的观察者模式来实现的。

观察者模式

观察者模式面向的需求是:A 对象(观察者)对 B 对象(被观察者)的某种变化高度敏感,需要在 B 变化的一瞬间做出反应。举个例子,新闻里喜闻乐见的警察抓小偷,警察需要在小偷伸手作案的时候实施抓捕。在这个例子里,警察是观察者,小偷是被观察者,警察需要时刻盯着小偷的一举一动,才能保证不会漏过任何瞬间。程序的观察者模式和这种真正的『观察』略有不同,观察者不需要时刻盯着被观察者( 例如 A 不需要每过 2ms 就检查一次 B 的状态),而是采用注册 ( Register ) 或者称为订阅 ( Subscribe )的方式,告诉被观察者:我需要你的某某状态,你要在它变化的时候通知我。 Android 开发中一个比较典型的例子是点击监听器 OnClickListener 。对设置 OnClickListener 来说, View 是被观察者, OnClickListener 是观察者,二者通过 setOnClickListener() 方法达成订阅关系。订阅之后用户点击按钮的瞬间,Android Framework 就会将点击事件发送给已经注册的 OnClickListener 。采取这样被动的观察方式,既省去了反复检索状态的资源消耗,也能够得到最高的反馈速度。当然,这也得益于我们可以随意定制自己程序中的观察者和被观察者,而警察叔叔明显无法要求小偷『你在作案的时候务必通知我』。

OnClickListener 的模式大致如下图:
OnClickListener模式

如图所示,通过 setOnClickListener() 方法,Button 持有 OnClickListener 的引用;当用户点击时,Button 调用 OnClickListeneronClick() 方法。另外,如果把这张图中的概念抽象出来(Button -> 被观察者、OnClickListener -> 观察者、setOnClickListener() -> 订阅,onClick() -> 事件),就由专用的观察者模式(例如只用于监听控件点击)转变成了通用的观察者模式。如下图:
通用的观察者模式

RxJava 的观察者模式

RxJava 有四个基本概念:Onservable(被观察者)、Observer(观察者)、subscribe(订阅)、Event(事件)。ObservableObserver 通过 subscribe() 方法实现订阅关系,从而 Observable 可以在需要的时候发出 Event 来通知 Observer

与传统观察者模式不同, RxJava 的事件回调方法除了普通事件 onNext() (相当于 onClick() / onEvent())之外,还定义了两个特殊的事件:onCompleted()onError()

  • onCompleted(): 事件队列完结。RxJava 不仅把每个事件单独处理,还会把它们看做一个队列。RxJava 规定,当不会再有新的 onNext() 发出时,需要触发 onCompleted() 方法作为标志。
  • onError(): 事件队列异常。在事件处理过程中出异常时,onError() 会被触发,同时队列自动终止,不允许再有事件发出。
  • 在一个正确运行的事件序列中, onCompleted()onError() 有且只有一个,并且是事件序列中的最后一个。需要注意的是,onCompleted()onError() 二者也是互斥的,即在队列中调用了其中一个,就不应该再调用另一个。

RxJava 的观察者模式大致如下图:
RxJava 的观察者模式

2. 基本实现

基于以上的概念, RxJava 的基本实现主要有三点:

1) 创建 Observer

Observer 即观察者,它决定事件触发的时候将有怎样的行为。 RxJava 中的 Observer 接口的实现方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Observer<String> observer = new Observer<String>() {
@Override
public void onNext(String s) {
Log.d(tag, "Item: " + s);
}

@Override
public void onCompleted() {
Log.d(tag, "Completed!");
}

@Override
public void onError(Throwable e) {
Log.d(tag, "Error!");
}
};

除了 Observer 接口之外,RxJava 还内置了一个实现了 Observer 的抽象类:Subscriber。 Subscriber 对 Observer 接口进行了一些扩展,但他们的基本使用方式是完全一样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Subscriber<String> subscriber = new Subscriber<String>() {
@Override
public void onSubscribe(Subscription s) {
Log.d(tag, "onSubscribe");
}

@Override
public void onNext(String s) {
Log.d(tag, "Item: " + s);
}

@Override
public void onError(Throwable e) {
Log.d(tag, "Error!");
}

@Override
public void onComplete() {
Log.d(tag, "Completed!");
}
};

不仅基本使用方式一样,实质上,在 RxJava 的 subscribe 过程中,Observer 也总是会先被转换成一个 Subscriber 再使用。所以如果你只想使用基本功能,选择 Observer 和 Subscriber 是完全一样的。它们的区别对于使用者来说主要有:

  • onSubscribe(): 这是 Subscriber 增加的方法。它会在 subscribe 刚开始,而事件还未发送之前被调用,可以用于做一些准备工作,例如数据的清零或重置。这是一个可选方法,默认情况下它的实现为空。需要注意的是,如果对准备工作的线程有要求(例如弹出一个显示进度的对话框,这必须在主线程执行), onSubscribe() 就不适用了,因为它总是在 subscribe 所发生的线程被调用,而不能指定线程。要在指定的线程来做准备工作,可以使用 doOnSubscribe() 方法,具体可以在后面的文中看到。

2) 创建 Observable

Observable 即被观察者,它决定什么时候触发事件以及触发怎样的事件。 RxJava 使用 create() 方法来创建一个 Observable ,并为它定义事件触发规则:

1
2
3
4
5
6
7
8
9
Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Hello");
subscriber.onNext("Hi");
subscriber.onNext("Aloha");
subscriber.onCompleted();
}
});

可以看到,这里传入了一个 OnSubscribe 对象作为参数。OnSubscribe 会被存储在返回的 Observable 对象中,它的作用相当于一个计划表,当 Observable 被订阅的时候,OnSubscribecall() 方法会自动被调用,事件序列就会依照设定依次触发(对于上面的代码,就是观察者 Subscriber 将会被调用三次 onNext() 和一次 onCompleted())。这样,由被观察者调用了观察者的回调方法,就实现了由被观察者向观察者的事件传递,即观察者模式。

这个例子很简单:事件的内容是字符串,而不是一些复杂的对象;事件的内容是已经定好了的,而不像有的观察者模式一样是待确定的(例如网络请求的结果在请求返回之前是未知的);所有事件在一瞬间被全部发送出去,而不是夹杂一些确定或不确定的时间间隔或者经过某种触发器来触发的。总之,这个例子看起来毫无实用价值。但这是为了便于说明,实质上只要你想,各种各样的事件发送规则你都可以自己来写。至于具体怎么做,后面都会讲到,但现在不行。只有把基础原理先说明白了,上层的运用才能更容易说清楚。

create() 方法是 RxJava 最基本的创造事件序列的方法。基于这个方法, RxJava 还提供了一些方法用来快捷创建事件队列,例如:

  • just(T...): 将传入的参数依次发送出来。
1
2
3
4
5
6
Observable observable = Observable.just("Hello", "Hi", "Aloha");
// 将会依次调用:
// onNext("Hello");
// onNext("Hi");
// onNext("Aloha");
// onCompleted();
  • from(T[]) / from(Iterable<? extends T>) : 将传入的数组或 Iterable 拆分成具体对象后,依次发送出来。
1
2
3
4
5
6
7
String[] words = {"Hello", "Hi", "Aloha"};
Observable observable = Observable.from(words);
// 将会依次调用:
// onNext("Hello");
// onNext("Hi");
// onNext("Aloha");
// onCompleted();

3) Subscribe (订阅)

创建了 ObservableObserver 之后,再用 subscribe() 方法将它们联结起来,整条链子就可以工作了。代码形式很简单:

1
2
3
observable.subscribe(observer);
// 或者:
observable.subscribe(subscriber);

有人可能会注意到, subscribe() 这个方法有点怪:它看起来是『observalbe 订阅了 observer / subscriber』而不是『observer / subscriber 订阅了 observalbe』,这看起来就像『杂志订阅了读者』一样颠倒了对象关系。这让人读起来有点别扭,不过如果把 API 设计成 observer.subscribe(observable) / subscriber.subscribe(observable) ,虽然更加符合思维逻辑,但对流式 API 的设计就造成影响了,比较起来明显是得不偿失的。

Observable.subscribe(Subscriber) 的内部实现是这样的(仅核心代码):

1
2
3
4
5
6
7
// 注意:这不是 subscribe() 的源码,而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。
// 如果需要看源码,可以去 RxJava 的 GitHub 仓库下载。
public Subscription subscribe(Subscriber subscriber) {
subscriber.onStart();
onSubscribe.call(subscriber);
return subscriber;
}

可以看到,subscriber() 做了3件事:

  1. 调用 Subscriber.onStart() 。这个方法在前面已经介绍过,是一个可选的准备方法。

  2. 调用 Observable 中的 OnSubscribe.call(Subscriber) 。在这里,事件发送的逻辑开始运行。从这也可以看出,在 RxJava 中, Observable 并不是在创建的时候就立即开始发送事件,而是在它被订阅的时候,即当 subscribe() 方法执行的时候。

  3. 将传入的 Subscriber 作为 Subscription 返回。这是为了方便 unsubscribe().

整个过程中对象间的关系如下图:
image

或者可以看动图:

image

4) 场景示例

a. 打印字符串数组

将字符串数组 names 中的所有字符串依次打印出来:

1
2
3
4
5
6
7
8
String[] names = ...;
Observable.from(names)
.subscribe(new Action1<String>() {
@Override
public void call(String name) {
Log.d(tag, name);
}
});

b. 由 id 取得图片并显示

由指定的一个 drawable 文件 id drawableRes 取得图片,并显示在 ImageView 中,并在出现异常的时候打印 Toast 报错:

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
int drawableRes = ...;
ImageView imageView = ...;
Observable.create(new OnSubscribe<Drawable>() {
@Override
public void call(Subscriber<? super Drawable> subscriber) {
Drawable drawable = getTheme().getDrawable(drawableRes));
subscriber.onNext(drawable);
subscriber.onCompleted();
}
}).subscribe(new Observer<Drawable>() {
@Override
public void onNext(Drawable drawable) {
imageView.setImageDrawable(drawable);
}

@Override
public void onCompleted() {

}

@Override
public void onError(Throwable e) {
Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
}
});

注意:在 RxJava 的默认规则中,事件的发出和消费都是在同一个线程的。也就是说,如果只用上面的方法,实现出来的只是一个同步的观察者模式。观察者模式本身的目的就是『后台处理,前台回调』的异步机制,因此异步对于 RxJava 是至关重要的。而要实现异步,则需要用到 RxJava 的另一个概念:Scheduler

3. 线程控制 —— Scheduler (一)

在不指定线程的情况下, RxJava 遵循的是线程不变的原则,即:在哪个线程调用 subscribe(),就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件。如果需要切换线程,就需要用到 Scheduler (调度器)。

Scheduler 的 API

RxJava 中,Scheduler ——调度器,相当于线程控制器,RxJava 通过它来指定每一段代码应该运行在什么样的线程。RxJava 已经内置了几个 Scheduler ,它们已经适合大多数的使用场景:

  • Schedulers.immediate(): 直接在当前线程运行,相当于不指定线程。这是默认的 Scheduler
  • Schedulers.newThread(): 总是启用新线程,并在新线程执行操作。
  • Schedulers.io(): I/O 操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。行为模式和 newThread() 差不多,区别在于 io() 的内部实现是是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下 io()newThread() 更有效率。不要把计算工作放在 io() 中,可以避免创建不必要的线程。
  • Schedulers.computation(): 计算所使用的 Scheduler。这个计算指的是 CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。
  • 另外, Android 还有一个专用的 AndroidSchedulers.mainThread(),它指定的操作将在 Android 主线程运行。

有了这几个 Scheduler ,就可以使用 subscribeOn()observeOn() 两个方法来对线程进行控制了。

  • subscribeOn(): 指定 subscribe() 所发生的线程,即 Observable.OnSubscribe 被激活时所处的线程。或者叫做事件产生的线程。
  • observeOn(): 指定 Subscriber 所运行在的线程。或者叫做事件消费的线程。
1
2
3
4
5
6
7
8
9
10
Observable.just(1, 2, 3, 4)
.subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer number) {
Log.d(TAG, "number: " + number);
Log.d(TAG, "Thread: " + Thread.currentThread());
}
});

4. 变换

RxJava 提供了对事件序列进行变换的支持,这是它的核心功能之一,也是大多数人说『RxJava 真是太好用了』的最大原因。所谓变换,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列。概念说着总是模糊难懂的,来看 API。

1) API

首先看一个 map() 的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
Observable.just("images/logo.png") // 输入类型 String
.map(new Func1<String, Bitmap>() {
@Override
public Bitmap call(String filePath) { // 参数类型 String
return getBitmapFromPath(filePath); // 返回类型 Bitmap
}
})
.subscribe(new Action1<Bitmap>() {
@Override
public void call(Bitmap bitmap) { // 参数类型 Bitmap
showBitmap(bitmap);
}
});
  • map(): 事件对象的直接变换,具体功能上面已经介绍过。它是 RxJava 最常用的变换。 map() 的示意图:

image

  • flatMap(): 这是一个很有用但非常难理解的变换,因此我决定花多些篇幅来介绍它。 首先假设这么一种需求:假设有一个数据结构『学生』,现在需要打印出一组学生的名字。实现方式很简单:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Student[] students = ...;
Subscriber<String> subscriber = new Subscriber<String>() {
@Override
public void onNext(String name) {
Log.d(tag, name);
}
...
};
Observable.from(students)
.map(new Func1<Student, String>() {
@Override
public String call(Student student) {
return student.getName();
}
})
.subscribe(subscriber);

很简单。那么再假设:如果要打印出每个学生所需要修的所有课程的名称呢?(需求的区别在于,每个学生只有一个名字,但却有多个课程。)首先可以这样实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Student[] students = ...;
Subscriber<Student> subscriber = new Subscriber<Student>() {
@Override
public void onNext(Student student) {
List<Course> courses = student.getCourses();
for (int i = 0; i < courses.size(); i++) {
Course course = courses.get(i);
Log.d(tag, course.getName());
}
}
...
};
Observable.from(students)
.subscribe(subscriber);

依然很简单。那么如果我不想在 Subscriber 中使用 for 循环,而是希望 Subscriber 中直接传入单个的 Course 对象呢(这对于代码复用很重要)?用 map() 显然是不行的,因为 map() 是一对一的转化,而我现在的要求是一对多的转化。那怎么才能把一个 Student 转化成多个 Course 呢?

这个时候,就需要用 flatMap() 了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Student[] students = ...;
Subscriber<Course> subscriber = new Subscriber<Course>() {
@Override
public void onNext(Course course) {
Log.d(tag, course.getName());
}
...
};
Observable.from(students)
.flatMap(new Func1<Student, Observable<Course>>() {
@Override
public Observable<Course> call(Student student) {
return Observable.from(student.getCourses());
}
})
.subscribe(subscriber);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//RxJava2
Flowable.fromIterable(students)
.flatMap(new Function<Student, Publisher<?>>() {
@Override
public Publisher<?> apply(Student student) throws Exception {
return Flowable.fromIterable(student.mSources);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Object>() {
@Override
public void accept(Object o) throws Exception {
Log.d(TAG, "accept: " + ((Cource) o).name);
}
});

从上面的代码可以看出, flatMap()map() 有一个相同点:它也是把传入的参数转化之后返回另一个对象。但需要注意,和 map() 不同的是, flatMap() 中返回的是个 Observable 对象,并且这个 Observable 对象并不是被直接发送到了 Subscriber 的回调方法中。 flatMap() 的原理是这样的:

  1. 使用传入的事件对象创建一个 Observable 对象;
  2. 并不发送这个 Observable, 而是将它激活,于是它开始发送事件;
  3. 每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable ,而这个 Observable 负责将这些事件统一交给 Subscriber 的回调方法。这三个步骤,把事件拆成了两级,通过一组新创建的 Observable 将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是 flatMap() 所谓的 flat。

flatMap() 示意图:
image

5. 线程控制:Scheduler (二)

1) Scheduler 的 API (二)

前面讲到了,可以利用 subscribeOn() 结合 observeOn() 来实现线程控制,让事件的产生和消费发生在不同的线程。可是在了解了 map() flatMap() 等变换方法后,有些好事的(其实就是当初刚接触 RxJava 时的我)就问了:能不能多切换几次线程?

答案是:能。因为 observeOn() 指定的是 Subscriber 的线程,而这个 Subscriber 并不是(严格说应该为『不一定是』,但这里不妨理解为『不是』)subscribe() 参数中的 Subscriber ,而是 observeOn() 执行时的当前 Observable 所对应的 Subscriber ,即它的直接下级 Subscriber 。换句话说,observeOn() 指定的是它之后的操作所在的线程。因此如果有多次切换线程的需求,只要在每个想要切换线程的位置调用一次 observeOn() 即可。上代码:

1
2
3
4
5
6
7
8
Observable.just(1, 2, 3, 4) // IO 线程,由 subscribeOn() 指定
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.newThread())
.map(mapOperator) // 新线程,由 observeOn() 指定
.observeOn(Schedulers.io())
.map(mapOperator2) // IO 线程,由 observeOn() 指定
.observeOn(AndroidSchedulers.mainThread)
.subscribe(subscriber); // Android 主线程,由 observeOn() 指定

如上,通过 observeOn() 的多次调用,程序实现了线程的多次切换。

不过,不同于 observeOn()subscribeOn() 的位置放在哪里都可以,但它是只能调用一次的。

2) Scheduler 的原理(二)

其实, subscribeOn()observeOn() 的内部实现,也是用的 lift()。具体看图(不同颜色的箭头表示不同的线程):

subscribeOn() 原理图:
image

observeOn() 原理图:
image

从图中可以看出,subscribeOn()observeOn() 都做了线程切换的工作(图中的 “schedule…” 部位)。不同的是, subscribeOn() 的线程切换发生在 OnSubscribe 中,即在它通知上一级 OnSubscribe 时,这时事件还没有开始发送,因此 subscribeOn() 的线程控制可以从事件发出的开端就造成影响;而 observeOn() 的线程切换则发生在它内建的 Subscriber 中,即发生在它即将给下一级 Subscriber 发送事件时,因此 observeOn() 控制的是它后面的线程。

最后,我用一张图来解释当多个 subscribeOn()observeOn() 混合使用时,线程调度是怎么发生的(由于图中对象较多,相对于上面的图对结构做了一些简化调整):
image

图中共有 5 处含有对事件的操作。由图中可以看出,①和②两处受第一个 subscribeOn() 影响,运行在红色线程;③和④处受第一个 observeOn() 的影响,运行在绿色线程;⑤处受第二个 onserveOn() 影响,运行在紫色线程;而第二个 subscribeOn() ,由于在通知过程中线程就被第一个 subscribeOn() 截断,因此对整个流程并没有任何影响。这里也就回答了前面的问题:当使用了多个 subscribeOn() 的时候,只有第一个 subscribeOn() 起作用。

链接

Github 源码地址:

引入依赖:

  • implementation "io.reactivex.rxjava2:rxjava:2.1.7"
  • implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'

参考资料

Recyclerview加载更多

布局文件

1
2
3
4
5
6
7
8
9
10
11
12
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/view_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

</android.support.v4.widget.SwipeRefreshLayout>

普通 item_with_image.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">

<ImageView
android:id="@+id/img_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
tools:src="@mipmap/ic_launcher"/>

<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:textColor="@color/text_black"
android:textSize="15sp"
tools:text="seyo+"/>

</LinearLayout>

底部 item_foot.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="40dp"
android:gravity="center"
android:orientation="horizontal">

<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginRight="6dp"/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/loading"/>

</LinearLayout>

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
public class TestAdapter extends RecyclerView.Adapter {
private Context mContext;
private List<DynamicModel> mVideoList;
private int TYPE_IMAGE = 0;
private int TYPE_FOOTER = 2;

public TestAdapter(Context context, List<DynamicModel> videoList) {
mContext = context;
mVideoList = videoList;
}

@Override
public int getItemViewType(int position) {
if (position + 1 == getItemCount()) {
return TYPE_FOOTER;
}
return TYPE_IMAGE;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

RecyclerView.ViewHolder holder = null;

if (viewType == TYPE_IMAGE) {
View itemView = LayoutInflater.from(mContext).inflate(R.layout.item_with_image, parent, false);
holder = new ImageHolder(itemView);
} else if (viewType == TYPE_FOOTER) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_foot, parent,
false);
return new FootViewHolder(view);
}

return holder;
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ImageHolder) {
((ImageHolder) holder).bindView(mVideoList.get(position));
}
}


@Override
public int getItemCount() {
return mVideoList.size() == 0 ? 0 : mVideoList.size() + 1;
}

private class ImageHolder extends RecyclerView.ViewHolder {

private final ImageView imgAvatar;
private final TextView tvName;

public ImageHolder(View itemView) {
super(itemView);
imgAvatar = ((ImageView) itemView.findViewById(R.id.img_avatar));
tvName = ((TextView) itemView.findViewById(R.id.tv_name));
}

private void bindView(DynamicModel item) {

if (item.getUsers().size() > 0) {
Glide.with(mContext).load(AppConfig.GetImageUrl(item.getUsers().get(0).getAvatarImage())).transform(new GlideCircleTransform(mContext)).into(imgAvatar);
tvName.setText(item.getUsers().get(0).getUserName());
} else {
Glide.with(mContext).load(R.drawable.default_seyo).transform(new GlideCircleTransform(mContext)).into(imgAvatar);
tvName.setText("seyo+");
}
}
}


private class FootViewHolder extends RecyclerView.ViewHolder {

public FootViewHolder(View itemView) {
super(itemView);
}
}
}

Fragment

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
public void initView(
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(layoutManager);
adapter = new VideoAdapter(getActivity(), mList);
mRecyclerView.setAdapter(adapter);
mRefreshLayout = ((SwipeRefreshLayout) view.findViewById(R.id.view_refresh));
mRefreshLayout.setOnRefreshListener(this);
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState){
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItemPosition + 1 == adapter.getItemCount()) {
boolean isRefreshing = mRefreshLayout.isRefreshing();
if (isRefreshing) {
adapter.notifyItemRemoved(adapter.getItemCount());
return;
}
if (!isLoading) {
isLoading = true;
loadData();
}
}
}

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
lastVisibleItemPosition = layoutManager.findLastCompletelyVisibleItemPosition();
}
});
)

@Override
public void onRefresh() {
mList.clear();
loadData();
}

private void loadData() {

NetService service = new NetService();
service.getDynamicList(0, AppConfig.TYPE_RECOMMEND, AppConfig.PAGESIZE,curIndex, new Observer<DetailList<DynamicModel>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
MethodUtils.LoadingDialog(getContext(), "正在加载");
}

@Override
public void onNext(@NonNull DetailList<DynamicModel> list) {
mList.addAll(list.getData());
adapter.notifyDataSetChanged();
}

@Override
public void onError(@NonNull Throwable e) {
MethodUtils.loadingDialogDismiss();
ToastUtils.showMessage(getContext(), "网络异常,请求失败");
mRefreshLayout.setRefreshing(false);
isLoading = false;
if (adapter.getItemCount() > 0) {
adapter.notifyItemRemoved(adapter.getItemCount());
}
}

@Override
public void onComplete() {
mRefreshLayout.setRefreshing(false);
isLoading = false;
MethodUtils.loadingDialogDismiss();
if (adapter.getItemCount() > 0) {
adapter.notifyItemRemoved(adapter.getItemCount());
}
}
});
}

挺简单的,主要就是多布局,当滑动到最后一个 item 时,让他绑定 item_foot.xml 布局。然后在加载导数据之后 adapter.notifyItemRemoved(adapter.getItemCount()); 去掉最后 foot。

Handler源码解析

因为 Android 只允许在主线程中更新 UI ,所以每个 Android 开发者都会使用到 Handler ,最近面试也一直在问这个问题。先说一下我自己的理解吧

每个 Activity 都会自动初始化一个 Looper 对象,这个 Looper 对象通过 loop() 方法,不断的遍历 MessageQueue,来查看消息队列里是否存在 Message,在代码里 在其他线程中可以通过 handler.sendMessage(message) 来把 Message 传到 MessageQueue 里,可以在 handler 重写的 handlerMessage(Message msg) 里获取到发送的 Message 来执行所要完成的动作。

从 Handler 的无参构造方法开始看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Handler() {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = null;
}

可以看到在第十行调用了 Looper.myLooper()方法来获取一个 Looper 对象,如果这个对象为空则抛出一个异常。看一下 myLooper() 方法

1
2
3
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}

这个方法非常简单,就是从sThreadLocal对象中取出Looper。如果sThreadLocal中有Looper存在就返回Looper,如果没有Looper存在自然就返回空了。因此你可以想象得到是在哪里给sThreadLocal设置Looper了吧,当然是Looper.prepare()方法!我们来看下它的源码:

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

可以看到,首先判断sThreadLocal中是否已经存在Looper了,如果还没有则创建一个新的Looper设置进去。这样也就完全解释了为什么我们要先调用Looper.prepare()方法,才能创建Handler对象。同时也可以看出每个线程中最多只会有一个Looper对象。

所以在非 UI 线程中创建 Handler 对象之前要先调用 Looper.prepare() 方法,否则会出现异常。而且在程序启动时,系统已经自动在主线程中调用了Looper.prepare() 方法。所以在主线程中我们可以直接新建 Handler 。

看完了如何创建Handler之后,接下来我们看一下如何发送消息,这个流程相信大家也已经非常熟悉了,new出一个Message对象,然后可以使用setData()方法或arg参数等方式为消息携带一些数据,再借助Handler将消息发送出去就可以了,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.arg1 = 1;
Bundle bundle = new Bundle();
bundle.putString("data", "data");
message.setData(bundle);
handler.sendMessage(message);
}
}).start();

可是这里Handler到底是把Message发送到哪里去了呢?为什么之后又可以在Handler的handleMessage()方法中重新得到这条Message呢?看来又需要通过阅读源码才能解除我们心中的疑惑了,Handler中提供了很多个发送消息的方法,其中除了sendMessageAtFrontOfQueue()方法之外,其它的发送消息方法最终都会辗转调用到sendMessageAtTime()方法中,这个方法的源码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
else {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}

sendMessageAtTime() 方法接收两个参数,其中 msg 参数就是我们发送的 Message 对象,而 uptimeMillis 参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到 MessageQueue 的 enqueueMessage() 方法中。这个 MessageQueue 又是什么东西呢?其实从名字上就可以看出了,它是一个消息队列,用于将所有收到的消息以队列的形式进行排列,并提供入队和出队的方法。这个类是在 Looper 的构造函数中创建的,因此一个 Looper 也就对应了一个 MessageQueue。

那么enqueueMessage()方法毫无疑问就是入队的方法了:

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
final boolean enqueueMessage(Message msg, long when) {
if (msg.when != 0) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
if (msg.target == null && !mQuitAllowed) {
throw new RuntimeException("Main thread not allowed to quit");
}
synchronized (this) {
if (mQuiting) {
RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
} else if (msg.target == null) {
mQuiting = true;
}
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
this.notify();
} else {
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
msg.next = prev.next;
prev.next = msg;
this.notify();
}
}
return true;
}

首先你要知道,MessageQueue 并没有使用一个集合把所有的消息都保存起来,它只使用了一个 mMessages 对象表示当前待处理的消息。然后观察上面的代码的 16~31 行我们就可以看出,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间当然就是我们刚才介绍的 uptimeMilli 参数。具体的操作方法就根据时间的顺序调用 msg.next,从而为每一个消息指定它的下一个消息是什么。当然如果你是通过sendMessageAtFrontOfQueue()方法来发送消息的,它也会调用enqueueMessage()来让消息入队,只不过时间为0,这时会把 mMessages 赋值为新入队的这条消息,然后将这条消息的 next 指定为刚才的 mMessages,这样也就完成了添加消息到队列头部的操作。
现在入队操作我们就已经看明白了,那出队操作是在哪里进行的呢?这个就需要看一看Looper.loop()方法的源码了,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static final void loop() {
Looper me = myLooper();
MessageQueue queue = me.mQueue;
while (true) {
Message msg = queue.next(); // might block
if (msg != null) {
if (msg.target == null) {
return;
}
if (me.mLogging!= null) me.mLogging.println(
">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what
);
msg.target.dispatchMessage(msg);
if (me.mLogging!= null) me.mLogging.println(
"<<<<< Finished to " + msg.target + " "
+ msg.callback);
msg.recycle();
}
}
}

可以看到,这个方法从第4行开始,进入了一个死循环,然后不断地调用的MessageQueue的next()方法,我想你已经猜到了,这个next()方法就是消息队列的出队方法。不过由于这个方法的代码稍微有点长,我就不贴出来了,它的简单逻辑就是如果当前MessageQueue中存在mMessages(即待处理消息),就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态,一直等到有新的消息入队。继续看loop()方法的第14行,每当有一个消息出队,就将它传递到msg.target的dispatchMessage()方法中,那这里msg.target又是什么呢?其实就是Handler啦,你观察一下上面sendMessageAtTime()方法的第6行就可以看出来了。接下来当然就要看一看Handler中dispatchMessage()方法的源码了,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

在第5行进行判断,如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。这样我相信大家就都明白了为什么handleMessage()方法中可以获取到之前发送的消息了吧!

另外除了发送消息之外,我们还有以下几种方法可以在子线程中进行UI操作:

  1. Handler的post()方法

  2. View的post()方法

  3. Activity的runOnUiThread()方法

Handler中的post()方法,代码如下所示:

1
2
3
4
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}

原来这里还是调用了sendMessageDelayed()方法去发送一条消息啊,并且还使用了getPostMessage()方法将 Runnable 对象转换成了一条消息,我们来看下这个方法的源码:

1
2
3
4
5
private final Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}

在这个方法中将消息的 callback 字段的值指定为传入的 Runnable 对象。咦?这个 callback 字段看起来有些眼熟啊,喔!在 Handler 的dispatchMessage()方法中原来有做一个检查,如果 Message 的 callback 等于 null 才会去调用handleMessage()方法,否则就调用handleCallback()方法。那我们快来看下handleCallback()方法中的代码吧:

1
2
3
private final void handleCallback(Message message) {
message.callback.run();
}

再来看一下View中的post()方法,代码如下所示:

1
2
3
4
5
6
7
8
9
10
public boolean post(Runnable action) {
Handler handler;
if (mAttachInfo != null) {
handler = mAttachInfo.mHandler;
} else {
ViewRoot.getRunQueue().post(action);
return true;
}
return handler.post(action);
}

原来就是调用了Handler中的post()方法,我相信已经没有什么必要再做解释了。
最后再来看一下Activity中的runOnUiThread()方法,代码如下所示:

1
2
3
4
5
6
7
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}

如果当前的线程不等于UI线程(主线程),就去调用Handler的post()方法,否则就直接调用Runnable对象的run()方法。

线程是怎么切换的?

当 A 线程创建 Handler 的时候,同时创建了 Looper 和 MessageQueue,Looper 随即在 A 线程中调用 loop() 方法,而有因为在 loop() 方法中从 MessageQueue 取出消息并进行下一步的操作,所以操作又回到了 A 线程中。

参考文章:Android Handler、Message完全解析,带你从源码的角度彻底理解

LruCache 解析

LRU 全称为 Least Recently Used 即最近最少使用,是一种缓存置换算法。淘汰最长时间未使用的对象。下面是 LruCache 的结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private final LinkedHashMap<K, V> map;

private int size; //当前缓存内容的大小。它不一定是元素的个数,比如如果缓存的是图片,一般用的是图片占用的内存大小
private int maxSize; // 最大可缓存的大小

private int putCount; // put 方法被调用的次数
private int createCount; // create(Object) 被调用的次数
private int evictionCount; // 被置换出来的元素的个数
private int hitCount; // get 方法命中缓存中的元素的次数
private int missCount; // get 方法未命中缓存中元素的次数

public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}

LinkedHashMap

可以看出,LruCache 是使用 LinkedHashMap 来存储内容的。LinkedHashMap 继承自 HashMap ,不同的是,它是一个双向循环链表,它的每一个数据结点都有两个指针,分别指向直接前驱和直接后继。LinkedHashMap 的 put(key ,value) 方法,我觉得是当 map 中已经插入过 key 的话,把newValue插入到map中,返回之前存储的 oldValue ,否则返回null。

新建一个节点时的插入过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override void addNewEntry(K key, V value, int hash, int index) {
LinkedEntry<K, V> header = this.header;

// Remove eldest entry if instructed to do so.
LinkedEntry<K, V> eldest = header.nxt;
if (eldest != header && removeEldestEntry(eldest)) {
remove(eldest.key);
}

// Create new entry, link it on to list, and put it into table
LinkedEntry<K, V> oldTail = header.prv;
LinkedEntry<K, V> newTail = new LinkedEntry<K,V>(
key, value, hash, table[index], header, oldTail);
table[index] = oldTail.nxt = header.prv = newTail;
}

可以看到,当加入一个新结点时,结构如下:

当accessOrder为true时,更新或者访问一个结点时,它会把这个结点移到尾部,对应代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
private void makeTail(LinkedEntry<K, V> e) {
// Unlink e
e.prv.nxt = e.nxt;
e.nxt.prv = e.prv;

// Relink e as tail
LinkedEntry<K, V> header = this.header;
LinkedEntry<K, V> oldTail = header.prv;
e.nxt = header;
e.prv = oldTail;
oldTail.nxt = header.prv = e;
modCount++;
}

以上代码分为两步,第一步是先把该节点取出来(Unlink e),如下图:

第二步是把这个这个结点移到尾部(Relink e as tail),也就是把旧的尾部的nxt以及头部的prv指向它,并让它的nxt指向头部,把它的prv指向旧的尾部。如下图:

LruCache

safeSizeOf(K key, V value) 是返回 value 对应的 size 大小

通过重写 sizeOf( ) 方法来确定每个 size 的大小

来读一下它的get方法:

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
public final V get(K key) {

if (key == null) {
throw new NullPointerException("key == null");
}

V mapValue;
synchronized (this) { //获取到值时,就返回该值
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}

/*
* Attempt to create a value. This may take a long time, and the map
* may be different when create() returns. If a conflicting value was
* added to the map while create() was working, we leave that value in
* the map and release the created value.
*/

//尝试创建一个值,这个方法的默认实现是直接返回null。但是在它的设计中,这个方法可能执行完成之后map已经有了变化。
V createdValue = create(key);
if (createdValue == null) {
return null; // 如果不为没有命名的key创建新值,则直接返回 null
}

synchronized (this) {
createCount++;
//将创建的值放入map中,如果map在前面的过程中正好放入了这对key-value,那么会返回放入的value
mapValue = map.put(key, createdValue);

if (mapValue != null) {
//如果不为空,说明不需要我们所创建的值,所以又把返回的值放进去
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
//为空,说明我们更新了这个key的值,需要重新计算大小
size += safeSizeOf(key, createdValue);
}
}

//上面放入的值有冲突
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);// 通知之前创建的值已经被移除,而改为mapValue
return mapValue;
} else {
trimToSize(maxSize);//没有冲突时,因为放入了新创建的值,大小已经有变化,所以需要修整大小
return createdValue;
}
}

LruCache 是可能被多个线程同时访问的,所以在读写 map 时进行加锁。当获取不到对应的 key 的值时,它会调用其 create(K key) 方法,这个方法用于当缓存没有命名时计算一个 key 所对应的值,它的默认实现是直接返回 null。这个方法并没有加上同步锁,也就是在它进行创建时,map 可能已经有了变化。
所以在 get 方法中,如果 create(key) 返回的 V 不为 null,会再把它给放到 map 中,并检查是否在它创建的期间已经有其他对象也进行创建并放到 map 中了,如果有,则会放弃这个创建的对象,而把之前的对象留下,否则因为我们放入了新创建的值,所以要计算现在的大小并进行 trimToSize。
trimToSize 方法是根据传进来的 maxSize,如果当前大小超过了这个 maxSize,则会移除最老的结点,直到不超过。代码如下:

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
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}

if (size <= maxSize || map.isEmpty()) {
break;
}

Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}

entryRemoved(true, key, value, null);
}
}

再来看put方法,它的代码也很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}

V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}

if (previous != null) {
entryRemoved(false, key, previous, value);
}

trimToSize(maxSize);
return previous;
}

主要逻辑是,计算新增加的大小,加入 size ,然后把 key-value 放入 map 中,如果是更新旧的数据(map.put(key, value) 会返回之前的 value),则减去旧数据的大小,并调用 entryRemoved(false, key, previous, value) 方法通知旧数据被更新为新的值,最后也是调用 trimToSize(maxSize) 修整缓存的大小。

本文参考: Android源码解析——LruCache

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×

keyboard_arrow_up 回到顶端