Android SDK 開發(fā)經(jīng)驗(yàn)談

在公司做了兩年多的 SDK 開發(fā)参淫,結(jié)合自己的所知所學(xué),分享一些 SDK 開發(fā)的經(jīng)驗(yàn)。

1. SDK 是什么

相信做 Android 開發(fā)的朋友浦马,一定使用過第三方的 SDK,比如推送 SDK张漂、分享 SDK 等。SDK 的全稱是 Software Development Kit谨娜,翻譯為“軟件開發(fā)工具包”航攒。SDK 通常是為輔助開發(fā)某類軟件而編寫的特定軟件包、框架集合等趴梢。

SDK 可以分為系統(tǒng) SDK 和應(yīng)用 SDK漠畜。所謂系統(tǒng) SDK 是為使用特定的軟件框架、硬件平臺(tái)等所開發(fā)的工具集合坞靶。而應(yīng)用 SDK 則是基于系統(tǒng) SDK 開發(fā)的獨(dú)立于具體業(yè)務(wù)憔狞、擁有特定功能的工具集合。

SDK 的使用者主要是 B 端客戶彰阴,最終交付產(chǎn)品是代碼瘾敢、示例和文檔,客戶接入 SDK 也是和 SDK 提供方交流的過程尿这,對(duì)外溝通的成本比對(duì)內(nèi)更高簇抵,遇到的問題也會(huì)更多。所以 SDK 開發(fā)對(duì)開發(fā)者的要求比對(duì)應(yīng)用開發(fā)更高射众。能開發(fā)好 SDK 一定能開發(fā)好應(yīng)用碟摆,但能開發(fā)好應(yīng)用,未必能開發(fā)好 SDK叨橱。

2. SDK 實(shí)現(xiàn)目標(biāo)

SDK 的實(shí)現(xiàn)目標(biāo)烁登,概括來說:簡(jiǎn)潔、穩(wěn)定慕购、高效舟舒。

簡(jiǎn)潔

對(duì)于用戶而言,一款好的產(chǎn)品應(yīng)該是簡(jiǎn)潔易用的颜骤,不該讓他們花費(fèi)太長(zhǎng)的時(shí)間學(xué)習(xí)。SDK 也當(dāng)如此,它不該出現(xiàn)復(fù)雜繁瑣的對(duì)接工作厢洞,使用者通過閱讀代碼和文檔,花費(fèi)很少的時(shí)間就能做好 SDK 的對(duì)接典奉。

比如當(dāng)開發(fā)者需要使用 SDK 的服務(wù)時(shí)躺翻,只需要在代碼中新增一行即可。在項(xiàng)目中初始化 SDK 只要一行代碼卫玖,開發(fā)者不用關(guān)心 GLContext公你,內(nèi)部已做好處理,也不用關(guān)心同步或異步問題假瞬。

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

穩(wěn)定

站在 SDK 使用者角度來看陕靠,我們期望第三方 SDK 的服務(wù)是穩(wěn)定高效的,體現(xiàn)在提供穩(wěn)定可靠的服務(wù)脱茉,同時(shí)運(yùn)行時(shí)性能要高效剪芥。這就要求我們?cè)谠O(shè)計(jì)實(shí)現(xiàn) SDK 時(shí)要盡可能做到以下幾點(diǎn):

  • 對(duì)外提供穩(wěn)定的 API。SDK 的 API 一旦確定琴许,除非特殊情況不可更改税肪,提供方變更 API 的成本非常大。
  • 對(duì)外提供穩(wěn)定的業(yè)務(wù)榜田。在提供了穩(wěn)定的 API 后益兄,必須要有穩(wěn)定的業(yè)務(wù)作為支撐。
  • 運(yùn)行時(shí)的穩(wěn)定箭券。確保 SDK 自身穩(wěn)定運(yùn)行净捅,不能出現(xiàn)因?yàn)榻尤肓?SDK 而導(dǎo)致宿主應(yīng)用不穩(wěn)定的情況。
  • 版本穩(wěn)定更新辩块。SDK 版本迭代非常緩慢蛔六,要盡可能對(duì)使用者屏蔽迭代過程,避免帶來不必要的適配成本废亭。

