Android SDK 開發(第一部分)

男怕入錯行,女怕嫁錯郎。html

肥肥工做六年有餘,其中四年的時間致力於 SDK 開發。細細想來,唏噓不已,感觸頗多。android

SDK 開發是一份不被理解而又枯燥無趣的工做。據說,每一個 SDK 開發者上輩子都是折翼的天使。因此,程序猿與程序媛們,若是你身邊有 SDK 開發者,請愛護他們。git

這些年來,肥肥都假設,使用個人 SDK 的開發者都是一羣肥肥打不過又跑不贏的暴力狂,最關鍵的是還知道我住哪裏。github

六年來一路荊棘,一路坎坷,一事無成。數據庫

揚雄亦慕仲尼之教者,以著書立言爲事,得自易哉。編程

第一節 Library、API、SDK 以及 Framework

目前來講,並無統一的、官方的文檔定義 Android 應用開發中常見的 LibraryAPISDK 以及 Framework 這些概念。咱們基於其字面意思以及平常使用習慣作以下的解釋:安全

  • Library Library 是一組或幾組類的集合,能夠直接調用,使得開發更高效。Library每每是對系統已有功能的加強或是對應用程序架構中功能模塊的具體實現。好比 Android SDK 中提供的Support Library,著名的開源項目如VolleyPicassoAndroid-Universal-Image-Loader
  • APIApplication Programming Interface,也就是軟件系統不一樣組成部分(模塊)銜接的約定。因爲軟件的規模日趨龐大,經常須要把複雜的系統劃分紅小的組成部分,API 的設計就顯示尤其更要。良好的程序設計實踐中,API 的設計首先要使軟件系統的職責獲得合理的劃分,下降系統各組成部分的相互依賴,並提高組成單元的內聚性,從而提升系統的可維護性以及擴展性。
  • SDKSoftware Development Kit,普遍意義上的 SDK 通常都是爲特定的軟件包、軟件框架、硬件平臺、操做系統等創建應用程序時所使用的開發工具的集合(系統 SDK)。而狹義上的 SDK應用 SDK) 則是基於系統 SDK 進行開發的新的、獨立於具體業務且完成特定功能的一組工具的集合。例如友盟統計 SDK極光推送 SDK多盟廣告 SDK
  • Framework Framework 是整個或部分系統的可重用設計,表現爲一組抽象構件及構件實例間交互的方法。另外一種定義認爲,Framework 是被應用開發者定製的應用骨架。能夠說,一個 Framework 是一個可複用的設計構件,它規定了應用的體系結構,闡明瞭整個設計、協做構件之間的依賴關係、責任分配和控制流程,表現爲一組抽象類(或接口)以及其實例之間的協做方法,它爲構件複用提供了上下文(Context)關係。

通常來講,SDKFrameworkAPI 以及 Library 的集合。Framework 定義了 SDK 總體的可重用設計,規定了 SDK 各功能模塊的職責以及依賴關係。SDK 中功能模塊的具體實現則是 Library 的主要職責。各模塊之間的通訊以及 SDK 所能提供的服務則經過 API 體現出來。網絡

一般狀況下,SDK 在應用程序中是做爲特定功能提供者的角色出現的。例如推送功能的 SDK、統計功能的 SDK、廣告功能的 SDK、性能監測功能的 SDK 以及分享功能的 SDK 等等。架構

第二節 SDK 設計

前文中說到 SDK 是做爲應用程序中特定功能的提供者而存在的。一般狀況下,SDK 是做爲第三方服務而被引入到應用程序中的,SDK 的品質可以影響到應用程序的品質。框架

易用性

肥肥認爲,好的 SDK 產品應該是易於使用的。咱們想要創造一種簡單的模式,讓 SDK 的使用者在他們的應用中方便的使用 SDK,那麼這種模式應該是不須要侵入太多的代碼或者不須要繁瑣的集成工做的。

