如何入門學習SDK:Android兩年SDK開發經驗全部奉上!實現目標+架構設計+設計規範...

在公司做了兩年多的 SDK 開發,結合自己的所知所學,分享一些 SDK 開發的經驗。

1. SDK 是什麼

相信做 Android 開發的朋友,一定使用過第三方的 SDK,比如推送 SDK、分享 SDK 等。SDK 的全稱是 Software Development Kit,翻譯爲“軟件開發工具包”。SDK 通常是爲輔助開發某類軟件而編寫的特定軟件包、框架集合等。

SDK 可以分爲系統 SDK 和應用 SDK。所謂系統 SDK 是爲使用特定的軟件框架、硬件平臺等所開發的工具集合。而應用 SDK 則是基於系統 SDK 開發的獨立於具體業務、擁有特定功能的工具集合。

SDK 的使用者主要是 B 端客戶,最終交付產品是代碼、示例和文檔,客戶接入 SDK 也是和 SDK 提供方交流的過程,對外溝通的成本比對內更高,遇到的問題也會更多。所以 SDK 開發對開發者的要求比對應用開發更高。能開發好 SDK 一定能開發好應用,但能開發好應用,未必能開發好 SDK。

2. SDK 實現目標

SDK 的實現目標,概括來說:簡潔、穩定、高效。

簡潔

對於用戶而言,一款好的產品應該是簡潔易用的,不該讓他們花費太長的時間學習。SDK 也當如此,它不該出現複雜繁瑣的對接工作,使用者通過閱讀代碼和文檔,花費很少的時間就能做好 SDK 的對接。

比如當開發者需要使用 SDK 的服務時,只需要在代碼中新增一行即可。在項目中初始化 SDK 只要一行代碼,開發者不用關心 GLContext,內部已做好處理,也不用關心同步或異步問題。

public class FURenderer {
    // 定義    
    public static void setup(Context context) {
        //...
    }
}
// 一行代碼調用
FURenderer.setup(context);

穩定

站在 SDK 使用者角度來看,我們期望第三方 SDK 的服務是穩定高效的,體現在提供穩定可靠的服務,同時運行時性能要高效。這就要求我們在設計實現 SDK 時要儘可能做到以下幾點:

  • 對外提供穩定的 API。SDK 的 API 一旦確定,除非特殊情況不可更改,提供方變更 API 的成本非常大。
  • 對外提供穩定的業務。在提供了穩定的 API 後,必須要有穩定的業務作爲支撐。
  • 運行時的穩定。確保 SDK 自身穩定運行,不能出現因爲接入了 SDK 而導致宿主應用不穩定的情況。
  • 版本穩定更新。SDK 版本迭代非常緩慢,要儘可能對使用者屏蔽迭代過程,避免帶來不必要的適配成本。

高效

無論是普通的應用開發還是 SDK 開發,都應該考慮到性能問題,SDK 設計者要着重考慮以下問題:

  • 更少的內存佔用。一般 SDK 和 App 運行在同一進程,此時 SDK 要管理好自己佔用的內存,合理分配,注意釋放。
  • 更少的內存抖動。在佔用更少內存的前提下,SDK 設計者必須減少頻繁 GC 造成的內存抖動問題。
  • 更少的電量消耗。低電量消耗和高性能表現之間很難做到權衡,可以從 CPU 計算量、屏幕刷新幀率等角度考量。

3. SDK 架構設計

SDK 的架構實現決定了後續的維護難度,所以最好能夠結合實際業務確定合適的方案。以項目中的模塊化開發爲例,講講架構設計的原則。

遵循面向對象開發的幾大原則,目的是達到三個目標:可維護性、可重用性和可擴展性。具體來講:

  • 根據單一職責原則,將系統拆分爲多個小模塊,每個模塊保持相對獨立,降低實現類的複雜度。
  • 根據接口隔離原則,爲每個模塊定義契約接口,接口的粒度要小,功能要細,越細小越易維護。
  • 模塊之間通過協議或接口通信,避免直接相互依賴,以降低耦合,互相瞭解最少,體現了迪米特法則。
  • 根據開閉原則,定義各個模塊的公共行爲,通過模版方法設計模式提供骨架實現,易於功能擴展。
  • 根據組合優於繼承的原則,當多個模塊功能疊加時,使用類的組合保證設計的靈活性。