高效

無論是普通的應(yīng)用開發(fā)還是 SDK 開發(fā)古今,都應(yīng)該考慮到性能問題,SDK 設(shè)計(jì)者要著重考慮以下問題:

  • 更少的內(nèi)存占用滔以。一般 SDK 和 App 運(yùn)行在同一進(jìn)程捉腥,此時(shí) SDK 要管理好自己占用的內(nèi)存,合理分配你画,注意釋放抵碟。
  • 更少的內(nèi)存抖動(dòng)桃漾。在占用更少內(nèi)存的前提下,SDK 設(shè)計(jì)者必須減少頻繁 GC 造成的內(nèi)存抖動(dòng)問題拟逮。
  • 更少的電量消耗撬统。低電量消耗和高性能表現(xiàn)之間很難做到權(quán)衡,可以從 CPU 計(jì)算量敦迄、屏幕刷新幀率等角度考量恋追。

3. SDK 架構(gòu)設(shè)計(jì)

SDK 的架構(gòu)實(shí)現(xiàn)決定了后續(xù)的維護(hù)難度,所以最好能夠結(jié)合實(shí)際業(yè)務(wù)確定合適的方案罚屋。以項(xiàng)目中的模塊化開發(fā)為例苦囱,講講架構(gòu)設(shè)計(jì)的原則。

遵循面向?qū)ο箝_發(fā)的幾大原則脾猛,目的是達(dá)到三個(gè)目標(biāo):可維護(hù)性撕彤、可重用性和可擴(kuò)展性。具體來講:

  • 根據(jù)單一職責(zé)原則猛拴,將系統(tǒng)拆分為多個(gè)小模塊羹铅,每個(gè)模塊保持相對(duì)獨(dú)立,降低實(shí)現(xiàn)類的復(fù)雜度愉昆。
  • 根據(jù)接口隔離原則职员,為每個(gè)模塊定義契約接口,接口的粒度要小跛溉,功能要細(xì)廉邑,越細(xì)小越易維護(hù)。
  • 模塊之間通過協(xié)議或接口通信倒谷,避免直接相互依賴,以降低耦合糙箍,互相了解最少渤愁,體現(xiàn)了迪米特法則。
  • 根據(jù)開閉原則深夯,定義各個(gè)模塊的公共行為抖格,通過模版方法設(shè)計(jì)模式提供骨架實(shí)現(xiàn),易于功能擴(kuò)展咕晋。
  • 根據(jù)組合優(yōu)于繼承的原則雹拄,當(dāng)多個(gè)模塊功能疊加時(shí),使用類的組合保證設(shè)計(jì)的靈活性掌呜。

比如項(xiàng)目第三方 demo 的功能模塊借鑒了 Java 集合框架的架構(gòu)滓玖,分為契約接口、抽象類和具體實(shí)現(xiàn)三部分质蕉。

  • 首先定義 IEffectModule 作為特效的契約接口势篡,包括創(chuàng)建翩肌、設(shè)置參數(shù)、銷毀等各個(gè)功能模塊的公共操作禁悠。
  • AbstractEffectModule 作為 IEffectModule 的骨架實(shí)現(xiàn)念祭,實(shí)現(xiàn)了共同使用的方法,定義了公共的成員變量碍侦。
  • 定義美顏 IFaceBeautyModule 接口粱坤,其繼承 IEffectModule 接口,包括額外的設(shè)置參數(shù)操作瓷产,F(xiàn)aceBeautyModule 作為其實(shí)現(xiàn)類站玄,同時(shí)繼承 AbstractEffectModule,復(fù)用基類的代碼拦英。
  • 美妝美體模塊類似蜒什,先定義契約接口,然后定義具體實(shí)現(xiàn)疤估,接口間相互隔離灾常,接口內(nèi)高度內(nèi)聚。
  • FURenderer 實(shí)現(xiàn) IFURenderer 渲染接口和 IModuleManager 模塊管理接口铃拇,組合各個(gè)功能模塊钞瀑。
