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'

参考资料

Android 百度地图仿饿了么地图定位

今天遇到了一个需求,就是在用户选择地理位置的时候,现在地图上选择一个位置,然后再手动输入具体门牌号信息。和饿了么类似。

想法是 在屏幕中间放一个 ImageView 固定,然后拖动屏幕的话,只有地图在动。因为如果是用 Marker 来绘制的话,有可能会出现重影而且可能会卡。

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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178

public class MapActivity extends BaseActivity implements View.OnClickListener {

private MapView mapView;
private BaiduMap baiduMap;
public static final int LOADING = 997;
public static final int FINISH = 998;
private String address;
private TextView tvAddress;
private GeoCoder geoCoder;
private TextView tvConfirm;
public LocationClient locationClient = null;
//是否首次定位
boolean isFirstLoc = true;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case LOADING:
tvAddress.setText("正在获取位置");
break;
case FINISH:
break;
}
}
};

private LocationListener myLitenner = new BDLocationListener {

@Override
public void onReceiveLocation(BDLocation bdLocation) {
//MAP VIEW 销毁后不在处理新接收的位置
if (mapView == null)
return;
MyLocationData locData = new MyLocationData.Builder()
//此处设置开发者获取到的方向信息,顺时针0-360
.accuracy(bdLocation.getRadius())
.direction(100).latitude(bdLocation.getLatitude())
.longitude(bdLocation.getLongitude()).build();
baiduMap.setMyLocationData(locData);
//设置定位数据
if (isFirstLoc) {
isFirstLoc = false;
LatLng ll = new LatLng(bdLocation.getLatitude(), bdLocation.getLongitude());
MapStatusUpdate mapStatusUpdate = MapStatusUpdateFactory.newLatLngZoom(ll, 16);
//设置地图中心点以及缩放级别
baiduMap.animateMapStatus(mapStatusUpdate);
}
}
};

private OnMapStatusChangeListener mapChangeListener = new BaiduMap.OnMapStatusChangeListener() {
@Override
public void onMapStatusChangeStart(MapStatus mapStatus) {
handler.sendEmptyMessage(LOADING);
}

@Override
public void onMapStatusChangeStart(MapStatus mapStatus, int i) {
handler.sendEmptyMessage(LOADING);
}

@Override
public void onMapStatusChange(MapStatus mapStatus) {

}

@Override
public void onMapStatusChangeFinish(MapStatus mapStatus) {
searchAddress(mapStatus.target);
}
};


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

initView();
}

@Override
protected void onResume() {
mapView.onResume();
super.onResume();
}

@Override
protected void onPause() {
mapView.onPause();
super.onPause();
}

private void initView() {
findViewById(R.id.btn_back).setOnClickListener(this);
tvAddress = (TextView) findViewById(R.id.tv_address);
tvAddress.setText("正在获取位置");
tvConfirm = (TextView) findViewById(R.id.tv_confirm);
tvConfirm.setOnClickListener(this);

mapView = (MapView) findViewById(R.id.map_view);
baiduMap = mapView.getMap();
baiduMap.setMapType(BaiduMap.MAP_TYPE_NORMAL);
baiduMap.setMyLocationEnabled(true);

//定位
locationClient = new LocationClient(getApplicationContext());
locationClient.registerLocationListener(myLitenner);//注册监听函数

LocationClientOption option = new LocationClientOption();
option.setOpenGps(true);//打开GPS
option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy);//设置定位模式
option.setCoorType("bd09ll");//返回的定位结果是百度经纬度,默认值是gcj02
option.setScanSpan(5000);//设置发起定位请求的时间间隔为5000ms
option.setIsNeedAddress(true);//返回的定位结果包含地址信息
option.setNeedDeviceDirect(true);// 返回的定位信息包含手机的机头方向
locationClient.setLocOption(option);

baiduMap.setOnMapStatusChangeListener(mapChangeListener);
locationClient.start();
}

private OnGetGeoCoderResultListener geoCodeListener = new OnGetGeoCoderResultListener() {
@Override
public void onGetGeoCodeResult(GeoCodeResult geoCodeResult) {

}

@Override
public void onGetReverseGeoCodeResult(ReverseGeoCodeResult reverseGeoCodeResult) {

if (reverseGeoCodeResult != null) {
tvAddress.setText("当前位置:" + reverseGeoCodeResult.getAddress());
address = reverseGeoCodeResult.getAddress();
} else {
tvAddress.setText("暂无地址");
}
}
};

