跨平台应用开发进阶(六) :uni-app实现原生插件集成

目录

一、前言

二、集成

[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 选择 FrameworkStatic 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 referencesAdd to targets 两项,然后点击Add。

这时在 Xcode 左侧目录中可以看到插件工程已经添加到了主工程中,如下图所示。

2.4 工程配置

然后在 Xcode 项目左侧目录选中主工程名 ,在TARGETS->Build Phases->Dependencies中点击+。

在弹窗中选中插件工程,如图所示,然后点击Add,将插件工程添加到Dependencies中。

然后在Link Binary With Libraries中点击+,同样在弹窗中选中插件工程,点击Add。

此时可以看到 DependenciesLink 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 :需要参与页面布局,比如:mapimage等需要显示UI的功能,通过扩展component即组件的方法来实现;

需要根据实际的情况选择扩展方式,当然插件中可以同时存在 modulecomponent,也可以是多个 module 和 多个 component

特别注意⚠️: 如果需要扩展自定义的 module 或者 component ,一定注意不要将 ocruntime 暴露给 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->ReleaseNo
  • Valid Architectures 中至少包含 arm64armv7(一般保持工程默认配置即可)

然后点击运行按钮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) 继承父类依赖包。

四、延伸阅读

相关推荐
zqx_716 分钟前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己33 分钟前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称1 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色1 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2342 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河2 小时前
CSS总结
前端·css
BigYe程普2 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H2 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍2 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai2 小时前
网站开发的发展(后端路由/前后端分离/前端路由)
前端