UML類圖

4. SDK 設(shè)計(jì)規(guī)范

API 設(shè)計(jì)在任何開發(fā)中都非常重要,許多時(shí)候軟件的質(zhì)量好壞體現(xiàn)在 API 的設(shè)計(jì)上慷荔。在普通的應(yīng)用開發(fā)中雕什,API 只會(huì)在開發(fā)人員間流通,不會(huì)暴露給非本應(yīng)用開發(fā)的其他人員显晶。但是 SDK 作為一種服務(wù)贷岸,需要向開發(fā)者暴露一部分 API,這樣才能使用 SDK 的服務(wù)磷雇。

下面列出一些應(yīng)該重點(diǎn)關(guān)注的原則偿警。

  1. 方法名表明其用途

好的方法名最直觀表明它的功能,名字是自解釋的唯笙,不需要額外的文檔螟蒸,這樣做會(huì)減少不必要的溝通成本。對(duì)于開發(fā)者而言崩掘,還有什么比直接讀代碼更直觀呢七嫌?《重構(gòu)》一書中講到,要像給自己孩子起名一樣給每個(gè)變量命名苞慢,這個(gè)要求不算過分吧诵原。

  1. 參數(shù)的合法性檢驗(yàn)

如果程序運(yùn)行時(shí)出現(xiàn)異常,會(huì)破壞使用者的體驗(yàn),影響非常不好皮假。我們采用“防御式編程”的思想鞋拟,能夠避免非法輸入對(duì)系統(tǒng)的破壞性。

當(dāng)合法性校驗(yàn)不通過時(shí)惹资,針對(duì)方法權(quán)限不同分別對(duì)應(yīng)不同不同的處理策略:

  • 對(duì)于公開方法顯式檢查拋出異常贺纲,并使用 @throw 來說明拋出異常的原因
  • 對(duì)于私有方法通過斷言的方式來檢查參數(shù)的合法。
  • 檢查構(gòu)造方法的參數(shù)的合法性褪测,以使對(duì)象處在統(tǒng)一狀態(tài)猴誊。

需要注意的是,如果檢查的代價(jià)太大侮措,那就需要綜合考量懈叹。

  1. 方法只實(shí)現(xiàn)單一功能

一個(gè)方法應(yīng)該具有單一的功能,盡可能做更少分扎、更專的事情澄成,這也是單一職責(zé)原則的體現(xiàn)∥废牛“阿里巴巴代碼規(guī)約”規(guī)定一個(gè)方法
最好不要超過 80 行墨状,對(duì)龐大的方法要拆分成更小的。

另外注意菲饼,寧可提供小而美的方法也不要提供大而全的方法肾砂,大而全的方法往往經(jīng)常發(fā)生變動(dòng),產(chǎn)生風(fēng)險(xiǎn)的可能性更高宏悦。因此不如提供更小的方法以便組合使用镐确,小而美的方法更易做到代碼復(fù)用。

  1. 訪問權(quán)限控制

包括類方法的權(quán)限和變量的權(quán)限饼煞,能聲明私有的不要公開源葫,外部知道得越少越好。能聲明靜態(tài)的方法就用靜態(tài)砖瞧,靜態(tài)方法天然線程安全息堂,體現(xiàn)繼承關(guān)系的用 protected 修飾,確保公開的方法和變量是安全可靠的芭届。

  1. 避免過長(zhǎng)參數(shù)

