2022中国现状调查全面启动! >>>
不懂移动开发,也可以贡献移动代码
无限提升小程序安全运行沙盒
小程序安全运行的沙盒,可以由App开发者以SDK的形式嵌入,让他们的App成为秒级运行小程序的超级App。在这里,“App”不仅仅指 iOS 或应用程序。主机可以像具有多核 CPU 和更多内存的 PC 一样强大,也可以是计算能力有限的嵌入式设备 ( ),例如带有触摸屏的 PI。
主机硬件环境和软件环境不同,需要暴露给小程序使用的功能也不同;此外,在不同的业务环境中,应用集成的原生能力(如地图、支付、加密等)、音视频,甚至AR/XR等)也不同。支持开发者自定义各种API接口,注入SDK,供小程序开发者通过这种方式调用。
这样,任何小程序理论上都可以利用其宿主环境的任何技术能力,而不仅限于SDK提供的标准接口。
官方支持的自定义接口扩展方法
目前官方支持的自定义接口需要使用宿主环境的原生技术实现,e.g.在 iOS 上需要用 -C 或 Swift 开发,在 Java/etc 上。这种方式需要工程师至少在两个平台上分别开发——当然,这在过去不是问题,任何移动应用程序都必须维护两个团队来开发两个版本。但是当你要支持更多类型的终端时,就变得很麻烦了。
在“小程序+Rust”系列中提到,对于音视频编码处理、加解密等纯逻辑和算法功能,完全没有人机交互部分。使用跨平台的通用语言来实现更方便。但是我们不想乱用 C 代码,Rust 是一个不错的选择。作为一种新兴的、内存安全的、线程安全的语言,Rust 可以跨平台编译,性能高,体积小。特别适合设备端编程,包括在低算力、低功耗、低内存的物联网设备上开发。代码;而且,Rust 已经是一种官方支持的系统语言。
那么,是否可以支持使用 Rust 的开发人员为其安全运行的沙箱提供自定义扩展?
我们来看看环境中的分工与协作
在回答上述问题之前,我们在这里想象一下,如果在一个端到端的应用软件生产环节,技术是每个环节的“粘合剂”。新型技术团队的角色(技能)构成是怎样的:
这是一条基本的“流水线”,每个岗位使用不同的语言技能,分工越清晰越好。即使这些事情是同一个人“遍天下”,你也需要明确自己会在不同的角色下做不同的事情,这有助于你理出一个合理的结构。在端到端的技术链接上,定义每个链接的功能范围。
胶水代码与生态
这项技术,纯粹从软件工程的角度来看,起到胶水代码(glue)的作用,将多种技术的软件系统粘合在一起。胶水代码往往是最繁琐、最容易出错的地方。解决这个级别的技术可以极大的释放开发者的生产力和创造力。相当于建造了流水线,每个环节都可以独立润滑、丰富和加强。并且每个环节都变得更加专业,甚至形成了自己的零部件“供应链”。
例如通过将HTML/部分技术与设备端原生技术对接,引入大量可以专注于小程序开发的工程师,提供丰富的应用场景;将设备的通用逻辑解耦成一个可插拔的“插件”,可以进一步推动只专注于这部分工作的人,生产出丰富优质的插件。只要有标准化,就有机会形成生态。
提供端到端的应用解决方案,已经成为一个“集成”,在技术环节的每个环节的“供应链”中,选择你需要的部件来组装你自己的应用。
插件开发者:使用Rust实现SDK的“插件”
就像小程序开发者不需要了解任何iOS/ObjC/Swift、/Java/技能一样,只要掌握HTML/就可以开发出有用的应用程序,SDK的开发者可以自己开发无需过多了解操作系统平台的编程知识,甚至不会处理 ObjC 和 Java。
我们可以在技术上做到这一点。请按照以下步骤操作 – 请注意,以下所有操作都发生在 Rust 端,并且需要在 ObjC/Java 空间中进行零代码开发。
准备构建静态库所需的环境
创建一个带有cargo的lib-type项目,例如
cargo new --lib myplugin
然后修改生成的Cargo.toml:
[package]
name = "myplugin"
version = "0.1.0"
edition = "2021"
[lib]
name = "myplugin"
# this is needed to build for iOS and Android.
crate-type = ["staticlib", "lib"]
[dependencies]
serde_json = "1.0.81" # 建议使用这个crate实现json对象序列化
# 其他你准备封装或者依赖的crate
定义要注入的 API
在 src/lib.rs 中,开始定义和封装您计划提供给本地主机应用程序开发人员以注入其 SDK 的函数。
首先,定义一个新类型:
type FinClipCall = fn(&String) -> String;
请将此类型命名为”,该类型所代表的函数的签名必须是:
fn(&String) -> String
其实是一个函数指针,可以指向这样一个函数,例如:
fn invoke(param: &String) -> String {...}
注意这种类型的函数,期望的输入参数是一个有效的JSON字符串,返回的也必须是一个有效的JSON字符串。由于自定义的API,统一使用JSON作为输入输出参数,方便小程序端代码的处理。因此,建议在上面引入这个 crate 来帮助做一些 JSON 相关的数据转换。
其次,开始实现你的函数实现,例如:
fn api_drinker(input: &String) -> String {
// 先处理一下入参,把进来的字符串检测为合法JSON对象,
// 再用serde_json把它转化为某个类型的参数对象,供后续使用
println!("invoked with parameter {}", input);
//中间的逻辑算法从略,这里应该是你自己的算法,产生的结果对象,可以
//用serde_json进行Json serialization
let john = json!({
"name": "john doe",
"phones": "1234567"
});
john.to_string()
}
fn api_whisky(input: &String) -> String {
// 先处理一下入参,把进来的字符串检测为合法JSON对象,
// 再用serde_json把它转化为某个类型的参数对象,供后续使用
println!("invoked with parameter {}", input);
//中间的逻辑算法从略,这里应该是你自己的算法,产生的结果对象,可以
//用serde_json进行Json serialization
let brands = json!({
"whisky": {
"jack": "daniel",
"johny": "walker",
"henry": "Mckenna",
"suntory": "toki"
}
});
brands.to_string()
}
等等,用类似的签名 API 实现你的函数。
以文本命名您的 API 并注册“花名”
在小程序方面,调用自定义 API 的方式是通过 API 的名称。比如你把一个API接口命名为’abc’,那么这个接口注入SDK后,它在侧边的调用就是’ft.abc(…)’。
这里,你需要给每个自定义函数一个文本名称并映射它们的关系,这确实有点像代码编译器中的表格。在我们开发的这个项目中,还有一点需要注意:
pub unsafe extern "C" fn myplugin_register_apis() -> *mut HashMap<String, FinClipCall> {
let mut map: HashMap<String, FinClipCall> = HashMap::new();
map.insert("get_drinker".to_string(), api_drinker);
map.insert("get_whisky".to_string(), api_whisky);
Box::into_raw(Box::new(map))
}
这个函数有什么作用?虽然只有几行代码,但还是需要说明一下:
至此,我们的工作基本完成了90%,是不是很简单?
向其他语言公开提供“名册”的函数
到目前为止,我们一直在 Rust 世界中辗转反侧。但最终这些结果必须被外界发现和使用。最后,异步是将“名册”发送到异构语言的世界。我们需要使用 Rust FFI( ) 来让 Rust 编译器在编译上述代码时生成 C 风格的代码库,所以我们需要对上述函数做一点补充:
#[no_mangle]
pub unsafe extern "C" fn myext_register_apis() -> *mut HashMap<String, FinClipCall> {
let mut map: HashMap<String, FinClipCall> = HashMap::new();
map.insert("api_drinker".to_string(), api_drinker);
map.insert("api_whisky".to_string(), api_whisky);
Box::into_raw(Box::new(map))
}
”告诉Rust编译器在编译的时候不要混淆或者改变’i()’函数的名字,否则ObjC或者Java/JNI端没有办法知道调用什么名字。
注意,在上述函数的声明中,有”和'”C”‘的符号。很容易理解,就是以C函数调用的形式标明这个函数被异构语言使用,而”涉及到Rust关于内存安全和线程安全的规则或思路是什么,读者可以理解细节。这里我们主要使用’Box::’函数,也就是我们取一个只有Rust才能解析的数据结构,通过一个原始指针丢给C端,相当于这块内存被异构语言下的代码被“持有”,其内存安全不再被Rust监控和保证,因此并不安全。
这里有一个问题,那就是:既然Rust端不能被C端解析,那扔这个东西的raw指针有什么用呢?很有用,因为它实际上相当于一个不透明的指针,由主机应用程序端“持有”。当宿主需要调用一个 Rust 函数时,它只是将其传递回去。
有去有去,记得防止内存泄漏
在 Rust FFI 中,生成的每个 ” 操作最终都必须有一个对应的 ” 操作。前者将一块Rust管理的内存的控制权转移,后者是外部异构语言中的代码必须将内存的控制权交还给Rust,否则会发生内存泄漏。所以,最后,我们需要为异构语言添加一个函数应用软件是指,使用上面的东西后记得通知Rust回收:
#[no_mangle]
pub unsafe extern "C" fn myext_release(ptr: *mut HashMap<String, FinClipCall>) {
if !ptr.is_null() {
drop(Box::from_raw(ptr));
}
}
注意,调用这个函数是宿主应用程序开发者的责任,所以必须在你的使用文档中强调它们。这很丑但是好像没有什么好办法,而且跨语言调用总有一些小不便。
回顾:使用 Rust 开发 SDK 扩展的步骤
其实很简单自由,没有专门的库或协议来继承和实现,就是“徒手”写一个Rust lib,只要包含以下元素即可:
交付物是一个静态库
正如《小程序+Rust》系列中介绍的,Rust代码需要跨平台编译构建相关目标架构下的-apple-ios、-apple-ios和二进制库。例如,构建适合在 ios 和 ios 设备上运行的东西:
cbindgen src/lib.rs -l c > myplugin.h
cargo lipo --release
您最终交付给宿主应用开发者的内容应包括:
至此,作为 Rust 开发者提供 SDK 的使命就完成了。再次明确,”的头文件名、创建名册数据结构和释放名册内存的函数名都是由开发者决定的。
应用开发者:如何使用 Rust 插件
接下来,由宿主应用程序的开发人员来使用它。宿主应用是运行在iOS或其他设备上的应用软件,它嵌入SDK,获得运行小程序的能力。
写代码只需要加三行
集成SDK详情见官网,“小程序+Rust”也有介绍,这里不再赘述。下面以iOS App的集成为例,在.m中添加三行代码(下面有注释的三行):
#import "AppDelegate.h"
#import
#import "FinClipExt.h" //引入一个特殊的库支持Rust扩展
#import "myplugin.h" // 引入要安装供FinClip小程序开发者使用的SDK extension
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSString *appKey = @"22LyZEib0gLTQdU3MUauARgvo5OK1UkzIY2eR+LFy28NAKxKlxHnzqdyifD+rGyG";
FATConfig *config = [FATConfig configWithAppSecret:@"8fe39ccd4c9862ae" appKey:appKey];
config.apiServer = @"http://127.0.0.1:8000";
[[FATClient sharedClient] initWithConfig:config error:nil];
[[FATClient sharedClient] setEnableLog:NO];
// 安装 myplugin 到在这里初始化的FinClip SDK中
[[FinClipExt singleton] installFor:[FATClient sharedClient] withExt :myplugin_register_apis()];
return YES;
}
#pragma mark - UISceneSession lifecycle
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
}
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
@end
以上代码,. m,在使用 xcode 创建 ObjC 项目时自动生成。我们这里初始化SDK(详见官网,或者《小程序+Rust》系列。在此基础上再安装,代码很简单:
这里的 ” 负责将 Rust API 转换为 ObjC 方法并注入到 SDK 中。
编译构建需要链接静态库
这个库,我们从上面已经可以看出,目前还没有专门的库直接相关。唯一的限制是两个规范:
就是这样。那么这个库是如何注入到SDK中并被小程序调用的呢?神奇的是宿主App的开发者在他的项目中引入了一个名为.a的静态库(这个库还不是官方支持的标准工具,只是我的个人项目,目前只有iOS版本。有兴趣的朋友可以优化,欢迎提供版本,源码在上面,由ObjC和Rust代码组成)。作为用户,你不需要关注实现,只需下载这个静态库,在编译和构建应用程序时指定依赖并链接它。是的。
最后,当然要安装的静态库,.a,必须导入到项目中,一起构建。
作为宿主应用开发者,必须引入一个叫做SDK的SDK,小程序开发者调用的任务也已经完成。
小程序开发者:如何调用 Rust 接口
在上面的“花表”中,有两个分别暴露给了”和”的 API。这两个用 Rust 编写的函数,以 JSON 字符串作为输入和输出参数,经过 .a 的一些“神奇”操作应用软件是指,转化为 ObjC,并动态注入 SDK。要使用这些API,小程序开发者需要在自己的小程序项目的根目录下写一个.js:
module.exports = {
extApi:[
{
name: 'get_drinker',
sync: true, //同步api
params: { //扩展api 的参数格式,可以只列必须的属性
}
},
{
name: 'get_whisky',
sync: true,
params: {
}
}
]
}
此后,在’ft’对象中调用这些API只需通过’ft’对象,如’ft’。
总结
在现代软件项目中,多语言混合编程在所难免——不同的语言适合解决端到端技术环节中不同环节的问题,但也不可避免地带来了集成和集成的麻烦。集成,往往影响开发效率,带来很多麻烦。例如,跨语言传输涉及 API 接口的生成和异构语言中数据结构的重复“翻译”。编写胶水代码非常麻烦。更顺畅地解决了前端异构技术对接的问题。本文进一步介绍了一种更“透明”的方法,让完全不熟悉、不懂ObjC、不懂终端开发的工程师可以使用Rust这个强大的语言,开发逻辑通用的SDK扩展,最终可以被小程序开发者使用。
本文的示例代码在这里。
{{o.name}}
{{m.name}}
自考资料网:建议开通永久VIP超级会员更划算,除特殊资源外,全站所有资源永久免费下载
1. 本站所有网课课程资料来源于用户上传和网络收集,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,助力考生上岸!
3. 如果你想分享自己的经验或案例,可在后台编辑,经审核后发布在“自考资料网”,有下载币奖励哦!
4. 本站提供的课程资源,可能含有水印,介意者请勿下载!
5. 如有链接无法下载、失效或广告,请联系管理员处理(在线客服)!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 星光不问赶路人,岁月不负有心人,不忘初心,方得始终!