Retrofit
對網絡請求接口的封裝,定義網絡請求方法的接口,及添加方法的註解和參數。內部經過動態代理攔截須要處理的接口,並把註解和方法參數解析成須要的http api請求,給OkHttp庫進行實際的網絡請求。
A、Retrofit的設計模式
1、構建者模式
一、將複雜對象的構建與表示相分離
不關心成員對象的建立,直接調用Builder()內部類經過鏈式調用內部不一樣方法,知足成員的初始化操做。
Retrofit的構建者模式:
建立Retrofit 對象使用到了build構建者模式,利用構建者模式的Build()內部類進行Retrofit成員變量的初始化,最後經過build()完成建立。
優勢:
簡便易操做,若不適用構建者模式就須要建立Retrofit構造方法來初始化操做,不一樣的場景須要構造不一樣的Retrofit對象。
二、針對不一樣的網絡場景須要配置不一樣的環境參數,這時候就須要用到了addCallAdapterFactory 網絡適配器,解析的效果是不一樣的。
ps:ServiceMethod 等對象
2、工廠模式
addCallAdapterFactory(xx.create) :添加返回數據所要支持的格式。
Factory abstract內部類,三大函數方法get() 、getRawType()、getParameterUpperBound()
abstract class Factory {
protected static Type getParameterUpperBound(int index, ParameterizedType type) {
return Utils.getParameterUpperBound(index, type);
}
protected static Class<?> getRawType(Type type) {
return Utils.getRawType(type);
}
//讓不一樣類型的(可自定義)工廠繼承CallAdapter,經過實現不一樣的get()方法,返回不一樣類型的Calladatper使用
public abstract @Nullable CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,
Retrofit retrofit);
在get()方法中有三個實現類,
RxJavaCallAdapter、
DefaultCallAdapter、
ExecutorCallAdapter
A、RxJavaCallAdapter:
CallAdapter<Observable<?>> callAdapter = getCallAdapter(returnType, scheduler);
在RxJavaCallAdapter裏面返回的類型,傳遞的類型是範型的Observable
private CallAdapter<Observable<?>> getCallAdapter(Type returnType, Scheduler scheduler) {
B、DefaultCallAdapter:返回的是
CallAdapter<Object, Call<?>>(){}通配符
ps:如何添加一個新的CallAdapter呢?
答:extends繼承 這個Factory類,並覆蓋get方法:就能添加新的CallAdapter使用
@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit){}
靜態工廠模式:簡單工廠模式
實例:Platform類
含有一個靜態域:private static final Platform PLATFORM = findPlatform();
//經過findPlatform()返回給客戶端調用,findPlatform就是一個靜態工廠方法
private static Platform findPlatform(){
try{
//根據Class.forName的返回值來判斷所須要不一樣的平臺,默認的是android平臺
Class.forName(「android.os.Build」);
}
...
}
靜態工廠與動態工廠的區別
3、外觀模式
外部與子系統的通訊方式是必須經過一個統一的外觀對象(門面)進行,爲這個子系統的接口提供一個一致的界面
特色:高內聚 低耦合 對外提供一個統一的接口
在客戶端 和子系統間 定義一個高級接口,橋接二者
客戶端只須要跟中間類交互就能夠,中間類把具體操做和子系統進行內部交互,再把結果交還給客戶端
在Retrofit中的使用:
Retrofit的初始化操做就使用了外觀模式,Retrofit自身就至關於外觀類,內部封裝所須要的7大成員變量的初始化操做
4、策略模式
根據環境和條件的不一樣選擇不一樣的方法策略,完成須要的任務。
Context:上下文角色;
維護一個策略角色的引用;定義一個接口ContextInterface()。來讓Strategy來訪問Context的數據
使用Strategy的接口來調用某個定義的具體的Strategy策略算法
Strategy:策略角色;
不一樣的具體策略須要實現的方法在該方法中定義
ConcreteStrategy xx :具體的策略
Retrofit 中的使用:
addCallAdapterFactory(xx.create) 設置平臺適配模式
interface CallAdapter<T> 接口就是提到的抽象的Strategy
具體的策略實現:CallAdapter的實現類完成
在addCallAdapterFactory中()裏會生成一個具體的adapter工廠,實際上就是一種具體的策略
Context:在Retrofit當中配置的類就是對應的Context
與工廠模式的區別:
5、適配器模式
將一個接口轉換成客戶端但願的另一個接口,使與這個接口本不兼容的類能夠在一塊兒協同工做
Android當中會將封裝的OkHttpCall和主線程executor兩個實例對象適配成一個新的實例對象,這樣這個call就具有了UI線程切換的功能
@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) != Call.class) {
return null;
}
final Type responseType = Utils.getCallResponseType(returnType);
return new CallAdapter<Object, Call<?>>() {
@Override
public Type responseType() {
return responseType;
}
@Override
public Call<Object> adapt(Call<Object> call) {
return call;
}
};
}
因爲CallAdapter支持多種平臺,因此不一樣的平臺須要適配不一樣的Calladapter
經過適配器模式:
將平臺特性和該OkHttpCall適配成可使用的Call的實例
6、觀察者模式-一對多
一個對象和多個對象之間的依賴關係,當一個被觀察者對象發生變化的時候會自動通知其餘觀察他的對象做出相應的改變,這些觀察者們之間能夠沒有相互聯繫,增長代碼的可擴展性。
Retrofit中:
一、建立Retrofit實例
二、定義一個含有註解的接口方法
三、經過調用Retrofit的Create方法完成網絡請求接口實例的建立
四、經過動態代理將定義的接口以及接口裏的註解、方法和參數轉換成Retrofit中OkHttpCall對象call再進行網絡請求
Create()方法就是使用了動態代理的所在之處:
public <T> T create(final Class<T> service) {
...
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
//在invoke方法裏實現java的反射機制的實現
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
...
}
全部的網絡請求相關的操做確定支持異步請求,而異步請求內部實現經過維護一個內部的隊列進行
Callcall = interface.getCall();//調用接口中的方法完成OkHttpCall的建立
這個Call接口就是被觀察者,裏面有相應的實現方法。
call.enqueue(new retrofit2.Callback(){...});
這裏的Callback就是觀察者,根據call做出反應的變化
B、Retrofit的題
(1)Retrofit中如何完成線程切換(子線程<->主線程)?
解析:
在構建Retrofit對象的時候使用Builder()內部類構建並經過.build()完成Retrofit的建立,在該方法中:(1)最早判斷baseUrl是否存在,不存在拋異常;(2)其次會判斷是否callFactor對象爲空,若爲空Retrofit本身則建立一個新的OkHttpClient;(3)第三步會建立一個Executor線程池,這個callbackExecutor(內部經過handle機制完成)就是Retrofit進行主線程到子線程切換的callback;(4)第四步會建立一個AdapterFactory用於產生callAdapter適配器對象,請求封裝類Call的適配器,負責將call請求轉換成另外一個能夠被當前平臺識別的對象。該對象會被添加到platform(在.Builder()內部建立的該對象)的默認適配器defaultCallAdapterFactory工廠;(5)第五步添加一個ConverterFactory數據轉換器工廠,將http請求返回的數據解析成java所能用的對象;最後將前面5個對象返回給Retrofit對象構造函數中完成Retrofit對象的建立。
其中第4步中的platform用於適配平臺,自己是一個靜態platform對象,經過findPlatForm()初始化實例。默認狀況下調用defaultCallbackExecutor()函數中返回了一個Android.MainThreadExecutor(),來進行主線程切換操做。在該方法中有一個主線程的handler(傳入Looper.getMainLooper()一個主線程Looper)
在call.enqueue()中Callback回調函數會調用ExecutorCallbackCall.this.callbackExecutor.execute(…)。
callbackExecutor在默認狀況下就是使用的MainThreadExecutor,因此最後會回調到主線程中。
答:
static class mainThreadExecutor implements Executor{
//構建持有主線程Looper的Handler
private final Handler handler = new Handler(Looper.getMainLooper());
MainThreadExecutor(){}
public void execute(Runnable r){
this.handler.post(r);
}
}
Retrofit 運用android基本的異步機制;經過構建主線程的Looper,經過Looper.getMainLooper()建立handler,因爲handler構造函數中傳入的是主線程的Looper,因此能實現經過handler.post(runnable)將消息被主線程Looper獲取,將整個的運行在主線程中
(2)Retrofit和RxJava結合 完成基本網絡請求
a、不使用RxJava的Retrofit網絡請求-生成call對象
一、建立一個用於描述網絡請求的接口(含有註解及方法參數)
ps:每個接口當中的方法的參數也必須用註解進行標註 *用於@GET方法的@Query(),用於查詢參數,不然出錯*
二、利用構建者模式 建立Retrofit對象
三、調用Retrofit對象的Create()方法建立自定義網絡請求接口實例
四、經過接口對象調用接口方法建立Call實例,用於真實的請求實例
五、經過call對象創建同步/異步請求
b、使用RxJava的網絡請求-生成observable對象
一、在Retrofit初始化的時候添加RxJavaCallAdapterFactory適配器工廠,Retrofit和RxJava進行關聯-區別與普通Retrofit對象的構建
二、定義新的接口,方法返回值是Observable 被觀察者 -與普通Retrofit對象流程的差別化之一
三、經過新的接口方法 獲取Observable 對象實例
四、調用被觀察者.subscribeOn()函數:來指定io線程處理數據
五、調用.observeOn(AndroidSchedulers.mainThread()) :在主線程中顯示數據
ps:RxJava線程操做符切換線程,由於不能在主線中耗時操做,不在工做線程顯示Ui,因此工做結束後要通知主線程更新Ui
六、在.subscribe(onCompleted() / onError() )中處理返回結果
(3)Hook與動態代理
Hook:
經過某種手段對一件事物進行改頭換面,從而劫持Hook的目標來達到控制目標的行爲的目的。
在不改變原代碼的狀況下替換原有的對象、方法、函數從而改變原有行爲的目的
DroidPlugin 歷史:
Hook不少android系統底層代碼,不安全!如今已被廢棄
Hook的兩種實現機制:
<1>、反射
把須要替換的目標類替換成需求類,注意:該類須要有和目標類有同樣的特徵。
hook理念: 實現目標類含有的接口,或是繼承目標類的實現類,攔截其內部方法並修改
一、經過反射機制獲取目標類的私有成員變量
Field carEngineFiled = 目標類PrivatePlane.class.getDeclaredField(「目標類的引用");
二、經過setAccessible 設爲true 表示能夠訪問私有方法和它的成員變量
carEngineFiled.setAccessible(true);
三、改變原有成員變量 privatePlane—》ReflectPrivatePlane
PrivatePlaneEngine privatePlaneEngine = (PrivatePlaneEngine)carEngineFiled.get(privatePlaneEngine);
carEngineFiled.set(privatePlane,new ReflectPrivatePlane新修改類(privatePlaneEngine) );
<2>、動態代理——JDK形式
一、 建立ProxyHandler 代理類並實現 InvocationHandler 接口
Public class PrivatePlaneProxyHandler implements InvocationHandler {
private Object object;
public PrivatePlaneProxyHandler(Object object){
this.object = object;
}
//在invoke()方法中添加須要的邏輯,修改返回值
@Override
public Object invoke(Object proxy,Method method,Object[] args){
if(「xxx」 .equals(method.getName())){
return …;
}
return method.invoke(object, args):
}
}
二、代理測試類
Filed carEngineFiled = PrivatePlane.class.getDeclared(「privatePlaneEngine");
carEngineFiled.setAccessible(true);
PrivatePlane carEngine = (PrivatePlane) carEngineField.get(privatePlane);
//調用Proxy,newProxyInstance,傳入目標實現了字節碼,interface接口字節碼,及代理類Handler對象
PrivatePlaneEngineInterface
carEngineProxy = (PrivatePlaneEngineInterface) Proxy,newProxyInstance(
privatePlaneEngine.class.getClassLoader(),
new Class[] {PrivatePlaneEngineInterface.class},
new PrivatePlaneProxyHandler(carEngine) );
carEngineField .set(privatePlane , carEngineProxy);
(4)MVP MVC模式
MVC - Model View Controller
數據模型顯示和業務邏輯相分離的操做,在改變Ui界面的時候不須要從新的修改業務邏輯
M:處理數據和業務邏輯
V:更新UI顯示
C:控制V層與M層通訊,分離更新UI和更新業務邏輯的操做
Android中的MVC:
V:XML文件描述
C:Activity or Fragment 的操做(不進行耗時操做),將數據層的改變交給Ui層
M:JavaBean 實體類
缺陷:
一、全部邏輯都在A or F層進行,形成A/F 層代碼的臃腫, 對代碼擴展維護不利
二、XML功能太弱,邏輯的簡單不能構成複雜的內容代碼
三、容許M數據層和V層進行通訊和數據交互
MVP
V:更新UI顯示
P:邏輯處理,提供V層和M層之間交互的操做
M:提供數據
Android中的MVP:
V:實現V層的接口,繪製Ui界面,與用戶的交互操做,Activity or Fragment 極簡操做,進行負責與P層關聯(持有P層引用)
P:實現P層的接口,V層和M層交互的核心紐帶,ps:將A/F層複雜邏輯交予P層處理,經過接口回調的形式返回給V層 ;持有V、M層引用
M:實現M層的接口,數據的下載,存儲及與服務器接口數據交互操做
優點:
將UI層和業務邏輯層的交互經過接口進行
缺陷:
一、P層繼承了Activity or Fragment的隨着業務代碼的增長邏輯愈來愈複雜,代碼愈來愈臃腫的問題
二、V層與P層的耦合問題,相互持有引用(問題在MVP解析文章中進行詳細描述)
三、接口的顆粒度,接口的過多過少都會影響
(5)sp跨進層問題及apply/commit
sharedPreferences跨進城讀寫 —does not work reliably
Context.MODE_MULTI_PROCESS
數據安全問題
Sp底層使用的是XML文件的形式,Android的讀取有本身的緩存策略。在使用的時候會在內存中緩存一份sp的緩存。這樣就會致使在多進程操做下,每個進程都會保存一份sp的緩存文件,形成讀寫的不一致狀況的產生。
相對安全的跨進程數據操做:ContentProvider
apply /commit :提交數據
區別:
- apply 是一個void 沒有返回值的異步方法;先將修改的數據提交到內存(commitToMemery),(run)異步將真實數據提交到硬盤中(enqueueDiskWrite(mcr,runnable))
ps:提交失敗不會收到通知;
在子線程中提交數據到HD中
- commit是一個boolean 類型返回值的同步方法;同步將數據提交保存到內存和硬盤中,多個併發提交commit時候會阻塞,會等待真正執行的commit執行完後纔會執行相應的其餘commit操做——enqueueDiskWrite(mcr,null)在當前主線程操做
ps:提交失敗會有boolean值反饋,不須要返回值反饋的時候推薦使用apply
在當前主線程中同步的阻塞式提交數據
在執行了apply(),再執行commit()方法 ,commit()會被阻塞