若是一個 API 的調用方式正好是開發者所預期的方式,那麼咱們認爲該 API 的調用方式是易用的表現。多數狀況下,API 的品質直接決定了 SDK 的品質。SDK 的易用性體如今 API 的易用性上,那麼,好的 API 設計也就顯得尤其重要。

一般狀況下,API 難以被誤用 也是易用性的一種,這樣能夠有效地避免一些錯誤的發生。好比,對參數的校驗、對邊界的嚴格檢查以及詳細的說明文檔,都將使得開發者在使用 SDK 的時候,可以有效地避免一些錯誤的。

穩定性

SDK 使用者角度來講,在 SDK 使用過程當中,咱們假設 SDK 自己是可靠的,不會影響到程序自己的穩定性。那麼,從 SDK 設計者的角度來講,SDK 做爲第三方服務,其穩定性是尤其重要的。這種穩定性體如今以下四個方面:

  • 對外提供服務的 API 的穩定性,SDK 對外的 API 一旦肯定,其變動的成本(即便僅僅在某個 API 上增長或減小一個參數)是很是高昂的。
  • 業務的穩定性,業務的穩定性是對 API 穩定性的補充。底層的 SDK 業務一般決定了上層 API 的形態。
  • SDK 運行時的穩定性,做爲第三方服務提供者,自身的穩定性很是重要。
  • SDK 版本迭代的穩定性,相對於應用程序的 Release 版本迭代速度,SDKRelease 版本的迭代速度是相對緩慢的。頻繁的 SDK 升級會給應用程序開發者帶來額外的升級成本,並給應用程序開發者留下 SDK 不穩定的印象。

靈活性

一般狀況下,SDK 開發者並不能像應用開發者那樣擁有更多的選擇權。咱們不能選擇設備,系統版本,甚至是目標客戶。相應的,咱們須要最大化支持設備,提供高度靈活的 API 設計,以知足不一樣客戶羣的須要。

可讓開發者選擇不一樣的依賴管理器或者構建工具來集成 SDK,是靈活性的一大致現。面對形形色色的應用程序開發團隊,咱們也要儘量的去迎合這些團隊所使用的開發環境,提供一些主要的開發工具插件的支持,包括 GradleMaven以及 Ant等。

靈活性設計的關鍵是瞭解你的 SDK 用戶的需求,而後作出須要支持的最低系統版本的決定。咱們很但願咱們的 SDK 可以支持儘量多的系統設備,對於這一點,下降支持最低操做系統版本是頗有必要盡力去作的。

但從另外一方面來看,兼容低版本也是要付出代價的。並無什麼直接的法則可以告訴咱們如何才能在繁瑣度和更好的兼容性上權衡。支持舊的操做系統版本,一般意味着不能使用操做系統的新特性,同時還要面對一些舊版本存在的問題。除此以外,咱們還要花費更多精力去測試代碼的正確性以及兼容性。

最小資源開銷

