uniapp--原生插件开发

原生插件开发

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) {
 
 }];
相关推荐
海南java第二人2 小时前
Spring MVC核心流程深度解析:从请求到响应的完美掌控
java·springmvc
未来之窗软件服务2 小时前
幽冥大陆(一百10)PHP打造Java的Jar安全——东方仙盟筑基期
java·php·phar·仙盟创梦ide·东方仙盟
羑悻的小杀马特2 小时前
PostgreSQL + Cpolar 组合拳,彻底打破局域网限制,远程访问数据库像本地一样简单
数据库·postgresql
松涛和鸣3 小时前
DAY61 IMX6ULL UART Serial Communication Practice
linux·服务器·网络·arm开发·数据库·驱动开发
程序猿_极客5 小时前
【2025 年最新版】Java JDK 安装与环境配置教程(附图文超详细,Windows+macOS 通用)
java·开发语言·windows·macos·jdk
猫头虎6 小时前
macOS 双开/多开微信WeChat完整教程(支持 4.X 及以上版本)
java·vscode·macos·微信·编辑器·mac·脚本
二哈喇子!9 小时前
Java开发工具——IDEA(修改全局配置,提升工作效率)
java·编辑器·intellij-idea
二哈喇子!9 小时前
MySQL数据更新操作
数据库·sql
二哈喇子!9 小时前
MySQL命令行导入数据库
数据库·sql·mysql·vs code