手把手教学:如何设计 SDK
App 的開發更偏向于用戶層面,從 UI 展示到業務邏輯處理,全程處理用戶的行為。而 SDK 面向的是開發者,開發更偏向于功能方面,注重功能的開發實現。在今天的文章中,我們一起來聊聊設計 SDK 的那些事。
本期文章屬于《手把手系列教學》的第二篇,如果你還不太了解這一系列,可以點擊 這里 查看詳情。
一、什么是 SDK?
SDK 全稱 Software Development Kit,廣義上的 SDK 是為特定的軟件包、軟件框架、硬件平臺、操作系統等建立應用程序時所使用的開發工具的集合(在 iOS 項目中,SDK 也被稱為庫)。
在 iOS 開發或 Android 開發中,不可避免會需要使用第三方工具提升產品的開發效率,比如用于消息推送的極光,用于第三方支付與登錄的支付寶,微信等等。但大多數商用產品都不會直接給出源碼(可能只有為愛發電的開源項目才會無私提供源碼),而我們在開發 App 時就需要將這些第三方 SDK 集成在我們的項目之中。
SDK 的全稱是 Software Development Kit,翻譯過來是軟件開發工具包,這是一種被用來輔助開發某類軟件而編寫的特定軟件包。
二、SDK 設計的基本原則
一款好用且設計充分的 SDK 必須要遵循以下 4 條基本原則,即:
1、SDK 安全,穩定
2、統一的開發規范
3、Library 小而精
4、不依賴第三方 SDK
- 安全,穩定:考慮到 SDK 是需要嵌入到 App 里面去的,所以 SDK 最重要的特性就是安全性,不會因為亂開放接口而導致 App 數據泄露;其次重要的是 SDK 的穩定性, SDK 的 Crash 如果沒有被捕獲進行處理,則會導致應用徹底崩潰(這樣就會導致第三方接入的 App 體驗性非常差),甚至會直接導致接入方的用戶流失;
- 統一的開發規范:對于 SDK 開發規范來說,統一的命名規范很重要,最好的狀態是“接入方看到接口命名就能知道是哪家廠商的 SDK”,換句話說就是 SDK 的命名規范統一,形成自己公司的品牌效應,此外也方便開發者進行接入使用。此外也需要具有自己的編碼規范,你可以在網上找到大廠的規范模板,并通過借鑒整理出屬于自己的規范,從而盡早統一代碼風格;
- Library 小而精:小是指要避免造成接入方的App增加很大,不然會引起接入方的不滿,甚至下架。精是指功能要專注,比如極光推送,就是專注推送相關的功能;
- 不依賴第三方 SDK:這個也很好理解,SDK 中如果又依賴其他第三方 SDK, 不僅會導致 SDK 的體積變大,也會影響接入方集成 SDK 的相關成本。
三、在 iOS 環境下開發 SDK
如同上文所說,在 iOS 開發中,我們將 SDK 稱為“庫”,我們是這樣對其定義的:
- 一般是給應用提供通用服務的,非獨立運行的程序集合;
- 一般都是編譯過的,方便使用。
我們會根據庫的調用方法分為“靜態庫”和“動態庫”兩種:- 靜態連接:一般是指在創建應用程序的時候,將庫集成進去,這樣做的好處就是應用程序包自身可以獨立運行,而不好的地方就是包會略顯臃腫,庫不能共享(靜態庫經常以 .a 結尾);
- 動態連接:創建應用的時候只約定好與庫之間的調用關系,而不徹底將庫包集成進應用。這樣在應用運行時,需要運行環境中提供庫,并且連接裝載。優劣與靜態庫相反,動態鏈接庫需要庫環境,但由于本身不集成庫內容,會比較小,同時也為和其他應用共享庫的使用提供了可能(常見的動態庫是 Windows 下的 .dll,Linux 下的 .so,Mac 下的 .dylib/.tbd)。
特別注意:平時我們經常說的 Framework (in Apple) 是 Cocoa/Cocoa Touch 程序中使用的一種資源打包方式,可以將代碼文件、頭文件、資源文件、說明文檔等集中在一起,方便開發者使用。也就是說我們的 Framework 其實是資源打包的方式,和靜態庫動態庫的本質是沒有什么關系。
如果說要找出靜態庫與動態庫的區別,那可以從文件鏈接(每個源代碼模塊獨立編譯,然后按照需要將他們組裝起來,這個組裝模塊的過程,就是鏈接)的角度進行解釋:
- 靜態庫:鏈接時會被完整的復制到可執行文件中,所以如果兩個程序都用了某個靜態庫,那么每個二進制可執行文件里面,都會含有這份靜態庫的代碼;
- 動態庫:鏈接時不復制,而是在程序啟動后動態加載,然后再進行符號決議(符號綁定)。理論上動態庫只存在一份就可以了。其他的程序都可以動態鏈接到這個動態庫上面,從而節省內存(內存中只有一份動態庫)。另外一個好處是,由于動態庫并不綁定到可執行程序上,所以我們想升級這個動態庫就很容易,windows和linux上面一般插件和模塊機制都是這樣實現的。
具體的優劣勢可以看這張表:
靜態庫可以簡單理解為一堆目標文件(.o/.obj)的打包體(并非二進制文件),而動態庫可以簡單理解為 一個沒有 main 函數的可執行文件。
有一個背景知識需要注意,iOS 官方規定不允許存在動態庫,并且所有的 IPA 都需要經過 Apple 的私鑰加密后才能用,即使你用了動態庫也會因為簽名錯誤而無法加載(越獄和非 App Store 除外)。于是這就把開發者自己開發動態庫這件事變成為了天方夜譚。
iOS8 之前的 iOS 應用都是運行在沙盒當中的,不同程序之間不能共享代碼,并且 iOS 又是單進程運行的(也就是某一時刻只有一個進程在運行),那么即使你寫個共享庫也無法共享給他人。
而動態下載代碼又是被蘋果官方明令禁止的,也就是說動態庫的優勢完全無法發揮,所以動態庫也就沒有存在的必要了。
但是這一切問題都隨著 iOS8 發布之后的 App Extesion 特性, Swift 的誕生發生了奇妙的改變。
由于 iOS 主 App 需要和 Extension 共享代碼,Swift 語言機制也需要動態庫,于是蘋果后來提出了 Embedded Framework,這種動態庫允許 APP 和 App Extension 共享代碼(動態庫的生命被限定在一個APP進程內)。
更簡單的解釋:雖然提供了動態庫,但這是被閹割的動態庫。
盡管如此,這種動態庫(Embedded Framework) 和系統的 UIKit.Framework 還是有很大區別的。傳統的動態庫是給多個進程使用的,而這里的動態庫(Embedded Framework)是給單個進程里面多個可執行文件用的。
系統的 Framework 不再需要拷貝到目標程序中,我們自己做出來的動態庫(Embedded Framework) 哪怕是動態的,最后也還是要拷貝到 App 中(App 和 Extension 的 Bundle 是共享的)。所以蘋果沒有直接把這種 Embedded Framework稱作動態庫而是叫 Embedded Framework。
上面提到的 Swift 也有原因,在 Swift 的項目中如果要在項目中使用外部代碼,可選的方式只有兩種,一種是把代碼拷貝到工程中,另一種是用動態 Framework。使用靜態庫是不支持的。
這個問題的根本原因是, Swift 的運行庫沒有被包含在 iOS 系統中,反而會被打包進 App 中(這也是造成 Swift App 體積大的原因),靜態庫會導致最終的目標程序中包含重復的運行庫。
第一步:創建 App 工程,命名為 RealDemo
如果不清楚怎么創建,可以點擊 這里
第二步:關閉 RealDemo 工程,然后在 RealDemo 目錄下創建 Framework 工程,命名為 RealSDK
選擇下方的 Framework 進行創建
切記目錄不要選擇錯誤
第三步:設置 Framework 工程的 Build Settings
創建動態庫需要選擇 Dynamic Library,如果要制作靜態庫則要選擇 Static Library
第四步:關閉RealSDK工程,創建 WorkSpace,命名為 RealDemo
逐次點擊 File - New - Workspace
創建后會出現對應的 workspace 文件
第五步:連接 Framework 工程和 App 工程
我們需要先打開 RealDemo.xcworkspace,打開后你會發現這里空空如也。
然后我們直接把需要連接的 Framework 工程(RealSDK.xcodeproj)和 App 工程(RealDemo.xcodeproj)拖進來就可以了!
直接拖拽進 Xcode 即可
拖拽后你會發現兩者的層級關系相同
第六步:把 Framework 添加到 App 工程中
逐次點擊,不要點錯了
選擇前面創建的 framework
有過 SDK 開發經驗的同學到這里應該已經看明白了,所謂實時聯調說白了就是用 WorkSpace 把兩個工程連接起來而已,跟 Pod 的原理有幾分相似。
第七步:給 Framework 加點功能
我們需要增加一個 RealDog 類,定義一個 eat 方法,實現里面打印一句話“吃骨頭”。然后修改 RealDog.h 的 Target Membership 為 Public,意思為公開頭文件。
在這里我定義了 eat 方法
RealDog的實現如下:
第八步:在 App 的 ViewController 調用一下 SDK 的方法
第九步:運行一下,可以發現App工程成功調用了SDK的方法
OK,實時聯調到此結束
第一步:添加一個 Aggregate Target
RealSDK Project -> TARGETS -> “+”(左下角) -> Cross-platform - Other -> Aggregate
第二步:將 Aggregate Target 命名為“RealSDK-Script”
命名后點擊 Finish 即可
第三步:依賴 RealSDK
在 Dependencies 中配置依賴關系
第四步:添加腳本
點擊這里的 New Run Script Phase
復制下方腳本即可
這個腳本是通用的,各位同學直接復制粘貼即可:
第五步:運行腳本
依然是點擊左側的播放按鈕
第六步:查看結果
能看到這個文件夾就代表編譯成功啦!
如果你的 Mac 是最新的 M1芯片,那么可能會出現以下報錯:
你只需要去除 iOS 模擬器的 arm64 架構即可,方法如下:
點擊 arm64 前面的減號即可
1、 Framework 中使用 Category
在 Framework 工程的 Build Setting 中添加 -ObjC。另外,使用我們 SDK 的 App 的 Build Setting 中也要添加 -ObjC。
2、 Framework 支持 bitcode
四、在 Android 環境中開發 SDK
1、 Android SDK 介紹
Android App 集成第三方 SDK 的文件類型,主要有三種,一種是 JAR 包文件,和 SO 文件 ,另外一種是 AAR 文件, JAR 包是 Java 提供的 SDK 文件類型,里面包含的是純 Java 編譯過后的代碼。SO 一般是 C 和 C++ 打包成庫的文件。
AAR 名字來源于 Android Archive,見名知義,是一個 Android 庫項目的二進制歸檔文件,使用 Android Studio ,非常簡單可以生成一個 AAR 文件。AAR 庫文件里面,包含了 JAR 和 SO,還有資源 Res 等文件,結構等同一個 App。
它可以提供構建應用所需的一切內容,包括源代碼、資源文件和 Android 清單。不過,Android 庫將編譯為您可以用作 Android 應用模塊依賴項的 Android ARchive (AAR) 文件,而不是編譯為在設備上運行的 APK。
與 JAR 文件不同,AAR 文件會為 Android 應用提供以下功能:
- AAR 文件可以包含多項 Android 資源和一個清單文件,讓您除了能夠在 Java 類和方法中進行捆綁以外,還能夠在布局和可繪制對象等共享資源中進行捆綁;
- AAR 文件可以包含 C/C++ 庫,供應用模塊的 C/C++ 代碼使用。
2、創建 SDK 工程
打開上個章節我們創建的示例工程,在工程上創建一個 library module,命名為GPush,讓我們模擬實現一個推送簡短新聞的接口。
如果你忘記怎么做的話,可以點 這里
選擇 Android Library,并在名稱中輸入 GPush
添加成功后我們會在左側看到對應的 Libray 文件
如需在同一項目中的另一個應用或庫模塊中使用新的 Android 庫代碼,就需要這樣添加一個項目級依賴項:
1、依次轉到 File > Project Structure > Dependencies;
2、選擇要在其中使用庫的模塊;
3、在 Declared Dependencies 標簽頁中,點擊 +,然后在下拉菜單中選擇 Module Dependency。
先點擊加號,再選中對應的 Module Dependency
將其加入依賴中
4、接口設計
既然是做一個推送新聞的接口,那就必須要分為客戶端和推送端,即 Client#onReceiveMessage 和 GPush#pushMessage。
從下面給出 UML 圖可以看出,只需要一個方法就可以監聽到新聞推送了,GPushImpl#start(Client client)。
可以看我畫的 UML 圖,幫助你更進一步理解
GPush 類
GPushImpl 類
package com.myname.library;import android.os.Handler; import android.os.HandlerThread;import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Random;public class GPushImpl implements GPush {private List<Client> mClients;private HandlerThread mHandlerThread;private Handler mHandler;private Random mRandom = new Random();private List<String> msgs = new ArrayList<String>() {{add("1、文旅部:嚴查以中老年為目標的包價游產品");add("2、加快推進沿長江戶籍改革,服務長江經濟帶高質量發展。");add("3、今年首批10家非法社會組織網站被關停,含中國文藝名人協會等。");add("4、上海:5月1日起,電動自行車騎乘人員必須戴頭盔。");add("5、廣州:清明祭掃實行實名預約,倡導網上祭掃、錯峰延后祭掃。");add("6、河北武安鐵礦致6死事故涉嫌瞞報,企業相關人員被控制。");add("7、黃崢辭任拼多多董事長:將放棄超級表決權,投入科學研究。");add("8、打破國外20年壟斷,國產人工心臟研發成功,但商用落地時間暫不確定。");add("9、調查:六成青年入睡時間晚于23點,夢多睡眠淺成年輕人睡眠主要問題。");}};GPushImpl() {mClients = new ArrayList<>();mHandlerThread = new HandlerThread("Push-Thread");mHandlerThread.start();mHandler = new Handler(mHandlerThread.getLooper());}@Overridepublic void pushMessage(String msg) {Iterator<Client> iterator = mClients.iterator();while (iterator.hasNext()) {iterator.next().onReceiveMessage(msg);}}public static void start(Client client) {GPushImpl gPush = new GPushImpl();gPush.mClients.add(client);gPush.mHandler.post(gPush.mRunnable);}private Runnable mRunnable = new Runnable() {@Overridepublic void run() {mHandler.postDelayed(mRunnable, mRandom.nextInt(10000));pushMessage(msgs.get(mRandom.nextInt(msgs.size())));}}; }Client 類
public interface Client {void onReceiveMessage(String msg); }開始監聽新聞推送
GPushImpl.start {Toast.makeText(MainActivity@this,it,Toast.LENGTH_LONG).show()}SDK 打包
./gradlew :GPush:assembleRelease打包完成后就行生成一個 aar 文件, 這個文件就是我們打包的結果了
5、最后注意事項 — 混淆
基于代碼保護的目的,Gradle 打包默認會根據 build.gradle 和 proguard-rules.pro 配置的混淆規則,來對代碼進行一個混淆, 如果 library 里面使用了如 GSON 或者反射等技術則需要對部分類進行 keep 操作。
buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}} -keep class com.myname.library.** {*;}如果正確按照教程,那相信你已經成功的做出了屬于自己的第一個 iOS 與 Android SDK,本期教程依然基于 mac 電腦進行實現,如果你的電腦是 Windows 或者其他操作系統,還需要進行一些其他的靈活配置。
在下一期的文章中,我們將會一起聊聊如何引入 SDK ,敬請期待。
說明:本教學系列均由FinClip 工程師出品。
總結
以上是生活随笔為你收集整理的手把手教学:如何设计 SDK的全部內容,希望文章能夠幫你解決所遇到的問題。