Android常用庫源碼解析
- 2019 年 10 月 3 日
- 筆記
圖片載入框架比較
共同優點
- 都對多級快取、執行緒池、快取演算法做了處理
- 自適應程度高,根據系統性能初始化快取配置、系統資訊變更後動態調整策略。比如根據 CPU 核數確定最大並發數,根據可用記憶體確定記憶體快取大小,網路狀態變化時調整最大並發數等。
- 支援多種數據源支援多種數據源,網路、本地、資源、Assets 等
不同點
- Picasso所能實現的功能,Glide都能做,無非是所需的設置不同。但是Picasso體積比起Glide小太多。
- Glide 不僅是一個圖片快取,它支援 Gif、WebP、縮略圖。Glide 支援載入 Gif 動態圖,而 Picasso 不支援該特性
- Fresco在5.0以下的記憶體優化非常好,代價就是體積也非常的大,按體積算Fresco>Glide>Picasso
- UIL可以算是老牌最火的圖片載入庫了,該作者在項目中說明已經停止了對該項目的維護。這就意味著以後任何的 bug 都不會修復,任何的新特性都不會再繼續開發,所以毫無疑問 UIL 不推薦在項目中使用了。
圖片框架的快取
- MemorycCache圖片記憶體快取。默認使用了 LRU 演算法。
- DiskCache圖片磁碟快取,默認使用LruDiskCache演算法,在快取滿時刪除最近最少使用的圖片
glide源碼
一般看源碼先看他的使用方法,通過使用的方法看對應的程式碼。
Glide.with(MainActivity.this).load(url).into(headerImage);
- with方法把context傳進去,返回GlideBuilder的對應,在這裡做一些初始化操作,比如構建執行緒池(包括sourceExecutor ,diskCacheExecutor ),快取大小和快取器,默認的連接監聽工廠(connectivityMonitorFactory ),Engine對象和RequestManagerRetriever 對象等等。
- load(URL)方法沒做什麼事情,主要就是把URL傳進去,獲取RequestBuilder對象。
- 主要的操作都在into方法里(在這裡會取lru快取還是本地快取,還是沒有,告訴RequestBuilder)。RequestBuilder的into方法里開啟了執行緒池進行載入資源。網路請求是通過url打開連接,返回一個HttpURLConnection對象,進行網路請求的。載入得資源後轉換到主執行緒並進行回調(沒注意看這個)。設置給imageview
- glide為什麼有lru還會記憶體溢出。因為直接把整個大圖片的整個記憶體載入進去了。對於大圖可以下載下來,asdrawale來載入,drawable更省記憶體,Drawable應該不屬於常駐記憶體的對象,不然的話,不可能不會出現OOM的~~
- Glide內部處理了網路圖片載入的錯位或者閃爍(tag)。
public Request getRequest() { //本質還是getTag Object tag = getTag(); Request request = null; if (tag != null) { if (tag instanceof Request) { request = (Request) tag; } else { throw new IllegalArgumentException("You must not call setTag() on a view Glide is targeting"); } } return request; } @Override public void setRequest(Request request) { //本質是setTag setTag(request); }
對圖片載入用到了LruCache(最少最近使用)演算法
他會把記憶體控制在一定大小內,超過最大值時會自動回收,這個最大值可以自己定,一個太小的快取空間,有可能造成圖片頻繁地被釋放和重新載入,這並沒有好處。而一個太大的快取空間,則有可能還是會引起 java.lang.OutOfMemory 的異常。一般使用最大可用記憶體的1/8作為快取的大小。LruCache的主要演算法原理是把最近使用的對象用強引用存儲在 LinkedHashMap (頻繁增刪、不需要排序)中,並且把最近最少使用的對象在快取值達到預設定值之前從記憶體中移除。
public class BitmapCache implements ImageCache { private LruCache<String, Bitmap> mCache; public BitmapCache() { int maxSize = 10 * 1024 * 1024; mCache = new LruCache<String, Bitmap>(maxSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getRowBytes() * bitmap.getHeight(); } }; } @Override public Bitmap getBitmap(String url) { return mCache.get(url); } @Override public void putBitmap(String url, Bitmap bitmap) { mCache.put(url, bitmap); } }
網路框架比較
常用網路庫使用方法
public interface netApi { @GET("repos/{owner}/{repo}/contributors") Call<ResponseBody> contributorsBySimpleGetCall(@Path("owner") String owner, @Path("repo") String repo); } public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); volleyStringRequest(); volleyJsonRequest(); retrofitHttpRequest(); try { okhttpAsyGet(); OkHttpSyncGet(); } catch (Exception e) { e.printStackTrace(); } } //volley第一步 RequestQueue mQueue = Volley.newRequestQueue(MainActivity.this); private void volleyStringRequest() { //volley第二步 StringRequest stringRequest = new StringRequest("http://www.baidu.com", new Response.Listener<String>() { @Override public void onResponse(String response) { Log.d("TAG", response); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.e("TAG", error.getMessage(), error); } }); //volley第三步 mQueue.add(stringRequest); } private void volleyJsonRequest() { JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://www.sina.com/sports/101010100.html", null, new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { Log.d("TAG", response.toString()); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.e("TAG", error.getMessage(), error); } }); mQueue.add(jsonObjectRequest); } //okhttp第一步 private final OkHttpClient client = new OkHttpClient(); public void okhttpAsyGet() throws Exception { //okhttp第二步 Request request = new Request.Builder() .url("http://publicobject.com/helloworld.txt") .build(); //okhttp第三步 okhttp3.Response response = client.newCall(request).execute(); if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); Headers responseHeaders = response.headers(); for (int i = 0; i < responseHeaders.size(); i++) { System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i)); } System.out.println(response.body().string()); } public void OkHttpSyncGet() throws Exception { Request request = new Request.Builder() .url("http://publicobject.com/helloworld.txt") .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(Call call, okhttp3.Response response) throws IOException { if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); Headers responseHeaders = response.headers(); for (int i = 0, size = responseHeaders.size(); i < size; i++) { System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i)); } System.out.println(response.body().string());//只能獲取一次,可以用string保存 } }); } public void retrofitHttpRequest() { Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .build(); netApi repo = retrofit.create(netApi.class); retrofit2.Call<ResponseBody> call = repo.contributorsBySimpleGetCall("userName", "path"); call.enqueue(new retrofit2.Callback<ResponseBody>() { @Override public void onResponse(retrofit2.Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) { //response } @Override public void onFailure(retrofit2.Call<ResponseBody> call, Throwable t) { } }); } } String post(String url, String json) throws IOException { RequestBody formBody = new FormEncodingBuilder() .add("platform", "android") .add("name", "bug") .add("subject", "XXXXXXXXXXXXXXX") .build(); Request request = new Request.Builder() .url(url) .post(body) .build(); Response response = client.newCall(request).execute(); if (response.isSuccessful()) { return response.body().string(); } else { throw new IOException("Unexpected code " + response); } }
- HttpURLConnection和HttpClient。這兩種方式都支援HTTPS協議、以流的形式進行上傳和下載、配置超時時間、IPv6、以及連接池等功能。
- 在Android 2.2版本之前,HttpClient是最好的選擇。因為HttpURLConnection有一些bug。比如說對一個可讀的InputStream調用close()方法時,就有可能會導致連接池失效了。那麼我們通常的解決辦法就是直接禁用掉連接池的功能。
- 在Android 2.3版本及以後,HttpClientHttpURLConnection則是最佳的選擇,HttpURLConnection的API提供的比較簡單,可以更加容易地去使用和擴展它。而且速度快、節省電量。
- OkHttp 處理了很多網路問題:自動重連、會從很多常用的連接問題中自動恢復。如果您的伺服器配置了多個IP地址,當第一個IP連接失敗的時候,OkHttp會自動嘗試下一個IP。OkHttp還處理了代理伺服器問題和SSL握手失敗問題。
- volley的設計目標就是非常適合數據量小,通訊量大的客戶端,而對於大數據量的網路操作,比如說下載文件等,Volley的表現就會非常糟糕。Volley停止了更新,而OkHttp得到了官方的認可,並在不斷優化。因此我最終替換為了OkHttp
volley原理
主執行緒中調用RequestQueue的add()方法來添加一條網路請求,這條請求會先被加入到快取隊列當中,如果發現可以找到相應的快取結果就直接讀取快取並解析,然後回調給主執行緒。如果在快取中沒有找到結果,則將這條請求加入到網路請求隊列中,然後處理髮送HTTP請求,解析響應結果,寫入快取,並回調主執行緒。
為什麼說Volley適合數據量小,通訊頻繁的網路操作
volley中為了提高請求處理的速度,採用了ByteArrayPool進行記憶體中的數據存儲的,如果下載大量的數據,這個存儲空間就會溢出,所以不適合大量的數據,但是由於他的這個存儲空間是記憶體中分配的,當存儲的時候優是從ByteArrayPool中取出一塊已經分配的記憶體區域, 不必每次存數據都要進行記憶體分配,而是先查找緩衝池中有無適合的記憶體區域,如果有,直接拿來用,從而減少記憶體分配的次數 ,所以他比較適合據量小,通訊量大網路數據交互情況。
Retrofit原理
- Retrofit 2.0底層依賴OkHttp實現,也就是說Retrofit本質上就是對OkHttp的更進一步封裝,還支援Rxjava。Retrofit和其它Http庫最大區別在於通過大範圍使用註解簡化Http請求。Retrofit使用註解來描述HTTP請求(請求方式、請求參數)。
- 內部也是調用okhttp的網路請求方式
- Retrofit主要是在create方法中採用動態代理模式實現介面方法,這個過程構建了一個ServiceMethod對象,根據方法註解獲取請求方式,參數類型和參數註解拼接請求的鏈接,當一切都準備好之後會把數據添加到Retrofit的RequestBuilder中。然後當我們主動發起網路請求的時候會調用okhttp發起網路請求,okhttp的配置包括請求方式,URL等在Retrofit的RequestBuilder的build()方法中實現,並發起真正的網路請求。
- 網路請求的工作本質上是OkHttp完成,而 Retrofit 僅負責網路請求介面的封裝。
自己寫網路請求框架
- volley,okHttp等,這類優秀的框架其底層的實現大部分也是基於系統的 執行緒池 和 httpClient 或 HttpUrlConnection的網路請求類框架,Android中是不能在主執行緒中(又稱UI執行緒)進行網路操作的,那麼框架中必不可少地要使用到子執行緒,可以使用簡單的 Thread + Runnable + Handler或者重量級點的AsyncTask。
- 處理好並發操作,一個應用中往往要進行多執行緒操作,而Java虛擬機對於一個執行緒的記憶體分配大約在1M左右,具體多少要看它執行的任務而定。所有就要使用執行緒池,例如newFixdThreadPool 可以控制並發數量,且在整個APP運行過程中有幾個常駐執行緒在,避免使用時反覆地new,退出時再銷毀,而 newCacheThreadPool 則會在任務完成後,自動回收執行緒,它會幫你釋放執行緒記憶體,也就不會有常駐執行緒。
- 還要注意使介面分離,降低耦合,而且介面能夠我們帶來很大的方便。
okhttp源碼
- 在構造器中利用建造者模式來構建 OkHttpClient 的對象,OkHttpClient 的構造器中主要是默認的配置。在newCall(Request request) (request是請求參數和URL)的時候,其實是裡面創建了一個 RealCall 的對象,裡面有execute() 方法。裡面有getResponseWithInterceptorChain() ,添加了很多Interceptor,並返回 Response 對象的。
- Interceptor 是 OkHttp 最核心的一個東西,它負責攔截請求進行一些額外的處理(例如 設置cookie),Interceptor有負責失敗重試、重定向的(RetryAndFollowlnterceptor)、讀取快取、更新快取的(Cachelnterceptor)、負責和伺服器建立連接的(Connectlnterceptor)、負責向伺服器發送請求數據(Bridgelnterceptor)、從伺服器讀取響應數據的(Networklnterceptor)。
- 每一個功能都只是一個 Interceptor,它們再連接成一個 Interceptor.Chain,環環相扣,最終完成一次網路請求。
- 根據響應碼判斷是否是重定向(3開頭3xx)。RetryAndFollowUpInterceptor:如果不需要重定向,那麼 followUp 為空,會釋放資源,返回 response。 若為重定向就銷毀舊連接,創建新連接,將重定向操作得到的新請求設置給 request。
- 同步請求通過Call.execute()直接返回當前的Response,而非同步請求會把當前的請求Call.enqueue添加(AsyncCall)到請求隊列中,並通過回調(Callback)的方式來獲取最後結果。
@Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); timeout.enter(); eventListener.callStart(this); try { client.dispatcher().executed(this); Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } catch (IOException e) { e = timeoutExit(e); eventListener.callFailed(this, e); throw e; } finally { client.dispatcher().finished(this); } }
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); }
- 復用機制:Http中添加了一種KeepAlive機制,當數據傳輸完畢後仍然保持連接,等待下一次請求時直接復用該連接。
ConnectionPool :取到的話復用,沒有取到放到連接池中。
ConnectionPool關鍵程式碼: - OkHttp 默認最大並發數 64,單域名最大並發 5,為了實現請求的並發,Dispatcher 配置了一個執行緒池,
//執行緒池,核心執行緒數為0,最大執行緒數為最大整數,執行緒空閑存活時間60s,//SynchronousQueue 直接提交策略 private static final Executor executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE , 60L , TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true)); //空閑連接的最大連接數 private final int maxIdleConnections; //保持連接的周期 private final long keepAliveDurationNs; //雙端隊列,存放具體的連接 private final Deque<RealConnection> connections = new ArrayDeque<>(); //用於記錄連接失敗的route final RouteDatabase routeDatabase = new RouteDatabase(); //構造函數//從這裡可以知道,空閑連接的最大連接數為5,保持連接的周期是5分鐘 public ConnectionPool() { this(5, 5, TimeUnit.MINUTES); } public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) { this.maxIdleConnections = maxIdleConnections; this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration); // Put a floor on the keep alive duration, otherwise cleanup will spin loop. if (keepAliveDuration <= 0) { throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration); } }
同步
Dispatcher會在同步執行任務隊列中記錄當前被執行過得任務Call,同時在當前執行緒中去執行Call的getResponseWithInterceptorChain()方法,直接獲取當前的返回數據Response;
非同步
Dispatcher內部實現了懶載入無邊界限制的執行緒池方式,同時該執行緒池採用了SynchronousQueue這種阻塞隊列。非同步執行是通過Call.enqueue(Callback responseCallback)來執行,在Dispatcher中添加一個封裝了Callback的Call的匿名內部類Runnable來執行當前的Call。這裡一定要注意的地方這個AsyncCall是Call的匿名內部類。AsyncCall的execute方法仍然會回調到Call的getResponseWithInterceptorChain方法來完成請求,同時將返回數據或者狀態通過Callback來完成。
使用快取,有多種不同的快取方式
自定義一個攔截器
class LoggingInterceptor implements Interceptor { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request request = chain.request(); long t1 = System.nanoTime(); logger.info(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers())); Response response = chain.proceed(request); long t2 = System.nanoTime(); logger.info(String.format("Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers())); return response; } }
其他庫
LeakCanary原理解析
- lifecycleCallbacks監聽Activity的onDestroy方法,正常情況下activity在onDestroy後需要立即被回收,onActivityDestroyed方法最終會調用RefWatcher.watch方法:
- 通過將Activity包裝到WeakReference(弱引用)中,被WeakReference包裝過的Activity對象如果被回收,該WeakReference引用會被放到ReferenceQueue中,通過監測ReferenceQueue裡面的內容就能檢查到Activity是否能夠被回收
- 如果Activity沒有被回收,調用GcTigger.runGc方法運行GC,如果這時候還沒有被回收,那就說明Activity可能已經泄露。
Evenbus是做什麼的?和RXjava有什麼區別?
- 採用EventBus作為事件管理,可以跨執行緒,跨組件通訊。 以前我們做組件間的消息分發更新,一般會採用觀察者模式,或者介面數據回調的相關方式。但是這樣的做法雖然可以解決問題,但是組件之間的耦合比較嚴重,而且程式碼也不易閱讀和相關維護。為了解決這樣的問題我們可以使用消息匯流排EventBus框架。
- EventBus是一款針對Android優化的發布/訂閱事件匯流排。主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,執行緒之間傳遞消息.優點是開銷小,程式碼更優雅。以及將發送者和接收者解耦。
- RxJava要比EventBus的應用更廣泛,RxJava裡面幾乎可以做任何事情。做非同步、網路的數據處理,寫出來的程式碼比較優雅。
黏性事件
簡單講,就是在發送事件之後再訂閱該事件也能收到該事件,跟黏性廣播類似,但是它只能收到最新的一次消息,比如說在未訂閱之前已經發送了多條黏性消息了,然後再訂閱只能收到最近的一條消息。
EventBus源碼
register(this)就是去當前類,遍歷所有的方法,找到onEvent開頭的然後進行存儲(把匹配的方法最終保存在subscriptionsByEventType(Map,key:eventType ; value:CopyOnWriteArrayList<Subscription> ),eventType是我們方法參數的Class,Subscription中則保存著subscriber, subscriberMethod(method, threadMode, eventType), priority;包含了執行改方法所需的一切),然後post的時候,根據post傳入的參數,去找到匹配的方法,反射調用。數據傳遞是通過handler。