App 的开发更偏向于用户层面,从 UI 展示到业务逻辑处理,全程处理用户的行为。而 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
如同上文所说,在 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 体积大的原因),静态库会导致最终的目标程序中包含重复的运行库。
以动态库为例,开始制作SDK第一步:创建 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,实时联调到此结束
使用脚本合并真机、模拟器等多种架构的 Framework第一步:添加一个 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 mClients;
private HandlerThread mHandlerThread;
private Handler mHandler;
private Random mRandom = new Random();
private List msgs = new ArrayList() {
{
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());
}
@Override
public void pushMessage(String msg) {
Iterator 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() {
@Override
public 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 false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
-keep class com.myname.library.** {*;}
如果正确按照教程,那相信你已经成功的做出了属于自己的第一个 iOS 与 Android SDK,本期教程依然基于 mac 电脑进行实现,如果你的电脑是 Windows 或者其他 *** 作系统,还需要进行一些其他的灵活配置。
在下一期的文章中,我们将会一起聊聊如何引入 SDK ,敬请期待。
说明:本教学系列均由FinClip 工程师出品。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)