Android SDK開(kāi)發(fā)雜談

自從前段時(shí)間離職后,因?yàn)閭€(gè)人的事情一直沒(méi)有選擇再工作,也導(dǎo)致原有的文章并沒(méi)有按時(shí)產(chǎn)出.最近個(gè)人的事情整理的也差不多了,恰好有不少朋友來(lái)問(wèn)有關(guān)SDK開(kāi)發(fā)方面的事情,在此就做個(gè)簡(jiǎn)單的梳理,希望能幫助各位.

目前更多開(kāi)發(fā)者熱衷于應(yīng)用開(kāi)發(fā),極少數(shù)的開(kāi)發(fā)者才有機(jī)會(huì)從事SDK開(kāi)發(fā)工作,而市面上關(guān)于SDK開(kāi)發(fā)介紹的文章少之又少,以至于讓大家覺(jué)得SDK開(kāi)發(fā)是相對(duì)比較難而且非常無(wú)聊的工作,今天我們就來(lái)簡(jiǎn)單的聊聊SDK開(kāi)發(fā)的哪點(diǎn)事.

另外最近好像有博客之星評(píng)選,我也來(lái)湊個(gè)熱鬧,只求一票,不求其他:
點(diǎn)我,點(diǎn)我,投一票,一切為了妹紙
點(diǎn)我,點(diǎn)我,投一票,一切為了妹紙


關(guān)于SDK的解釋

什么是SDK

在開(kāi)始正文之前,首先來(lái)聊聊SDK是個(gè)啥玩意.

SDK是Software Development Kit的縮寫(xiě),譯為"軟件開(kāi)發(fā)工具包",通常是為輔助開(kāi)發(fā)某類軟件而編寫(xiě)的特定軟件包,框架集合等,SDK一般包含相關(guān)文檔,范例和工具.

SDK可以分為系統(tǒng)SDK和應(yīng)用SDK.所謂的系統(tǒng)SDK是為特定的軟件包,軟件框架,硬件平臺(tái),操作系統(tǒng)等簡(jiǎn)歷應(yīng)用時(shí)所使用的開(kāi)發(fā)工具集合.而應(yīng)用SDK則是基于系統(tǒng)SDK開(kāi)發(fā)的獨(dú)立于具體業(yè)務(wù)而具有特定功能的集合.

比如在進(jìn)行Android 應(yīng)用開(kāi)發(fā)時(shí),我們使用Google提供的系統(tǒng)SDK(Android SDK),而我們經(jīng)常使用的友盟SDK,極光SDK則是基于系統(tǒng)SDK開(kāi)發(fā)的.

明確SDK的概念之后,再來(lái)聊一聊這三個(gè)概念:Library,API,Framework

什么是Library

Library即我們所說(shuō)的庫(kù),通常是一組或者幾組類的集合,通常是應(yīng)用中某些功能的具體實(shí)現(xiàn)或者對(duì)系統(tǒng)已有功能的增強(qiáng)或補(bǔ)充.對(duì)Android開(kāi)發(fā)者而言,最常見(jiàn)的莫過(guò)于是Support Library,另外就是我們經(jīng)常使用各種網(wǎng)絡(luò)請(qǐng)求庫(kù)(OkHttp,Volley),數(shù)據(jù)庫(kù)操作,圖片加載庫(kù)(Glide,ImageLoader)等.

什么是Framework

Framework即我們所說(shuō)的框架,通常是系統(tǒng)或者應(yīng)用的骨架,很多時(shí)候,它表現(xiàn)為一組抽象的構(gòu)建及構(gòu)件實(shí)例間交互的方法.因此,可以認(rèn)為,Framework規(guī)定了應(yīng)用的體系結(jié)構(gòu),闡明了整體設(shè)計(jì),寫(xiě)作構(gòu)件之間的依賴關(guān)系以及控制流程.注意自處的Framework并不完全等同于你所熟知的Android Framework框架,可以認(rèn)為Android Framework中體現(xiàn)了Framework的思想,并進(jìn)行了實(shí)現(xiàn).

什么是API

API是Application Programming Interface,又稱為應(yīng)用編程接口油够,是軟件系統(tǒng)不同組成部分銜接的約定锈死。更加通俗的說(shuō)就API就是我們常見(jiàn)和編寫(xiě)的方法或函數(shù).

小結(jié)

明確了上面提到的概念之后,現(xiàn)在就可以來(lái)描述這四者之間的關(guān)聯(lián):
SDK主要包含F(xiàn)ramework,API及Library的三部分.Framework定義了SDK整體的可重用設(shè)計(jì),規(guī)定了SDK各功能模塊的職責(zé)以及依賴關(guān)系.其中個(gè)功能模塊體現(xiàn)為L(zhǎng)ibrary.模塊之間的內(nèi)部通信及SDK外部通信(SDK對(duì)外提供服務(wù)的接口)則通過(guò)API進(jìn)行.

另外完整的SDK還應(yīng)該包含大量的示例和其他工具.比如在Android SDK的tools目錄下提供了大量的輔助開(kāi)發(fā)工具.