相對於 PC 來講,移動設備的硬件資源顯得尤其珍貴。SDK 應儘量的下降以下幾種系統資源開銷:

  • 內存以及 CPUSDK 在儘量下降內存佔用的狀況下,也應該儘可能保證內存佔用的穩定性(避免內存抖動)。
  • 電量,對於 SDK 的電量開銷很難有統一的標準來衡量。手機的各個硬件模塊的耗電量是不同的,有些模塊很是的耗電,而有的模塊耗電量則相對顯得很小。可是,儘量的爲用戶省電是值得推薦的作法。
  • 網絡流量,相較於內存及 CPU 的開銷來講,應用程序的使用者對於電量以及網絡流量消耗更爲敏感。
  • 存儲資源,對於應用程序來說,Android 設備的存儲路徑大體可以分爲兩類:
  1. 應用程序目錄下存儲(/data/data/package_name//storage/sdcard0/Android/data/package_name/目錄)
  2. SD 卡下非應用程序目錄存儲

對於 SDK 來講,若是沒有徹底的必要性(好比使用 SharedPreference),選擇 SD 卡目錄存儲數據是一種不錯的選擇。

這樣作的好處在於,一方面能夠減小/data/data/package_name//storage/sdcard0/Android/data/package_name/目錄的存儲壓力,另外一方面則方便多個應用程序間共享文件。

固然,不管使用何種存儲目錄,爲 SDK 建立獨立的文件夾是很是有必要的(好比 SDK 使用/data/data/package_name/sdk_cache目錄),這也是爲了方便將 SDK 文件與應用程序文件區分開來。

相較於目前動輒16G 起步的存儲空間來講,應用程序的使用者對於電量和網絡流量的消耗顯得尤其敏感。究其緣由,多是電量和網絡流量是應用程序使用者可以直接接觸到的一些指標。在即使是網絡流量白菜價的年達,也會有很大一部分用戶由於摳門亦或是運營商等緣由,仍舊使用着每個月幾十兆流量的套餐。而對於電量的敏感,可能就是由於現代人都有的 低電量恐懼症 這樣時髦的毛病了。

內存以及 CPU 的過分使用,一方面帶來了過分的電量開銷,另外一方面則可能形成應用程序卡頓甚至 ANR 等問題。

這些問題都可以或直接或間接的影響到應用程序使用者對一款應用程序的評價。

主線程

衆所周知,Android 系統中主線程又被稱爲 UI 線程,理想狀況下,主線程只負責向 UI 組件分發事件(觸屏事件渲染事件等)。

系統並不會爲每一個組件建立單獨線程,在同一個進程中的 UI 組件都會在 UI 線程中實例化,系統對每個組件的調用都從 UI 線程分發出去。那麼由此引起的問題就是,響應系統回調的方法(組件生命週期觸屏事件等)都是在 UI 線程中執行的。

若是全部的工做都是在 UI 線程中執行,特別是作一些耗時的操做(Http 請求、數據庫查詢以及文件讀寫等),都會阻塞 UI 線程,致使事件的分發中止。從用戶的角度來看,是應用程序卡頓甚至卡住了。更爲糟糕的狀況是,若是 UI 線程阻塞的時間過長(UI 線程中大約5秒,BroadcastReceiver 中大概10秒),系統就會彈出 ANRApplication Not Response)對話框。

從另外一個方面來講,AndroidUI 組件並不是是線程安全的,也就意味着不能從非 UI 線程操做 UI 組件。因此,SDK 的線程模型有四條重要的設計原則:

  1. 不能阻塞 UI 線程
  2. 不能在 UI 線程以外操做 UI 組件SurfaceView 不受該原則限制)。
  3. 除非 SDK 必須,不然不能使用應用程序主線程。若是必須使用主線程,那麼不能長時間佔用。
  4. SDK 應該有一個專門的線程來處理 SDK 的相關業務。

最小權限原則

Android 應用程序權限機制限制應用程序訪問特定的資源,如照相機、網絡、存儲系統以及查詢用戶數據以及以及某些 API 的調用。

通常來講,系統會在應用程序安裝過程當中提醒用戶該應用程序所申請的權限,若是所申請的權限太高(Root 權限)則會在應用程序申請該權限時彈出窗口進行通知。

而自 Android 6.0 開始則使用了全新的權限控制系統,除了以上權限控制的機制以外,還會在應用程序訪問特定系統功能時(好比使用藍牙模塊),也會經過彈出窗口的形式的進行通知。

相對應的,在 SDK 開發過程當中,咱們應該爲 Android 6.0 及以上版本單獨作權限方面的適配工做。

那麼,做爲第三方服務的 SDK 必定要遵循的一個原則就是:最小權限原則最小權限原則指的是,SDK 儘量不要申請非必要的權限,儘量的不要給使用 SDK 的應用程序帶來額外的權限申請。

舉例來講,若是 SDK 中並無使用到撥打電話的功能,可是卻要求應用程序開發者在AndroidManifest.xml文件中聲明 android.permission.CALL_PHONE權限,那麼就是違反了最小權限原則

