Android 面試開源框架篇

本文是Android面試題整理中的一篇,內容包括html

  • LeakCanary
  • Okhttp
  • Retrofit
  • Fresco 使用
  • EventBus

LeakCanary


  1. 在Application中註冊一個ActivityLifecycleCallbacks來監聽Activity的銷燬
  2. 經過IdleHandler在主線程空閒時進行檢測
  3. 檢測是經過WeakReference實現的,若是沒有被回收會再次調用gc再確認一遍
  4. 確認有泄漏後,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. 封裝

由於如下緣由,因此咱們須要封裝:android

  • 避免重複代碼編寫
  • 請求的回調改成UI線程
  • 其餘須要的邏輯:例如加解密等

OkHttp中的設計模式

  1. Builder模式:OkHttpClient 和Request等都是經過Builder模式建立的
  2. 責任鏈模式:攔截器經過責任鏈模式進行工做
  3. 門面模式:總體採用門面模式,OkHttpClient爲門面,向子系統委派任務
  4. 享元模式:鏈接池等採用了享元模式
  5. 其餘:工廠模式、代理模式等

源碼分析

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是用責任鏈的方式,執行攔截器,對請求和請求結果進行處理git

  • 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中取得github

1. Connection

  • Connection 是客戶端和服務器創建的數據通路,一個Connection上可能存在幾個連接
  • Connection的實現類是RealConnection,是socket物理鏈接的包裝
  • Connection內部維持着一個List<Reference>引用

2. StreamAllocation

StreamAllocation是Connection維護的鏈接,如下是類內註解web

<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進行了封裝,使網絡請求更加方便segmentfault

使用

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的建立分爲兩步:api

  • 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使用

  1. 引入依賴
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'
}

複製代碼
  1. 初始化
Fresco.initialize(Context context);
複製代碼
  1. 使用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"
  />
複製代碼
  1. 加載圖片
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);

複製代碼
  1. 以上是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加載圖片流程

  1. 檢查內存緩存,若有,返回
  1. 後臺線程開始後續工做
  2. 檢查是否在未解碼內存緩存中。若有,解碼,變換,返回,而後緩存到內存緩存中。
  3. 檢查是否在磁盤緩存中,若是有,變換,返回。緩存到未解碼緩存和內存緩存中。
  4. 從網絡或者本地加載。加載完成後,解碼,變換,返回。存到各個緩存中。

ImagePipeline的線程池

Image pipeline 默認有3個線程池:

  1. 3個線程用於網絡下載
  1. 2個線程用於磁盤操做: 本地文件的讀取,磁盤緩存操做。
  2. 2個線程用於CPU相關的操做: 解碼,轉換,以及後處理等後臺操做。

ImagePipeline的 緩存

ImagePipeLine有三級緩存

  1. 解碼後的Bitmap緩存
  2. 未解碼圖片的內存緩存
  3. 磁盤緩存

對比

功能

Fresco 相對於Glide/Picaso等擁有更多的功能,如圖片的漸進式加載/動圖/圓角等,

性能

Fresco採用三級緩存:

  1. 解碼後的Bitmap緩存
  2. 未解碼圖片的內存緩存
  3. 磁盤緩存

Glide兩級緩存:

  1. 根據ImageView控件尺寸得到對應的大小的bitmap來展現,能夠緩存原始數據或者resize後數據
  2. 磁盤緩存

使用

Fresco經過CloseableReference管理圖片,經過圖片控件DraweeView來顯示圖片和控制圖片釋放,雖然擴展性高,可是擴展起來麻煩;對項目有必定侵入性

EventBus


EventBus使用了觀察者模式,方便咱們項目中進行數據傳遞和通訊

使用

  1. 添加依賴
compile 'org.greenrobot:eventbus:3.0.0'
複製代碼
  1. 註冊和解綁
EventBus.getDefault().register(this);

EventBus.getDefault().unregister(this);
複製代碼
  1. 添加訂閱消息方法
@Subscribe(threadMode = ThreadMode.MAIN) 
public void onEvent(MessageEvent event) {
    /* Do something */
}
複製代碼
  1. 發送消息
EventBus.getDefault().post(new MessageEvent("Hello !....."));
    
複製代碼

@Subscribe註解

該註解內部有三個成員,分別是threadMode、sticky、priority。

  1. threadMode表明訂閱方法所運行的線程
  2. sticky表明是不是粘性事件
  3. priority表明優先級

threadMode

  1. POSTING:表示訂閱方法運行在發送事件的線程。
  2. MAIN:表示訂閱方法運行在UI線程,因爲UI線程不能阻塞,所以當使用MAIN的時候,訂閱方法不該該耗時過長。
  3. BACKGROUND:表示訂閱方法運行在後臺線程,若是發送的事件線程不是UI線程,那麼就使用該線程;若是發送事件的線程是UI線程,那麼新建一個後臺線程來調用訂閱方法。
  4. ASYNC:訂閱方法與發送事件始終不在同一個線程,即訂閱方法始終會使用新的線程來運行。

sticky 粘性事件

在註冊以前便把事件發生出去,等到註冊以後便會收到最近發送的粘性事件(必須匹配)。注意:只會接收到最近發送的一次粘性事件,以前的會接受不到,demo

源碼解析

參見連接

性能

  1. EventBus經過反射的方式對@Subscribe方法進行解析。
  2. 默認狀況下,解析是運行時進行的,可是咱們也能夠經過設置和加載依賴庫,使其編譯時造成索引,其性能會大大提高
相關文章
相關標籤/搜索