本文是Android面試題整理中的一篇,內容包括
- LeakCanary
- Okhttp
- Retrofit
- Fresco 使用
- EventBus
LeakCanary原 理
- 在Application中註冊一個ActivityLifecycleCallbacks來監聽Activity的銷燬
- 通過IdleHandler在主執行緒空閒時進行檢測
- 檢測是通過WeakReference實現的,如果沒有被回收會再次呼叫gc再確認一遍
- 確認有洩漏後,dump hprof檔案,並開啟一個程式IntentService通過HAHA進行分析
OkHttp(基於3.9版本)
使用
1. 在gradle中新增依賴
compile 'com.squareup.okhttp3:okhttp:3.9.0'
compile 'com.squareup.okio:okio:1.13.0'
複製程式碼
2. 建立OkHttpClient,並對timeout等進行設定
File sdcache = getExternalCacheDir();
int cacheSize = 10 * 1024 * 1024;
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.cache(new Cache(sdcache.getAbsoluteFile(), cacheSize));
OkHttpClient mOkHttpClient=builder.build();
複製程式碼
3. 建立Request
- get請求
Request request = new Request.Builder()
.url("http://www.baidu.com")
.build();
複製程式碼
- post請求(post需要傳入requsetBody)
RequestBody formBody = new FormEncodingBuilder()
.add("size", "10")
.build();
Request request = new Request.Builder()
.url("http://api.1-blog.com/biz/bizserver/article/list.do")
.post(formBody)
.build();
複製程式碼
4. 建立Call並執行(okHttp的返回結果並沒有在ui執行緒)
Call call = mOkHttpClient.newCall(request);
複製程式碼
- 同步執行
Response mResponse=call.execute();
if (mResponse.isSuccessful()) {
return mResponse.body().string();
} else {
throw new IOException("Unexpected code " + mResponse);
}
複製程式碼
- 非同步執行
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
@Override
public void onResponse(Response response) throws IOException {
String str = response.body().string();
Log.i("wangshu", str);
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "請求成功", Toast.LENGTH_SHORT).show();
}
});
}
});
複製程式碼
4. 封裝
因為以下原因,所以我們需要封裝:
- 避免重複程式碼編寫
- 請求的回撥改為UI執行緒
- 其他需要的邏輯:例如加解密等
OkHttp中的設計模式
- Builder模式:OkHttpClient 和Request等都是通過Builder模式建立的
- 責任鏈模式:攔截器通過責任鏈模式進行工作
- 門面模式:整體採用門面模式,OkHttpClient為門面,向子系統委派任務
- 享元模式:連線池等採用了享元模式
- 其他:工廠模式、代理模式等
原始碼分析
1. Call
- Call的實現類為RealCall
- 在執行execute或者enqueue時,會取出okHttpClient中的Dispatcher執行對應的方法
client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
複製程式碼
2. Diapatcher
- Diapatcher在OkHttpClient build時進行初始化
- Dispatcher負責進行任務排程,內部維護一個執行緒池,處理併發請求
- Dispatcher內部有三個佇列
/** 將要執行的非同步請求佇列 */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/**正在執行的非同步請求佇列 */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** 正在執行的同步請求佇列 */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
複製程式碼
- 執行時,執行緒會呼叫AsyncCall的excute方法
3. AsyncCall
- AsyncCall是RealCall的一個內部類,實現了Runnalbe介面
- AsyncCall 通過 getResponseWithInterceptorChain方法取得Response
- 執行完畢後通過client.dispatcher().finished(this);將自身從dispatcher佇列中取出,並取出下一個加入相應佇列
//AsyncCall 的excute方法
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain(forWebSocket);
if (canceled) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
複製程式碼
4. getResponseWithInterceptorChain
getResponseWithInterceptorChain是用責任鏈的方式,執行攔截器,對請求和請求結果進行處理
- getResponseWithInterceptorChain 中建立攔截器,並建立第一個RealInterceptorChain,執行其proceed方法
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);
}
複製程式碼
- RealInterceptorChain的proceed方法中,會取出攔截器,並建立下一個Chain,將其作為引數傳給攔截器的intercept方法
// If there's another interceptor in the chain, call that.
if (index < client.interceptors().size()) {
Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
//從攔截器列表取出攔截器
Interceptor interceptor = client.interceptors().get(index);
Response interceptedResponse = interceptor.intercept(chain);
if (interceptedResponse == null) {
throw new NullPointerException("application interceptor " + interceptor
+ " returned null");
}
return interceptedResponse;
}
// No more interceptors. Do HTTP.
return getResponse(request, forWebSocket);
}
複製程式碼
攔截器
1. 自定義攔截器
- 自定義攔截器分為兩類,interceptor和networkInterceptor(區別:networkInterceptor處理網路相關任務,如果response直接從快取返回了,那麼有可能不會執行networkInterceptor)
- 自定義方式:實現Interceptor,重寫intercept方法,並註冊攔截器
2. 系統攔截器
- RetryAndFollowUpInterceptor:進行失敗重試和重定向
- BridgeInterceptor:新增頭部資訊
- CacheInterceptor:處理快取
- ConnectInterceptor:獲取可用的connection例項
- CallServerInterceptor:發起請求
連線池複用
在ConnectInterceptor中,我們獲取到了connection的例項,該例項是從ConnectionPool中取得
1. Connection
- Connection 是客戶端和伺服器建立的資料通路,一個Connection上可能存在幾個連結
- Connection的實現類是RealConnection,是socket物理連線的包裝
- Connection內部維持著一個List<Reference>引用
2. StreamAllocation
StreamAllocation是Connection維護的連線,以下是類內註解
<ul>
* <li><strong>Connections:</strong> physical socket connections to remote servers. These are
* potentially slow to establish so it is necessary to be able to cancel a connection
* currently being connected.
* <li><strong>Streams:</strong> logical HTTP request/response pairs that are layered on
* connections. Each connection has its own allocation limit, which defines how many
* concurrent streams that connection can carry. HTTP/1.x connections can carry 1 stream
* at a time, HTTP/2 typically carry multiple.
* <li><strong>Calls:</strong> a logical sequence of streams, typically an initial request and
* its follow up requests. We prefer to keep all streams of a single call on the same
* connection for better behavior and locality.
* </ul>
複製程式碼
3. ConnectionPool
ConnectionPool通過Address等來查詢有沒有可以複用的Connection,同時維護一個執行緒池,對Connection做回收工作
Retrofit
Retrofit幫助我們對OkHttp進行了封裝,使網路請求更加方便
使用
1. 新增依賴
dependencies {
compile 'com.squareup.retrofit2:retrofit:2.0.2'
}
複製程式碼
2. 建立Retrofit例項
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://fanyi.youdao.com/") // 設定網路請求的Url地址
.addConverterFactory(GsonConverterFactory.create()) // 設定資料解析器
.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支援RxJava平臺 .build();
複製程式碼
3. 建立網路介面
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)
複製程式碼
4. 建立Call
GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);
//對 傳送請求 進行封裝
Call<Reception> call = request.getCall();
複製程式碼
5. 執行Call的請求方法
//傳送網路請求(非同步) call.enqueue(new Callback<Translation>() {
//請求成功時回撥
@Override
public void onResponse(Call<Translation> call, Response<Translation> response) {
//請求處理,輸出結果
response.body().show();
}
//請求失敗時候的回撥
@Override
public void onFailure(Call<Translation> call, Throwable throwable) {
System.out.println("連線失敗");
}
});
// 傳送網路請求(同步) Response<Reception> response = call.execute();
複製程式碼
原始碼解析
1. Retrofit
Retrofit 通過builder模式建立,我們可以對其進行各種設定:
- baseUrl:請求地址的頭部,必填
- callFactory:網路請求工廠(不進行設定的話預設會生成一個OkHttpClient)
- adapterFactories:網路請求介面卡工廠的集合,這裡有介面卡因為Retrofit不僅支援Android,還支援Ios等其他平臺(不進行設定的話會根據平臺自動生成)
- converterFactories:資料轉換器工廠的集合(將網路返回的資料轉換成我們需要的類)
- callbackExecutor:回撥方法執行器(Android平臺預設通過Handler傳送到主執行緒執行)
2. Call
我們的每個method對應一個Call, Call的建立分為兩步:
- retorfit.create(myInfterfaceClass.class)建立我們網路請求介面類的例項
- 呼叫對應方法拿到對應網路請求的Call
關鍵在第一步,第一步是通過動態代理實現的
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, Object... args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod serviceMethod = loadServiceMethod(method);//1
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
複製程式碼
- 通過loadServiceMethod方法生成mehtod對應的ServiceMethod
- 將ServiceMethod和方法引數傳進OkHttpCall生成OkHttpCall
- 呼叫callAdapter方法對OkHttpCall進行處理並返回
1. ServiceMethod
loadServiceMethod方法會首先在快取裡查詢是否有該method對應的ServiceMethod,沒有的話呼叫build方法建立一個
ServiceMethod loadServiceMethod(Method method) {
ServiceMethod result;
// 設定執行緒同步鎖
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
// ServiceMethod類物件採用了單例模式進行建立
// 即建立ServiceMethod物件前,先看serviceMethodCache有沒有快取之前建立過的網路請求例項
// 若沒快取,則通過建造者模式建立
serviceMethod 物件 if (result == null) {
// 下面會詳細介紹ServiceMethod生成例項的過程
result = new ServiceMethod.Builder(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}
複製程式碼
ServiceMethod的建立過程即是對method的解析過程,解析過程包括:對註解的解析,尋找合適的CallAdapter和Convert等
2. OkHttpCall
OkHttpCall實現了Call介面,當執行excute或enqueue請求命令時,內部通過傳入的CallFactory(OkHttpClient)執行網路請求
3. callAdapter
如果我們沒有對CallAdapter進行設定,它的值將是Android平臺的預設設定,其adapt方法如下
public <R> Call<R> adapt(Call<R> call) {
return new ExecutorCallbackCall<>(callbackExecutor, call);
}
ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
this.delegate = delegate;
// 把上面建立並配置好引數的OkhttpCall物件交給靜態代理delegate
// 靜態代理和動態代理都屬於代理模式
// 靜態代理作用:代理執行被代理者的方法,且可在要執行的方法前後加入自己的動作,進行對系統功能的擴充
this.callbackExecutor = callbackExecutor;
// 傳入上面定義的回撥方法執行器
// 用於進行執行緒切換 }
複製程式碼
ExecutorCallbackCall對OkHttpCall進行了裝飾,會呼叫CallBackExcutor對OkHttpCall執行的返回結果進行處理,使其位於主執行緒
自定義Convert和CallAdapter
Fresco
Fresco是一個圖片載入庫,可以幫助我們載入圖片顯示,控制多執行緒,以及管理快取和記憶體等
Fresco使用
- 引入依賴
dependencies {
// 其他依賴
compile 'com.facebook.fresco:fresco:0.12.0'
// 在 API < 14 上的機器支援 WebP 時,需要新增
compile 'com.facebook.fresco:animated-base-support:0.12.0'
// 支援 GIF 動圖,需要新增
compile 'com.facebook.fresco:animated-gif:0.12.0'
// 支援 WebP (靜態圖+動圖),需要新增
compile 'com.facebook.fresco:animated-webp:0.12.0'
compile 'com.facebook.fresco:webpsupport:0.12.0'
// 僅支援 WebP 靜態圖,需要新增
compile 'com.facebook.fresco:webpsupport:0.12.0'
}
複製程式碼
- 初始化
Fresco.initialize(Context context);
複製程式碼
- 使用SimpleView
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/my_image_view"
android:layout_width="130dp"
android:layout_height="130dp"
fresco:placeholderImage="@drawable/my_drawable"
/>
複製程式碼
- 載入圖片
Uri uri = Uri.parse("https://raw.githubusercontent.com/facebook/fresco/gh-pages/static/logo.png");
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);
複製程式碼
- 以上是Fresco的基本載入流程,此外,我們可以定製載入和顯示的各個環節
Fresco由兩部分組成,Drawees負責圖片的呈現,ImagePipeline負責圖片的下載解碼和記憶體管理
Drawees
Drawees 負責圖片的呈現。它由三個元素組成,有點像MVC模式。
DraweeView
-
繼承於 View, 負責圖片的顯示。
-
一般情況下,使用 SimpleDraweeView 即可。 你可以在 XML 或者在 Java 程式碼中使用它,通過 setImageUri 給它設定一個 URI 來使用,這裡有簡單的入門教學:開始使用
-
你可以使用 XML屬性來達到各式各樣的效果。
DraweeHierarchy
-
DraweeHierarchy 用於組織和維護最終繪製和呈現的 Drawable 物件,相當於MVC中的M。
-
你可以通過它來在Java程式碼中自定義圖片的展示
DraweeController
-
DraweeController 負責和 image loader 互動( Fresco 中預設為 image pipeline, 當然你也可以指定別的),可以建立一個這個類的例項,來實現對所要顯示的圖片做更多的控制。
-
如果你還需要對Uri載入到的圖片做一些額外的處理,那麼你會需要這個類的。
DraweeControllerBuilder
- DraweeControllers 由 DraweeControllerBuilder 採用 Builder 模式建立,建立之後,不可修改。具體參見: 使用ControllerBuilder。
Listeners
- 使用 ControllerListener 的一個場景就是設定一個 Listener監聽圖片的下載。
ImagePipeline
-
Fresco 的 Image Pipeline 負責圖片的獲取和管理。圖片可以來自遠端伺服器,本地檔案,或者Content Provider,本地資源。壓縮後的檔案快取在本地儲存中,Bitmap資料快取在記憶體中。
-
在5.0系統以下,Image Pipeline 使用 pinned purgeables 將Bitmap資料避開Java堆記憶體,存在ashmem中。這要求圖片不使用時,要顯式地釋放記憶體
-
SimpleDraweeView自動處理了這個釋放過程,所以沒有特殊情況,儘量使用SimpleDraweeView,在特殊的場合,如果有需要,也可以直接控制Image Pipeline。
-
ImagePipeline載入圖片流程
- 檢查記憶體快取,如有,返回
- 後臺執行緒開始後續工作
- 檢查是否在未解碼記憶體快取中。如有,解碼,變換,返回,然後快取到記憶體快取中。
- 檢查是否在磁碟快取中,如果有,變換,返回。快取到未解碼快取和記憶體快取中。
- 從網路或者本地載入。載入完成後,解碼,變換,返回。存到各個快取中。
ImagePipeline的執行緒池
Image pipeline 預設有3個執行緒池:
- 3個執行緒用於網路下載
- 2個執行緒用於磁碟操作: 本地檔案的讀取,磁碟快取操作。
- 2個執行緒用於CPU相關的操作: 解碼,轉換,以及後處理等後臺操作。
ImagePipeline的 快取
ImagePipeLine有三級快取
- 解碼後的Bitmap快取
- 未解碼圖片的記憶體快取
- 磁碟快取
對比
功能
Fresco 相對於Glide/Picaso等擁有更多的功能,如圖片的漸進式載入/動圖/圓角等,
效能
Fresco採用三級快取:
- 解碼後的Bitmap快取
- 未解碼圖片的記憶體快取
- 磁碟快取
Glide兩級快取:
- 根據ImageView控制元件尺寸獲得對應的大小的bitmap來展示,可以快取原始資料或者resize後資料
- 磁碟快取
使用
Fresco通過CloseableReference管理圖片,通過圖片控制元件DraweeView來顯示圖片和控制圖片釋放,雖然擴充套件性高,但是擴充套件起來麻煩;對專案有一定侵入性
EventBus
EventBus使用了觀察者模式,方便我們專案中進行資料傳遞和通訊
使用
- 新增依賴
compile 'org.greenrobot:eventbus:3.0.0'
複製程式碼
- 註冊和解綁
EventBus.getDefault().register(this);
EventBus.getDefault().unregister(this);
複製程式碼
- 新增訂閱訊息方法
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(MessageEvent event) {
/* Do something */
}
複製程式碼
- 傳送訊息
EventBus.getDefault().post(new MessageEvent("Hello !....."));
複製程式碼
@Subscribe註解
該註解內部有三個成員,分別是threadMode、sticky、priority。
- threadMode代表訂閱方法所執行的執行緒
- sticky代表是否是粘性事件
- priority代表優先順序
threadMode
- POSTING:表示訂閱方法執行在傳送事件的執行緒。
- MAIN:表示訂閱方法執行在UI執行緒,由於UI執行緒不能阻塞,因此當使用MAIN的時候,訂閱方法不應該耗時過長。
- BACKGROUND:表示訂閱方法執行在後臺執行緒,如果傳送的事件執行緒不是UI執行緒,那麼就使用該執行緒;如果傳送事件的執行緒是UI執行緒,那麼新建一個後臺執行緒來呼叫訂閱方法。
- ASYNC:訂閱方法與傳送事件始終不在同一個執行緒,即訂閱方法始終會使用新的執行緒來執行。
sticky 粘性事件
在註冊之前便把事件發生出去,等到註冊之後便會收到最近傳送的粘性事件(必須匹配)。注意:只會接收到最近傳送的一次粘性事件,之前的會接受不到,demo
原始碼解析
參見連結
效能
- EventBus通過反射的方式對@Subscribe方法進行解析。
- 預設情況下,解析是執行時進行的,但是我們也可以通過設定和載入依賴庫,使其編譯時形成索引,其效能會大大提升