【0175】Android 面试-开源框架相关
1.okhttp网络框架
【使用的3个步骤】



【构建者模式的使用】将复杂对象的构建和表示相分离;同样的构建过程可以使用不同的表示;
【源码解读--同步请求】




【说明】okhttp内部其实也是用了分层的概念,每一个intercepter都是一层;将复杂的内容拆分成了一个一个独立的内容;
【源码解读--异步请求】





2.volley网路请求框架
2.1 volley的使用
【volley】适合使用到请求数据频繁,但是数据量小的app;







【dispatcher】


【netWork】


3.butterknife 注解框架
3.1 butterknife的使用
![]()


3.2 Butterknife的原理

【反射进行注解框架的劣势】反射会影响app的性能,会产生很多的临时变量,临时变量会引起垃圾回收; 大量的变量回收汇会造成UI的卡顿,
【注解原理】在java代码在生成字节码的时候已经处理好了注解的代码;注解的处理技术是在编译阶段执行的;原理是通过读入java的源代码,解析注解,然后构成新的java代码;
新生成的java代码最后编译成为java字节码;由于注解解释器是无法改变读入的java类;





4.Glide框架
4.1 Glide的使用
【说明】Glide是绑定组件(activity或者fragment)的生命周期来进行图片处理的;


4.2 Glide方法
4.2.1 Glid.with()
第一个with()方法的源码得到一个RequestManager对象,
然后Glide会根据我们传入with()方法的参数来确定图片加载的生命周期。
【分析】
【情况1】getApplicationManager()方法来获取一个RequestManager对象。其实这是最简单的一种情况,
因为Application对象的生命周期即应用程序的生命周期,因此Glide并不需要做什么特殊的处理,
它自动就是和应用程序的生命周期是同步的,如果应用程序关闭的话,Glide的加载也会同时终止。
【情况2】接下来我们看传入非Application参数的情况。
不管你在Glide.with()方法中传入的是Activity、FragmentActivity、v4包下的Fragment、还是app包下的Fragment,最终的流程都是一样的,
那就是会向当前的Activity当中添加一个隐藏的Fragment。
具体添加的逻辑是在上述代码的第117行和第141行,分别对应的app包和v4包下的两种Fragment的情况。
那么这里为什么要添加一个隐藏的Fragment呢?
因为Glide需要知道加载的生命周期。
很简单的一个道理,如果你在某个Activity上正在加载着一张图片,结果图片还没加载出来,Activity就被用户关掉了,那么图片还应该继续加载吗?
当然不应该。可是Glide并没有办法知道Activity的生命周期,于是Glide就使用了添加隐藏Fragment的这种小技巧,因为Fragment的生命周期和Activity是同步的,
如果Activity被销毁了,Fragment是可以监听到的,这样Glide就可以捕获这个事件并停止图片加载了。
这里额外再提一句,从第48行代码可以看出,如果我们是在非主线程当中使用的Glide,
那么不管你是传入的Activity还是Fragment,都会被强制当成Application来处理。
不过其实这就属于是在分析代码的细节了。
4.2.2 Glid.load()
最终load()方法返回的其实就是一个DrawableTypeRequest对象
4.2.3
在缓存这一功能上,Glide又将它分成了两个模块,一个是内存缓存,一个是硬盘缓存。
默认情况下,Glide自动就是开启内存缓存的。也就是说,当我们使用Glide加载了一张图片之后,这张图片就会被缓存到内存当中,
只要在它还没从内存中被清除之前,下次使用Glide再加载这张图片都会直接从内存当中读取,而不用重新从网络或硬盘上读取了,这样无疑就可以大幅度提升图片的加载效率。
高级技巧
虽说Glide将缓存功能高度封装之后,使得用法变得非常简单,但同时也带来了一些问题。
比如之前有一位群里的朋友就跟我说过,他们项目的图片资源都是存放在七牛云上面的,而七牛云为了对图片资源进行保护,会在图片url地址的基础之上再加上一个token参数。也就是说,一张图片的url地址可能会是如下格式:
http://url.com/image.jpg?token=d9caa6e02c990b0a
而使用Glide加载这张图片的话,也就会使用这个url地址来组成缓存Key。
但是接下来问题就来了,token作为一个验证身份的参数并不是一成不变的,很有可能时时刻刻都在变化。而如果token变了,那么图片的url也就跟着变了,图片url变了,缓存Key也就跟着变了。结果就造成了,明明是同一张图片,就因为token不断在改变,导致Glide的缓存功能完全失效了。
这其实是个挺棘手的问题,而且我相信绝对不仅仅是七牛云这一个个例,大家在使用Glide的时候很有可能都会遇到这个问题。
那么该如何解决这个问题呢?我们还是从源码的层面进行分析,首先再来看一下Glide生成缓存Key这部分的代码:
public class Engine implements EngineJobListener,
MemoryCache.ResourceRemovedListener,
EngineResource.ResourceListener {
public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,
DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,
Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {
Util.assertMainThread();
long startTime = LogTime.getLogTime();
final String id = fetcher.getId();
EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),
loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),
transcoder, loadProvider.getSourceEncoder());
...
}
...
}
来看一下第11行,刚才已经说过了,这个id其实就是图片的url地址。那么,这里是通过调用fetcher.getId()方法来获取的图片url地址,而我们在上一篇文章中已经知道了,fetcher就是HttpUrlFetcher的实例,我们就来看一下它的getId()方法的源码吧,如下所示:
public class HttpUrlFetcher implements DataFetcher<InputStream> {
private final GlideUrl glideUrl;
...
public HttpUrlFetcher(GlideUrl glideUrl) {
this(glideUrl, DEFAULT_CONNECTION_FACTORY);
}
HttpUrlFetcher(GlideUrl glideUrl, HttpUrlConnectionFactory connectionFactory) {
this.glideUrl = glideUrl;
this.connectionFactory = connectionFactory;
}
@Override
public String getId() {
return glideUrl.getCacheKey();
}
...
}
可以看到,getId()方法中又调用了GlideUrl的getCacheKey()方法。那么这个GlideUrl对象是从哪里来的呢?其实就是我们在load()方法中传入的图片url地址,然后Glide在内部把这个url地址包装成了一个GlideUrl对象。
很明显,接下来我们就要看一下GlideUrl的getCacheKey()方法的源码了,如下所示:
public class GlideUrl {
private final URL url;
private final String stringUrl;
...
public GlideUrl(URL url) {
this(url, Headers.DEFAULT);
}
public GlideUrl(String url) {
this(url, Headers.DEFAULT);
}
public GlideUrl(URL url, Headers headers) {
...
this.url = url;
stringUrl = null;
}
public GlideUrl(String url, Headers headers) {
...
this.stringUrl = url;
this.url = null;
}
public String getCacheKey() {
return stringUrl != null ? stringUrl : url.toString();
}
...
}
这里我将代码稍微进行了一点简化,这样看上去更加简单明了。GlideUrl类的构造函数接收两种类型的参数,一种是url字符串,一种是URL对象。然后getCacheKey()方法中的判断逻辑非常简单,如果传入的是url字符串,那么就直接返回这个字符串本身,如果传入的是URL对象,那么就返回这个对象toString()后的结果。
其实看到这里,我相信大家已经猜到解决方案了,因为getCacheKey()方法中的逻辑太直白了,直接就是将图片的url地址进行返回来作为缓存Key的。那么其实我们只需要重写这个getCacheKey()方法,加入一些自己的逻辑判断,就能轻松解决掉刚才的问题了。
创建一个MyGlideUrl继承自GlideUrl,代码如下所示:
public class MyGlideUrl extends GlideUrl {
private String mUrl;
public MyGlideUrl(String url) {
super(url);
mUrl = url;
}
@Override
public String getCacheKey() {
return mUrl.replace(findTokenParam(), "");
}
private String findTokenParam() {
String tokenParam = "";
int tokenKeyIndex = mUrl.indexOf("?token=") >= 0 ? mUrl.indexOf("?token=") : mUrl.indexOf("&token=");
if (tokenKeyIndex != -1) {
int nextAndIndex = mUrl.indexOf("&", tokenKeyIndex + 1);
if (nextAndIndex != -1) {
tokenParam = mUrl.substring(tokenKeyIndex + 1, nextAndIndex + 1);
} else {
tokenParam = mUrl.substring(tokenKeyIndex);
}
}
return tokenParam;
}
}
可以看到,这里我们重写了getCacheKey()方法,在里面加入了一段逻辑用于将图片url地址中token参数的这一部分移除掉。这样getCacheKey()方法得到的就是一个没有token参数的url地址,从而不管token怎么变化,最终Glide的缓存Key都是固定不变的了。
当然,定义好了MyGlideUrl,我们还得使用它才行,将加载图片的代码改成如下方式即可:
Glide.with(this)
.load(new MyGlideUrl(url))
.into(imageView);
也就是说,我们需要在load()方法中传入这个自定义的MyGlideUrl对象,而不能再像之前那样直接传入url字符串了。不然的话Glide在内部还是会使用原始的GlideUrl类,而不是我们自定义的MyGlideUrl类。
浙公网安备 33010602011771号