DCloud iOS 原生插件开发指南

[TOC]

要开发原生插件的话,首先得去 DCloud 官网下载 DCloud SDK[1]。DCloud SDK 下载下来解压后,内容如下:

|--iOSSDK    
    |-- HBuilder-Hello                  // uni-app 离线打包工程
    |-- HBuilder-ExampleDemo            // Demo 工程
    |-- HBuilder-uniPluginDemo          // uni-app 插件开发主工程 (本文档需要使用的工程)
    |-- SDK                             // 依赖库及依赖资源文件
    |-- Feature-iOS.xls                 // 功能模块与依赖库对应关系说明表格
    |-- readme.txt                      // 目录说明

在聊插件开发之前,先讲一点预备知识,前面三节都是预备知识。后面插件的开发过程中,会涉及到这些知识内容。

1. 自定义基座[2]

自定义基座,有两种方式获取,一种是用打包工程生成,另一种是通过云打包获取。

1.1 用打包工程生成自定义基座

  1. 在打包原生工程[3]里找到 control.xml 文件,在 HBuilder 节点里查看是否有这2个:debug="true" syncDebug="true"配置,没有的话加上。

  2. 修改打包工程的 Bundle identifier 为你 App 的 Bundle identifier

  3. 在原生打包工程里找到 info.plist 文件并增加一项:Application supports iTunes file sharing, 值设为 yes

  4. 确保原生工程里Pandora文件夹下的apps文件夹里只有一个文件夹,并且文件夹的名称和里面的 manifestid 值相同。

  5. 确保 control.xml 文件里的 appid 的值和 apps 目录下的第一个文件夹的名称一致。

  6. 确保 HBuilderX 里要调试的代码的 appid 和 control.xml 的appid值一致。

  7. 使用 Xcode 的 Product 下的 archive 打包,然后生成ipa,并把 ipa 名称命名为:iOS_debug.ipa

  8. 在 uni-app 工程里主目录下新建一个名称为 unpackage 的文件夹,再在unpackage文件夹下新建一个名称为 debug 文件夹,并把生成的iOS_debug.ipa包放入 debug 文件夹下。

到这里,自定义基座就制作完成了。

要调试的话,在 HBuildX 里,找到之前 app id 相同的 uni-app 工程,点击运行 --> 运行到手机或模拟器 --> 使用自定义基座运行(iOS),等待连接成功之后,然后运行 --> 运行到手机或模拟器 --> 选择真机或者模拟器,HBuilderX 就会用自定义基座,把 App 运行到 iOS 设备上了。

1.2 云打包生成自定义基座

HBuilderX 的菜单: 运行 --> 运行到手机或模拟器 --> 制作自定义基座, 并配置好,然后点击页面右下角的打包,打包后有下载链接地址,下载下来的文件命名成iOS_debug.ipa,这就是自定义基座。

要调试的话,基座下载下来后,放到项目的 unpackage --> debug 下目录下。

提示1: 配置云打包的自定义基座需要 iOS 证书与 Profile 文件。

2. 运行到设备上

2.1 Xcode

运行 uni-app 到设备上也有两种方式,一种是通过 HbuilderX 运行到设备上,另一种是通过 Xcode。

首先用 HBuilderX 打包 uni-app,操作路径:发行 --> 原生 App 本地打包 --> 生成本地 App 资源,HBuilderX 处理好之后,会在控制台输出 App 包资源的路径,得到 App 包资源后,把 App 包资源放到原生 iOS 打包工程的指定目录Pandora/apps/下,并修改 control.xml 文件的 appid 与 打包资源的文件夹名称相同。

然后,运行打包工程到设备上即可,运行打包工程之前,最好配置好 App 相应的信息,例如名称,icon,版本等。

2.2 HBuilderX

在 HBuilderX 上运行比较简单,操作路径: 运行 --> 运行到手机或模拟器 --> 选择真机或者模拟器

这里的运行环境,会有两个:标准基座自定义基座,看开发需要,选择相应环境。

3. 离线打包

去到原生 iOS 打包工程,按照下面的步骤操作:

  1. 这里主要是配置工程,诸如 App 名字,版本,icon,国际化等的一些信息。照着教程配置就好。

  2. 配置好之后,把从 HBuilderX 打包出来的 App 包资源放到 Pandora 的 apps 文件夹下去。

  3. 最后一步是打包,上面都配置好的话,就跟 iOS 原生打包一样,操作路径,在 Xcode 里:Product --> Archive

提示2: 记得修改 contol.xml 文件里的内容,比如环境是 Debug 还是 Release。打 Release 包的话,syncDebug 配置记得去掉。

4. 插件开发

插件从类型上区分,有两种类型,这根 uni-app 的历史发展有关。

  1. H5+ 插件:不过这种插件已经成为过去式了;
  2. uni-app 插件:这种是目前 DCloud官方推荐的。

插件从形式上区分,又有两种形式,比如Dcloud SDK 里的插件工程[4]里有个DCTestUniPlugin插件,这个插件,提供了 modulecomponent 两种形式。

  1. module: 是纯功能性的插件,没有具体界面的,比如获取手机电量,地理位置等功能。
  2. component:是提供iOS 或者 Android 原生界面的插件类型。

创建工程导入插件工程配置工程都比较简单,建议大家直接看官网,照葫芦画瓢就是。

本篇文章是关于 uni-app 的 module 插件,这里直接从代码开发部分开始聊。

4.1 原生方法导出

iOS 中有两种方法处理方式,一种是同步的,一种是异步的。原生的方法要通过某种途径导出后,uni-app 才能使用。

4.1.1 同步方法导出