對(duì)我們而言,大部分情況下是為某種具體的業(yè)務(wù)需求開(kāi)發(fā)對(duì)應(yīng)的SDK,以便作為第三正提供給其他需求方使用.比如百度推送的SDK主要實(shí)現(xiàn)消息推送功能,需求方只需要集成百度推送的SDK便可以使自己應(yīng)用具備推送功能.

到現(xiàn)在已經(jīng)介紹了SDK的主要構(gòu)成,接下來(lái)我們重點(diǎn)來(lái)介紹SDK的實(shí)現(xiàn)目標(biāo)以及在SDK架構(gòu)中的一些核心點(diǎn).


淺談SDK實(shí)現(xiàn)目標(biāo)

上面介紹了開(kāi)發(fā)中常見(jiàn)的概念,現(xiàn)在來(lái)談?wù)凷DK的實(shí)現(xiàn)目標(biāo).任何應(yīng)用都應(yīng)具備:簡(jiǎn)潔易用,穩(wěn)定,高效,輕量,SDK作為一種特定應(yīng)用當(dāng)然也不例外.

簡(jiǎn)潔易用

按照"奧卡姆剃須刀"理論,一個(gè)好的產(chǎn)品對(duì)第三方使用者使用而言應(yīng)該是簡(jiǎn)潔易用,不用改讓使用者花費(fèi)太長(zhǎng)時(shí)間學(xué)習(xí)的.這對(duì)SDK同樣適用---SDK不應(yīng)該對(duì)宿主應(yīng)用有過(guò)多的代碼侵入,也不應(yīng)該有復(fù)雜頻繁的接入工作.比如當(dāng)開(kāi)發(fā)者需要使用SDK的服務(wù)時(shí),只需要在緣由的代碼中新增一行即可.常見(jiàn)的SDK初始化如下:

public class Ad{

    @TargetApi(9)
    public synchronized static void init(Context context, SdkParams params) {
        //省略多行代碼
    }
}

當(dāng)我們需要使用該SDK的服務(wù)時(shí),通過(guò)一行代碼便可啟用Ad.init(this,params)

要保證較少的代碼侵入主要在對(duì)外提供服務(wù)時(shí)充分考慮到使用者的使用場(chǎng)景來(lái)設(shè)計(jì)出優(yōu)良的API.一個(gè)優(yōu)良的API在定義的時(shí)候應(yīng)該滿足絕大數(shù)開(kāi)發(fā)者所預(yù)期的方式---語(yǔ)義上要求通俗易懂,使用上要求簡(jiǎn)單可靠.

一個(gè)優(yōu)良的API首先是簡(jiǎn)單可靠的.在正常使用的情況下體現(xiàn)為穩(wěn)定可靠的執(zhí)行,在異常情況下體現(xiàn)為及時(shí)的告知使用者使用錯(cuò)誤.初次之外,遵循一致的明明規(guī)則,并是所有的API呈現(xiàn)出一致的風(fēng)格對(duì)開(kāi)發(fā)而言無(wú)疑是個(gè)好消息.

穩(wěn)定

站在SDK使用者角度來(lái)看,我們期望第三方的SDK服務(wù)應(yīng)該是穩(wěn)定高效的,體現(xiàn)在提供穩(wěn)定可靠的服務(wù),在不影響宿主穩(wěn)定性的前提下足夠的高效,這就要求我們SDK設(shè)計(jì)者在設(shè)計(jì)并實(shí)現(xiàn)SDK時(shí)要盡可能的做到以下幾點(diǎn):

  • 對(duì)外提供穩(wěn)定的API.SDK的API一旦確定,如無(wú)非常嚴(yán)重情況不可更改.作為提供服務(wù)方,發(fā)生API變更所帶來(lái)的變工成本非常大.
  • 對(duì)外提供穩(wěn)定的業(yè)務(wù).在穩(wěn)定的API后,必須要有穩(wěn)定的業(yè)務(wù)來(lái)支撐.
  • SDK運(yùn)行時(shí)的穩(wěn)定,作為服務(wù)提供方,我們必須確保SDK自身運(yùn)行的穩(wěn)定,并且保證接入方不會(huì)因?yàn)槲覀兊腟DK產(chǎn)生不穩(wěn)定的情況.
  • 版本迭代穩(wěn)定.和面向普通用戶的應(yīng)用相比,SDK版本的迭代是非常緩慢的.并且需要盡可能的對(duì)開(kāi)發(fā)者屏蔽迭代過(guò)程,以免給開(kāi)發(fā)者帶來(lái)不必要的適配開(kāi)銷.

高效

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

  • 更少的內(nèi)存占用.在不使用多進(jìn)程的情況下,SDK服務(wù)和宿主程序運(yùn)行在同一進(jìn)程中,這種情況下必須要求限制SDK內(nèi)存的占用,不能因?yàn)檎f(shuō)因?yàn)槲覀僑DK占用太多的內(nèi)存資源,導(dǎo)致應(yīng)用的存活時(shí)間變短.
  • 更少的內(nèi)存抖動(dòng).在占用更少內(nèi)存的前提下,SDK設(shè)計(jì)者必須刻意的減少反復(fù)GC造成的內(nèi)存抖動(dòng)問(wèn)題.
  • 更少的電量消耗.盡管很多時(shí)候無(wú)法對(duì)電量消耗做一個(gè)很好的權(quán)衡,但是仍然有一些可以參考的做法,比如減少使用耗電模塊的時(shí)間.比如在使用定位服務(wù)時(shí),不要求非常高的精度下優(yōu)先使用網(wǎng)絡(luò)定位而不是GPS定位.
  • 更少的流量消耗.