過長(zhǎng)的參數(shù)會(huì)造成記憶上困難,還有調(diào)用傳參容易出錯(cuò)感耙,應(yīng)當(dāng)盡力避免褂乍。在無法避免過長(zhǎng)參數(shù)的情況下,考慮其他的方法進(jìn)行解決:

  • 通過使用 Builder 模式來實(shí)現(xiàn)
  • 通過將多個(gè)參數(shù)封裝成類對(duì)象

例如即硼,項(xiàng)目里有個(gè)方法逃片,參數(shù)非常多。

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

重構(gòu)后,把參數(shù)封裝成對(duì)象褥实,調(diào)用方法只用構(gòu)造一個(gè)對(duì)象傳入呀狼,避免大量參數(shù)帶來不好的體驗(yàn)感。

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. 慎用方法重載

濫用重載容易讓開發(fā)者感到疑惑损离,在需要重載方法的時(shí)候哥艇,可以使用不同方法名來代替。對(duì)于構(gòu)造函數(shù)僻澎,可以通過靜態(tài)工廠來代替重載貌踏。

Java 中提供的 ObjectOutputStream 類就是個(gè)很好的示范:它的 write 對(duì)于每個(gè)基本類型都有一個(gè)變形,比如寫出字符窟勃、寫出 boolean 等操作祖乳。設(shè)計(jì)者并沒有使用重載將其設(shè)計(jì)成 write(Long l)、write(Boolean b)秉氧,而是將其設(shè)計(jì)為 writeLong(l)眷昆、writeBoolean(b)。

例如汁咏,項(xiàng)目對(duì)外的處理方法全部是重載亚斋,只能根據(jù)參數(shù)區(qū)分,迷惑性非常大梆暖。修改為不同的方法名后伞访,看到名字就知道要調(diào)用的方法,清楚了不少轰驳。

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

對(duì)于需要返回?cái)?shù)組或集合的方法,不要返回null级解。比如我們?nèi)ベI糕點(diǎn)店買面包冒黑,面包沒了是一種正常狀態(tài),就不應(yīng)該返回 null勤哗,而是返回長(zhǎng)度為 0 的數(shù)組或集合抡爹。Java 提供了 Collections.emptyXXX() 表示空集合。

  1. 避免引入第三方庫(kù)

GitHub有許多開源的第三方庫(kù)芒划,比如網(wǎng)絡(luò)請(qǐng)求 OkHttp冬竟、圖片加載 Glide 等,但在 SDK 開發(fā)中民逼,遵循的基本的原則是:

  • 最小可用性原則泵殴,即用最少的代碼,如無必要勿增實(shí)體拼苍。
  • 最少依賴性原則笑诅,即用最低限度的外部依賴,如無必要勿增依賴。

引入第三方庫(kù)可能帶來下面幾個(gè)問題:

  • 宿主應(yīng)用的第三方庫(kù)和 SDK 依賴的版本不一致吆你,容易引起沖突弦叶,增加對(duì)接的成本。
  • 開源庫(kù)不斷更新妇多,SDK 也要及時(shí)更新伤哺,增加額外的維護(hù)工作量。
  • 由于引入開源庫(kù)砌梆,出現(xiàn)問題難以排查默责。
  1. 保證兼容性

SDK 是不斷迭代的,每次發(fā)布都會(huì)有新功能和 bug 修復(fù)咸包。對(duì)于使用者來說桃序,升級(jí)版本不該有太大的改動(dòng),一般直接替換庫(kù)文件或者修改遠(yuǎn)程依賴庫(kù)的版本號(hào)就夠了烂瘫。避免直接對(duì)公開接口的重命名媒熊,如果舊接口廢棄,要通過 @Deprecated 關(guān)鍵字標(biāo)明坟比,并給出替代方案和廢棄的時(shí)間芦鳍。

  1. 減少入侵性

