目录
[2.1 下载最新 SDK](#2.1 下载最新 SDK "#%C2%A02.1.1%E4%B8%8B%E8%BD%BD%E6%9C%80%E6%96%B0%20SDK")
[2.2 创建插件工程](#2.2 创建插件工程 "#2.1%E5%88%9B%E5%BB%BA%E6%8F%92%E4%BB%B6%E5%B7%A5%E7%A8%8B")
[2.3 导入插件工程](#2.3 导入插件工程 "#%E5%AF%BC%E5%85%A5%E6%8F%92%E4%BB%B6%E5%B7%A5%E7%A8%8B")
[2.4 工程配置](#2.4 工程配置 "#%E5%B7%A5%E7%A8%8B%E9%85%8D%E7%BD%AE")
[2.5 插件扩展方式](#2.5 插件扩展方式 "#%E6%8F%92%E4%BB%B6%E6%89%A9%E5%B1%95%E6%96%B9%E5%BC%8F")
[3.1 扩展 module](#3.1 扩展 module "#3.1%20%E6%89%A9%E5%B1%95%20module")
[3.2 插件功能本地验证](#3.2 插件功能本地验证 "#3.2%20%E6%8F%92%E4%BB%B6%E5%8A%9F%E8%83%BD%E6%9C%AC%E5%9C%B0%E9%AA%8C%E8%AF%81")
[3.3 插件制作](#3.3 插件制作 "#3.3%20%E6%8F%92%E4%BB%B6%E5%88%B6%E4%BD%9C%C2%A0")
[3.4 使用插件](#3.4 使用插件 "#3.4%20%E4%BD%BF%E7%94%A8%E6%8F%92%E4%BB%B6")
[3.5 问题分析:Include of non-modular header inside framework module](#3.5 问题分析:Include of non-modular header inside framework module "#3.5%20%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90%EF%BC%9AInclude%20of%20non-modular%20header%20inside%20framework%20module")
[3.6 .h file not found](#3.6 .h file not found "#3.6%20.h%20file%20not%20found")
一、前言
因项目需求,需要uni-app
实现原生插件集成,现将集成过程梳理得出此文。
二、集成
2.1 下载最新 SDK
在 Dcloud里下载最新的SDK。
下载解压后目录如下:
lua
|--iOSSDK
|-- HBuilder-Hello // uni-app 离线打包工程
|-- HBuilder-uniPluginDemo // uni-app 插件开发主工程 (本文档需要使用的工程)
|-- SDK // 依赖库及依赖资源文件
|-- Feature-iOS.xls // 功能模块与依赖库对应关系说明表格
|-- readme.txt // 目录说明
其中,SDK 目录中的 **HBuilder-uniPluginDemo
**为 uni原生插件开发主工程 ,该工程已经将各项配置都配置齐全,开发uni原生插件需要依赖此工程。插件示例工程DCTestUniPlugin
也在目录中,另外插件市场的 原生增强提示框插件https://ext.dcloud.net.cn/plugin?id=36 对应的原生插件工程DCRichAlert
也放到了此目录中提供给开发者作为参考。
PS:最省事的办法就是直接在
DCTestUniPlugin``项目基础上做修改,将原生功能搬运过来。
2.2 创建插件工程
打开 Xcode,创建一个新的工程,template 选择 Framework
或 Static Library
(示例工程选择的是 Framework),然后点击 Next
在 Product Name 中输入插件工程名称(建议使用一个性化的前缀,避免与其他人的插件包名冲突),其他项不需要修改保持工程默认填充的即可,然后点击Next
然后选择工程存放路径,建议直接存放在 iOSSDK目录中的 HBuilder-uniPluginDemo
插件开发主工程目录下,如下图所示,然后点击 Create。
然后选中工程名,在TARGETS->Build Settings
中,将 Mach-O Type
设置为 Static Library
如下图所示。
然后将插件工程关闭,接下来需要将插件工程导入到插件开发主工程中。
2.3 导入插件工程
接下来打开 iOSSDK/HBuilder-uniPluginDemo
工程目录,双击目录中的HBuilder-uniPlugin.xcodeproj
文件运行插件开发主工程。
在 Xcode 项目左侧目录选中主工程名 ,然后点击右键选择Add Files to "HBuilder-uniPlugin" ...
然后选择刚刚创建的插件工程路径,选中插件工程文件,勾选 Create folder references
和 Add to targets
两项,然后点击Add。
这时在 Xcode 左侧目录中可以看到插件工程已经添加到了主工程中,如下图所示。
2.4 工程配置
然后在 Xcode 项目左侧目录选中主工程名 ,在TARGETS->Build Phases->Dependencies
中点击+。
在弹窗中选中插件工程,如图所示,然后点击Add
,将插件工程添加到Dependencies
中。
然后在Link Binary With Libraries
中点击+
,同样在弹窗中选中插件工程,点击Add。
此时可以看到 Dependencies
和 Link Binary With Libraries
都添加了插件工程,如下图所示。
接下来需要在插件工程的Header Search Paths
中添加开发插件所需的头文件引用,头文件存放在主工程的HBuilder-Hello/inc
中,添加方法如下图所示,在 Xcode 项目左侧目录选中插件工程名 ,找到TARGETS->Build Settings->Header Search Paths
双击右侧区域打开添加窗口,然后将inc
目录拖入会自动填充相对路径,然后将模式改成recursive。
2.5 插件扩展方式
原生插件是基于 DCUniPlugin 规范来实现,扩展原生功能有两种方式:
- module :不需要参与页面布局,只需要通过 API 调用原生功能,比如:获取当前定位信息、数据请求等功能,通过扩展
module
的方式来实现; - component :需要参与页面布局,比如:
map
、image
等需要显示UI
的功能,通过扩展component
即组件的方法来实现;
需要根据实际的情况选择扩展方式,当然插件中可以同时存在 module
和 component
,也可以是多个 module
和 多个 component
;
特别注意⚠️: 如果需要扩展自定义的
module
或者component
,一定注意不要将oc
的runtime
暴露给JS
,不要将一些诸如dlopen()
,dlsym()
,respondsToSelector:
,performSelector:
,method_exchangeImplementations()
的动态和不可控的方法暴露给JS,也不要将系统的私有API暴露给JS。否则将可能面临苹果上架审核问题。
三、代码实现
3.1 扩展 module
新建TestModule类,继承 DCUniModule,引入 DCUniModule.h 头文件。然后在 TestModule.m 文件中添加实现方法。
less
//
// TestModule.m
// DCTestUniPlugin
//
// Created by SHQ5785 on 2023/6/27.
// Copyright © 2020 DCloud. All rights reserved.
//
#import "TestModule.h"
@implementation TestModule
// 通过宏 UNI_EXPORT_METHOD 将异步方法暴露给 js 端
UNI_EXPORT_METHOD(@selector(testAsyncFunc:callback:))
/// 异步方法(注:异步方法会在主线程(UI线程)执行)
/// @param options js 端调用方法时传递的参数
/// @param callback 回调方法,回传参数给 js 端
- (void)testAsyncFunc:(NSDictionary *)options callback:(UniModuleKeepAliveCallback)callback {
// options 为 js 端调用此方法时传递的参数
NSLog(@"%@",options);
// 可以在该方法中实现原生能力,然后通过 callback 回调到 js
// 回调方法,传递参数给 js 端 注:只支持返回 String 或 NSDictionary (map) 类型
if (callback) {
// 第一个参数为回传给js端的数据,第二个参数为标识,表示该回调方法是否支持多次调用,如果原生端需要多次回调js端则第二个参数传 YES;
callback(@"success",NO);
}
}
// 通过宏 UNI_EXPORT_METHOD_SYNC 将同步方法暴露给 js 端
UNI_EXPORT_METHOD_SYNC(@selector(testSyncFunc:))
/// 同步方法(注:同步方法会在 js 线程执行)
/// @param options js 端调用方法时传递的参数
- (NSString *)testSyncFunc:(NSDictionary *)options {
// options 为 js 端调用此方法时传递的参数
NSLog(@"%@",options);
/*
可以在该方法中实现原生功能,然后直接通过 return 返回参数给 js
*/
// 同步返回参数给 js 端 注:只支持返回 String 或 NSDictionary (map) 类型
return @"success";
}
@end
3.2 插件功能本地验证
uni-app 中集成插件功能实现代码如下:
html
<template>
<view>
<button type="primary" @click="testAsyncFunc">testAsyncFunc</button>
<button type="primary" @click="testSyncFunc">testSyncFunc</button>
</view>
</template>
<script>
// 首先需要通过 uni.requireNativePlugin("ModuleName") 获取 module
var testModule = uni.requireNativePlugin("DCTestUniPlugin-TestModule")
console.log('---------------------testModule----------------:', testModule)
export default {
methods: {
testAsyncFunc() {
// 调用异步方法
testModule.testAsyncFunc({
'name': 'uni-app',
'age': 1
},
(ret) => {
uni.showToast({
title:'调用异步方法 ' + ret,
icon: "none"
})
})
},
testSyncFunc() {
// 调用同步方法
var ret = testModule.testSyncFunc({
'name': 'uni-app',
'age': 1
})
uni.showToast({
title:'调用同步方法 ' + ret,
icon: "none"
})
}
}
}
</script>
将引入插件功能的 uni-app 项目生成 uni-app 本地打包资源。首先需要生成本地打包资源,在 HBuilderX 中选你的 uni-app 工程,右键->发现->原生App-本地打→生成本地打包App资源。
后续步骤就是将生成的本地打包APP资源导入XCode,详参博文:跨平台应用开发进阶(十四) :uni-app 实现IOS原生APP-本地打包集成极光推送(JG-JPUSH)详细教程https://shq5785.blog.csdn.net/article/details/124472780然后运行项目测试,如下图所示(能调到 module 的方法,并且可以获取 module 返回的数据,则说明功能正常)
3.3 插件制作
插件功能验证无误后,便可制作插件,生成插件包。
首先,编译生成插件库文件(.framework 或 .a)。
如下图所示,将编译工程选择为插件项目(DCTestUniPlugin),运行设备选择Generic iOS Device.
然后点击Edit Scheme...
在弹窗中,将Run->Info->Build Configuration
切换到Release
,然后点击Close
关闭弹窗。
然后在 Xcode 左侧目录中选中插件工程名,查看TARGETS->Build Settings->Architectures
,确保
Build Active Architecture Only->Release
为No
Valid Architectures
中至少包含arm64
和armv7
(一般保持工程默认配置即可)
然后点击运行按钮
或 Command + B
编译运行工程。
编译完成后,在插件工程 Products 下生成的库(DCTestUniPlugin.framework
)即为插件所需要的依赖库文件,右键->Show in Finder
,可打开库所在文件夹:
然后,编写 package.json 配置文件。
package.json 为插件的配置文件,配置了插件id、格式、插件资源以及插件所需权限等等信息。
新建一个或从别的项目拷贝 package.json
文件,根据插件实际情况填写插件配置信息。
xml
{
"name": "TestUniPlugin",
"id": "DCTestUniPlugin",
"version": "1.0.0",
"description": "uni示例插件",
"_dp_type": "nativeplugin",
"_dp_nativeplugin": {
"ios": {
"plugins": [{
"type": "module",
"name": "DCTestUniPlugin-TestModule",
"class": "TestModule"
}, {
"type": "component",
"name": "dc-testmap",
"class": "TestComponent"
}],
"frameworks": ["MapKit.framework"],
"integrateType": "framework",
"deploymentTarget": "9.0"
}
}
}
然后以插件id为名新建一个文件夹,将编辑好的 package.json 放进去,然后在文件夹中在新建一个 ios (小写)文件夹,将刚刚生成的依赖库(DCTestUniPlugin.framework)copy 到 ios 根目录,这样我们的插件包就构建完成了,如下图所示
3.4 使用插件
HBuilderX 的 uni-app 项目创建中"nativeplugins"目录(如不存在则创建)将插件配置到uni-app项目下的"nativeplugins"目录
将原生插件配置到uni-app项目的"nativeplugins"下,还需要在manifest.json文件的"App原生插件配置"项下点击"选择本地插件",在列表中选择需要打包生效的插件:
保存后,重新提交云端打包生效。原生插件调试仅支持自定义基座调试。
注意⚠️:若项目中原先已有自定义基座且安装至手机中,需要将手机中的应用删除,重新安装测试。
3.5 问题分析:Include of non-modular header inside framework module
在组件 Framework 化的时候,如果在 public 头文件引入了另一个未 Framework 化的组件(.a静态库)时就会触发该问题。报错日志提示 Framework 里包含了非 modular 的头文件,也就是说如果我们要做 Framework 化的话,其依赖的内容也都应该是 Framework 化的,所以这个过程应该是一个从底层库到高层逐步进行的过程。如果底层依赖无法轻易修改,可以使用一些别的手段绕过这个编译错误。
Build Settings 里搜索 non-modular,将以下Allow Non-modular Includes In Framework Modules
选项设置为 Yes。
该选项进对 OC 模块代码有作用,对于 Swift 的引用还需要加另外一个编译参数:-Xcc -Wno-error=non-modular-include-in-framework-module
。添加位置为:
注意这两处设置均是对项目的设置,而非组件库。另外这些方案均是临时方案,最好还是要将所有依赖库全部 modular 化。
3.6 .h file not found
解决方法:在Build Setting -> Header Search Path 中添加 $(inherited) 继承父类依赖包。
四、延伸阅读
- uni-app 原生插件开发官方文档https://nativesupport.dcloud.net.cn/NativePlugin/
- 《跨平台应用开发进阶(九) :uni-app 实现Android原生APP-本地打包集成极光推送(JG-JPUSH)详细教程》https://blog.csdn.net/sunhuaqiang1/article/details/124314011