SDK整體架構(gòu)設(shè)計(jì)

SDK的架構(gòu)實(shí)現(xiàn)決定了SDK后續(xù)的維護(hù)難度,因此有必要在此對(duì)SDK整體架構(gòu)中的一些點(diǎn)做些簡(jiǎn)單的說(shuō)明.

模塊化開(kāi)發(fā)

根據(jù)單一職責(zé)將系統(tǒng)拆分為不同的小模塊,每個(gè)模塊保持相對(duì)獨(dú)立夺饲。

模塊之間通過(guò)協(xié)議或接口通信颊艳,以減少相互之間的依賴耦合.模塊內(nèi)部按照設(shè)計(jì)的幾大原則進(jìn)行實(shí)現(xiàn),以保證模塊本身可以靈活實(shí)現(xiàn)

對(duì)于現(xiàn)代開(kāi)發(fā)而言,模塊化是常用的手段,從宏觀角度來(lái)看,模塊是系統(tǒng)最小的組成單元.

組件化開(kāi)發(fā)

組件開(kāi)發(fā)同樣是個(gè)老生常提的概念,但從我個(gè)人的感受來(lái)說(shuō),組件是對(duì)邏輯的封裝,并具備單個(gè)可移植性.比如可以把日志記錄做成一個(gè)組件,之后它可以被輕松在應(yīng)用在不同的項(xiàng)目中.對(duì)于android 開(kāi)發(fā)者而言,Android 提供的每個(gè)UI 控件同樣也是組件,比如Button,TextView等.

在明確了組件這一概念之后,組件化開(kāi)發(fā)也就不難理解:所謂的組件化就是將整個(gè)項(xiàng)目劃分成多個(gè)模塊,幾個(gè)模塊或者單個(gè)模塊作為一個(gè)組件,開(kāi)發(fā)過(guò)程中我們可以對(duì)每個(gè)組件進(jìn)行并行開(kāi)發(fā),最后發(fā)布時(shí)通過(guò)依賴將組件合并成完整的應(yīng)用.

那為什么要使用組件化呢?
隨著android的逐漸成熟,現(xiàn)在的app業(yè)務(wù)越來(lái)越復(fù)雜,與此同時(shí),android工程也變得日益龐大,代碼行數(shù)十幾萬(wàn)已經(jīng)是常態(tài),此時(shí)有幾個(gè)問(wèn)題便會(huì)凸顯出來(lái):

  1. 工程任何一點(diǎn)改動(dòng)都會(huì)造成整個(gè)工程的重新編譯.記憶最深的就是早期在沒(méi)有進(jìn)行組件化的時(shí)候,龐大的工程動(dòng)輒需要十幾分鐘的編譯時(shí)間,一杯茶的時(shí)間就出來(lái)了,很多時(shí)候,不得不眼巴巴的等著,盡管現(xiàn)在可以使用facebook出品的buck以及來(lái)自阿里的feeline來(lái)加速編譯過(guò)程,單仍然不夠.
  2. 整個(gè)工程中充斥的大量重復(fù)或者冗余的子模塊,業(yè)務(wù)耦合度非常高,牽一發(fā)而動(dòng)全身.這就造成了"老人不敢改,新人無(wú)法改",因?yàn)檎l(shuí)也不能預(yù)知在做修改之后,會(huì)產(chǎn)生什么影響.
  3. 協(xié)作開(kāi)發(fā)基本上是不可能的,天知道彼此在做什么.代碼合并的的時(shí)候更是令人痛苦.
  4. 不方便測(cè)試.高度耦合的業(yè)務(wù)和模塊導(dǎo)致無(wú)法下手進(jìn)行測(cè)試,只能草草了事.

通過(guò)引入組件化,上面遇到的問(wèn)題便可迎刃而解.在SDK當(dāng)中,根據(jù)實(shí)際情況對(duì)其進(jìn)行組件化,比如我們將分享功能組件化,可以輕松的支持多種渠道的分享,在需要更新分享功能時(shí),可以對(duì)其進(jìn)行單獨(dú)的編譯和測(cè)試.

通過(guò)組件化,我們也可以輕松的實(shí)現(xiàn)SDK的定制功能,通過(guò)編寫(xiě)編譯腳本,我們可以決定哪些組件被依賴,最終合并到完整的應(yīng)用當(dāng)中.比如友盟中的提供的可定制分享組件(如下圖)的原理就是如此.

這里寫(xiě)圖片描述

插件化開(kāi)發(fā)

什么是插件化開(kāi)發(fā)這里就不做介紹了,一方面插件化并不是個(gè)新概念,另外就是插件化到目前為止理論層次上已經(jīng)非常成熟,不想15念開(kāi)始研究的時(shí)候資料相對(duì)較少.

