如何對 Android 庫進行依賴管理?

OneAPM官方技術部落格發表於2016-01-13

Android 開發人員為專案選擇庫的時候,考慮的因素不僅僅是功能、可用性、效能、文件豐富度和技術支援情況。他們還關心庫的大小,以及要新增的方法數量。因為專案越大,依賴也越多,要把應用的方法數量控制在65k 以下,開發人員感覺很有壓力。另外,對於非發行版專案而言,Proguard 使用起來效率太低,而且開發人員視 multidex 如瘟疫,避之唯恐不及。因此,編寫庫的作者必須特別注意專案的大小。

為了減少庫的方法數量,最簡單的途徑就是不包含任何多餘的依賴。因為你包含的所有依賴,都會被傳遞並新增至使用者的專案裡。舉個例子,如果你只需要幾個簡單的工具方法,比如默默地關閉一個資源,那就沒必要為此新增 [Guava](https://github.com/google/guava 「Guava」 )。自己編寫方法,或者從一個現有的庫中提取(但是務必做出說明)就可以了。使用者肯定會感激你去除了多餘的14k方法。

但是,這並不是說你不該使用外部庫,而是你要做出明智的選擇。比如,像 HTTP 客戶端這樣的庫已經有了,你若再去重寫一個,最終結果只能是浪費時間,倒不如用這些時間改進自己的庫。

除了有選擇地使用庫以外,還有幾個策略也可以幫助你保持庫的精簡。其中一個策略就是使用 provided scope(已提供範圍)來宣告依賴。 這是 gradle 中 Android build system(Android 構建系統)的一部分。與 compile scope(編譯範圍)不同,provided scope 僅在編譯時包含依賴。這就意味著,使用者在構建專案時,該依賴不會隨著 APK 檔案打包。如果想在執行時使用該依賴,使用者需要在應用的 build.gradle 裡顯式宣告。

注意: 還有一種與 provided scope 相反的機制叫 package scope(包範圍)。這種依賴會隨 APK 檔案打包,但是在編譯時不可用。

你可能還想在庫中使用可選擇性依賴。其中一個原因是,某些功能可能只有部分使用者使用。例如 [Retrofit 1.x](https://github.com/square/retrofit/tree/version-one 「Retrofit 1.x」 ),該庫可以使用 REST 呼叫來響應,而不使用回撥。那些想使用 RxJava 的使用者可以新增之,而不想使用它的使用者也可以不新增,以免加重負擔。自從 Retrofit 使用 maven build system(maven 構建系統)以後,其配置稍有更改,但理念還是相似的。

在此筆者要提醒大家,如果你發現自己庫中的某些功能只對少數使用者有用,你應該認真考慮一下是否還要保留這些功能。關於這一點,後文中還會講到。

在庫裡包含可選擇性依賴的另一個原因,是Android 框架已經提供了一種解決方案,但是某個外部庫提供的解決方案效能更好。如果使用者本就依賴於該外部庫,或者願意增加方法數量以獲得更好的效能,就可以新增可選擇性依賴。

我最近看到的PlacesAutocompleteTextView庫,就屬於這種情況。該庫使用的內部 HTTP 客戶端,既可以是 OkHttpClient,也可以是 HttpURLConnection。通常,前者的效能更好,但是需要新增 OkHttp 作為依賴。 如果使用者不想包含該依賴,可以自動從標準庫回退到 HttpURLConnection。

為此,需要一個「resolver」 類以確定執行時要使用的依賴。 例如,以下的類就用於選擇 HTTP 客戶端:

public final class PlacesHttpClientResolver {
  public static final PlacesHttpClient PLACES_HTTP_CLIENT;

  static {
    boolean hasOkHttp;

    try {
      Class.forName("com.squareup.okhttp.OkHttpClient");
      hasOkHttp = true;
    } catch (ClassNotFoundException e) {
      hasOkHttp = false;
    }

    PlacesApiJsonParser parser = JsonParserResolver.JSON_PARSER;

    PLACES_HTTP_CLIENT = hasOkHttp ? new OkHttpPlacesHttpClient(parser) : new HttpUrlConnectionMapsHttpClient(parser);
  }

  private PlacesHttpClientResolver() {
    throw new RuntimeException("No Instances!");
  }
} 

該類被載入時,會檢查 OkHttpClient 的完全限定類名是否可用。如果丟擲 ClassNotFoundException,我們就知道使用者沒有新增 OkHttp,於是回退到 HttpURLConnection。PlacesHttpClient 是包裝以上兩種實現方式的公共介面,因此在整個程式碼庫中,這兩種實現方式可以交換使用。JSON 解析也採用了同樣的方法,Gson 可選擇性地作為依賴包含在庫中。

如果效能表現與庫的大小之間的權衡係數很大,這個方法確實不錯。但是,如果回退的實現方式比較困難(比如 JSON 解析就是這種情況),筆者建議你先使用外部庫來節省時間,在後續的版本中再考慮新增回退實現。

筆者在前文中提到,你應該對庫中包含的功能做出明智的選擇。如果某個功能幾乎所有使用者都不需要,最好將其除去,而且這裡也沒有必要使用前面提到的第一種可選擇性依賴。再次以 Retrofit 為例,在 2.x 版本 中,使用 REST 呼叫來響應這個功能,不再作為核心庫的一部分提供給使用者,而是移到一個單獨的模組上,並作為 Retrofit 的 maven 構件釋出 。

同樣地,不同的響應轉換器也被拆成了獨立的依賴。例如,Retrofit 使用者想要轉換一個 JSON 響應,而且已經依賴於 Gson,他們可以在 build.gradle 檔案中新增以下依賴:

dependencies {
  compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
}

而那些使用其他 JSON 庫(比如 Jackson)的使用者,或者需要解析其他資料格式(比如 XML 或 protocol buffers)的使用者,也可以採用這種方式新增自己需要的依賴,而且避免通用型依賴帶來的額外負擔。與此同時,核心庫也不會被這些附加功能干擾,可以專注於需要解決的主要問題。

總而言之,如果你正在編寫的庫有意給 Android 開發人員使用,在設計時務必記住以上幾個策略。庫的大小,不應該只當做屬性,而應該視為一種特性予以考慮,你的使用者絕對會因此而感激你!

OneAPM Mobile Insight 以真實使用者體驗為度量標準進行 Crash 分析,監控網路請求及網路錯誤,提升使用者留存。訪問 OneAPM 官方網站感受更多應用效能優化體驗,想閱讀更多技術文章,請訪問 OneAPM 官方技術部落格

本文轉自 OneAPM 官方部落格

原文地址:http://johnpetitto.com/android-lib-dependency-management/

相關文章