原生插件开发
-
- [1、下载uniapp SDK](#1、下载uniapp SDK)
- 2、选择主工程
- 3、创建插件工程
-
- 3.1关闭插件工程,打开主工程。
- [3.2 主工程进行相关配置](#3.2 主工程进行相关配置)
- [3.3 代码实现](#3.3 代码实现)
-
- [3.3.1 运行项目测试。](#3.3.1 运行项目测试。)
- 4、下载友盟SDK
- 5、在主工程里面配置友盟SDK
- 6、将友盟SDK文件再次存放到插件工程文件里面。这样插件工程才能引入友盟相关的头文件。
- 7、插件工程新建启动文件,在启动文件里面增加友盟相关设置。(只在主功能里面配置友盟,运行的之后,插件工程里面的方法无法唤起相关的第三方平台)
- 注意的地方
1、下载uniapp SDK
在开发原生插件之前需要下载UniAPPSDK,下载链接可以在
官网下载:(百度云点击下载历史版本,提取码: egxg)
或者从本文件下载(百度云网盘 密码:irf9)
SDK里面一共有三个工程文件,作用分别是
HBuilder-uniPluginDemo:插件开发使用的主工程文件,
HBuilder-Hello:打包使用的主工程文件
HBuilder-ExampleDemo:离线示例工程
2、选择主工程
插件开发选择的就是HBuilder-uniPluginDemo功能文件。打开之后,需要先build一下,看有没有什么问题。官网的下载的主工程运行可能会报错,是因为一些配置文件没有进行配置。本文件下载的是已经配置好的项目,可以运行起来。
选择好主工程文件之后,下一步就是要创一个插件工程了。
3、创建插件工程
打开xcdoe-file-new-project,找到framework&library选择下面的类别,我选择的是framework。

下一步。
这个地方基本上除了项目名字,基本上都是默认的选项。

选择插件项目的位置,强烈建议选择下图的项目位置。因为很多我们所需要的基本配置这个项目都配置好了。
方便我们后续运行uniapp包。

插件项目已经新建完成。下一步我们就需要进行配置插件项目了。选择插件项目--targets--buildsetting--Mach-O type。改成StaticLibrary。如下图:

插件工程基本配置已经完成,下面我们就要进行主功能配置了。
截止到这一步,基本配置步骤都和官网上的文档一样,没有踩雷的地方。
3.1关闭插件工程,打开主工程。
第一步 :打开主工程文件

第二部 :选择添加文件到主工程。
因为这个工程之前已经添加了一次插件项目,这里只是演示步骤。不影响之后的编译与运行。

选择新建的插件工程项目,添加到主工程里面。

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

然后在 Xcode 项目左侧目录选中主工程名,在TARGETS->Build Phases->Dependencies中点击+
3.2 主工程进行相关配置

在弹窗中选中插件工程,如图所示,然后点击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

3.3 代码实现
原生插件是基于 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。否则将可能面临苹果上架审核问题。
扩展 module
以YMModule为例,源码请查看HBuilder-uniPluginDemo/cymtestPlugins 插件工程;
新建YMModule类,继承 DCUniModule,引入 DCUniModule.h 头文件。
YMModule.h 文件
objectivec
#import <Foundation/Foundation.h>
// 引入 DCUniModule.h 头文件
#import "DCUniModule.h"
#import <UMShare/UMShare.h> // 友盟SDK
NS_ASSUME_NONNULL_BEGIN
@interface YMModule : DCUniModule
@end
NS_ASSUME_NONNULL_END
如图:

然后在 YMModule.m 文件中添加实现方法
objectivec
// 通过宏 UNI_EXPORT_METHOD 将异步方法暴露给 js 端
UNI_EXPORT_METHOD(@selector(testAsyncFunc:callback:))
// 通过宏 UNI_EXPORT_METHOD_SYNC 将同步方法暴露给 js 端
UNI_EXPORT_METHOD_SYNC(@selector(testSyncFunc:))
/// 异步方法(注:异步方法会在主线程(UI线程)执行)
/// @param options js 端调用方法时传递的参数 支持:String、Number、Boolean、JsonObject 类型
/// @param callback 回调方法,回传参数给 js 端 支持: NSString、NSDictionary(只能包含基本数据类型)、NSNumber 类型
- (void)testAsyncFunc:(NSDictionary *)options callback:(UniModuleKeepAliveCallback)callback {
// options 为 js 端调用此方法时传递的参数 NSLog(@"%@",options); // 可以在该方法中实现原生能力,然后通过 callback 回调到 js
if (callback) {
// 第一个参数为回传给js端的数据,第二个参数为标识,表示该回调方法是否支持多次调用,如果原生端需要多次回调js端则第二个参数传 YES;
callback(@"success",NO);
}
}
/// 同步方法(注:同步方法会在 js 线程执行)
/// @param options js 端调用方法时传递的参数 支持:String、Number、Boolean、JsonObject 类型
- (NSString *)testSyncFunc:(NSDictionary *)options {
// options 为 js 端调用此方法时传递的参数
NSLog(@"%@",options);
/*
可以在该方法中实现原生功能,然后直接通过 return 返回参数给 js
*/
// 同步返回参数给 js 端 支持:NSString、NSDictionary(只能包含基本数据类型)、NSNumber 类型
return @"success";
}

Hook系统事件
如果需要在 App 启动时初始化或者需要获取系统的一些事件, 需要新建一个XXXXProxy类(注意命名加前缀防止冲突),继承 NSObject 遵守UniPluginProtocol协议。
因为之后会进行友盟SDK的引入,所以这里就直接新建YMPluginProxy类了。
YMPluginProxy.h文件

YMPluginProxy.m文件

配置插件信息
选中工程中的HBuilder-uniPlugin-Info.plist文件右键->Open As->Source Code找到dcloud_uniplugins节点,copy下面的内容添加到dcloud_uniplugins节点下,按您插件的实际信息填写对应的项
html
<dict>
<key>hooksClass</key>
<string>填写 hooksClass 类名 </string>
<key>plugins</key>
<array>
<dict>
<key>class</key>
<string>填写 module 或 component 的类名</string>
<key>name</key>
<string>填写暴露给js端对应的 module 或 component 名称</string>
<key>type</key>
<string>填写 module 或 component</string>
</dict>
</array>
</dict>
配置说明
hooksClass:App系统方法钩子类,值是类名,是给有些插件需要在 app 启动时做初始化或者获取系统事件用的,如果没有可以不填为空
class:module 或 component 对应的原生类名(示例中为 TestModule)
name:暴露给js端使用的 module 或 component 对应的名称(注意:module 的 name必须以插件id为前缀或和插件id相同,示例为DCTestUniPlugin-TestModule,其中 DCTestUniPlugin为插件的id,需要保证唯一性,避免与其他插件冲突,component 的name 没有强制要求,但是也要保证唯一比如 dc-map)
type:module 或 component (示例为module)
配置完如下图所示(必须严格按照格式配置):

到此,我们已经完成了一个简单的 module 扩展,接下来讲解如何在 uni-app 项目中调用刚刚扩展的 module 方法
在 uni-app 项目中调用 module 方法
module 支持在 vue 和 nvue 中调用,添加如下代码
html
<template>
<div>
<button type="primary" @click="testAsyncFunc">testAsyncFunc</button>
<button type="primary" @click="testSyncFunc">testSyncFunc</button>
</div>
</template>
<script>
// 首先需要通过 uni.requireNativePlugin("ModuleName") 获取 module
var testModule = uni.requireNativePlugin("DCTestUniPlugin-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
本地打包资源,导出步骤就不在展示了。可以参考官方文档进行操作。下面以现有的包进行操作,将导出的uniapp包添加到主工程apps文件夹下面,如下图所示

导出的uniapp的 包名复制下来。更改主项目工程文件里面control文件里面的appid的值。
appid要和uniapp的包名一样。就像是下图1和3的名字保持一致。

3.3.1 运行项目测试。
如果用的是官方的SDK项目,这个时候运行可能会报错,是因为缺少了配置。
如下图

解决方法是查看主工程的info文件,这个dcloud_appkey进行了配置,这个值需要到uniapp的开发者中心进行配置。
配置步骤如下图


新建完成之后,会出现一个应用名称,这个应用名称的APP id是要和control文件里面的appid的值以及uniapp的包名保持一致。
点击应用进入下一个页面,如下图

iOS填写bundleId,这个ID是主项目工程的ID,两者需要保持一致。如图

点击保存按钮,生成appkey。将生成的appkey填入主项目功能的info文件里面的dcloud_appkey。
这个时候运行项目,点击testAsyncFunc和testSyncFunc有弹框出现,如下图所示就是成功了

4、下载友盟SDK
这里面有手动接入和自动接入两种方式,我的项目里面使用的是手动接入。并且友盟的SDK和相关配置是需要在主工程文件里面进行配置。
5、在主工程里面配置友盟SDK
6、将友盟SDK文件再次存放到插件工程文件里面。这样插件工程才能引入友盟相关的头文件。
7、插件工程新建启动文件,在启动文件里面增加友盟相关设置。(只在主功能里面配置友盟,运行的之后,插件工程里面的方法无法唤起相关的第三方平台)
注意的地方
1、插件工程需要使用viewcontroller进行页面跳转之类的功能。
Module本身是不支持viewcontroller,如果强制使用就会出现警告,运行之后也不会跳转成功。这时候就需要下面的方法了。
objectivec
#import "YMModule.h"
#import "DCUniDefine.h"
@implementation YMModule
#pragma mark - 获取viewcontroller
// 获取当前显示的 UIViewController
+ (UIViewController *)dc_findCurrentShowingViewController {
//获得当前活动窗口的根视图
UIViewController *vc = [UIApplication sharedApplication].keyWindow.rootViewController;
UIViewController *currentShowingVC = [self findCurrentShowingViewControllerFrom:vc];
return currentShowingVC;
}
+ (UIViewController *)findCurrentShowingViewControllerFrom:(UIViewController *)vc
{
// 递归方法 Recursive method
UIViewController *currentShowingVC;
if ([vc presentedViewController]) {
// 当前视图是被presented出来的
UIViewController *nextRootVC = [vc presentedViewController];
currentShowingVC = [self findCurrentShowingViewControllerFrom:nextRootVC];
} else if ([vc isKindOfClass:[UITabBarController class]]) {
// 根视图为UITabBarController
UIViewController *nextRootVC = [(UITabBarController *)vc selectedViewController];
currentShowingVC = [self findCurrentShowingViewControllerFrom:nextRootVC];
} else if ([vc isKindOfClass:[UINavigationController class]]){
// 根视图为UINavigationController
UIViewController *nextRootVC = [(UINavigationController *)vc visibleViewController];
currentShowingVC = [self findCurrentShowingViewControllerFrom:nextRootVC];
} else {
// 根视图为非导航类
currentShowingVC = vc;
}
return currentShowingVC;
}
在使用的地方直接调用就可以了。比如我这里写的弹出第三方的viewcontroller
objectivec
// 这是我引用友盟一键登录的时候使用的,
// 本来这个地方应该是self,但是对于插件工程是不能使用的,我们就需要上面的方法来获取当前viewcontroller,
[UMCommonHandler getLoginTokenWithTimeout:timeout controller:[YMModule dc_findCurrentShowingViewController] model:model complete:^(NSDictionary * _Nonnull resultDic) {
}];