在SDK中為什么使用插件化呢?SDK不同于普通應(yīng)用,不能頻繁的進(jìn)行更新,以免讓開(kāi)發(fā)者覺(jué)得SDK不穩(wěn)定或者讓開(kāi)發(fā)者頻繁的集成.SDK看起來(lái)變化較慢,實(shí)則變化頻繁.就以以前做的廣告SDK而言,有時(shí)候經(jīng)常需要對(duì)某類機(jī)型進(jìn)行數(shù)據(jù)采集或者及時(shí)更新反作弊模塊,在沒(méi)有使用插件化之前,解決該問(wèn)題是非常麻煩的.但是在我們利用插件化之后,解決該問(wèn)題就變得非常容易:我們將SDK整體劃分為兩部分:宿主和插件.宿主只向開(kāi)發(fā)者提供必要的服務(wù)接口,并提供了自定義插件加載器.而核心的邏輯則是存在于插件中.當(dāng)需要采集數(shù)據(jù)的時(shí)候,只需要由開(kāi)發(fā)人員開(kāi)發(fā)好數(shù)據(jù)采集插件并下發(fā)到指定設(shè)備即可;當(dāng)需要修復(fù)SDK缺陷時(shí),同樣也只需要下發(fā)新的插件包即可.

通過(guò)在SDK使用插件化方案,可以有效的對(duì)開(kāi)發(fā)者屏蔽手動(dòng)更新的過(guò)程.宿主相對(duì)穩(wěn)定,一旦確定,一般不會(huì)變動(dòng),而后續(xù)的業(yè)務(wù)變化則只需要通過(guò)更新插件來(lái)支撐.

除了上面談到的利用插件化解決動(dòng)態(tài)更新之外,通過(guò)將整個(gè)工程分為宿主和插件可以實(shí)現(xiàn)宿主的并行開(kāi)發(fā)和分開(kāi)編譯,并且能有效的解決方法數(shù)65535的限制.在沒(méi)有使用插件化之前,我們整個(gè)項(xiàng)目是由很多組件通過(guò)依賴形成的龐大工程,不得不通過(guò)


SDK初始化

和應(yīng)用開(kāi)發(fā)不同,很多情況下SDK沒(méi)有自身的上下文Context,而必須要借助應(yīng)用提供.SDK初始化的常見(jiàn)做法:Ad.init(Context context,AdParams params),我們往往推薦開(kāi)發(fā)者在應(yīng)用Application組件中的onCreate()中去掉用該方法,這就意味著該初始化過(guò)程是同步的,假如SDK本身初始化時(shí)間較長(zhǎng),就會(huì)影響應(yīng)用的啟動(dòng)速度.

在這種情況下,作為SDK的設(shè)計(jì)者必須著手解決該問(wèn)題.通常將SDK服務(wù)進(jìn)一步劃分成核心服務(wù)和輔助服務(wù),之后通過(guò)并行初始化和延遲初始化的手段來(lái)減少SDK初始化耗時(shí).曾經(jīng)在我所負(fù)責(zé)的廣告SDK中,有開(kāi)發(fā)者反饋我們的SDK啟動(dòng)較慢,通過(guò)對(duì)整個(gè)SDK啟動(dòng)流程進(jìn)行分析后,我們將插件加載服務(wù)和云控服務(wù)并行初始化,而對(duì)于像日志服務(wù)則采用顏值初始化,通過(guò)該手段有效的減少了初始化耗時(shí)

云更新控制

云控服務(wù)作為一種服務(wù)端控制客戶端的手段在SDK中開(kāi)發(fā)中非常重要,現(xiàn)在的SDK開(kāi)發(fā)可以不支持插件化,但是必須要提供云控服務(wù),以便讓服務(wù)端能控制SDK,比如在不需要進(jìn)行數(shù)據(jù)采集的時(shí)候,可以通過(guò)云控服務(wù)關(guān)閉SDK采集功能,在需要的時(shí)候在將其打開(kāi).

對(duì)本身是基于插件化開(kāi)發(fā)的SDK而言,云控服務(wù)更是不可或缺.

從實(shí)現(xiàn)的角度而言,云控服務(wù)分為服務(wù)端主動(dòng)和客戶端主動(dòng).服務(wù)端主動(dòng)是指服務(wù)端會(huì)將最新的云控開(kāi)關(guān)的信息推送到SDK,而客戶端主動(dòng)則是SDK在進(jìn)行操作之前會(huì)首先請(qǐng)求云控信息.對(duì)有推送開(kāi)發(fā)經(jīng)營(yíng)的同學(xué)而言,這非常容易理解,就是像是為了實(shí)現(xiàn)消息推送功能,我們可以通過(guò)客戶端輪訓(xùn)也可以通過(guò)服務(wù)端保持長(zhǎng)連接進(jìn)行消息推送一樣.

安全

SDK自身安全

為了區(qū)分接入者并挑高SDK自身安全性,我們通常會(huì)為開(kāi)發(fā)者分配api key和api secret,SDK會(huì)讀取開(kāi)發(fā)者配置的api key和api secret,并用于隨后的網(wǎng)絡(luò)通信中.這是非常常見(jiàn)的做法,比如當(dāng)你集成極光推送SDK的時(shí)候,它也許需要你提供api key和api secret,如果沒(méi)有則需要到官網(wǎng)進(jìn)行申請(qǐng).

核心邏輯采用C/C++