//根据 Laglng 来获取位置信息
private void searchAddress(LatLng latLng) {
geoCoder = GeoCoder.newInstance();
geoCoder.setOnGetGeoCodeResultListener(geoCodeListener);
ReverseGeoCodeOption codeOption = new ReverseGeoCodeOption();
codeOption.location(latLng);
geoCoder.reverseGeoCode(codeOption);
}

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_back:
finish();
break;
case R.id.tv_confirm:
if (address != null) {
Intent intent = new Intent();
intent.putExtra("address", address);
setResult(RESULT_OK, intent);
}
finish();
break;
}
}

@Override
protected void onDestroy() {
locationClient.stop();
baiduMap.setMyLocationEnabled(false);
mapView.onDestroy();
if (geoCoder != null) {
geoCoder.destroy();
}
super.onDestroy();
}
}

布局文件

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
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/head_bg"
android:gravity="center_horizontal"
android:paddingLeft="10dp"
android:paddingRight="10dp">

<!-- 返回 -->

<LinearLayout
android:id="@+id/btn_back"
android:layout_width="50dp"
android:layout_height="fill_parent"
android:gravity="center">

<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@drawable/back" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="返回"
android:textColor="@color/white" />
</LinearLayout>
<!-- 页面标题 -->

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_gravity="center_vertical"
android:layout_weight="2.5"
android:gravity="center"
android:orientation="horizontal">

<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/map"
android:textColor="@color/white" />

</LinearLayout>
<!-- 小工具 -->

<LinearLayout
android:layout_width="50dp"
android:layout_height="fill_parent"
android:gravity="center">

<TextView
android:id="@+id/tv_confirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/border_white_jiaofeijilu"
android:paddingBottom="@dimen/dimen_3_dp"
android:paddingLeft="@dimen/dimen_5_dp"
android:paddingRight="@dimen/dimen_5_dp"
android:paddingTop="@dimen/dimen_3_dp"
android:text="确定"
android:textColor="@color/white"
android:textSize="12sp" />

</LinearLayout>
</LinearLayout>

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">

<com.baidu.mapapi.map.MapView
android:id="@+id/map_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<ImageView
android:layout_width="@dimen/dimen_15_dp"
android:layout_height="@dimen/dimen_15_dp"
android:layout_centerInParent="true"
android:src="@drawable/location" />

</RelativeLayout>

<TextView
android:id="@+id/tv_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:maxLines="1"
android:padding="10dp"
android:textColor="@color/text_black" />

</LinearLayout>

遇到的几个坑:

  • 百度的 sdk 文档真是垃圾,能不能用点心,好好写行不行。
  • option.setCoorType("bd09ll") 类型一定要写对 是 l 不是 1。因为这个问题,导致定位一直不太准确。
  • 开始想用 POI检索 来通过 Laglng 来获取到位置信息,但是不知道为什么一直获取不到,而且 POI检索 还需要参数 keyword 就是关键词。后来通过查阅发现使用 GeoCoderReverseGeoCodeOption 反向地理编码来获取位置信息。
  • 因为百度地图需要注册时需要用到 SHA1 ,所以每次都要打签名包,然后我并不知道AS要怎么配置才能生成 debug 签名包,很尴尬,所以每次都要生成签名包然后拷到手机上,很麻烦。

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。

用 viewpager 创建欢迎页轮播图

使用 viewpager 创建欢迎页轮播图

布局文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<android.support.v4.view.ViewPager
android:id="@+id/view_pager_2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:layout_weight="1"/>

<LinearLayout
android:id="@+id/view_dot_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="horizontal"/>

其中 view_dot_container 是轮播图下显示第几页的指示点的容器

adapter 需要继承自 PagerAdapter

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
public class HelloAdapter extends PagerAdapter {

private Context mContext;
private int[] imgs;
private List<ImageView> mImageViews;

public HelloAdapter(Context context, int[] imgs) {
mContext = context;
this.imgs = imgs;
mImageViews = new ArrayList<>();
for (int img : imgs) {
ImageView view = new ImageView(context);

ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
view.setLayoutParams(params);
Glide.with(context).load(img).into(view);
mImageViews.add(view);
}
}

@Override
public int getCount() {
return imgs.length;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(mImageViews.get(position));
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
container.addView(mImageViews.get(position));
return mImageViews.get(position);
}

@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}

在这里。我是传进来一组本地的 drawable 资源 ID,也可以传进来一个 View 的集合(如 List<ImageView> )。

