图片加载框架比较
共同优点
- 都对多级缓存、线程池、缓存算法做了处理
- 自适应程度高,根据系统性能初始化缓存配置、系统信息变更后动态调整策略。比如根据 CPU 核数确定最大并发数,根据可用内存确定内存缓存大小,网络状态变化时调整最大并发数等。
- 支持多种数据源支持多种数据源,网络、本地、资源、Assets 等
不同点
- Picasso所能实现的功能,Glide都能做,无非是所需的设置不同。但是Picasso体积比起Glide小太多。
- Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图。Glide 支持加载 Gif 动态图,而 Picasso 不支持该特性
- Fresco在5.0以下的内存优化非常好,代价就是体积也非常的大,按体积算Fresco>Glide>Picasso
- UIL可以算是老牌最火的图片加载库了,该作者在项目中说明已经停止了对该项目的维护。这就意味着以后任何的 bug?都不会修复,任何的新特性都不会再继续开发,所以毫无疑问 UIL 不推荐在项目中使用了。
图片框架的缓存
- MemorycCache图片内存缓存。默认使用了 LRU 算法。?
- DiskCache图片磁盘缓存,默认使用LruDiskCache算法,在缓存满时删除最近最少使用的图片
glide源码
一般看源码先看他的使用方法,通过使用的方法看对应的代码。
Glide.with(MainActivity.this).load(url).into(headerImage);
- with方法把context传进去,返回GlideBuilder的对应,在这里做一些初始化操作,比如构建线程池(包括sourceExecutor ,diskCacheExecutor ),缓存大小和缓存器,默认的连接监听工厂(connectivityMonitorFactory ),Engine对象和RequestManagerRetriever 对象等等。
- load(URL)方法没做什么事情,主要就是把URL传进去,获取RequestBuilder对象。
- 主要的操作都在into方法里(在这里会取lru缓存还是本地缓存,还是没有,告诉RequestBuilder)。RequestBuilder的into方法里开启了线程池进行加载资源。网络请求是通过url打开连接,返回一个HttpURLConnection对象,进行网络请求的。加载得资源后转换到主线程并进行回调(沒注意看這個)。设置给imageview
- glide为什么有lru还会内存溢出。因为直接把整个大图片的整个内存加载进去了。对于大图可以下载下来,asdrawale来加载,drawable更省内存,Drawable应该不属于常驻内存的对象,不然的话,不可能不会出现OOM的~~
- Glide内部处理了网络图片加载的错位或者闪烁(tag)。
?? public Request getRequest() {
??????? //本质还是getTag
??????? Object tag = getTag();
??????? Request request = null;
??????? if (tag != null) {
??????????? if (tag instanceof Request) {
??????????????? request = (Request) tag;
??????????? } else {
??????????????? throw new IllegalArgumentException("You must not call setTag() on a view
Glide is targeting");
??????????? }
??????? }
??????? return request;
??? }
???
??? @Override
??? public void setRequest(Request request) {
??????? //本质是setTag
??????? setTag(request);
??? }
对图片加载用到了LruCache(最少最近使用)算法
他会把内存控制在一定大小内,超过最大值时会自动回收,这个最大值可以自己定,一个太小的缓存空间,有可能造成图片频繁地被释放和重新加载,这并没有好处。而一个太大的缓存空间,则有可能还是会引起 java.lang.OutOfMemory 的异常。一般使用最大可用内存的1/8作为缓存的大小。LruCache的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap (频繁增删、不需要排序)中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。?
public class BitmapCache implements ImageCache {
private LruCache<String, Bitmap> mCache;
public BitmapCache() {
int maxSize = 10 * 1024 * 1024;
mCache = new LruCache<String, Bitmap>(maxSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
}
};
}
@Override
public Bitmap getBitmap(String url) {
return mCache.get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
mCache.put(url, bitmap);
}
}
网络框架比较
常用网络库使用方法
public interface netApi {
@GET("repos/{owner}/{repo}/contributors")
Call<ResponseBody> contributorsBySimpleGetCall(@Path("owner") String owner, @Path("repo") String repo);
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
volleyStringRequest();
volleyJsonRequest();
retrofitHttpRequest();
try {
okhttpAsyGet();
OkHttpSyncGet();
} catch (Exception e) {
e.printStackTrace();
}
}
//volley第一步
RequestQueue mQueue = Volley.newRequestQueue(MainActivity.this);
private void volleyStringRequest() {
//volley第二步
StringRequest stringRequest = new StringRequest("http://www.baidu.com",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d("TAG", response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
//volley第三步
mQueue.add(stringRequest);
}
private void volleyJsonRequest() {
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://www.sina.com/sports/101010100.html", null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Log.d("TAG", response.toString());
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
mQueue.add(jsonObjectRequest);
}
//okhttp第一步
private final OkHttpClient client = new OkHttpClient();
public void okhttpAsyGet() throws Exception {
//okhttp第二步
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
//okhttp第三步
okhttp3.Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " response);
Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i ) {
System.out.println(responseHeaders.name(i) ": " responseHeaders.value(i));
}
System.out.println(response.body().string());
}
public void OkHttpSyncGet() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, okhttp3.Response response) throws IOException {
if (!response.isSuccessful()) throw new IOException("Unexpected code " response);
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i ) {
System.out.println(responseHeaders.name(i) ": " responseHeaders.value(i));
}
System.out.println(response.body().string());//只能获取一次,可以用string保存
}
});
}
public void retrofitHttpRequest() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
netApi repo = retrofit.create(netApi.class);
retrofit2.Call<ResponseBody> call = repo.contributorsBySimpleGetCall("userName", "path");
call.enqueue(new retrofit2.Callback<ResponseBody>() {
@Override
public void onResponse(retrofit2.Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
//response
}
@Override
public void onFailure(retrofit2.Call<ResponseBody> call, Throwable t) {
}
});
}
}
String post(String url, String json) throws IOException {
RequestBody formBody = new FormEncodingBuilder()
.add("platform", "android")
.add("name", "bug")
.add("subject", "XXXXXXXXXXXXXXX")
.build();
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
} else {
throw new IOException("Unexpected code " response);
}
}
- HttpURLConnection和HttpClient。这两种方式都支持HTTPS协议、以流的形式进行上传和下载、配置超时时间、IPv6、以及连接池等功能。
- 在Android 2.2版本之前,HttpClient是最好的选择。因为HttpURLConnection有一些bug。比如说对一个可读的InputStream调用close()方法时,就有可能会导致连接池失效了。那么我们通常的解决办法就是直接禁用掉连接池的功能。
- 在Android 2.3版本及以后,HttpClientHttpURLConnection则是最佳的选择,HttpURLConnection的API提供的比较简单,可以更加容易地去使用和扩展它。而且速度快、节省电量。
- OkHttp 处理了很多网络问题:自动重连、会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP。OkHttp还处理了代理服务器问题和SSL握手失败问题。
- volley的设计目标就是非常适合数据量小,通信量大的客户端,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕。Volley停止了更新,而OkHttp得到了官方的认可,并在不断优化。因此我最终替换为了OkHttp
volley原理
主线程中调用RequestQueue的add()方法来添加一条网络请求,这条请求会先被加入到缓存队列当中,如果发现可以找到相应的缓存结果就直接读取缓存并解析,然后回调给主线程。如果在缓存中没有找到结果,则将这条请求加入到网络请求队列中,然后处理发送HTTP请求,解析响应结果,写入缓存,并回调主线程。
为什么说Volley适合数据量小,通信频繁的网络操作
volley中为了提高请求处理的速度,采用了ByteArrayPool进行内存中的数据存储的,如果下载大量的数据,这个存储空间就会溢出,所以不适合大量的数据,但是由于他的这个存储空间是内存中分配的,当存储的时候优是从ByteArrayPool中取出一块已经分配的内存区域,?不必每次存数据都要进行内存分配,而是先查找缓冲池中有无适合的内存区域,如果有,直接拿来用,从而减少内存分配的次数?,所以他比较适合据量小,通信量大网络数据交互情况。
Retrofit原理
- Retrofit 2.0底层依赖OkHttp实现,也就是说Retrofit本质上就是对OkHttp的更进一步封装,还支持Rxjava。Retrofit和其它Http库最大区别在于通过大范围使用注解简化Http请求。Retrofit使用注解来描述HTTP请求(请求方式、请求参数)。
- 内部也是调用okhttp的网络请求方式
- Retrofit主要是在create方法中采用动态代理模式实现接口方法,这个过程构建了一个ServiceMethod对象,根据方法注解获取请求方式,参数类型和参数注解拼接请求的链接,当一切都准备好之后会把数据添加到Retrofit的RequestBuilder中。然后当我们主动发起网络请求的时候会调用okhttp发起网络请求,okhttp的配置包括请求方式,URL等在Retrofit的RequestBuilder的build()方法中实现,并发起真正的网络请求。
- 网络请求的工作本质上是OkHttp完成,而 Retrofit 仅负责网络请求接口的封装。
自己写网络请求框架
- volley,okHttp等,这类优秀的框架其底层的实现大部分也是基于系统的 线程池 和 httpClient 或 HttpUrlConnection的网络请求类框架,Android中是不能在主线程中(又称UI线程)进行网络操作的,那么框架中必不可少地要使用到子线程,可以使用简单的 Thread Runnable Handler或者重量级点的AsyncTask。
- 处理好并发操作,一个应用中往往要进行多线程操作,而Java虚拟机对于一个线程的内存分配大约在1M左右,具体多少要看它执行的任务而定。所有就要使用线程池,例如newFixdThreadPool 可以控制并发数量,且在整个APP运行过程中有几个常驻线程在,避免使用时反复地new,退出时再销毁,而?newCacheThreadPool 则会在任务完成后,自动回收线程,它会帮你释放线程内存,也就不会有常驻线程。
- 还要注意使接口分离,降低耦合,而且接口能够我们带来很大的方便。
okhttp源码
- 在构造器中利用建造者模式来构建?OkHttpClient?的对象,OkHttpClient?的构造器中主要是默认的配置。在newCall(Request request)?(request是请求参数和URL)的时候,其实是里面创建了一个?RealCall?的对象,里面有execute()?方法。里面有getResponseWithInterceptorChain()?,添加了很多Interceptor,并返回?Response?对象的。
- Interceptor?是 OkHttp 最核心的一个东西,它负责拦截请求进行一些额外的处理(例如 设置cookie),Interceptor有负责失败重试、重定向的(RetryAndFollowlnterceptor)、读取缓存、更新缓存的(Cachelnterceptor)、负责和服务器建立连接的(Connectlnterceptor)、负责向服务器发送请求数据(Bridgelnterceptor)、从服务器读取响应数据的(Networklnterceptor)。
- 每一个功能都只是一个?Interceptor,它们再连接成一个?Interceptor.Chain,环环相扣,最终完成一次网络请求。
- 根据响应码判断是否是重定向(3开头3xx)。RetryAndFollowUpInterceptor:如果不需要重定向,那么 followUp 为空,会释放资源,返回 response。 若为重定向就销毁旧连接,创建新连接,将重定向操作得到的新请求设置给 request。
- 同步请求通过Call.execute()直接返回当前的Response,而异步请求会把当前的请求Call.enqueue添加(AsyncCall)到请求队列中,并通过回调(Callback)的方式来获取最后结果。
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
- 复用机制:Http中添加了一种KeepAlive机制,当数据传输完毕后仍然保持连接,等待下一次请求时直接复用该连接。
ConnectionPool :取到的话复用,没有取到放到连接池中。
ConnectionPool关键代码: - OkHttp 默认最大并发数 64,单域名最大并发 5,为了实现请求的并发,Dispatcher 配置了一个线程池,
//线程池,核心线程数为0,最大线程数为最大整数,线程空闲存活时间60s,//SynchronousQueue 直接提交策略private static final Executor executor = new ThreadPoolExecutor(0,
Integer.MAX_VALUE , 60L , TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
//空闲连接的最大连接数
private final int maxIdleConnections;
//保持连接的周期
private final long keepAliveDurationNs;
//双端队列,存放具体的连接
private final Deque<RealConnection> connections = new ArrayDeque<>();
//用于记录连接失败的route
final RouteDatabase routeDatabase = new RouteDatabase();
//构造函数//从这里可以知道,空闲连接的最大连接数为5,保持连接的周期是5分钟public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.maxIdleConnections = maxIdleConnections;
this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
// Put a floor on the keep alive duration, otherwise cleanup will spin loop.
if (keepAliveDuration <= 0) {
throw new IllegalArgumentException("keepAliveDuration <= 0: " keepAliveDuration);
}
}
同步
Dispatcher会在同步执行任务队列中记录当前被执行过得任务Call,同时在当前线程中去执行Call的getResponseWithInterceptorChain()方法,直接获取当前的返回数据Response;
异步
Dispatcher内部实现了懒加载无边界限制的线程池方式,同时该线程池采用了SynchronousQueue这种阻塞队列。异步执行是通过Call.enqueue(Callback responseCallback)来执行,在Dispatcher中添加一个封装了Callback的Call的匿名内部类Runnable来执行当前的Call。这里一定要注意的地方这个AsyncCall是Call的匿名内部类。AsyncCall的execute方法仍然会回调到Call的getResponseWithInterceptorChain方法来完成请求,同时将返回数据或者状态通过Callback来完成。
使用缓存,有多种不同的缓存方式
自定义一个拦截器
class LoggingInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
其他库
LeakCanary原理解析
- lifecycleCallbacks监听Activity的onDestroy方法,正常情况下activity在onDestroy后需要立即被回收,onActivityDestroyed方法最终会调用RefWatcher.watch方法:
- 通过将Activity包装到WeakReference(弱引用)中,被WeakReference包装过的Activity对象如果被回收,该WeakReference引用会被放到ReferenceQueue中,通过监测ReferenceQueue里面的内容就能检查到Activity是否能够被回收
- 如果Activity没有被回收,调用GcTigger.runGc方法运行GC,如果这时候还没有被回收,那就说明Activity可能已经泄露。
Evenbus是做什么的?和RXjava有什么区别?
- 采用EventBus作为事件管理,可以跨线程,跨组件通信。?以前我们做组件间的消息分发更新,一般会采用观察者模式,或者接口数据回调的相关方式。但是这样的做法虽然可以解决问题,但是组件之间的耦合比较严重,而且代码也不易阅读和相关维护。为了解决这样的问题我们可以使用消息总线EventBus框架。
- EventBus是一款针对Android优化的发布/订阅事件总线。主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息.优点是开销小,代码更优雅。以及将发送者和接收者解耦。
- RxJava要比EventBus的应用更广泛,RxJava里面几乎可以做任何事情。做异步、网络的数据处理,写出来的代码比较优雅。
黏性事件
简单讲,就是在发送事件之后再订阅该事件也能收到该事件,跟黏性广播类似,但是它只能收到最新的一次消息,比如说在未订阅之前已经发送了多条黏性消息了,然后再订阅只能收到最近的一条消息。
EventBus源码
register(this)就是去当前类,遍历所有的方法,找到onEvent开头的然后进行存储(把匹配的方法最终保存在subscriptionsByEventType(Map,key:eventType ; value:CopyOnWriteArrayList<Subscription>?),eventType是我们方法参数的Class,Subscription中则保存着subscriber, subscriberMethod(method, threadMode, eventType), priority;包含了执行改方法所需的一切),然后post的时候,根据post传入的参数,去找到匹配的方法,反射调用。数据传递是通过handler。