為了安全起見(jiàn),數(shù)據(jù)加密類,模塊算法類都都應(yīng)該采用NDK開(kāi)發(fā),將其封裝在so文件當(dāng)中.有很多開(kāi)發(fā)者不明白為什么這樣會(huì)增強(qiáng)安全性.這里我們簡(jiǎn)單的做個(gè)說(shuō)明.由于.so文件是通過(guò)c/c++編譯出的文件,相對(duì)于java的反編譯文件來(lái)說(shuō),可讀性更差,另外大部分的Android開(kāi)發(fā)者并不具備較深的C/C++能力,因此一定程度上增加了被破解的能力.

通訊加密

針對(duì)實(shí)際情況對(duì)通訊協(xié)議進(jìn)行加密,具體是采用對(duì)稱加密還是非對(duì)稱加密,則需要根據(jù)實(shí)際情況做選擇.另外,請(qǐng)盡可能使用https來(lái)代替http.

設(shè)備安全

在很多情況下,比如廣告SDK中,有一些開(kāi)發(fā)者會(huì)通過(guò)虛擬機(jī)來(lái)刷廣告,因此有必要針對(duì)此情況做判斷.一旦SDK檢測(cè)出非法請(qǐng)求后可以采取兩種方案,一種是SDK拒絕服務(wù),另外一種則是正常服務(wù),SDK會(huì)將作弊信息上傳至服務(wù)器,以便后端服務(wù)定向排除數(shù)據(jù).

減少傳輸數(shù)據(jù)大小

在設(shè)計(jì)SDK和服務(wù)端通訊之間的數(shù)據(jù)協(xié)議時(shí),需要根據(jù)實(shí)際情況考慮,但有以下幾條建議值得我們接受:

  • 如果對(duì)傳輸?shù)臄?shù)據(jù)大小有要求,建議對(duì)數(shù)據(jù)進(jìn)行壓縮.
  • 可以采用json/xml/Protobuf等協(xié)議,如果它們?nèi)匀徊荒軡M足則可以考慮自定義二進(jìn)制協(xié)議.

選擇支持最低系統(tǒng)版本

作為SDK的設(shè)計(jì)者,面臨一個(gè)很大的問(wèn)題是我們不得不考慮開(kāi)發(fā)者應(yīng)用所支持的系統(tǒng)最小版本,但是在SDK發(fā)布之前,我們并不知道會(huì)什么樣的開(kāi)發(fā)者使用我們提供的服務(wù),因此為了讓SDK支持更廣泛的設(shè)備,我們需要降低最低支持的系統(tǒng)版本.比如現(xiàn)在失眠上主流的系統(tǒng)版本是Android 5.0,那么對(duì)SDK而言,起碼要支持到Android 4.0,甚至是Android 2.3.

降低最低支持版本看起來(lái)很容易,但是我們不得不做更多的工作來(lái)確保SDK能表現(xiàn)出一致的工作行為(通常,我們?cè)赟DK內(nèi)部檢測(cè)當(dāng)前系統(tǒng)版本來(lái)確定哪些方法可以被調(diào)用).更殘酷的真相是我們花費(fèi)了很大的精力去支持2.3,但來(lái)自2.3系統(tǒng)版本的請(qǐng)求量卻連1%都不到.

權(quán)限管理

Android中任何開(kāi)發(fā)都避不開(kāi)權(quán)限申請(qǐng).作為SDK的設(shè)計(jì)者,對(duì)于權(quán)限遵循"如無(wú)必要,無(wú)需增加",換句話說(shuō)就是用不到的權(quán)限,就不要加上去,這也是我們所謂的最小權(quán)限原則,該原則同樣適用于普通應(yīng)用開(kāi)發(fā).

在剛接觸SDK開(kāi)發(fā)時(shí),某些早期功能需要某些權(quán)限,但是后期該功能被砍掉了,但是權(quán)限卻忘記去掉,這就導(dǎo)致不必要權(quán)限仍然存在的情況.

另外過(guò)多的權(quán)限申請(qǐng),會(huì)讓開(kāi)發(fā)者懷疑你的目的.比如一個(gè)廣告SDK的你申請(qǐng)照相機(jī)權(quán)限是想干嘛?恩,我懷疑你在偷拍我....好吧,這里我只是開(kāi)個(gè)玩笑.

另外,從android 6.0以上,google改變了權(quán)限申請(qǐng)的策略,因此需要單獨(dú)對(duì)此做適配.

日志服務(wù)

無(wú)論系統(tǒng)大小,日志服務(wù)是基本的服務(wù).一個(gè)良好的日志服務(wù)能夠幫助我們快速的發(fā)現(xiàn)問(wèn)題,定位缺陷,從而獲得問(wèn)題的解決方案.

SDK的日志服務(wù)和其他常見(jiàn)的日志服務(wù)并無(wú)太大的不同,但是要保證以下幾點(diǎn):

  1. 日志服務(wù)能夠記錄有效的信息,在SDK要關(guān)鍵位置進(jìn)行打點(diǎn).
  2. 日志服務(wù)上傳日志信息到服務(wù)器時(shí),要保證最大的可靠性,不能發(fā)生上傳失敗后拋棄日志的情況.
  3. 日志服務(wù)不能影響對(duì)正常的操作流程有過(guò)多的性能影響.SDK產(chǎn)生的日志信息往往是非常多的,因此必須考慮日志IO操作所帶來(lái)的開(kāi)銷.