PagerAdapter 的重点是 instantiateItem 和 isViewFromObject 方法。

instantiateItem 这个方法,return 一个对象,这个对象表明了 PagerAdapter 选择哪个对象放在当前的 ViewPager 中;

isViewFromObject 用来判断 instantiateItem(ViewGroup, int) 函数所返回来的 Key 与一个页面视图是否是代表的同一个视图(即它俩是否是对应的,对应的表示同一个View)

destroyItem 在重写该方法的时候主动从 ViewPager 中移除该 ContentView 。

初始化

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
private List<ImageView> mDotViews;//用来存放指示点的集合

private void initViewPager() {
int[] imgs = new int[]{R.drawable.a, R.drawable.b, R.mipmap.c};
HelloAdapter helloAdapter = new HelloAdapter(this, imgs);
ViewPager viewPager = (ViewPager) findViewById(R.id.view_pager);
viewPager.setAdapter(helloAdapter);
viewPager.addOnPageChangeListener(this);//监听 ViewPager 的滚动事件
mDotViews = new ArrayList<>();

dotContainer = (LinearLayout) findViewById(R.id.view_dot_container);
for (int img : imgs) {
ImageView dotView = new ImageView(this);
dotView.setImageResource(R.drawable.bg_radius_4);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(30, 30);
params.setMargins(15, 0, 15, 0);
dotView.setLayoutParams(params);
mDotViews.add(dotView);
dotContainer.addView(dotView);
}
viewPager.setCurrentItem(0);
mDotViews.get(0).setImageResource(R.drawable.check_dot);
}

@Override
public void onPageScrolled(int position, float positionOffset, intpositionOffsetPixels) {

}

@Override
public void onPageSelected(int position) {
for (int i = 0; i < mDotViews.size(); i++) {
tvTitle.setText(titles[position]);
if (i == position) {
mDotViews.get(i).setImageResource(R.drawable.check);
} else {
mDotViews.get(i).setImageResource(R.drawable.uncheck);
}
}
}

@Override
public void onPageScrollStateChanged(int state) {

}
  1. onPageSelected(int position):这个方法有一个参数 position ,代表哪个页面被选中。当用手指滑动翻页的时候,如果翻动成功了(滑动的距离够长),手指抬起来就会立即执行这个方法,position 就是当前滑动到的页面。如果直接 setCurrentItem 翻页,那 position 就和 setCurrentItem 的参数一致,这种情况在 onPageScrolled 执行方法前就会立即执行。

  2. onPageScrolled(int position,float positionOffset, int positionOffsetPixels):这个方法会在屏幕滚动过程中不断被调用。有三个参数,第一个position,这个参数要特别注意一下。当用手指滑动时,如果手指按在页面上不动,position和当前页面index是一致的;如果手指向左拖动(相应页面向右翻动),这时候position大部分时间和当前页面是一致的,只有翻页成功的情况下最后一次调用才会变为目标页面;如果手指向右拖动(相应页面向左翻动),这时候position大部分时间和目标页面是一致的,只有翻页不成功的情况下最后一次调用才会变为原页面。当直接设置setCurrentItem翻页时,如果是相邻的情况(比如现在是第二个页面,跳到第一或者第三个页面),如果页面向右翻动,大部分时间是和当前页面是一致的,只有最后才变成目标页面;如果向左翻动,position和目标页面是一致的。这和用手指拖动页面翻动是基本一致的。如果不是相邻的情况,比如我从第一个页面跳到第三个页面,position先是0,然后逐步变成1,然后逐步变成2;我从第三个页面跳到第一个页面,position先是1,然后逐步变成0,并没有出现为2的情况。positionOffset是当前页面滑动比例,如果页面向右翻动,这个值不断变大,最后在趋近1的情况后突变为0。如果页面向左翻动,这个值不断变小,最后变为0。positionOffsetPixels是当前页面滑动像素,变化情况和positionOffset一致。

  3. onPageScrollStateChanged(int state):这个方法在手指操作屏幕的时候发生变化。有三个值:0(END),1(PRESS) , 2(UP) 。当用手指滑动翻页时,手指按下去的时候会触发这个方法,state值为1,手指抬起时,如果发生了滑动(即使很小),这个值会变为2,然后最后变为0 。总共执行这个方法三次。一种特殊情况是手指按下去以后一点滑动也没有发生,这个时候只会调用这个方法两次,state值分别是1,0 。

Your browser is out-of-date!

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

×

keyboard_arrow_up 回到顶端