我們的項目是一個圖片分享社區,所以要對圖片處理的一些邏輯進行學習。
學習自郭霖大神glide系列博客,自己經過思考重新整理。
一。全局替換加載策略
首先要知道Glide在實例化時的工作(也就是框架的初始化):
設計模式是builder模式,可以分爲兩部分,首先builder調用Glide構造器傳入所需模塊(內存策略,圖片解碼模式等),然後Glide構造器中對各種類型的圖片需求加載進行註冊(如File,int,String,GlideUrl等,需要注意的是String類型方法實際調用了GlideUrl的方法)。
相關代碼:
遍歷Manifest文件尋找所有的GlideModule放入列表,然後遍歷執行這些GlideModule的applyOptions(此方法中可以對builder的幾個模塊使用set方法配置),然後調用構造器生成Glide實例,然後遍歷執行這些GlideModule的registerComponents(此方法中可以調用Glide單例註冊新的圖片類型加載策略,或者替換默認的策略。)
綜上,因爲我們的目標是對String(實際是GlideUrl)類型加載請求進行改寫,所以需要自定義一個GlideModule並且在其registerComponents中對GlideUrl的策略進行替換。另外,因爲網絡請求要用okhttp而不是默認的httpUrlClient所以要添加okhttp的依賴。
首先自定義MyGlideModule implements GlideModule,然後在Manifest文件中註冊:
看看原本Glide構造器中是怎麼樣註冊的:
前兩個參數都不用變,我們只需要自定義第三個參數,參照原HttpUrlGlideUrlLoader自定義MyModelLoader imp ModelLoader<GlideUrl,InputStream>。
public class MyModolLoader implements ModelLoader<GlideUrl,InputStream> {
private OkHttpClient client;
public MyModolLoader(OkHttpClient client) {
this.client = client;
}
@Override
public DataFetcher getResourceFetcher(GlideUrl model, int width, int height) {
return new MyDataFetcher(client,model);
}
public static class Factory implements ModelLoaderFactory<GlideUrl,InputStream>{
private OkHttpClient client;
public Factory(){
}
public Factory(OkHttpClient client) {
this.client = client;
}
@Override
public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
return new MyModolLoader(getOkhttpClient());
}
private OkHttpClient getOkhttpClient() {
if (client==null){
synchronized (this){
if (client==null){
client=new OkHttpClient();
}
}
}
return client;
}
@Override
public void teardown() {
}
}
}
這裏有一個構造器傳入了一個okhttpclient,因爲後面的操作需要用到我們改造的okhttpclient,用了兩次判空做單例。
這個類可以看到並沒什麼操作,只是將OkHttpClient和GlideUrl交給了一個DataFetcher.
所以自定義MyDataFetcher imp DataFetcher<InputStream>(依然是參照源碼寫):
public class MyDataFetcher implements DataFetcher<InputStream> {
private OkHttpClient client;
private InputStream stream;
private GlideUrl glideUrl;
private volatile boolean isCanceled = false;
private ResponseBody responseBody;
public MyDataFetcher(OkHttpClient client, GlideUrl glideUrl) {
this.client = client;
this.glideUrl = glideUrl;
}
@Override
public InputStream loadData(Priority priority) throws Exception {
Log.v("tag","loaddata");
Request.Builder requestBuilder = new Request.Builder()
.url(glideUrl.toStringUrl());
for (Map.Entry<String, String> headerEntry : glideUrl.getHeaders().entrySet()) {
String key = headerEntry.getKey();
requestBuilder.addHeader(key, headerEntry.getValue());
}
//requestBuilder.addHeader("httplib", "OkHttp");
Request request = requestBuilder.build();
if (isCanceled) {//在發出請求前進行最後一遍確認
return null;
}
Response response = client.newCall(request).execute();
responseBody = response.body();
if (!response.isSuccessful() || responseBody == null) {
throw new IOException("Request failed with code: " + response.code());
}
stream = ContentLengthInputStream.obtain(responseBody.byteStream(),
responseBody.contentLength());
return stream;
}
@Override
public void cleanup() {
if (stream!=null){
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (responseBody!=null){
responseBody.close();
}
}
@Override
public String getId() {
return glideUrl.getCacheKey();
}
@Override
public void cancel() {
isCanceled = true;
}
}
只是在loadData中使用okhttp進行網絡請求操作而已。
到這裏,我們已經用自定義的模塊替換掉原模塊,實現了用okhttp加載圖片,接下來要獲取下載進度,要通過自定義okhttp的攔截器。在自定義的攔截器中,我們將響應體(ResponseBody)替換爲自定義的ResponseBody,獲取進度的操作就在這個ResponseBody中。
先來看這個自定義ResponseBody,構造器傳入原ResponseBody:
public class ProgressResponseBody extends ResponseBody {
private ResponseBody responseBody;
private BufferedSource bufferedSource;
public ProgressResponseBody(ResponseBody responseBody) {
this.responseBody = responseBody;
}
@Nullable
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public long contentLength() {
return responseBody.contentLength();
}
@Override
public BufferedSource source() {
if (bufferedSource==null){
bufferedSource= Okio.buffer(new ProgressSource(responseBody.source()));
}
return bufferedSource;
}
private class ProgressSource extends ForwardingSource{
private long currentBytes;//已讀取的數據長度
private long totalLength;//數據總長度
public ProgressSource(Source delegate) {
super(delegate);
totalLength=responseBody.contentLength();
}
@Override
public long read(Buffer sink, long byteCount) throws IOException {
Log.v("tag","read");
long result=super.read(sink,byteCount);//這一次讀取的長度
if (result==-1){
currentBytes=totalLength;
} else {
currentBytes+=result;
}
int progress= (int) (currentBytes*100f/totalLength);
Log.v("tag","進度"+progress);
return result;
}
}
}
主要操作在自定義內部類的read中,read指的是每次讀取數據流的過程,返回值爲本次成功讀取的長度,-1表示讀完。所以思路也很清楚了,首先在ProgressSource的構造器中獲取數據源總長度,然後維護一個currentBytes變量每次讀完累加,相除就是當前的加載進度。
在自定義攔截器中用這個body替換原body:
public class ProcessInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
//將響應體換成我們自定義的響應體(讀取時觸發進度回調)
Request request = chain.request();
Response response = chain.proceed(request);
//String url = request.url().toString();
ResponseBody body = response.body();
Response newResponse = response.newBuilder().body(new ProgressResponseBody(body)).build();
return newResponse;
}
}
最後在自定義GlideModule中創建okhttpclient並add這個攔截器,傳入自定義ModuleLoader即可:
public class MyGlideMoudule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
}
@Override
public void registerComponents(Context context, Glide glide) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(new ProcessInterceptor());
OkHttpClient okHttpClient = builder.build();
glide.register(GlideUrl.class, InputStream.class,new MyModolLoader.Factory(okHttpClient));
}
}
至此,我們已經成功的獲取到了加載進度。但是這肯定不夠,實際開發中一般需要把進度通過回調接口與活動或者其他組件交互。這裏郭霖大神的方案是找個地方放一個全局static Map<String url,回調接口>,每次加載圖片時手動向map中加入回調接口,然後在上面的read中在map裏取出回調接口執行回調方法。
感覺這樣使用起來有點彆扭,想了很久有沒有更好的辦法,也沒想出來。。。。、
二。單次使用自定義加載
上面的方案直接替換了框架對於網絡請求的加載,是全局性的。如果我們想要只進行單個的替換也是可以的:
Glide.with.using(StreamModelLoader).load.into;
來看看StreamModelLoader裏有什麼:
是不是很熟悉,還是找DataFetcher,直接返回我們上面自定義的DataFetcher就可以了。