深究API設(shè)計(jì)

API的設(shè)計(jì)在任何開(kāi)發(fā)中都是非常重要的,很多時(shí)候軟件的質(zhì)量好不好在API的設(shè)計(jì)可以得到體現(xiàn).在普通的應(yīng)用開(kāi)發(fā)中,API只會(huì)在應(yīng)用開(kāi)發(fā)人員間流通而不會(huì)暴露給非本應(yīng)用開(kāi)發(fā)的其他人員,但是SDK作為一種服務(wù),需要向開(kāi)發(fā)者暴露一部分API.通常我們將內(nèi)部流通的API稱之為內(nèi)部API,而開(kāi)放給開(kāi)發(fā)者的稱之為SDK API.

兩者使用場(chǎng)景雖然不同,但是都遵循著一些通用的設(shè)計(jì)規(guī)則,這里無(wú)法細(xì)說(shuō),只列出我認(rèn)為需要重點(diǎn)關(guān)注的十一條原則:

方法名能夠表明其用途

方法名是理解方法含義的第一渠道.一個(gè)好的方法名首先是能夠向他人展示自身功能,這樣做的好處就是能夠減少不必要的溝通成本,對(duì)于開(kāi)發(fā)者而言,還有什么比直接讀代碼更直觀呢.

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

對(duì)參數(shù)進(jìn)行合法性檢驗(yàn)是非常重要的,請(qǐng)不要想當(dāng)然的認(rèn)為可以用運(yùn)行時(shí)異常來(lái)代替.當(dāng)合法性校驗(yàn)不通過(guò)時(shí),針對(duì)方法權(quán)限不同分別對(duì)應(yīng)不同不同的處理策略:

  • 對(duì)于公開(kāi)方法通過(guò)顯示檢查拋出異常的方式绰更,并且使用javadoc的@throw來(lái)說(shuō)明拋出異常的原因
  • 對(duì)于私有方法通過(guò)斷言的方式來(lái)檢查參數(shù)的合法
  • 檢查構(gòu)造方法的參數(shù)的合法性橡淆,以使對(duì)象處在統(tǒng)一狀態(tài)
    需要注意,如果檢查的代價(jià)太大,需要綜合考量动遭,比如如果接受的是一個(gè)很大的List,此時(shí)檢查的代價(jià)可能很大

方法要明確其單一的功能

一個(gè)方法應(yīng)該具有單一的功能神得,盡可能做更少厘惦,但是更專的事情.這也是我們常說(shuō)的單一職責(zé)原則.另外一定要記住寧可提供小而美的方法也不要提供大而全的方法,經(jīng)驗(yàn)正面大而全的方法往往發(fā)生變動(dòng),產(chǎn)生風(fēng)險(xiǎn)的可能性更高,因此不如提供更小的方法以便組合使用

方法異常問(wèn)題

對(duì)于需要暴露給開(kāi)發(fā)者的方法要及時(shí)的拋出可查異常來(lái)幫助開(kāi)發(fā)者在編譯階段發(fā)現(xiàn)問(wèn)題,另外,對(duì)于運(yùn)行時(shí)異常,SDK設(shè)計(jì)者必須保證該類異常不會(huì)導(dǎo)致宿主程序出問(wèn)題并且需要告知開(kāi)發(fā)者.

方法權(quán)限控制

方法的權(quán)限也是需要著重考慮的,SDK設(shè)計(jì)者必須同時(shí)從安全和業(yè)務(wù)的角度考慮哪些方法是可公開(kāi)的,哪些是不可公開(kāi)以及哪些是靜態(tài)的.

避免過(guò)長(zhǎng)參數(shù)

過(guò)長(zhǎng)的參數(shù)會(huì)造成記憶上困難,需要慎重對(duì)待.在無(wú)法避免過(guò)長(zhǎng)參數(shù)的情況下,需要考慮其他的方法進(jìn)行解決:

a. 通過(guò)使用Builder模式來(lái)實(shí)現(xiàn)
b. 通過(guò)使用輔助類,通常采用靜態(tài)內(nèi)部類的方式,具體見(jiàn)靜態(tài)內(nèi)部類的使用
c. 通過(guò)將多個(gè)參數(shù)封裝成類對(duì)象
d. 通過(guò)將參數(shù)拆解成多個(gè)方法的參數(shù)

謹(jǐn)慎使用方法重載

重載不應(yīng)該讓使用者感到疑惑,即不應(yīng)該出現(xiàn)這種情況:同樣的參數(shù),但是開(kāi)發(fā)者不能明確哪個(gè)方法會(huì)被執(zhí)行.換言之就是不要產(chǎn)生歧義性.