違反最小權限原則並不會對 SDK 自己的業務帶來任何影響,可是這會使得應用程序向系統申請沒必要要的權限而形成的額外的權限開支。由此帶來的後果是用戶對於應用程序的不良印象。

嚴格的生命週期把控

SDK 做爲服務的提供者,定義清晰且嚴謹的生命週期模型顯得尤其重要。一種簡單的作法就是 SDK 的生命週期託管給當前 Activity 的生命週期管理。由此帶來的好處就是,SDK 能夠在恰當的時機作恰當的事情。好比咱們能夠在 onCreate() 的生命週期中完成一些初始化的工做,而在onDestroy()的生命週期中完成對象的銷燬工做以及在應用程序的onPause()狀態暫定一些後臺的操做以節省資源。

第三節 API 設計

本文中,咱們假定 API 設計只涉及以下兩方面:

  • SDK 對外提供服務的 API 設計,後文簡稱 SDK API 或者公共 API
  • SDK 內部各模塊間的 API 設計,後文簡稱模塊 API

之因此將這兩方面拆分出來,是由於肥肥以爲這是兩種不一樣的 API 設計理念。首先是面向的用戶羣體不一樣,SDK API 面向的是 SDK 使用者,也就是商業用戶,而模塊 API 則是面向 SDK 團隊中的其餘開發人員。其次,SDK API 是由具體的使用場景而決定的,而模塊 API 則是由具體的功能而決定的。

從公司的角度來講,API 的通用商業價值是能夠進行評估的。從數據的角度來看,API 應該算是公司資產的一種,由於設計優良的 API 實現了數據的可訪問性、準確性、可應用性以及安全性。每個公共 API 都在某些程度上提供了特定數據的可訪問性,而設計優良的公共 API 則很大程度上保證了數據的準確性以及安全性。對於每個開發人員來講,只要參與到編程的過程當中,那麼你就是一名 API 的設計者——由於好的代碼便是模塊,每個模塊就是一個 API(雖然這並不適用於 SDK API 的開發)。

SDK 內部模塊 API 的設計相比,SDK API 的設計難度要更大一些。 咱們下文中的討論圍繞 SDK API 的設計展開,固然其也適用於模塊 API 的設計。

好的 API 設計來自於迭代過程。

在開始設計你的 API 以前,你應該先了解設計這個 API 的目的,這也就意味着咱們要設計出一種接口,讓它的使用方式符合 API 自己的設計目的。做爲 SDK 開發者,咱們對 API 設計所作的任何一個決策都會影響到 SDK 產品的質量。在咱們可以作出一個正確的決策以前,極可能會先作出一個錯誤的決策,並從中吸收教訓。實際上,在經歷了屢次的錯誤決策以後纔可能接近正確的決策。

這正是 API 設計中迭代的意義。在實際的操做過程當中,咱們所面臨的一項挑戰在於,在某個 API 發佈以後,再進行變動的成本變得很是高昂,並伴隨着很是大的風險。

咱們力求在 API 變動的成本變得高昂以前,就消除易用性與設計方面的問題。這須要強有力的對於產品需求的把控、全面的測試以及深厚的 API 設計功力來保證。

設計良好的 API 應該具有以下幾個特色:

  • 風格統一:有較爲統一的命名風格;
  • 易於學習:有完善的使用文檔以及示例代碼,儘量下降使用者的學習成本;
  • 易於使用:有詳盡的註釋以及易於理解且表意直觀的命名;
  • 接口安全:有詳細的錯誤提示,並對非法參數進行校驗;
  • 功能單一,可是足夠強大;

單一職責原則

單一職責原則說的是在類或方法的設計中,應該保證有且僅有一個引發類或方法變化的緣由。通俗來講就是一個類或方法只負責一項職責。若是有兩個比較接近的功能,可是使用一個接口實現有點繁瑣,那麼就應該使用兩個接口。不要爲了減小接口的數量而生硬的把兩個接口合併爲一個。