要保證較少的代碼侵入主要在對(duì)外提供服務(wù)時(shí),充分考慮開發(fā)者的使用場(chǎng)景來設(shè)計(jì)優(yōu)良的 API葛账。一套優(yōu)良的 API 在定義時(shí)要滿足絕大數(shù)開發(fā)者預(yù)期的方式——語義上要求通俗易懂柠衅,使用上要求簡(jiǎn)單可靠。具體的表現(xiàn)是籍琳,在正常情況下能夠穩(wěn)定可靠地運(yùn)行菲宴,在異常情況下及時(shí)地反饋錯(cuò)誤信息。

比如使用 Gradle 下載依賴庫(kù)趋急,AAR 包中有不必要的 bundle 資源喝峦,我們提供了打包 apk 時(shí)的構(gòu)建配置,自由選擇要打包的 bundle呜达,減少了對(duì)宿主應(yīng)用的侵入性谣蠢。

    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 平臺(tái)通常使用 jar 和 aar 發(fā)布 SDK,區(qū)別是 jar 只包含代碼查近,aar 可以包含代碼眉踱、資源和動(dòng)態(tài)庫(kù)。一般而言 aar 是最合適的交付方式霜威,把它上傳到 maven 服務(wù)器谈喳,使用者就可以一行代碼集成。對(duì)于需要靈活定制的客戶侥祭,我們也會(huì)提供 SDK 的源碼叁执,弊端就是升級(jí)困難,要改動(dòng)很多的代碼矮冬。

對(duì)于代碼混淆谈宛,公開接口和 native 使用的接口不要混,內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)可以混淆胎署,以減少 SDK 包的大小吆录。

接入文檔

接入文檔用來告訴 SDK 使用者,如何使用 SDK琼牧、詳細(xì)步使用驟和可能發(fā)生的問題恢筝。文檔內(nèi)容包括:更新記錄、基本信息巨坊、API 說明撬槽、集成步驟、FAQ等趾撵。好文檔的標(biāo)準(zhǔn)就是清晰明了侄柔,通俗易懂。一個(gè)完全不懂 SDK 的開發(fā)者看著文檔就能對(duì)接占调,對(duì)于經(jīng)常遇到的問題要逐條列出暂题,專業(yè)名詞要有對(duì)應(yīng)的解釋。

Demo 示例

集成 Demo 通常是一個(gè)簡(jiǎn)單的 App究珊,用來展示如何快速地接入 SDK薪者。Demo 的源碼托管到 GitHub,方便使用者參考剿涮,其版本變更策略和 SDK 版本的變化保持一致言津。盡管是個(gè) Demo,它的開發(fā)原則也要與 SDK 一致幔虏,確保高質(zhì)量的交付纺念。

參考文章:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市想括,隨后出現(xiàn)的幾起案子陷谱,更是在濱河造成了極大的恐慌,老刑警劉巖瑟蜈,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烟逊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡铺根,警方通過查閱死者的電腦和手機(jī)宪躯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來位迂,“玉大人访雪,你說我怎么就攤上這事详瑞。” “怎么了臣缀?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵坝橡,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我精置,道長(zhǎng)计寇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任脂倦,我火速辦了婚禮番宁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赖阻。我一直安慰自己蝶押,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布火欧。 她就那樣靜靜地躺著播聪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪布隔。 梳的紋絲不亂的頭發(fā)上离陶,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音衅檀,去河邊找鬼招刨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛哀军,可吹牛的內(nèi)容都是我干的沉眶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼杉适,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼谎倔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起猿推,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤片习,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蹬叭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體藕咏,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年秽五,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了孽查。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡坦喘,死狀恐怖盲再,靈堂內(nèi)的尸體忽然破棺而出西设,到底是詐尸還是另有隱情,我是刑警寧澤答朋,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布济榨,位于F島的核電站,受9級(jí)特大地震影響绿映,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜腐晾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一叉弦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧藻糖,春花似錦淹冰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至洋满,卻和暖如春晶乔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背牺勾。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工正罢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人驻民。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓翻具,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親回还。 傳聞我的和親對(duì)象是個(gè)殘疾皇子裆泳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容