另外需要注意,不要存在參數(shù)類型經(jīng)過(guò)自動(dòng)轉(zhuǎn)換就可以運(yùn)行在另外一個(gè)方法的情況,我曾經(jīng)在code review中看到這樣的代碼:list中的remove(Object)remove(int),請(qǐng)務(wù)必保證自己不會(huì)犯類似的錯(cuò)誤.盡管在java當(dāng)中能夠使用重載,但是我不建議使用,尤其是不要重載變長(zhǎng)參數(shù),在需要重載的時(shí)候?qū)幙墒褂貌煌椒麃?lái)代替也要好的多.關(guān)于這點(diǎn)java中提供的ObjectOutputStream類給我們做了很好的示范:它的write對(duì)于每個(gè)基本類型都有一個(gè)變形,比如寫(xiě)出字符,寫(xiě)出boolean等操作,我們發(fā)現(xiàn)設(shè)計(jì)者,并沒(méi)有使用重載將其設(shè)計(jì)成write(Long l),write(Boolean b),而是將其設(shè)計(jì)為writeLong(l),writeBoolean().

對(duì)于構(gòu)造函數(shù),則可以通過(guò)是用靜態(tài)工廠的方式來(lái)代替重載.

謹(jǐn)慎使用變長(zhǎng)參數(shù)

多數(shù)情況下不需要使用變長(zhǎng)參數(shù),一般方法的參數(shù)在5個(gè)以上的時(shí)候,才 建議使用變長(zhǎng)參數(shù).在還有其他非變長(zhǎng)參數(shù)的情況下,我覺(jué)得變長(zhǎng)參數(shù)放在形參列表的最后.

避免方法直接返回NUll

對(duì)于需要返回?cái)?shù)組或這集合的方法,不要返回null.比如我們?nèi)ベI(mǎi)糕點(diǎn)店買(mǎi)面包,面包沒(méi)了是一種正常狀態(tài),就不應(yīng)該返回null,而是返回長(zhǎng)度為0的數(shù)組或集合.

必要時(shí)進(jìn)行保護(hù)性拷貝

當(dāng)類接受來(lái)自客戶端的對(duì)象或者需要向客戶端返回對(duì)象,如果該類不能容忍進(jìn)來(lái)的對(duì)象再發(fā)生變化,那么有必要對(duì)對(duì)象進(jìn)行保護(hù)性拷貝.另外要注意參數(shù)的合法性檢驗(yàn)發(fā)生在保護(hù)性拷貝之后.
需要注意的是如果需要進(jìn)行保護(hù)性拷貝的對(duì)象非常大,比如list集合中存在十多萬(wàn)個(gè)對(duì)象,需要權(quán)衡處理.

這十一條原則是我在團(tuán)隊(duì)中推廣并要求嚴(yán)格遵守的,下面,將對(duì)這十條原則分別進(jìn)行說(shuō)明.


SDK開(kāi)發(fā)流程

關(guān)于SDK開(kāi)發(fā)流程,我會(huì)從以下三個(gè)方面寫(xiě):一時(shí)團(tuán)隊(duì)中如何協(xié)同開(kāi)發(fā),二是SDK的持續(xù)集成,三是SDK多倉(cāng)庫(kù)拆分和管理.

這三方面會(huì)再另外的篇章中展現(xiàn)(具體什么時(shí)候?qū)懲昴壳斑€未確定)


SDK版本管理策略

SDK 版本號(hào)命名及修改原則

SDK版本號(hào)命名和我們以往的命名規(guī)則并無(wú)太大不同,通由4部分組成,格式為:
V主版本號(hào)子版本號(hào)階段版本號(hào)_日期版本號(hào)加希臘字母版本號(hào).比如V1_1_2_161209_beta.

希臘字母版本號(hào)說(shuō)明

  • Alpha版:內(nèi)部測(cè)試版,此版本表示該軟件在該階段主要是以實(shí)現(xiàn)功能為主,Bug相對(duì)較多,需要繼續(xù)修改,通常只在內(nèi)部流通流通而不對(duì)外開(kāi)放.
  • Beta版:外部測(cè)試版,該版本相對(duì)Alpha已經(jīng)有了很大的改進(jìn),不存在嚴(yán)重的Bug,但還是存在一些缺陷,需要進(jìn)一步的測(cè)試以檢查和消除Bug.
  • RC版:該版本已經(jīng)相當(dāng)成熟,不存在導(dǎo)致錯(cuò)誤的Bug.與正式版相差無(wú)幾.
  • Release版:該版本意味著"最終版本",是最終交付用戶或者公開(kāi)發(fā)布的版本,也稱為標(biāo)準(zhǔn)版.需要注意的是,該版本在發(fā)布的時(shí)候回以符合R來(lái)代替Release單詞.

版本號(hào)修改規(guī)則

  1. 主版本號(hào)變化:當(dāng)功能模塊有較大的變化或者整體架構(gòu)發(fā)生變化
  2. 子版本號(hào)變化:當(dāng)功能有一定變化
  3. 階段版本號(hào)變化:一般是Bug修復(fù)或者較小的變動(dòng),根據(jù)反饋,需要經(jīng)常發(fā)布修訂版本.
  4. 日期版本號(hào)(161209):用于記錄修改項(xiàng)目的當(dāng)前日期,每天對(duì)項(xiàng)目的修改都要更改日期版本號(hào).
  5. 希臘字母版本號(hào):此版本?號(hào)用于標(biāo)注當(dāng)前軟件處于那個(gè)開(kāi)發(fā)階段,當(dāng)軟件進(jìn)入到另一個(gè)階段是需要修改.

API版本管理