// 通过宏 UNI_EXPORT_METHOD_SYNC 将同步方法暴露给 js 端
UNI_EXPORT_METHOD_SYNC(@selector(testSyncFunc:))
/// 同步方法(注:同步方法会在 js 线程执行)
/// @param options js 端调用方法时传递的参数   支持:String、Number、Boolean、JsonObject 类型
- (NSString *)testSyncFunc:(NSDictionary *)options {
    // options 为 js 端调用此方法时传递的参数
    NSLog(@"%@",options);

    /*
     可以在该方法中实现原生功能,然后直接通过 return 返回参数给 js
     */

    // 同步返回参数给 js 端  支持:NSString、NSDictionary(只能包含基本数据类型)、NSNumber 类型
    return @"success";
}

4.1.2 异步方法导出

// 通过宏 UNI_EXPORT_METHOD 将异步方法暴露给 js 端
UNI_EXPORT_METHOD(@selector(testAsyncFunc:callback:))
/// 异步方法(注:异步方法会在主线程(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);

    }
}

4.2 Hook APP事件

如果需要在 App 启动时初始化或者需要获取系统的一些事件, 需要新建一个XXXXProxy类(注意命名加前缀防止冲突),继承 NSObject遵守UniPluginProtocol协议

-(void)onCreateUniPlugin;

- (BOOL)application:(UIApplication *_Nullable)application didFinishLaunchingWithOptions:(NSDictionary *_Nullable)launchOptions;
- (void)application:(UIApplication *_Nullable)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *_Nullable)deviceToken;
- (void)application:(UIApplication *_Nullable)application didFailToRegisterForRemoteNotificationsWithError:(NSError *_Nullable)err;
- (void)application:(UIApplication *_Nullable)application didReceiveRemoteNotification:(NSDictionary *_Nullable)userInfo;
- (void)application:(UIApplication *_Nullable)application didReceiveLocalNotification:(UILocalNotification *_Nullable)notification;
- (BOOL)application:(UIApplication *_Nullable)application handleOpenURL:(NSURL *_Nullable)url;
- (BOOL)application:(UIApplication *_Nullable)app openURL:(NSURL *_Nonnull)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *_Nullable)options NS_AVAILABLE_IOS(9_0);

- (void)applicationWillResignActive:(UIApplication *)application;
- (void)applicationDidBecomeActive:(UIApplication *)application;
- (void)applicationDidEnterBackground:(UIApplication *)application;
- (void)applicationWillEnterForeground:(UIApplication *)application;

- (BOOL)application:(UIApplication *_Nullable)application continueUserActivity:(NSUserActivity *_Nullable)userActivity restorationHandler:(void(^_Nullable)(NSArray * __nullable restorableObjects))restorationHandler API_AVAILABLE(ios(8.0));

4.3 性能

这种跨端的 App 本身性能问题有一定的瓶颈,所以 Dcloud 也提供了原生代码的运行线程和队列的控制。

4.3.1 线程

想要在指定线程里运行原生代码,由于原生代码不一定能够一直运行在前台,所以线程也需要保活,可以这么实现uniExecuteThread

-(NSThread*)uniExecuteThread
{
    if ( nil == _uniExecuteThread) {
        _uniExecuteThread = [[NSThread alloc] initWithTarget:self selector:@selector(uniNewThread) object:nil];
        [_uniExecuteThread setName:@"TestUniModule"];
        [_uniExecuteThread start];
    }
    return _uniExecuteThread;
}

-(void)uniNewThread
{
    @autoreleasepool {
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
        [runLoop run];
    }
}

4.3.2 队列

想要在指定线程里运行原生代码,可以实现 uniExecuteQueue属性,可以在这个属性里返回一个具体的队列。

DCloud SDK 提供了插件相关的API信息

4.4 配置插件信息

选中工程中的 HBuilder-uniPlugin-Info.plist文件右键->Open As->Source Code找到dcloud_uniplugins节点,copy下面的内容添加到dcloud_uniplugins节点下,按您插件的实际信息填写对应的项

<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:module 或 component 对应的名称(注意:module 的 name 必须以插件id为前缀或和插件id相同,示例为DCTestUniPlugin-TestModule,其中 DCTestUniPlugin 为插件的id,需要保证唯一性,避免与其他插件冲突,component 的name 没有强制要求,但是也要保证唯一比如 dc-map)
  • type:module 或 component (示例为module)

4.5 生成插件包

此步骤应该在您插件所有功能都开发完毕,并在开发工程中测试完成后进行,插件目录结构类似:

|-- 插件id                               // 插件包是一个以插件id命名的文件夹
    |-- android                         // 存放 android 插件所需要的依赖库及资源文件
    |-- ios                             // 存放 ios 插件所需要的依赖库及资源文件
    |-- package.json                    // 插件配置文件

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.frameworkcopy 到 ios 目录下,这样我们的插件包就构建完成了。

接下来介绍一下如何使用本地原生的 iOS 插件。

5. uni-app 使用 iOS 原生插件

将原生插件导出来,按照要求放到 uni-app 里面去,然后在代码里,可以这么使用

<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>

写好代码后,生成本地 App 包资源导入到插件开发工程,选择 HBuilder Target 运行,然后测试一下功能是否正常。

提示 3: 前端代码修改后重新导入资源时,需要在插件开发工程中删除之前导入的资源,同时将设备上的 App 删除,避免因为缓存问题导致加载的还是旧的资源。

6. 插件市场

原生插件发布到插件市场,本篇文章不涉及这部分内容,具体见提交原生插件到Dcloud插件市场


  1. Dcloud SDK

  2. 自定义基座

  3. 打包工程是指 Dcloud SDK 目录下的 HBuilder-Hello iOS 原生项目

  4. 插件工程是指 Dcloud SDK 目录下的 HBuilder-uniPluginDemo iOS 原生项目

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容