比如項目第三方 demo 的功能模塊借鑑了 Java 集合框架的架構,分爲契約接口、抽象類和具體實現三部分。

  • 首先定義 IEffectModule 作爲特效的契約接口,包括創建、設置參數、銷燬等各個功能模塊的公共操作。
  • AbstractEffectModule 作爲 IEffectModule 的骨架實現,實現了共同使用的方法,定義了公共的成員變量。
  • 定義美顏 IFaceBeautyModule 接口,其繼承 IEffectModule 接口,包括額外的設置參數操作,FaceBeautyModule 作爲其實現類,同時繼承 AbstractEffectModule,複用基類的代碼。
  • 美妝美體模塊類似,先定義契約接口,然後定義具體實現,接口間相互隔離,接口內高度內聚。
  • FURenderer 實現 IFURenderer 渲染接口和 IModuleManager 模塊管理接口,組合各個功能模塊。

4. SDK 設計規範

API 設計在任何開發中都非常重要,許多時候軟件的質量好壞體現在 API 的設計上。在普通的應用開發中,API 只會在開發人員間流通,不會暴露給非本應用開發的其他人員。但是 SDK 作爲一種服務,需要向開發者暴露一部分 API,這樣才能使用 SDK 的服務。

下面列出一些應該重點關注的原則。

  1. 方法名錶明其用途

好的方法名最直觀表明它的功能,名字是自解釋的,不需要額外的文檔,這樣做會減少不必要的溝通成本。對於開發者而言,還有什麼比直接讀代碼更直觀呢?《重構》一書中講到,要像給自己孩子起名一樣給每個變量命名,這個要求不算過分吧。

  1. 參數的合法性檢驗

如果程序運行時出現異常,會破壞使用者的體驗,影響非常不好。我們採用“防禦式編程”的思想,能夠避免非法輸入對系統的破壞性。

當合法性校驗不通過時,針對方法權限不同分別對應不同不同的處理策略:

  • 對於公開方法顯式檢查拋出異常,並使用 @throw 來說明拋出異常的原因
  • 對於私有方法通過斷言的方式來檢查參數的合法。
  • 檢查構造方法的參數的合法性,以使對象處在統一狀態。

需要注意的是,如果檢查的代價太大,那就需要綜合考量。

  1. 方法只實現單一功能

一個方法應該具有單一的功能,儘可能做更少、更專的事情,這也是單一職責原則的體現。“阿里巴巴代碼規約”規定一個方法 最好不要超過 80 行,對龐大的方法要拆分成更小的。

另外注意,寧可提供小而美的方法也不要提供大而全的方法,大而全的方法往往經常發生變動,產生風險的可能性更高。因此不如提供更小的方法以便組合使用,小而美的方法更易做到代碼複用。

  1. 訪問權限控制

包括類方法的權限和變量的權限,能聲明私有的不要公開,外部知道得越少越好。能聲明靜態的方法就用靜態,靜態方法天然線程安全,體現繼承關係的用 protected 修飾,確保公開的方法和變量是安全可靠的。

  1. 避免過長參數

過長的參數會造成記憶上困難,還有調用傳參容易出錯,應當盡力避免。在無法避免過長參數的情況下,考慮其他的方法進行解決:

  • 通過使用 Builder 模式來實現
  • 通過將多個參數封裝成類對象

例如,項目裏有個方法,參數非常多。

int onDrawFrameSingleInput(byte[] img, int w, int h, int format, byte[] readBackImg, int readBackW, int readBackH);

重構後,把參數封裝成對象,調用方法只用構造一個對象傳入,避免大量參數帶來不好的體驗感。

public class VideoFrame {
    private int width;
    private int height;
    private byte[] data;
    private byte[] readback;
    private int readbackWidth;
    private int readbackHeight;
    private int pixelFormat;
    // ...
}

int onDrawFrameSingleInput(VideoFrame videoFrame);
  1. 慎用方法重載

濫用重載容易讓開發者感到疑惑,在需要重載方法的時候,可以使用不同方法名來代替。對於構造函數,可以通過靜態工廠來代替重載。

Java 中提供的 ObjectOutputStream 類就是個很好的示範:它的 write 對於每個基本類型都有一個變形,比如寫出字符、寫出 boolean 等操作。設計者並沒有使用重載將其設計成 write(Long l)、write(Boolean b),而是將其設計爲 writeLong(l)、writeBoolean(b)。

例如,項目對外的處理方法全部是重載,只能根據參數區分,迷惑性非常大。修改爲不同的方法名後,看到名字就知道要調用的方法,清楚了不少。

// 重構前
int onDrawFrame(byte[] img, int tex, int w, int h);
int onDrawFrame(byte[] img, int w, int h);
// 重構後
int onDrawFrameDualInput(byte[] img, int tex, int w, int h);
int onDrawFrameSingleInput(byte[] img, int w, int h, int format);
  1. 避免方法直接返回 null