參數儘量少

接口調用中應儘量少的要求調用中傳遞參數。若是 SDK 能本身獲取的參數就不須要讓開發者傳遞。

在同一個接口中使用大量的相同類型的參數也是不推薦的。若是沒法避免,建議將參數封裝成對象。

參數合法性校驗

參數合法性校驗應該是接口要作的第一件事情。全部的參數必須校驗其合法性,並視具體業務對不合法參數進行處理。通常狀況下,除了必要參數,對於其餘參數可使用默認值或者區間值(超過最大、最小值使用最大、最小值)的方式來確保業務的正常流程。若是必要參數不合法,能夠考慮使用拋出運行時異常的方式通知開發者。

優美的降解

開發者常常容易不耐煩,因此對於一些錯誤或異常,應該儘量早的拋出。好比一些可以在編譯期間就能拋出,終歸好於在運行期間拋出。也就是說,SDK 開發者應該儘量早的把一些能夠預期的異常拋出,以便讓開發者儘快處理這些異常。

實現不要影響 API

正式發佈的 SDK 的接口應該是穩定的,這其中包括其參數類型、返回值類型、異常類型。

咱們假設正式發佈的 SDK 中的任何一個接口,都有機會被調用。那麼,這樣也就要求咱們在後續的版本迭代中保證接口的參數類型、返回值類型以及異常類型是統一的。

若是須要變動接口功能,建議增長新的接口而不是改變現有接口。

第四節 版本管理

SDK 的升級、維護策略中,版本管理是一個很是重要的組成部分:

  • 應用程序開發者須要瞭解他們所使用的 SDK 版本的特定信息,以及已使用的 SDK 的升級版本的可用狀況;
  • SDK 開發者須要使用版本號來定位 SDK 使用過程當中所存在的問題,並創建 SDK 升級的依據。

版本號的命名及管理並無統一的標準,不一樣的團隊每每使用不一樣的命名風格。

可是不管使用哪一種版本命名風格,給出詳盡的版本變動記錄是一種不錯的選擇。

SDK 版本迭代狀態

按照軟件版本的發佈階段來看,一款成熟穩定的 SDK 產品的版本迭代每每會經歷以下階段:

  • alpha 版:該版本表示該 SDK 產品在此階段主要是以實現功能爲主,一般只在開發團隊內部交流使用。通常來講,該版本的 SDK 產品存在的 Bug 較多,須要經歷多個 alpha 版本的迭代才能進入 beta 版
  • beta 版:該版本相對於 alpha 版已經有了很大的改進,修復了嚴重的 Bug,可是還存在一些已知或是未知的 Bug,一般狀況下只在開發團隊以及測試團隊之間交流使用,須要經歷多個 beta 版本的迭代才能進入 rc 版
  • release candidate 版(rc 版):該版本的 SDK 趨於成熟,基本上不會出現致使錯誤的 Bug,原則上再也不增長新的功能,與正式發佈的正式版沒有太大的差別。一般狀況下該版本用於進行小規模灰度測試,原則上不會提供給應用程序開發者使用。
  • release 版:該版本意味着 最終發佈,在經歷了前面幾個版本的迭代以後產生的最終版本,也就是最終交付到應用程序開發者使用的版本。

SDK 版本號命名

一個比較合理的版本號命名規範由以下四部分組成:

V1_0_2_201511171733_beta

  1. 主版本號(1);
  2. 子版本號(0);
  3. 階段版本號(2);
  4. 迭代版本號(201511171733_beta)。

SDK 版本號修改原則

  • 主版本號:當功能模塊有較大的變更,好比增長多個模塊或者 SDK 總體架構發生變化時,由需求決定是否修改。
  • 子版本號:當功能有必定的增長或變化時,由項目決定是否修改。
  • 階段版本號:當修復 Bug 以及小規模調整時,須要常常發佈修訂版,此時可由項目經理決定是否修改。
  • 迭代版本號:用於記錄該版本的 SDK 發佈時的時間以及當前的迭代狀態。原則上,當項目處於 alphabeta以及 rc 版時,該版本號須要體現每一次的修改時間以及狀態。當項目處於 release 版時,該版本號用於記錄該版本的發版時間。