和普通應(yīng)用API版本管理不同,SDK設(shè)計(jì)者需要著重關(guān)注SDK API的管理.原則上SDK API一旦公開(kāi)發(fā)布后其狀態(tài)(簽名和具體實(shí)現(xiàn))應(yīng)為不可變.

對(duì)于特殊情況下API的變更,需要遵守"開(kāi)閉原則",即一個(gè)類,模塊,方法應(yīng)該對(duì)擴(kuò)展開(kāi)發(fā),對(duì)修改關(guān)閉.這就要求我們做到以下幾點(diǎn):

  1. 在需要調(diào)整SDK API時(shí),優(yōu)先選擇添加新方法,而不是在原方法上修改.對(duì)于實(shí)現(xiàn)相同功能的新方法,盡可能的要兼容原始方法.
  2. 在需要廢除某些方法時(shí),需要在正式版發(fā)版前使用@deprecated標(biāo)識(shí),并給出替代方案和廢棄的時(shí)間(通常是SDK版本號(hào))

接入文檔和API文檔版本管理

接入文檔是用來(lái)告訴SDK使用者,如何使用SDK,使用的詳細(xì)步驟和可能發(fā)生的問(wèn)題,每個(gè)公司會(huì)有自己的一套規(guī)則,這個(gè)不需要做太多的解釋.

另外,接入文檔通常分為兩份:內(nèi)部版和公開(kāi)版.內(nèi)部版通常用于內(nèi)部開(kāi)發(fā)人員和測(cè)試人員,信息較為詳細(xì),而公開(kāi)版則是面向開(kāi)發(fā)者,相比內(nèi)部版會(huì)省略的一些信息.

API文檔其實(shí)就是對(duì)SDK API的更詳細(xì)說(shuō)明,類似java中的api doc,可以借助jdk的自帶javadoc直接生成,當(dāng)然在android studio也提供了便捷的生成方式.

無(wú)論是接入文檔還是api說(shuō)明文檔,其變更一般發(fā)生在SDK版本發(fā)生變化時(shí).當(dāng)SDK發(fā)生變更時(shí),文檔必須隨之更新,不能出現(xiàn)SDK更新后說(shuō)明文檔不與之匹配的情況.

集成Demo版本管理

集成Demo通常是一個(gè)簡(jiǎn)單的app,用來(lái)展示如何快速的接入SDK.其版本變更策略和SDK版本的變化保持一致.

總結(jié)

SDK開(kāi)發(fā)中需要關(guān)注的點(diǎn)非常多,每個(gè)點(diǎn)都不能用三言兩語(yǔ)完成的,后面會(huì)在此基礎(chǔ)上慢慢的補(bǔ)充.

點(diǎn)我,點(diǎn)我可以打醬油

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市哩簿,隨后出現(xiàn)的幾起案子宵蕉,更是在濱河造成了極大的恐慌,老刑警劉巖节榜,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件羡玛,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡宗苍,警方通過(guò)查閱死者的電腦和手機(jī)稼稿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)薄榛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人让歼,你說(shuō)我怎么就攤上這事敞恋。” “怎么了谋右?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵硬猫,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我改执,道長(zhǎng)啸蜜,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任辈挂,我火速辦了婚禮衬横,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘终蒂。我一直安慰自己冕香,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布后豫。 她就那樣靜靜地躺著悉尾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挫酿。 梳的紋絲不亂的頭發(fā)上构眯,一...
    開(kāi)封第一講書(shū)人閱讀 51,521評(píng)論 1 304
  • 那天,我揣著相機(jī)與錄音早龟,去河邊找鬼惫霸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛葱弟,可吹牛的內(nèi)容都是我干的壹店。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼芝加,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼硅卢!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起藏杖,我...
    開(kāi)封第一講書(shū)人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤将塑,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蝌麸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體点寥,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年来吩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了敢辩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蔽莱。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖戚长,靈堂內(nèi)的尸體忽然破棺而出盗冷,到底是詐尸還是另有隱情,我是刑警寧澤历葛,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站嘀略,受9級(jí)特大地震影響恤溶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜帜羊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一咒程、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧讼育,春花似錦帐姻、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至痹籍,卻和暖如春呢铆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蹲缠。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工棺克, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人线定。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓娜谊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親斤讥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子纱皆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,133評(píng)論 25 707
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)芭商,斷路器抹剩,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139
  • From:郭霖 前言 目前更多開(kāi)發(fā)者熱衷于應(yīng)用開(kāi)發(fā),極少數(shù)的開(kāi)發(fā)者才有機(jī)會(huì)從事SDK開(kāi)發(fā)工作,而市面上關(guān)于SDK開(kāi)...
    胡二囧閱讀 1,031評(píng)論 3 7
  • 遍尋不著 尤嘆當(dāng)年小蠻腰 空余恨 一身五花膘
    荇采閱讀 680評(píng)論 1 0
  • 關(guān)于如何創(chuàng)新的問(wèn)題,一直以來(lái)都是每個(gè)企業(yè)關(guān)注地最多的問(wèn)題蓉坎,本文主要是聊一聊一些被大眾忽略掉的創(chuàng)新的核心要點(diǎn)(這些點(diǎn)...
    祺峰閱讀 568評(píng)論 0 2