uni-app实现原生插件集成

举报
SHQ5785 发表于 2023/10/07 09:03:36 2023/10/07
【摘要】 ​目录一、前言二、集成2.1 下载最新 SDK2.2 创建插件工程2.3 导入插件工程2.4 工程配置2.5 插件扩展方式三、代码实现3.1 扩展 module3.2 插件功能本地验证3.3 插件制作 3.4 使用插件3.5 问题分析:Include of non-modular header inside framework module3.6 .h file not found四、延伸阅...

目录

一、前言

二、集成

2.1 下载最新 SDK

2.2 创建插件工程

2.3 导入插件工程

2.4 工程配置

2.5 插件扩展方式

三、代码实现

3.1 扩展 module

3.2 插件功能本地验证

3.3 插件制作 

3.4 使用插件

3.5 问题分析:Include of non-modular header inside framework module

3.6 .h file not found

四、延伸阅读



一、前言

因项目需求,需要uni-app 实现原生插件集成,现将集成过程梳理得出此文。

二、集成

2.1 下载最新 SDK

 在 Dcloud里下载最新的SDK

下载解压后目录如下:

|--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:需要参与页面布局,比如:mapimage等需要显示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 文件中添加实现方法。

//
//  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 中集成插件功能实现代码如下:

<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 中至少包含 arm64armv7(一般保持工程默认配置即可)

然后点击运行按钮Command + B 编译运行工程。

编译完成后,在插件工程 Products 下生成的库(DCTestUniPlugin.framework)即为插件所需要的依赖库文件,右键->Show in Finder,可打开库所在文件夹:

然后,编写 package.json 配置文件。 

package.json 为插件的配置文件,配置了插件id、格式、插件资源以及插件所需权限等等信息。

新建一个或从别的项目拷贝 package.json 文件,根据插件实际情况填写插件配置信息。

{
    "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) 继承父类依赖包。

四、延伸阅读

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。