API 版本管理

API 的版本受到 SDK 版本迭代狀態的約束,可是不受 SDK 版本號修改原則的限制。

只有處於 release(或 rc ) 狀態的 API 才能是對外提供服務的,不然該 API 應該是對應用程序開發人員不可見的。換句話說就是,堅定不發佈處於 alphabeta 狀態的 API

API 一旦對外發布,其內部實現以及方法簽名原則上處於不可變動狀態:

  • 若是須要修改 API 的內部實現,在保證方法簽名不變的狀況下,API 必須經過測試用例的邊界及功能測試,並儘量的給出原 API 實現的備份——使用oldMethodName前綴標識原 API
  • 若是須要變動方法簽名,好比增長、刪除參數或是改變返回值類型,那麼在保證原 API 不變的狀況下,使用方法重載實現新的 API
  • 若是須要廢棄某些 API,應在 SDK release 版本迭代的前 N 個版本使用 @deprecated 標識須要廢棄的 API,並給出該 API 的替代方案以及具體的 API 移除時間(或是 SDK 版本)。

Http 接口版本管理

SDK 一旦發佈,你將沒法強制要求應用程序開發者跟隨你的 SDK 版本迭代而更新他們的代碼。在必定週期內,將會有多個 SDK 版本在提供服務,除了建議開發者升級 SDK 以外,服務端將不得不爲多個 SDK 版本提供支持。

從另外一方面來看,隨着需求的變動,API 會相應的增長或聲明廢棄。與之相對應的,Http 接口每每也會發生相應的變化。

文檔以及 Demo 版本管理

一種比較合理的作法是,文檔以及 SDK 對應的 DemoSDK 版本的管理。更爲簡便的作法就是文檔以及 Demo 採用 SDK 版本號進行統一管理。廣泛的作法是,即使是 SDK 接口的輕微改變,也要及時的體如今對應的文檔上,並更新對應的 Demo。在 SDK 上線初期,其迭代頻率相對較高,那麼就會出現多個版本 SDK 共存的狀況。合理的文檔、SDK 以及 Demo 間的版本關係,也就顯得尤其重要。

第五節 總結

SDK 開發是一個很大的範疇,相較於應用程序的開發,有類似之處,也有不一樣之處。從面相的客戶全體來講,應用程序開發者面向的是普通用戶,而 SDK 開發者則面向應用程序開發人員。從服務的角度來講,應用程序開發人員在設計應用的時候,每每要考慮性能、兼容性、用戶體驗、渠道以及版本迭代。而 SDK 開發人員不只要全面考慮上面這些因素,還要近乎於苛刻的將性能、兼容性提高到極致。對於某項需求的驗證,應用程序開發人員會選擇在部分灰度版本中進行驗證,而 SDK 開發人員則沒有這樣的幸運,只能依賴對業務的高度抽象進行驗證。固然,目前廣泛的作法是基於本身的 SDK 開發相應的應用程序,一方面可以進行一些需求的驗證,另外一方面,本身成爲本身的客戶,也何嘗不是一件壞事。

肥肥不才,文章先後修改數次,歷經四月,終於寫完《Android SDK 開發》的第一部分。這期間肥肥仔細拜讀了 參考文獻 中各位前輩的文章,受益頗多。肥肥在文章的有些章節內容中,直接參考了一些前輩的觀點,甚至存在一些直接複製的行爲。在此向各位前輩致以最高的敬意,併爲肥肥的剽竊行爲做出道歉。

剩餘的內容將會圍繞 SDK 的測試、安全性、業務配置以及數據運營展開討論。

版本記錄

  • 2016年09月20日 初稿撰寫,發佈。
相關文章
相關標籤/搜索