對於需要返回數組或集合的方法,不要返回null。比如我們去買糕點店買麪包,麪包沒了是一種正常狀態,就不應該返回 null,而是返回長度爲 0 的數組或集合。Java 提供了 Collections.emptyXXX() 表示空集合。

  1. 避免引入第三方庫

GitHub有許多開源的第三方庫,比如網絡請求 OkHttp、圖片加載 Glide 等,但在 SDK 開發中,遵循的基本的原則是:

  • 最小可用性原則,即用最少的代碼,如無必要勿增實體。
  • 最少依賴性原則,即用最低限度的外部依賴,如無必要勿增依賴。

引入第三方庫可能帶來下面幾個問題:

  • 宿主應用的第三方庫和 SDK 依賴的版本不一致,容易引起衝突,增加對接的成本。
  • 開源庫不斷更新,SDK 也要及時更新,增加額外的維護工作量。
  • 由於引入開源庫,出現問題難以排查。
  1. 保證兼容性

SDK 是不斷迭代的,每次發佈都會有新功能和 bug 修復。對於使用者來說,升級版本不該有太大的改動,一般直接替換庫文件或者修改遠程依賴庫的版本號就夠了。避免直接對公開接口的重命名,如果舊接口廢棄,要通過 @Deprecated 關鍵字標明,並給出替代方案和廢棄的時間。

  1. 減少入侵性

要保證較少的代碼侵入主要在對外提供服務時,充分考慮開發者的使用場景來設計優良的 API。一套優良的 API 在定義時要滿足絕大數開發者預期的方式——語義上要求通俗易懂,使用上要求簡單可靠。具體的表現是,在正常情況下能夠穩定可靠地運行,在異常情況下及時地反饋錯誤信息。

比如使用 Gradle 下載依賴庫,AAR 包中有不必要的 bundle 資源,我們提供了打包 apk 時的構建配置,自由選擇要打包的 bundle,減少了對宿主應用的侵入性。

    applicationVariants.all { variant ->
        variant.mergeAssetsProvider.configure {
            doLast {
                delete(fileTree(dir: outputDir,
                        // 刪除不必要的 bundle 文件
                        includes: ['model/ai_face_processor_lite.bundle',
                                   'model/ai_gesture.bundle',
                                   'graphics/controller.bundle',
                                   'graphics/tongue.bundle']))
            }
        }
    }

5. SDK 交付

封裝包

Android 平臺通常使用 jar 和 aar 發佈 SDK,區別是 jar 只包含代碼,aar 可以包含代碼、資源和動態庫。一般而言 aar 是最合適的交付方式,把它上傳到 maven 服務器,使用者就可以一行代碼集成。對於需要靈活定製的客戶,我們也會提供 SDK 的源碼,弊端就是升級困難,要改動很多的代碼。

對於代碼混淆,公開接口和 native 使用的接口不要混,內部的實現細節可以混淆,以減少 SDK 包的大小。

接入文檔

接入文檔用來告訴 SDK 使用者,如何使用 SDK、詳細步使用驟和可能發生的問題。文檔內容包括:更新記錄、基本信息、API 說明、集成步驟、FAQ等。好文檔的標準就是清晰明瞭,通俗易懂。一個完全不懂 SDK 的開發者看着文檔就能對接,對於經常遇到的問題要逐條列出,專業名詞要有對應的解釋。

Demo 示例

集成 Demo 通常是一個簡單的 App,用來展示如何快速地接入 SDK。Demo 的源碼託管到 GitHub,方便使用者參考,其版本變更策略和 SDK 版本的變化保持一致。儘管是個 Demo,它的開發原則也要與 SDK 一致,確保高質量的交付。

最後

最後爲了幫助大家深刻理解NDK相關知識點的原理以及面試相關知識,這裏還爲大家整理了Android NDK七大模塊學習寶典

這份寶典主要涉及以下幾個方面:

  • NDK 模塊開發
  • JNI 模塊
  • Native 開發工具
  • Linux 編程
  • 底層圖片處理
  • 音視頻開發
  • 機器學習

不用多說,相信大家都有一個共識:無論什麼行業,最牛逼的人肯定是站在金字塔端的人。所以,想做一個牛逼的程序員,那麼就要讓自己站的更高,成爲技術大牛並不是一朝一夕的事情,需要時間的沉澱和技術的積累。

還有蒐集整理的2019-2020BAT 面試真題解析,幫助大家深刻理解Android相關知識點的原理以及面試相關知識。

以上內容均免費分享給大家,需要完整版的朋友,點這裏可以看到全部內容。或者關注主頁掃描加 微信 獲取。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章