Xutils3.0技術(shù)分享
1.這個(gè)技術(shù)分享的目的
1.首先要讓大家了解Xutil3.0是什么
Xtuils3.0的前身是Xutils,是兩年前就很火的一個(gè)開(kāi)源框架隐解,包含了很多實(shí)用的Android開(kāi)發(fā)工具鞍帝,支持大文件的上傳以及下載,更加全面的Http請(qǐng)求協(xié)議支持(Get,Post,Delete等等)煞茫,并且擁有更加靈活的ORM框架帕涌,更多的事件注解能夠讓你的代碼更加簡(jiǎn)潔高效,目前之前的Xutils已經(jīng)停止維護(hù) 所有的維護(hù)工作已經(jīng)在Xutils3.0中繼續(xù) 值得注意的是 Xutils3.0最低兼容android4.0
2.Xutils3.0的基本使用方式
在這邊技術(shù)文檔里面 會(huì)介紹Xtuils3.0幾大模塊的基本使用方式:
1.如何發(fā)起一個(gè)Http請(qǐng)求
2.如何使用DB模塊實(shí)現(xiàn)數(shù)據(jù)庫(kù)的增刪改查
3.如何使用BitMapUtils實(shí)現(xiàn)網(wǎng)路圖片以及本地控件的綁定以及顯示
4.如何給控件設(shè)置各種事件(這里主要是通過(guò)注解的方式)
3.Xutils3.0的幾大模塊的介紹以及實(shí)現(xiàn)原理
大家通過(guò)這篇文章會(huì)對(duì)Xtuils3.0的四大模塊有一定的了解 并且會(huì)大致了解每個(gè)模塊的底層實(shí)現(xiàn)续徽,這樣大家在使用的過(guò)程中基本上能夠做到心中有數(shù)
4.如何在項(xiàng)目中使用Xtuils3.0
當(dāng)然我們學(xué)習(xí)框架的主要原因就是要在項(xiàng)目中去使用蚓曼,通過(guò)這篇文檔大家就能夠了解Xtuils3.0的基本使用規(guī)則了,然后就大膽放心的去項(xiàng)目中實(shí)踐吧钦扭,因?yàn)槔碚摻Y(jié)合實(shí)踐才能真正理解這個(gè)框架的設(shè)計(jì)原理以及精髓之處纫版。
2.Xutils3.0的背景介紹
Xtutils的作者是 wyouflf 一個(gè)很牛的android開(kāi)源工作者 ,目前Xutils的六個(gè)500人QQ群已經(jīng)全部爆滿 可見(jiàn)這個(gè)框架的火熱程度以及大家對(duì)這個(gè)框架的關(guān)注 我們感謝作者讓我們有了避免重復(fù)造輪子的前提 對(duì)于進(jìn)度要求很緊的團(tuán)隊(duì) 真的可以直接拿來(lái)用 并且該框架的維護(hù)還是很及時(shí)的客情。目前由于Android系統(tǒng)的不斷更新 Xtuils框架也更新到了3.0版本 之前的版本已經(jīng)不再維護(hù) 所以建議大家使用過(guò)的時(shí)候盡量使用最新的版本其弊。
該開(kāi)源框架的下載地址:https://github.com/wyouflf/xUtils3
3.我們選擇Xutils3.0的原因
我們?yōu)槭裁催x擇Xtuis3.0呢癞己?因?yàn)樗锩姘乃拇竽K基本上能夠解決我們開(kāi)發(fā)中所遇到的問(wèn)題了,比如我們Android開(kāi)發(fā)中經(jīng)常涉及到的就是請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù) 加載網(wǎng)絡(luò)圖片 緩存本地?cái)?shù)據(jù)庫(kù) 以及響應(yīng)用戶事件等等 所有的框架設(shè)計(jì)的初衷基本上都是一致的 那就是封裝一些常見(jiàn)的操作 避免代碼的冗余以及程序結(jié)構(gòu)的臃腫梭伐。當(dāng)然也有很多一些其他的框架痹雅,比如Afinal ,AndroidOne等 也都很優(yōu)秀我們選擇Xtuils的原因主要是因?yàn)橄旅鎺讉€(gè)方面:
1.我們之前的程序中使用的是Xtuils1.0版本 通過(guò)實(shí)踐證明這個(gè)框架穩(wěn)定性方面得到了我們的認(rèn)可
2.Xtuils的入門成本比較低 主要是在于作者封裝的比較好糊识,比如我們要請(qǐng)求一條網(wǎng)絡(luò)數(shù)據(jù)绩社,一行代碼就可以了 這就省去了我們平時(shí)寫代碼的很多事情了,比如我們要設(shè)置請(qǐng)求頭
赂苗、設(shè)置請(qǐng)求參數(shù)以及請(qǐng)求方式 但是通過(guò)該框架我們只需要一句代碼 將必要的參數(shù)穿進(jìn)去就OK了
3.Xtuils的更新速度快 基本上問(wèn)題被拋出來(lái)后 作者以及團(tuán)隊(duì)成員就會(huì)很快跟近并且更新版本愉耙。
基于以上幾點(diǎn) 我們選擇使用Xtuils框架 當(dāng)然目前我們打算替換為最新的3.0版本
4.Xutils3.0的技術(shù)細(xì)節(jié)分解
4.1 Xutils3.0較之前的版本有了哪些改進(jìn)
4.1.1 HTTP實(shí)現(xiàn)替換HttpClient為UrlConnection, 自動(dòng)解析回調(diào)泛型, 更安全的斷點(diǎn)續(xù)傳策略.
4.1.2 支持標(biāo)準(zhǔn)的Cookie策略, 區(qū)分domain, path...
4.1.3 事件注解去除不常用的功能, 提高性能.
4.1.4 數(shù)據(jù)庫(kù)api簡(jiǎn)化提高性能, 達(dá)到和greenDao一致的性能.
4.1.5 圖片綁定支持gif, webp; 支持圓角, 圓形, 方形等裁剪, 支持自動(dòng)旋轉(zhuǎn)等等
4.2 Xutils3.0為什么最低兼容4.0
我們通過(guò)最新的2016年的Android版本分布狀況來(lái)看一下:
如果這個(gè)數(shù)據(jù)不夠直接的話 我們來(lái)看一下Umeng統(tǒng)計(jì)關(guān)于版本分布的情況吧
通過(guò)這兩組數(shù)據(jù) 大家覺(jué)得我們還有必要去維護(hù)4.0以下的版本嗎
這么低的活躍度甚至最新的Umeng統(tǒng)計(jì)已經(jīng)沒(méi)有4.0一下的統(tǒng)計(jì)了, 那些2.X的版本要么是應(yīng)用后臺(tái)自啟動(dòng), 要么都是各個(gè)軟件公司的測(cè)試機(jī).
現(xiàn)在2.3的測(cè)試機(jī)都買不到了, 沒(méi)法保證上線的穩(wěn)定性.
為兼容2.3話費(fèi)巨大的人力和資源, 幾乎沒(méi)有回報(bào), 不值得.
4.3 Xutils3.0能夠提供什么功能
這個(gè)其實(shí)之前我們已經(jīng)提前介紹了,其實(shí)Xtuils3.0能提供的主要功能就是四大模塊對(duì)開(kāi)發(fā)的支持 比如對(duì)HTTP請(qǐng)求 哑梳、圖片的處理劲阎、數(shù)據(jù)庫(kù)的簡(jiǎn)化、事件處理的注解機(jī)制 等四大功能模塊
4.4 Xutils3.0幾大模塊介紹
4.4.1 DbUtils模塊
由于個(gè)人時(shí)間問(wèn)題鸠真,這一模塊我就暫時(shí)不分析了 有興趣的同學(xué)可以根據(jù)其他模塊的邏輯進(jìn)行自行分析 如果我有時(shí)間 會(huì)將這一塊補(bǔ)上去的悯仙。其實(shí)這一塊的大致邏輯 跟之前的Xutils我個(gè)人認(rèn)為也不會(huì)改變太大 所以大家可以作為對(duì)Xutils3.0的深入認(rèn)識(shí)的一次鍛煉 自己分析一下
4.4.2 ViewUtils模塊
其實(shí)這個(gè)模塊是基于注解實(shí)現(xiàn)的 我們首先來(lái)看下這個(gè)模塊能給我們帶來(lái)什么好處而吸引這么多人去使用它呢?
我們來(lái)做一個(gè)對(duì)比:
首先是我們傳統(tǒng)的寫法:
public void initView() {
2.mPager = (CustomViewPager) findViewById(R.id.home_viewPager);
3.paid_tab_ll = (LinearLayout) findViewById(R.id.paid_tab_ll);
4.good_tab_ll = (LinearLayout) findViewById(R.id.good_tab_ll);
5.user_tab_ll = (LinearLayout) findViewById(R.id.user_tab_ll);
6.user_tab_img = (ImageButton) findViewById(R.id.user_tab_img);
7.good_tab_img = (ImageButton) findViewById(R.id.good_tab_img);
8.paid_tab_img = (ImageButton) findViewById(R.id.paid_tab_img);
9.paid_tab_tv = (TextView) findViewById(R.id.paid_tab_tv);
10.good_tab_tv = (TextView) findViewById(R.id.good_tab_tv);
11.user_tab_tv = (TextView) findViewById(R.id.user_tab_tv);
13.}
這段代碼相信Android的小伙伴不會(huì)陌生其實(shí)就是針對(duì)我們?cè)诓季治募袝鴮懙目丶囊恍┏跏蓟僮鱽?lái)找到對(duì)應(yīng)的組件
接下來(lái)我們來(lái)看下使用Xutils3.0之后我們的代碼的寫法:
@ViewInject(R.id.viewPager)
2.private CustomViewPager mPager 這里我們只寫一個(gè)就OK了 其他的類似
發(fā)現(xiàn)沒(méi)有我們不用再去重復(fù)的編寫findViewById了這一大長(zhǎng)串的功能了 有對(duì)比才有有差距 如果讓你去選擇 吠卷,你肯定也會(huì)傾向于使用第二種方式了 是吧 相信大家也猜到了 肯定是這個(gè)ViewInject注解里面做了些什么事情 而省去了我們重復(fù)編寫findviewById方法的麻煩 了解注解的人應(yīng)該已經(jīng)有所領(lǐng)悟了
那我們就來(lái)揭開(kāi)這個(gè)神秘的面紗吧
1.private static void injectObject(Object handler, Class<?> handlerType, ViewFinder finder) {
if (handlerType == null || IGNORED.contains(handlerType)) {
return;
}
// 從父類到子類遞歸
injectObject(handler, handlerType.getSuperclass(), finder);
// inject view
Field[] fields = handlerType.getDeclaredFields();
if (fields != null && fields.length > 0) {
for (Field field : fields) {
Class<?> fieldType = field.getType();
if (
/* 不注入靜態(tài)字段 */ Modifier.isStatic(field.getModifiers()) ||
/* 不注入final字段 */ Modifier.isFinal(field.getModifiers()) ||
/* 不注入基本類型字段 */ fieldType.isPrimitive() ||
/* 不注入數(shù)組類型字段 */ fieldType.isArray()) {
continue;
}
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
try {
View view = finder.findViewById(viewInject.value(), viewInject.parentId());
if (view != null) {
field.setAccessible(true);
field.set(handler, view);
} else {
throw new RuntimeException("Invalid @ViewInject for "
+ handlerType.getSimpleName() + "." + field.getName());
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
}
}
} // end inject view
怎么樣是不是看到了上面紅色標(biāo)記的一行27行很熟悉啊 原來(lái)前端的簡(jiǎn)潔 是因?yàn)楹蠖艘呀?jīng)幫我們處理了麻煩的查找邏輯了
但是我們要想使他生效 的話 必須要執(zhí)行這一行代碼的
x.view().inject(holder, view);
這樣系統(tǒng)加載你這個(gè)類的時(shí)候才會(huì)去初始化你所設(shè)置的注解的值以及初始化工作
其實(shí)這個(gè)模塊就是要求你對(duì)反射以及注解有一個(gè)基本的認(rèn)識(shí)和理解并且能夠在代碼中去使用他們 其實(shí)現(xiàn)在很多框架都是基于反射結(jié)合注解實(shí)現(xiàn)的
4.4.3 HTTP 模塊
首先我們來(lái)說(shuō)一下Xutils3.0中關(guān)于Http模塊優(yōu)秀于其他框架的原因
1.Xutils3.0支持大文件的上傳和下載 當(dāng)然肯定是支持?jǐn)帱c(diǎn)續(xù)傳以及斷點(diǎn)下載的 這是現(xiàn)在上傳下載的必備功能了
2.Xutils3.0支持Http緩存和Cookie緩存 我們來(lái)看一下源碼中的表現(xiàn)吧
從這里看出我們的HttpCache以及Cookie都是通過(guò)數(shù)據(jù)庫(kù) 來(lái)進(jìn)行緩存的 一般我們使用最多大概就是HttpCache了 這是在DBConfig這個(gè)類里面的 并且在構(gòu)造LRUDiskCache的時(shí)候初始化的锡垄。
1.首先我們我們知道HTTP支持多種謂詞比如GET POST等等,Xutils支持11種謂詞 我們從其源碼中就可以看出 我們此處來(lái)看一下源碼中如何表示的:
public enum HttpMethod {
2.GET("GET"),
3.POST("POST"),
4.PUT("PUT"),
5.PATCH("PATCH"),
6.HEAD("HEAD"),
7.MOVE("MOVE"),
8.COPY("COPY"),
9.DELETE("DELETE"),
10.OPTIONS("OPTIONS"),
11.TRACE("TRACE"),
12.CONNECT("CONNECT");
}
我們可以看到 這11種謂詞是通過(guò)一個(gè)枚舉變量HttpMethod來(lái)進(jìn)行表示的 說(shuō)明Xutils支持這么多的請(qǐng)求方式祭隔。這里面我們就不一一介紹了货岭,需要了解的朋友自行百度一下。
2.接下來(lái)我們就通過(guò)一個(gè)簡(jiǎn)單的GET請(qǐng)求來(lái)看看通過(guò)Xtuils我們?nèi)绾伟l(fā)送一個(gè)請(qǐng)求來(lái)請(qǐng)求服務(wù)器的某種資源
- Callback.Cancelable cancelable
= x.http().get(params,
new Callback.CommonCallback<List<BaiduResponse>>() {
4.@Override
5.public void onSuccess(List<BaiduResponse> result) {
6.Toast.makeText(x.app(), result.get(0).toString(), Toast.LENGTH_LONG).show();
7.}
9.@Override
10.public void onError(Throwable ex, boolean isOnCallback) {
11.Toast.makeText(x.app(), ex.getMessage(), Toast.LENGTH_LONG).show();
12.if (ex instanceof HttpException) { // 網(wǎng)絡(luò)錯(cuò)誤
13.HttpException httpEx = (HttpException) ex;
14.int responseCode = httpEx.getCode();
15.String responseMsg = httpEx.getMessage();
16.String errorResult = httpEx.getResult();
17.// ...
18.} else { // 其他錯(cuò)誤
19.// ...
20.}
21.}
23.@Override
24.public void onCancelled(CancelledException cex) {
25.Toast.makeText(x.app(), "cancelled", Toast.LENGTH_LONG).show();
26.}
28.@Override
29.public void onFinished() {
31.}
32.});
看上面的代碼 這就是一個(gè)發(fā)起一個(gè)GET請(qǐng)求所需要的代碼 是不是一句話就解決了 而且還帶有請(qǐng)求成功或者失敗的回調(diào) 是不是很強(qiáng)大呢 疾渴,其實(shí)我們?cè)诖a中當(dāng)發(fā)起一個(gè)GET請(qǐng)求的時(shí)候 我們只需要這么做:
1.封裝一個(gè)請(qǐng)求參數(shù)Params
2.自定義回調(diào)函數(shù)的處理就OK了千贯。
使用起來(lái)就是這么簡(jiǎn)單 ,但是它底層的實(shí)現(xiàn)還是很復(fù)雜的 接下來(lái)我們就從源碼角度去分析作者的設(shè)計(jì)思路搞坝∩η矗 其實(shí)這里教大家一個(gè)分析源碼的方法 ,其實(shí)我們就將源碼導(dǎo)進(jìn)AndroidStdio中 然后我們請(qǐng)求的入口開(kāi)始 一步一步的去跟并且做下標(biāo)記桩撮,這樣不至于層次太深之后頭腦混亂敦第。
接下來(lái)我就帶大家去從x.http.get()這個(gè)入口函數(shù)中開(kāi)始整個(gè)請(qǐng)求過(guò)程的分析:
首先:我們得知道get()方法是在哪個(gè)類里面定義的 通過(guò)源碼我們看到 x.http()返回的是一個(gè)HttpManagerImpl類實(shí)現(xiàn)的 是通過(guò)單例實(shí)現(xiàn)的,這個(gè)單例設(shè)計(jì)模式在Android源碼中也有用到過(guò) 這里就不對(duì)其做詳細(xì)解釋了 其實(shí)單例的寫法很多 目前我們經(jīng)常見(jiàn)到的并且使用頻率很高的是靜態(tài)內(nèi)部類寫法和雙重檢查加鎖機(jī)制來(lái)實(shí)現(xiàn)的加上關(guān)鍵字voliate修飾單例變量 由于JDK的不斷升級(jí) 目前這兩種寫法都算是比較安全并且穩(wěn)定的寫法了店量。這里只貼下源碼即可:
- public static void registerInstance() {
if (instance == null) {
synchronized (lock) {
if (instance == null) {
instance = new HttpManagerImpl();
}
}
}
x.Ext.setHttpManager(instance);
- }
作者就是通過(guò)雙重檢查加鎖來(lái)實(shí)現(xiàn)單例的 芜果,我們來(lái)看下標(biāo)準(zhǔn)的雙重加鎖實(shí)現(xiàn)單例的標(biāo)準(zhǔn)寫法
雙重檢查鎖定看起來(lái)似乎很完美,但這是一個(gè)錯(cuò)誤的優(yōu)化融师!在線程執(zhí)行到第4行代碼讀取到instance不為null時(shí)右钾,instance引用的對(duì)象有可能還沒(méi)有完成初始化。
問(wèn)題的根源
前面的雙重檢查鎖定示例代碼的第7行(instance = new Singleton();)創(chuàng)建一個(gè)對(duì)象。這一行代碼可以分解為如下的三行偽代碼:
memory = allocate(); //1:分配對(duì)象的內(nèi)存空間
ctorInstance(memory); //2:初始化對(duì)象
instance = memory; //3:設(shè)置instance指向剛分配的內(nèi)存地址
上面三行偽代碼中的2和3之間霹粥,可能會(huì)被重排序(在一些JIT編譯器上灭将,這種重排序是真實(shí)發(fā)生的疼鸟,詳情見(jiàn)參考文獻(xiàn)1的“Out-of-order writes”部分)后控。2和3之間重排序之后的執(zhí)行時(shí)序如下:
memory = allocate(); //1:分配對(duì)象的內(nèi)存空間
instance = memory; //3:設(shè)置instance指向剛分配的內(nèi)存地址
//注意,此時(shí)對(duì)象還沒(méi)有被初始化空镜!
ctorInstance(memory); //2:初始化對(duì)象
如果發(fā)生這種情況的話 浩淘,那么就會(huì)出現(xiàn)一個(gè)線程引用了還沒(méi)有初始化的instance 這就是雙重加鎖問(wèn)題的根源
那么其實(shí)上面的問(wèn)題也很好解決 :我們只需要將instance聲明為voliate類型的就能避免重排序造成的隱患
這里我推薦一種更加優(yōu)秀的解決方法
基于類初始化的解決方案
JVM在類的初始化階段(即在Class被加載后,且被線程使用之前)吴攒,會(huì)執(zhí)行類的初始化张抄。在執(zhí)行類的初始化期間,JVM會(huì)去獲取一個(gè)鎖洼怔。這個(gè)鎖可以同步多個(gè)線程對(duì)同一個(gè)類的初始化署惯。
基于這個(gè)特性,可以實(shí)現(xiàn)另一種線程安全的延遲初始化方案(這個(gè)方案被稱之為Initialization On Demand Holder idiom):
public class InstanceFactory {
private static class InstanceHolder {
public static Instance instance = new Instance();
}
public static Instance getInstance() {
return InstanceHolder.instance ; //這里將導(dǎo)致InstanceHolder類被初始化
}
}
假設(shè)兩個(gè)線程并發(fā)執(zhí)行g(shù)etInstance()镣隶,下面是執(zhí)行的示意圖:
好了 這里只是作為一個(gè)小插曲 給大家賣弄一下最簡(jiǎn)單的設(shè)計(jì)模式 這并不是我們這次分享的主要目的 我們接下來(lái)繼續(xù)往下分析:
這里我們知道了x.http()返回的對(duì)象是HttpManagerImpl 那么我就去看看get方法的實(shí)現(xiàn)
@Override
public <T> Callback.Cancelable request(HttpMethod method, RequestParams entity, Callback.CommonCallback<T> callback) {
//設(shè)置請(qǐng)求方法為GET
entity.setMethod(method);
Callback.Cancelable cancelable = null;
if (callback instanceof Callback.Cancelable) {
cancelable = (Callback.Cancelable) callback;
}
// 構(gòu)建一個(gè)HttpTask對(duì)象 然后后調(diào)用x.task().start()方法開(kāi)啟請(qǐng)求
HttpTask<T> task = new HttpTask<T>(entity, cancelable, callback);
return x.task().start(task);
}
1.其實(shí)HttpTask的構(gòu)造函數(shù)里面只是初始化了請(qǐng)求參數(shù)以及回調(diào)函數(shù)的設(shè)置 并且對(duì)線程池執(zhí)行器進(jìn)行初始化极谊。我們看到 對(duì)于我們上面的請(qǐng)求 此時(shí)我們的執(zhí)行器是// init executor
2.if (params.getExecutor() != null) {
3.this.executor = params.getExecutor();
4.} else {
5.if (cacheCallback != null) {
6.this.executor = CACHE_EXECUTOR;
7.} else {
8.this.executor = HTTP_EXECUTOR;
9.}
10.}
這個(gè)HTTP_EXECUTOR是個(gè)什么玩意呢 這里告訴大家 他的類型是PriorityExecutor
是一個(gè)支持優(yōu)先級(jí)的一個(gè)線程執(zhí)行器。
這樣將這個(gè)任務(wù)扔進(jìn)TaskControllerImpl中進(jìn)行執(zhí)行安岂。
我們將整個(gè)的請(qǐng)求框架流程圖畫一下:
這個(gè)只是整個(gè)主要的流程 當(dāng)然內(nèi)部還有很多的細(xì)節(jié) 轻猖,這個(gè)我們就通過(guò)閱讀源碼去了解就可以了。
接下來(lái)我們通過(guò)大致的時(shí)序圖來(lái)帶領(lǐng)大家去熟悉一下整個(gè)的請(qǐng)求過(guò)程:
主要步驟分為5步:
1.調(diào)用x.http().get()發(fā)起請(qǐng)求 然后會(huì)得到HttpManagerImpl的一個(gè)實(shí)例 然后調(diào)用該類的request方法
2.在request方法中創(chuàng)建一個(gè)HttpTask對(duì)象并且內(nèi)部確定了HttpTask的內(nèi)部線程執(zhí)行器默認(rèn)是PriorityExecutor
3.調(diào)用TaskController的實(shí)現(xiàn)類的start將我們剛才創(chuàng)建的HttpTask傳遞過(guò)去 然后構(gòu)建一個(gè)TaskProxy對(duì)象
4.調(diào)用TaskProxy對(duì)象的doBackGround方法
5.然后該方法內(nèi)部調(diào)用HttpTask的doBackGround方法
6 最后將得到的結(jié)果更新到UI線程
其實(shí)這里面主要的邏輯就在第5步 我們?nèi)绾握{(diào)用HTTPTask對(duì)象的doBackground方法得到請(qǐng)求的結(jié)果 接下來(lái)我們就詳細(xì)分析每一步:
這里面從發(fā)起請(qǐng)求到請(qǐng)求返回結(jié)果 一共經(jīng)歷了10步操作
接下來(lái)我們一步步來(lái)進(jìn)行講解:
1.這個(gè)方法實(shí)現(xiàn)如下
// 解析loadType
private void resolveLoadType() {
Class<?> callBackType = callback.getClass();
if (callback instanceof Callback.TypedCallback) {
loadType = ((Callback.TypedCallback) callback).getLoadType();
} else if (callback instanceof Callback.PrepareCallback) {
loadType = ParameterizedTypeUtil.getParameterizedType(callBackType, Callback.PrepareCallback.class, 0);
} else {
loadType = ParameterizedTypeUtil.getParameterizedType(callBackType, Callback.CommonCallback.class, 0);
}
}
其實(shí)這個(gè)方法的作用就是得到我們之前傳進(jìn)來(lái)的CommonCallBack泛型中填寫的參數(shù) 其實(shí)就BaiduResponse 這樣當(dāng)從服務(wù)器得到返回結(jié)果之后 我們就知道要將結(jié)果解析成什么類型的對(duì)象了
2.這一步主要是創(chuàng)建一個(gè)HttpRequest請(qǐng)求
1.private UriRequest createNewRequest() throws Throwable {
2.// init request
3.params.init();
4.UriRequest result = UriRequestFactory.getUriRequest(params, loadType);
5.result.setCallingClassLoader(callback.getClass().getClassLoader());
6.result.setProgressHandler(this);
7.this.loadingUpdateMaxTimeSpan = params.getLoadingUpdateMaxTimeSpan();
8.this.update(FLAG_REQUEST_CREATED, result);
9.return result;
10.}
11.這一步其實(shí)主要是通過(guò)UriRequestFactory.getUriRequest來(lái)獲得一個(gè)UriRequest對(duì)象我們來(lái)看下這個(gè)對(duì)象的實(shí)際類型是什么域那?
1.if (scheme.startsWith("http")) {
2.return new HttpRequest(params, loadType);
這就是這個(gè)方法內(nèi)部最后返回給我們的一個(gè)HttpRequest對(duì)象 然后返回給調(diào)用者
3.主要是檢查下載文件是否沖突的 這個(gè)就請(qǐng)讀者們自行閱讀源碼了 這個(gè)不是很重要 除非你要下載一個(gè)文件時(shí)候需要關(guān)注這一塊
4.其實(shí)就是創(chuàng)建一個(gè)重試的對(duì)象 然后設(shè)置最大的重試次數(shù) 這個(gè)也不多說(shuō)
5.這一步主要是檢查緩存中是否包含我們這次的請(qǐng)求 如果包含就將緩存結(jié)果取出來(lái)然后返回給客戶端 如果沒(méi)有 就繼續(xù)往下走
6.走到這里就會(huì)進(jìn)行while循環(huán) 直到重試次數(shù)大于最大重試次數(shù) 然后循環(huán)體內(nèi)主要是創(chuàng)建了RequestWorker對(duì)象 這是一個(gè)線程 創(chuàng)建完成之后會(huì)調(diào)用他的start方法 然后加入到HttpTask的所在線程中 我們只需關(guān)注這個(gè)線程的run方法中的一句代碼
1.try {
2.this.result = request.loadResult();
3.} catch (Throwable ex) {
4.this.ex = ex;
5.}
這個(gè)request對(duì)象我們已經(jīng)知道 他的類型是HttpRequest 我們來(lái)看下這個(gè)類里的實(shí)現(xiàn)
1.public Object loadResult() throws Throwable {
2.return this.loader.load(this);
3.}
我們發(fā)現(xiàn)其實(shí)調(diào)用了loader對(duì)象的load方法 這個(gè)loader又是個(gè)什么東西呢咙边?
1.public static Loader<?> getLoader(Type type, RequestParams params) {
2.Loader<?> result = converterHashMap.get(type);
3.if (result == null) {
4.result = new ObjectLoader(type);
5.} else {
6.result = result.newInstance();
7.}
8.result.setParams(params);
9.return result;
10.}
我們發(fā)現(xiàn)如果我們沒(méi)有自定義Loader的話 這里返回給我們的就是ObjectLoader的實(shí)體對(duì)象
我們來(lái)看這個(gè)類的load方法
1.@Override
2.public Object load(final UriRequest request) throws Throwable {
3.try {
4.request.sendRequest();
5.} finally {
6.parser.checkResponse(request);
7.}
8.return this.load(request.getInputStream());
9.}
然后此時(shí)調(diào)用了request的sendRequest其實(shí)進(jìn)去這個(gè)方法就知道 這個(gè)方法主要的作用就是設(shè)置請(qǐng)求參數(shù)的 比如添加請(qǐng)求頭 設(shè)置請(qǐng)求體(如果是Post請(qǐng)求的話) 設(shè)置完成之后 我們將isLoading==true 說(shuō)明已經(jīng)處于Loading狀態(tài)了
接下來(lái)就會(huì)調(diào)用第9步 然后利用IOUtils將請(qǐng)求的結(jié)果封裝成我們想要的類型返回給調(diào)用者
最后我們看下返回給調(diào)用者之后做了什么?
我們還記得我們之前是怎么一步一步走到現(xiàn)在的嗎? 是在調(diào)用HttpTask的setResult的方法中開(kāi)始的 而這個(gè)方法的調(diào)用是在TaskProxy類的DoBackGroud方法中調(diào)用的
然后接下來(lái)返回結(jié)果之后呢
- TaskProxy.this.setResult(task.getResult());
// 未在doBackground過(guò)程中取消成功
if (TaskProxy.this.isCancelled()) {
throw new Callback.CancelledException("");
}
// 執(zhí)行成功
TaskProxy.this.onSuccess(task.getResult());
設(shè)置結(jié)果 并且調(diào)用onSucecess方法將結(jié)果傳給UI線程 我們來(lái)看下這個(gè)方法
1.@Override
2.protected void onSuccess(ResultType result) {
3.this.setState(State.SUCCESS);
4.sHandler.obtainMessage(MSG_WHAT_ON_SUCCESS, this).sendToTarget();
5.}
我們看到了熟悉的Handler機(jī)制 見(jiàn)到這個(gè)Handler 我們首先能夠想到的就是肯定在Activity類里面有一個(gè)HanderMessage方法來(lái)處理這個(gè)消息
那么我們來(lái)驗(yàn)證一下
1.final static class InternalHandler extends Handler {
3.private InternalHandler() {
4.super(Looper.getMainLooper());
5.}
7.@Override
8.@SuppressWarnings("unchecked")
9.public void handleMessage(Message msg) {
10.if (msg.obj == null) {
是吧 我們看到了handleMessage方法
1.switch (msg.what) {
case MSG_WHAT_ON_WAITING: {
taskProxy.task.onWaiting();
break;
}
case MSG_WHAT_ON_START: {
taskProxy.task.onStarted();
break;
}
case MSG_WHAT_ON_SUCCESS: {
taskProxy.task.onSuccess(taskProxy.getResult());
break;
}
case MSG_WHAT_ON_ERROR: {
assert args != null;
Throwable throwable = (Throwable) args[0];
LogUtil.d(throwable.getMessage(), throwable);
taskProxy.task.onError(throwable, false);
break;
}
case MSG_WHAT_ON_UPDATE: {
taskProxy.task.onUpdate(msg.arg1, args);
break;
}
case MSG_WHAT_ON_CANCEL: {
if (taskProxy.callOnCanceled) return;
taskProxy.callOnCanceled = true;
assert args != null;
taskProxy.task.onCancelled((org.xutils.common.Callback.CancelledException) args[0]);
break;
}
case MSG_WHAT_ON_FINISHED: {
if (taskProxy.callOnFinished) return;
taskProxy.callOnFinished = true;
taskProxy.task.onFinished();
break;
}
default: {
break;
}
然后我們就知道這是如何調(diào)用到我們之前第一步x.http().get()里的第二個(gè)參數(shù)CommonCallBack的一系列方法的 這樣 整個(gè)請(qǐng)求的過(guò)程我們就分析完了 相信大家都有一定的了解了 所以就大膽的嘗試去使用Http請(qǐng)求吧
4.4.4 ImageRequest模塊
在這里我們不得不提一點(diǎn) Xutils3.0的作者的代碼的精細(xì)程度以及對(duì)各種場(chǎng)景的準(zhǔn)確把握 次员,這或許就是為什么Xutils3.0能夠在這么多的框架當(dāng)中得到這么多用戶的原因 下面我們就列出幾個(gè)場(chǎng)景 你在其他框架看不到但是在xutils3.0中卻能看到很精妙的解決方案
并且Xutils3.0中對(duì)圖片的請(qǐng)求下載也是支持?jǐn)帱c(diǎn)的 這跟你下載文件是一個(gè)邏輯
場(chǎng)景1: 我們打開(kāi)一個(gè)頁(yè)面 展示很多圖片 比如一個(gè)LIstview然后呢 我們點(diǎn)擊item之后跳到另一個(gè)頁(yè)面之后 也是一個(gè)圖片的列表 此時(shí)呢 第一個(gè)頁(yè)面并沒(méi)有被銷毀 那么imageview所持有的圖片也沒(méi)有被銷毀 然后第二個(gè)頁(yè)面加載圖片的時(shí)候 我們是往同一個(gè)MemearyCache中添加緩存的 如果超過(guò)我們?cè)O(shè)定的緩存的大小呢 就會(huì)將第一個(gè)頁(yè)面中緩存的頁(yè)面給清除掉 當(dāng)我們回到第一個(gè)頁(yè)面中 可能就會(huì)因?yàn)榫彺嬷幸呀?jīng)被清楚 而從磁盤加載圖片此時(shí)效率可能就會(huì)受影響 從而導(dǎo)致圖片的閃爍 而這段代碼的目的就是 如果我們第一個(gè)頁(yè)面的view所持有的圖片資源還沒(méi)有被銷毀 那就直接將它添加到緩存中去 然后接下來(lái)我們請(qǐng)求就是從內(nèi)存緩存中讀取而不是磁盤緩存了 這樣就能夠避免這種場(chǎng)景下導(dǎo)致的加載延遲或者頁(yè)面閃爍現(xiàn)象了
場(chǎng)景2:
當(dāng)前屏幕能夠顯示 3個(gè)item 那么就會(huì)調(diào)用三次bind方法
前三次imageview都為null 然后會(huì)進(jìn)行加載將imageview設(shè)置AsyncDrawable
此時(shí)進(jìn)入第四個(gè)Item 此時(shí)復(fù)用第一個(gè)item的布局 但是imageview的對(duì)象沒(méi)有變 但是關(guān)聯(lián)的數(shù)據(jù)已經(jīng)變了 那么之前進(jìn)入屏幕外的第一個(gè)item的圖片的加載過(guò)程可能還沒(méi)完成 也可能已經(jīng)完成了
假如沒(méi)有完成 此時(shí)呢 這段代碼就能起到 去取消那個(gè)請(qǐng)求 但是如果此時(shí)用戶又很快滑動(dòng)到第一個(gè)item此時(shí)判斷key相同 那么就什么都不做了 因?yàn)橹案鷌mageview關(guān)聯(lián)的Imageloade就會(huì)繼續(xù)之前的操作败许。
那么作者是如何巧妙的解決上述兩種背景引起的BUG呢 其實(shí)就是很簡(jiǎn)單的一段代碼以及自定義Drawable就解決了
那我們先睹為快 然后再一步一步帶領(lǐng)你去分析實(shí)現(xiàn)的原理
// stop the old loader
MemCacheKey key = new MemCacheKey(url, localOptions);
Drawable oldDrawable = view.getDrawable();
if (oldDrawable instanceof AsyncDrawable) {
ImageLoader loader = ((AsyncDrawable) oldDrawable).getImageLoader();
if (loader != null && !loader.stopped) {
if (key.equals(loader.key)) {
// repetitive url and options binding to the same View.
// not need callback to ui.
// key相同不做處理, url沒(méi)有變換.
// key不同, 取消之前的, 開(kāi)始新的加載
return null;
} else {
loader.cancel();
}
}
} else if (oldDrawable instanceof ReusableDrawable) {
MemCacheKey oldKey = ((ReusableDrawable) oldDrawable).getMemCacheKey();
if (oldKey != null && oldKey.equals(key)) {
MEM_CACHE.put(key, oldDrawable);
}
}
就是上述代碼就解決了我們場(chǎng)景1和2中可能遇到的比如說(shuō)圖片閃爍或者超出屏幕之外的不必要的請(qǐng)求等問(wèn)題。
下面我們就通過(guò)整個(gè)流程來(lái)分析一下作者的實(shí)現(xiàn)思路淑蔚。
首先我們來(lái)看下Bitmap這個(gè)模塊設(shè)置圖片的流程圖
其實(shí)如果你看了比較流行的UIL以及Volley等框架的網(wǎng)絡(luò)圖片的加載的話 你會(huì)發(fā)現(xiàn)其實(shí)他們的流程基本是一致的 也就是說(shuō)我們加載圖片的整個(gè)過(guò)程基本是類似的 不同只是一些代碼的實(shí)現(xiàn)細(xì)節(jié)方面 比如緩存機(jī)制啊 網(wǎng)絡(luò)加載機(jī)制啊 等等市殷。
所以我們就根據(jù)這個(gè)流程圖來(lái)看一下這個(gè)模塊的設(shè)計(jì)
Xutils3.0中對(duì)于圖片的加載遵循其實(shí)也遵循上面的那個(gè)流程圖 雖然這個(gè)流程圖是Xutils第一個(gè)版本的 但是對(duì)于Xutils3.0來(lái)說(shuō)照樣適用 我們來(lái)從源碼中來(lái)分析一下
-
x.image().bind(holder.imgItem,
imgSrcList.get(position),
imageOptions,
new CustomBitmapLoadCallBack(holder));
首先綁定Imageview 并且設(shè)置配置參數(shù) 以及回調(diào)函數(shù)
2.static Cancelable doBind(final ImageView view,
final String url,
final ImageOptions options,
final Callback.CommonCallback<Drawable> callback) {
這個(gè)是調(diào)用ImageLoader類的doBind方法實(shí)現(xiàn)ImageView和ImageLoader的綁定 然后我們來(lái)看一下這個(gè)方法里面的核心代碼邏輯:第一步: // check params
ImageOptions localOptions = options;
{
if (view == null) {
postArgsException(null, localOptions, "view is null", callback);
return null;
}if (TextUtils.isEmpty(url)) { postArgsException(view, localOptions, "url is null", callback); return null; } if (localOptions == null) { localOptions = ImageOptions.DEFAULT; } localOptions.optimizeMaxSize(view); }
這個(gè)就是首先對(duì)我們配置的圖片的Options進(jìn)行檢查 這個(gè)沒(méi)什么好說(shuō)的
第二步:
// stop the old loader
MemCacheKey key = new MemCacheKey(url, localOptions);
Drawable oldDrawable = view.getDrawable();
//每一個(gè)View都會(huì)綁定一個(gè)Drawable
//如果加載出來(lái)的類型都是ReusableDrawable 沒(méi)有加載出來(lái)之前都是AsyncDrawable
if (oldDrawable instanceof AsyncDrawable) {
ImageLoader loader = ((AsyncDrawable) oldDrawable).getImageLoader();
if (loader != null && !loader.stopped) {
if (key.equals(loader.key)) {
// repetitive url and options binding to the same View.
// not need callback to ui.
// key相同不做處理, url沒(méi)有變換.
// key不同, 取消之前的, 開(kāi)始新的加載
return null;
} else {
loader.cancel();
}
}
} else if (oldDrawable instanceof ReusableDrawable) {
MemCacheKey oldKey = ((ReusableDrawable) oldDrawable).getMemCacheKey();
if (oldKey != null && oldKey.equals(key)) {
MEM_CACHE.put(key, oldDrawable);
}
}
關(guān)鍵難點(diǎn):
這一步就是我們?cè)谶@個(gè)模塊開(kāi)頭處提到的處理邏輯了 其實(shí)實(shí)現(xiàn)原理就是 當(dāng)我們Imageview綁定一個(gè)drawable的時(shí)候但是并沒(méi)有被銷毀的時(shí)候 我們是可以獲取imageview綁定的drawable對(duì)象 后面當(dāng)初次加載的時(shí)候都會(huì)將imageview設(shè)置為asyncDrawable表示正在加載也就是正在請(qǐng)求網(wǎng)絡(luò)下載圖片 然后當(dāng)用戶不同滑動(dòng)ListView或者不同頁(yè)面之間的跳轉(zhuǎn) 重新執(zhí)行到這里的時(shí)候 我們就可以根據(jù)Imageview綁定的drawable對(duì)象 從而獲取跟這個(gè)drawable對(duì)象關(guān)聯(lián)的ImageLoader對(duì)象 然后根據(jù)加載的key來(lái)決定我們是否正在在一個(gè)相同的iamgeview加載同一個(gè)圖片 還是該Imageview已經(jīng)被復(fù)用但是關(guān)聯(lián)的圖片資源key卻改變的情況 這種情況 我們就取消之前的加載 因?yàn)樗呀?jīng)在屏幕外了 對(duì)用戶來(lái)說(shuō)已經(jīng)沒(méi)有加載的必要了就調(diào)用loader.cancle方法了
假如 圖片加載很快 用戶往下拉之后 很快又往上滑動(dòng)listview 此時(shí)呢 我們的imageview關(guān)聯(lián)的drawable已經(jīng)加載完畢 此時(shí)類型就是reusableDrawable了 此時(shí)我們就判斷key是否相同 ,如果相同 那么就將該drawable 放進(jìn)內(nèi)存緩存中 我們就沒(méi)必要進(jìn)行網(wǎng)絡(luò)請(qǐng)求了 這也解決了因?yàn)閮?nèi)存不夠 不斷滑動(dòng)屏幕或者切換頁(yè)面 導(dǎo)致內(nèi)存緩存不足 之前的緩存被清理掉 然后因?yàn)橐獜?磁盤或者網(wǎng)絡(luò)重新加載導(dǎo)致的屏幕閃爍問(wèn)題了束倍。
第三步:
// load from Memory Cache
Drawable memDrawable = null;
if (localOptions.isUseMemCache()) {
memDrawable = MEM_CACHE.get(key);
if (memDrawable instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) memDrawable).getBitmap();
if (bitmap == null || bitmap.isRecycled()) {
memDrawable = null;
}
}
}
if (memDrawable != null) { // has mem cache
boolean trustMemCache = false;
try {
if (callback instanceof ProgressCallback) {
((ProgressCallback) callback).onWaiting();
}
// hit mem cache
view.setScaleType(localOptions.getImageScaleType());
view.setImageDrawable(memDrawable);
trustMemCache = true;
if (callback instanceof CacheCallback) {
trustMemCache = ((CacheCallback<Drawable>) callback).onCache(memDrawable);
if (!trustMemCache) {
// not trust the cache
// load from Network or DiskCache
return new ImageLoader().doLoad(view, url, localOptions, callback);
}
} else if (callback != null) {
callback.onSuccess(memDrawable);
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
// try load from Network or DiskCache
trustMemCache = false;
return new ImageLoader().doLoad(view, url, localOptions, callback);
} finally {
if (trustMemCache && callback != null) {
try {
callback.onFinished();
} catch (Throwable ignored) {
LogUtil.e(ignored.getMessage(), ignored);
}
}
}
} else {
// load from Network or DiskCache
return new ImageLoader().doLoad(view, url, localOptions, callback);
}
從英文注釋 我們就可以明白 這個(gè)其實(shí)就判斷是否內(nèi)存緩存中存在我們想要的結(jié)果 如果存在就取出來(lái)然后調(diào)用回調(diào)方法 展示出來(lái)
但是這里面要注意的是 如果我們的回調(diào)函數(shù)的類型是CacheCallBack類型的話 那么是否從緩存中取就取決于 CacheCallBack的oncache方法的返回值了 如果為false 那就從網(wǎng)絡(luò)或者DISK中獲取了被丧。
第四步 其實(shí)就是 if (memDrawable != null) { // has mem cache
當(dāng)if判斷走else邏輯的時(shí)候 我們會(huì)請(qǐng)求網(wǎng)絡(luò)加載數(shù)據(jù)并放進(jìn)內(nèi)存緩存和磁盤緩存
return new ImageLoader().doLoad(view, url, localOptions, callback);
也就是會(huì)走這里面的邏輯。
大致步驟基本就是這樣 绪妹,但是我在研究源碼的時(shí)候曾經(jīng)阻塞在一個(gè)地方甥桂,就是我調(diào)用請(qǐng)求要求返回結(jié)果 我傳進(jìn)去的泛型參數(shù)是Drawable類型的 但是為什么最后變化成了AsyncDrawable 或者ReusableDrawable 這個(gè)其實(shí)就是第二步當(dāng)中我們判斷邏輯的實(shí)現(xiàn)的重要的一部分,我經(jīng)過(guò)多次閱讀這部分的源碼 終于明白了作者的設(shè)計(jì)意圖和實(shí)現(xiàn)方法
接下來(lái) 我就帶大家回顧一下我是如何找到這個(gè)答案的邮旷。
我們接著上面的第四步繼續(xù)往下分析:
在 return new ImageLoader().doLoad(view, url, localOptions, callback);
這個(gè)方法的最后調(diào)用了 cancelable = x.http().get(params, this); 看到這里大家應(yīng)該很熟悉了 這其實(shí)就是我們之前分析過(guò)的Http請(qǐng)求的邏輯了 這個(gè)如果不明白的就往回自己看下 這里對(duì)于這一塊的 如果一致的我就不重復(fù)啰嗦 我只列出來(lái)不一樣的地方
經(jīng)過(guò)前面Http模塊的分析 我們都知道 x.http().get(params, this)這個(gè)會(huì)調(diào)用HttpManangerImpl的request方法 這里要注意一點(diǎn) 我們傳進(jìn)去的this的類型是ImageLoader類型 這個(gè)類實(shí)現(xiàn)了四個(gè)接口
/*此時(shí)callback類型是ImageLoader類型這個(gè)類實(shí)現(xiàn)了
Callback.PrepareCallback<File, Drawable>,
Callback.CacheCallback<Drawable>,
Callback.ProgressCallback<Drawable>,
Callback.TypedCallback<Drawable>,
Callback.Cancelable 這幾個(gè)接口 然后構(gòu)造HttpTask的時(shí)候?qū)mageloader作為成員變量傳進(jìn)去 */
所以這里跟之前分析得不同的地方就是 當(dāng)我們構(gòu)造HttpTask對(duì)象的時(shí)候
//這里如果傳遞過(guò)來(lái)的是IamgeLoader類型的話 那么cacheCallback prepareCallback progressCallback 都會(huì)被賦值
if (callback instanceof Callback.CacheCallback) {
this.cacheCallback = (Callback.CacheCallback<ResultType>) callback;
}
if (callback instanceof Callback.PrepareCallback) {
this.prepareCallback = (Callback.PrepareCallback) callback;
}
if (callback instanceof Callback.ProgressCallback) {
this.progressCallback = (Callback.ProgressCallback<ResultType>) callback;
}
這些回調(diào)變量都會(huì)被賦值
然后在調(diào)用HttpTask的DoBackGroud方法的時(shí)候 所解析出來(lái)的請(qǐng)求類型是這樣的
// 解析loadType
private void resolveLoadType() {
Class<?> callBackType = callback.getClass();
if (callback instanceof Callback.TypedCallback) {
loadType = ((Callback.TypedCallback) callback).getLoadType();
} else if (callback instanceof Callback.PrepareCallback) {
loadType = ParameterizedTypeUtil.getParameterizedType(callBackType, Callback.PrepareCallback.class, 0);
} else {
loadType = ParameterizedTypeUtil.getParameterizedType(callBackType, Callback.CommonCallback.class, 0);
}
}
這個(gè)loadType的返回值就是File類型了 ((Callback.TypedCallback) callback).getLoadType();的實(shí)現(xiàn)
其實(shí)就是ImageLoader的實(shí)現(xiàn)
@Override
public Type getLoadType() {
return loadType;
}
而這個(gè)返回值是ImageLoader的成員變量
private static final Type loadType = File.class;
那么我們調(diào)用HttpTask的DoBackGroud方法中的try {
this.result = request.loadResult();
} catch (Throwable ex) {
this.ex = ex;
}的loadResult方法
public Object loadResult() throws Throwable {
return this.loader.load(this);
}
這里的loader類型其實(shí)就FileLoader了
public File load(final UriRequest request) throws Throwable 最后調(diào)用這個(gè)函數(shù) 去下載圖片資源 然后將下載下來(lái)的圖片對(duì)象返回給調(diào)用者
在這個(gè)方法里面我們涉及到了DiskCacheFile這個(gè)變量 其實(shí)這個(gè)就是我們之前所說(shuō)的緩存機(jī)制中的磁盤緩存了 在這個(gè)方法里面會(huì) initDiskCacheFile(request); 初始化這個(gè)變量 然后當(dāng)請(qǐng)求數(shù)據(jù)完成之后 會(huì)對(duì)這個(gè)變量進(jìn)行賦值
if (diskCacheFile != null) {
DiskCacheEntity entity = diskCacheFile.getCacheEntity();
entity.setLastAccess(System.currentTimeMillis());
entity.setEtag(request.getETag());
entity.setExpires(request.getExpiration());
entity.setLastModify(new Date(request.getLastModified()));
}
這里涉及到了Http協(xié)議中關(guān)于緩存這一塊的東西 這邊不了解的可以網(wǎng)上自行百度 這里不詳細(xì)討論 我們只說(shuō)這幾個(gè)變量的含義是什么意思
Request 請(qǐng)求頭
Cache-Control: max-age=0 以秒為單位
If-Modified-Since: Mon, 19 Nov 2012 08:38:01 GMT 緩存文件的最后修改時(shí)間黄选。
If-None-Match: "0693f67a67cc1:0" 緩存文件的Etag值
Cache-Control: no-cache 不使用緩存
Pragma: no-cache 不使用緩存
Response header
Cache-Control: public 響應(yīng)被緩存,并且在多用戶間共享, (公有緩存和私有緩存的區(qū)別办陷,請(qǐng)看另一節(jié))
Cache-Control: private 響應(yīng)只能作為私有緩存貌夕,不能在用戶之間共享
Cache-Control:no-cache 提醒瀏覽器要從服務(wù)器提取文檔進(jìn)行驗(yàn)證
Cache-Control:no-store 絕對(duì)禁止緩存(用于機(jī)密,敏感文件)
Cache-Control: max-age=60 60秒之后緩存過(guò)期(相對(duì)時(shí)間)
Date: Mon, 19 Nov 2012 08:39:00 GMT 當(dāng)前response發(fā)送的時(shí)間
Expires: Mon, 19 Nov 2012 08:40:01 GMT 緩存過(guò)期的時(shí)間(絕對(duì)時(shí)間)
Last-Modified: Mon, 19 Nov 2012 08:38:01 GMT 服務(wù)器端文件的最后修改時(shí)間
ETag: "20b1add7ec1cd1:0" 服務(wù)器端文件的Etag值
如果想詳細(xì)了解的朋友 請(qǐng)進(jìn)入這里 作者很相信的講解了關(guān)于Http協(xié)議中關(guān)于緩存這一塊的知識(shí)
這里我就簡(jiǎn)單說(shuō)一下客戶端與服務(wù)器端關(guān)于緩存機(jī)制的配合 我們來(lái)看一個(gè)圖:
通過(guò)最后修改時(shí)間, 來(lái)判斷緩存新鮮度
- 瀏覽器客戶端想請(qǐng)求一個(gè)文檔民镜, 首先檢查本地緩存啡专,發(fā)現(xiàn)存在這個(gè)文檔的緩存, 獲取緩存中文檔的最后修改時(shí)間制圈,通過(guò): If-Modified-Since们童, 發(fā)送Request給Web服務(wù)器。
- Web服務(wù)器收到Request鲸鹦,將服務(wù)器的文檔修改時(shí)間(Last-Modified): 跟request header 中的慧库,If-Modified-Since相比較, 如果時(shí)間是一樣的馋嗜, 說(shuō)明緩存還是最新的齐板, Web服務(wù)器將發(fā)送304 Not Modified給瀏覽器客戶端, 告訴客戶端直接使用緩存里的版本葛菇。如下圖甘磨。
- 假如該文檔已經(jīng)被更新了。Web服務(wù)器將發(fā)送該文檔的最新版本給瀏覽器客戶端熟呛, 如下圖宽档。
ETag是實(shí)體標(biāo)簽(Entity Tag)的縮寫庵朝, 根據(jù)實(shí)體內(nèi)容生成的一段hash字符串(類似于MD5或者SHA1之后的結(jié)果)贡歧,可以標(biāo)識(shí)資源的狀態(tài)。 當(dāng)資源發(fā)送改變時(shí)宣羊,ETag也隨之發(fā)生變化。
ETag是Web服務(wù)端產(chǎn)生的,然后發(fā)給瀏覽器客戶端泼舱。瀏覽器客戶端是不用關(guān)心Etag是如何產(chǎn)生的尺迂。
為什么使用ETag呢枪狂? 主要是為了解決Last-Modified 無(wú)法解決的一些問(wèn)題。
- 某些服務(wù)器不能精確得到文件的最后修改時(shí)間皇拣, 這樣就無(wú)法通過(guò)最后修改時(shí)間來(lái)判斷文件是否更新了氧急。
- 某些文件的修改非常頻繁哑蔫,在秒以下的時(shí)間內(nèi)進(jìn)行修改. Last-Modified只能精確到秒嵌纲。
- 一些文件的最后修改時(shí)間改變了,但是內(nèi)容并未改變。 我們不希望客戶端認(rèn)為這個(gè)文件修改了墓臭。
http://www.cnblogs.com/TankXiao/archive/2012/11/28/2793365.html
最后將下載下來(lái)的圖片保存在本地磁盤緩存中 大家可能還是不明白是如何將Dradble轉(zhuǎn)換為Async和Resueable類型的 這里我們就要進(jìn)入HttpTask的DoBackGroud方法中的檢查緩存那一塊了
因?yàn)橹拔覀兎治龅腍ttpget 模塊因?yàn)闆](méi)有使用緩存 直接略過(guò)了 但是我們調(diào)用ImageLoader加載圖片時(shí)候 就要使用緩存了 我們來(lái)看一下:
// 檢查緩存
Object cacheResult = null;
if (cacheCallback != null && HttpMethod.permitsCache(params.getMethod())) {
// 嘗試從緩存獲取結(jié)果, 并為請(qǐng)求頭加入緩存控制參數(shù).
try {
clearRawResult();
LogUtil.d("load cache: " + this.request.getRequestUri());
rawResult = this.request.loadResultFromCache();
} catch (Throwable ex) {
LogUtil.w("load disk cache error", ex);
}
if (this.isCancelled()) {
clearRawResult();
throw new Callback.CancelledException("cancelled before request");
}
if (rawResult != null) {
if (prepareCallback != null) {
try {
cacheResult = prepareCallback.prepare(rawResult);
} catch (Throwable ex) {
cacheResult = null;
LogUtil.w("prepare disk cache error", ex);
} finally {
clearRawResult();
}
} else {
cacheResult = rawResult;
}
因?yàn)檫@里callback實(shí)現(xiàn)了cachecallback 并且我們使用的是get方法 所以if為true進(jìn)入代碼塊里面執(zhí)行
因?yàn)檫@里prepareCallback 也不為空 所以就調(diào)用了prepare 方法 這里進(jìn)入了Imageloader的prepare 方法 我們來(lái)看一下 :
@Override
public Drawable prepare(File rawData) {
if (!validView4Callback(true)) return null;
try {
Drawable result = null;
if (prepareCallback != null) {
result = prepareCallback.prepare(rawData);
}
if (result == null) {
result = ImageDecoder.decodeFileWithLock(rawData, options, this);
}
if (result != null) {
if (result instanceof ReusableDrawable) {
((ReusableDrawable) result).setMemCacheKey(key);
MEM_CACHE.put(key, result);
}
}
return result;
} catch (IOException ex) {
IOUtil.deleteFileOrDir(rawData);
LogUtil.w(ex.getMessage(), ex);
}
return null;
}
看到這里 我們找到了ImageDecoder.decodeFileWithLock 這里其實(shí)就是真正進(jìn)行轉(zhuǎn)換的地方 我們進(jìn)去看下源碼就立馬清楚了
f (bitmap != null) {
result = new ReusableBitmapDrawable(x.app().getResources(), bitmap);
}
這個(gè)方法最后對(duì)bitmap進(jìn)行了封裝 然后將封裝后的結(jié)果返回給調(diào)用者 看到了吧 類型是
ReusableBitmapDrawable
然后在ImageLoder的prepare方法中將ImageDecoder.decodeFileWithLock 返回的結(jié)果 if (result != null) {
if (result instanceof ReusableDrawable) {
((ReusableDrawable) result).setMemCacheKey(key);
MEM_CACHE.put(key, result);
}
}放進(jìn)內(nèi)存緩存中 這就是整個(gè)的緩存的流程
相信大家可能還是有點(diǎn)云里霧里 但是我覺(jué)得大致思路知道了之后i 自己再去看一遍源碼 我覺(jué)得就應(yīng)該能夠清楚了吧
這段邏輯比較復(fù)雜 所以這里我們使用一個(gè)時(shí)序圖幫大家屢一下思路
上面這個(gè)時(shí)序圖就是整個(gè)請(qǐng)求網(wǎng)絡(luò)圖片的過(guò)程 起便,其中有一段是關(guān)于圖片緩存的這一塊比較復(fù)雜 所以我會(huì)單獨(dú)再畫一個(gè)時(shí)序圖 來(lái)專門講解圖片緩存的時(shí)序圖
這里面有一個(gè)關(guān)鍵的地方就是在HttpTask的doBackgroud方法中 有一段這樣的代碼:
if (cacheResult != null) {
// 同步等待是否信任緩存
this.update(FLAG_CACHE, cacheResult);
while (trustCache == null) {
synchronized (cacheLock) {
try {
cacheLock.wait();
} catch (Throwable ignored) {
}
}
}
// 處理完成
if (trustCache) {
return null;
}
}
這段代碼的作用就是給你一個(gè)機(jī)會(huì)讓你去調(diào)用你實(shí)現(xiàn)的OncacheCallback中的oncache方法 然后根據(jù)返回結(jié)果 來(lái)決定是否進(jìn)行下一步的網(wǎng)絡(luò)請(qǐng)求 如果你oncache方法返回true說(shuō)明你信任緩存 此時(shí)直接return null 就不再往下進(jìn)行 直接使用緩存的值
如果不信任緩存的話 那么就進(jìn)行網(wǎng)絡(luò)請(qǐng)求
好了 IamgeFragment這一模塊也基本分析完成了 幾個(gè)需要注意的點(diǎn)就是:
1.緩存機(jī)制是如何使用的?包括內(nèi)存緩存和磁盤緩存
2.我們自己設(shè)置的CacheCallback在什么時(shí)候調(diào)用的 起到了什么作用
3.關(guān)于Http協(xié)議的緩存部分的理解
我相信 只要上面三點(diǎn)弄明白了 我覺(jué)得這一塊也就沒(méi)什么問(wèn)題了
5.Xutils3.0總結(jié)
整個(gè)Xutils3.0的源碼基本分析完成了 榆综,整個(gè)框架還是很不錯(cuò)的 整體的設(shè)計(jì)也比較簡(jiǎn)潔,我相信讀者看完我的分析之后 對(duì)于日常當(dāng)中的使用應(yīng)該不會(huì)有太大問(wèn)題鼻疮,出問(wèn)題了 就深入去看源碼 你就會(huì)找到根源 基本能夠徹底的解決開(kāi)發(fā)過(guò)程中出現(xiàn)的BUG了
下面簡(jiǎn)單總結(jié)一下吧:
1.學(xué)會(huì)使用Xutils3.0請(qǐng)求服務(wù)器數(shù)據(jù)以及請(qǐng)求參數(shù)的設(shè)置
2.掌握Xutils3.0中關(guān)于緩存機(jī)制的應(yīng)用
3.了解Xutils3.0中對(duì)Google官方推薦內(nèi)存緩存LRU的使用方法
4.能夠在自己的項(xiàng)目當(dāng)中使用Xutils3.0進(jìn)行網(wǎng)絡(luò) 怯伊、數(shù)據(jù)庫(kù)判沟、圖片的處理
我相信 學(xué)以致用才是最好的理解一個(gè)框架的方法 只有在使用過(guò)程當(dāng)中 發(fā)現(xiàn)問(wèn)題解決問(wèn)題 才能夠真正理解作者的設(shè)計(jì)思想以及提高自身代碼質(zhì)量挪哄。
初次分享 ,可能有很多地方表達(dá)不是很流暢砂碉,希望大家指正 也幫我彌補(bǔ)自己的不足 增蹭。