DCloud iOS 原生插件開(kāi)發(fā)指南

[TOC]

要開(kāi)發(fā)原生插件的話,首先得去 DCloud 官網(wǎng)下載 DCloud SDK[1]。DCloud SDK 下載下來(lái)解壓后,內(nèi)容如下:

|--iOSSDK    
    |-- HBuilder-Hello                  // uni-app 離線打包工程
    |-- HBuilder-ExampleDemo            // Demo 工程
    |-- HBuilder-uniPluginDemo          // uni-app 插件開(kāi)發(fā)主工程 (本文檔需要使用的工程)
    |-- SDK                             // 依賴庫(kù)及依賴資源文件
    |-- Feature-iOS.xls                 // 功能模塊與依賴庫(kù)對(duì)應(yīng)關(guān)系說(shuō)明表格
    |-- readme.txt                      // 目錄說(shuō)明

在聊插件開(kāi)發(fā)之前,先講一點(diǎn)預(yù)備知識(shí),前面三節(jié)都是預(yù)備知識(shí)。后面插件的開(kāi)發(fā)過(guò)程中,會(huì)涉及到這些知識(shí)內(nèi)容。

1. 自定義基座[2]

自定義基座,有兩種方式獲取,一種是用打包工程生成,另一種是通過(guò)云打包獲取。

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

  1. 在打包原生工程[3]里找到 control.xml 文件,在 HBuilder 節(jié)點(diǎn)里查看是否有這2個(gè):debug="true" syncDebug="true"配置,沒(méi)有的話加上。

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

  3. 在原生打包工程里找到 info.plist 文件并增加一項(xiàng):Application supports iTunes file sharing, 值設(shè)為 yes

  4. 確保原生工程里Pandora文件夾下的apps文件夾里只有一個(gè)文件夾,并且文件夾的名稱和里面的 manifestid 值相同。

  5. 確保 control.xml 文件里的 appid 的值和 apps 目錄下的第一個(gè)文件夾的名稱一致。

  6. 確保 HBuilderX 里要調(diào)試的代碼的 appid 和 control.xml 的appid值一致。

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

  8. 在 uni-app 工程里主目錄下新建一個(gè)名稱為 unpackage 的文件夾,再在unpackage文件夾下新建一個(gè)名稱為 debug 文件夾,并把生成的iOS_debug.ipa包放入 debug 文件夾下。

到這里,自定義基座就制作完成了。

要調(diào)試的話,在 HBuildX 里,找到之前 app id 相同的 uni-app 工程,點(diǎn)擊運(yùn)行 --> 運(yùn)行到手機(jī)或模擬器 --> 使用自定義基座運(yùn)行(iOS),等待連接成功之后,然后運(yùn)行 --> 運(yùn)行到手機(jī)或模擬器 --> 選擇真機(jī)或者模擬器,HBuilderX 就會(huì)用自定義基座,把 App 運(yùn)行到 iOS 設(shè)備上了。

1.2 云打包生成自定義基座

HBuilderX 的菜單: 運(yùn)行 --> 運(yùn)行到手機(jī)或模擬器 --> 制作自定義基座, 并配置好,然后點(diǎn)擊頁(yè)面右下角的打包,打包后有下載鏈接地址,下載下來(lái)的文件命名成iOS_debug.ipa,這就是自定義基座。

要調(diào)試的話,基座下載下來(lái)后,放到項(xiàng)目的 unpackage --> debug 下目錄下。

提示1: 配置云打包的自定義基座需要 iOS 證書(shū)與 Profile 文件。

2. 運(yùn)行到設(shè)備上

2.1 Xcode

運(yùn)行 uni-app 到設(shè)備上也有兩種方式,一種是通過(guò) HbuilderX 運(yùn)行到設(shè)備上,另一種是通過(guò) Xcode。

首先用 HBuilderX 打包 uni-app,操作路徑:發(fā)行 --> 原生 App 本地打包 --> 生成本地 App 資源,HBuilderX 處理好之后,會(huì)在控制臺(tái)輸出 App 包資源的路徑,得到 App 包資源后,把 App 包資源放到原生 iOS 打包工程的指定目錄Pandora/apps/下,并修改 control.xml 文件的 appid 與 打包資源的文件夾名稱相同。

然后,運(yùn)行打包工程到設(shè)備上即可,運(yùn)行打包工程之前,最好配置好 App 相應(yīng)的信息,例如名稱,icon,版本等。

2.2 HBuilderX

在 HBuilderX 上運(yùn)行比較簡(jiǎn)單,操作路徑: 運(yùn)行 --> 運(yùn)行到手機(jī)或模擬器 --> 選擇真機(jī)或者模擬器

這里的運(yùn)行環(huán)境,會(huì)有兩個(gè):標(biāo)準(zhǔn)基座自定義基座,看開(kāi)發(fā)需要,選擇相應(yīng)環(huán)境。

3. 離線打包

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

  1. 這里主要是配置工程,諸如 App 名字,版本,icon,國(guó)際化等的一些信息。照著教程配置就好。

  2. 配置好之后,把從 HBuilderX 打包出來(lái)的 App 包資源放到 Pandora 的 apps 文件夾下去。

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

提示2: 記得修改 contol.xml 文件里的內(nèi)容,比如環(huán)境是 Debug 還是 Release。打 Release 包的話,syncDebug 配置記得去掉。

4. 插件開(kāi)發(fā)

插件從類型上區(qū)分,有兩種類型,這根 uni-app 的歷史發(fā)展有關(guān)。

  1. H5+ 插件:不過(guò)這種插件已經(jīng)成為過(guò)去式了;
  2. uni-app 插件:這種是目前 DCloud官方推薦的。

插件從形式上區(qū)分,又有兩種形式,比如Dcloud SDK 里的插件工程[4]里有個(gè)DCTestUniPlugin插件,這個(gè)插件,提供了 modulecomponent 兩種形式。

  1. module: 是純功能性的插件,沒(méi)有具體界面的,比如獲取手機(jī)電量,地理位置等功能。
  2. component:是提供iOS 或者 Android 原生界面的插件類型。

創(chuàng)建工程導(dǎo)入插件工程配置工程都比較簡(jiǎn)單,建議大家直接看官網(wǎng),照葫蘆畫(huà)瓢就是。

本篇文章是關(guān)于 uni-app 的 module 插件,這里直接從代碼開(kāi)發(fā)部分開(kāi)始聊。

4.1 原生方法導(dǎo)出

iOS 中有兩種方法處理方式,一種是同步的,一種是異步的。原生的方法要通過(guò)某種途徑導(dǎo)出后,uni-app 才能使用。

4.1.1 同步方法導(dǎo)出

// 通過(guò)宏 UNI_EXPORT_METHOD_SYNC 將同步方法暴露給 js 端
UNI_EXPORT_METHOD_SYNC(@selector(testSyncFunc:))
/// 同步方法(注:同步方法會(huì)在 js 線程執(zhí)行)
/// @param options js 端調(diào)用方法時(shí)傳遞的參數(shù)   支持:String、Number、Boolean、JsonObject 類型
- (NSString *)testSyncFunc:(NSDictionary *)options {
    // options 為 js 端調(diào)用此方法時(shí)傳遞的參數(shù)
    NSLog(@"%@",options);

    /*
     可以在該方法中實(shí)現(xiàn)原生功能,然后直接通過(guò) return 返回參數(shù)給 js
     */

    // 同步返回參數(shù)給 js 端  支持:NSString、NSDictionary(只能包含基本數(shù)據(jù)類型)、NSNumber 類型
    return @"success";
}

4.1.2 異步方法導(dǎo)出

// 通過(guò)宏 UNI_EXPORT_METHOD 將異步方法暴露給 js 端
UNI_EXPORT_METHOD(@selector(testAsyncFunc:callback:))
/// 異步方法(注:異步方法會(huì)在主線程(UI線程)執(zhí)行)
/// @param options js 端調(diào)用方法時(shí)傳遞的參數(shù)   支持:String、Number、Boolean、JsonObject 類型
/// @param callback 回調(diào)方法,回傳參數(shù)給 js 端   支持: NSString、NSDictionary(只能包含基本數(shù)據(jù)類型)、NSNumber 類型
- (void)testAsyncFunc:(NSDictionary *)options callback:(UniModuleKeepAliveCallback)callback { 

    // options 為 js 端調(diào)用此方法時(shí)傳遞的參數(shù) NSLog(@"%@",options); // 可以在該方法中實(shí)現(xiàn)原生能力,然后通過(guò) callback 回調(diào)到 js 

   if (callback) {
       // 第一個(gè)參數(shù)為回傳給js端的數(shù)據(jù),第二個(gè)參數(shù)為標(biāo)識(shí),表示該回調(diào)方法是否支持多次調(diào)用,如果原生端需要多次回調(diào)js端則第二個(gè)參數(shù)傳 YES;
        callback(@"success",NO);

    }
}

4.2 Hook APP事件

如果需要在 App 啟動(dòng)時(shí)初始化或者需要獲取系統(tǒng)的一些事件, 需要新建一個(gè)XXXXProxy類(注意命名加前綴防止沖突),繼承 NSObject遵守UniPluginProtocol協(xié)議

-(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 本身性能問(wèn)題有一定的瓶頸,所以 Dcloud 也提供了原生代碼的運(yùn)行線程和隊(duì)列的控制。

4.3.1 線程

想要在指定線程里運(yùn)行原生代碼,由于原生代碼不一定能夠一直運(yùn)行在前臺(tái),所以線程也需要保活,可以這么實(shí)現(xiàn)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 隊(duì)列

想要在指定線程里運(yùn)行原生代碼,可以實(shí)現(xiàn) uniExecuteQueue屬性,可以在這個(gè)屬性里返回一個(gè)具體的隊(duì)列。

DCloud SDK 提供了插件相關(guān)的API信息

4.4 配置插件信息

選中工程中的 HBuilder-uniPlugin-Info.plist文件右鍵->Open As->Source Code找到dcloud_uniplugins節(jié)點(diǎn),copy下面的內(nèi)容添加到dcloud_uniplugins節(jié)點(diǎn)下,按您插件的實(shí)際信息填寫(xiě)對(duì)應(yīng)的項(xiàng)

<dict>
    <key>hooksClass</key>
    <string>填寫(xiě) hooksClass 類名 </string>
    <key>plugins</key>
    <array>
        <dict>
            <key>class</key>
            <string>填寫(xiě) module 或 component 的類名</string>
            <key>name</key>
            <string>填寫(xiě)暴露給js端對(duì)應(yīng)的 module 或 component 名稱</string>
            <key>type</key>
            <string>填寫(xiě) module 或 component</string>
        </dict>
    </array>
</dict>
  • hooksClass:App系統(tǒng)方法鉤子類,值是類名,是給有些插件需要在 app 啟動(dòng)時(shí)做初始化或者獲取系統(tǒng)事件用的,如果沒(méi)有可以不填為空
  • class:module 或 component 對(duì)應(yīng)的原生類名(示例中為 TestModule)
  • name:module 或 component 對(duì)應(yīng)的名稱(注意:module 的 name 必須以插件id為前綴或和插件id相同,示例為DCTestUniPlugin-TestModule,其中 DCTestUniPlugin 為插件的id,需要保證唯一性,避免與其他插件沖突,component 的name 沒(méi)有強(qiáng)制要求,但是也要保證唯一比如 dc-map)
  • type:module 或 component (示例為module)

4.5 生成插件包

此步驟應(yīng)該在您插件所有功能都開(kāi)發(fā)完畢,并在開(kāi)發(fā)工程中測(cè)試完成后進(jìn)行,插件目錄結(jié)構(gòu)類似:

|-- 插件id                               // 插件包是一個(gè)以插件id命名的文件夾
    |-- android                         // 存放 android 插件所需要的依賴庫(kù)及資源文件
    |-- ios                             // 存放 ios 插件所需要的依賴庫(kù)及資源文件
    |-- package.json                    // 插件配置文件

package.json 文件是插件包的信息,具體配置參考這里,內(nèi)容類似:

{
    "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為名新建一個(gè)文件夾,將編輯好的 package.json 放進(jìn)去,然后在文件夾中在新建一個(gè) ios 文件夾,將生成的依賴庫(kù)DCTestUniPlugin.frameworkcopy 到 ios 目錄下,這樣我們的插件包就構(gòu)建完成了。

接下來(lái)介紹一下如何使用本地原生的 iOS 插件。

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

將原生插件導(dǎo)出來(lái),按照要求放到 uni-app 里面去,然后在代碼里,可以這么使用

<template>
    <div>
        <button type="primary" @click="testAsyncFunc">testAsyncFunc</button>
        <button type="primary" @click="testSyncFunc">testSyncFunc</button>
    </div>
</template>

<script>
    // 首先需要通過(guò) uni.requireNativePlugin("ModuleName") 獲取 module 
    var testModule = uni.requireNativePlugin("DCTestUniPlugin-TestModule")
    export default {
        methods: {
            testAsyncFunc() {
                // 調(diào)用異步方法
                testModule.testAsyncFunc({
                        'name': 'uni-app',
                        'age': 1
                    },
                    (ret) => {
                        uni.showToast({
                            title:'調(diào)用異步方法 ' + ret,
                            icon: "none"
                        })
                    })
            },
            testSyncFunc() {
                // 調(diào)用同步方法
                var ret = testModule.testSyncFunc({
                    'name': 'uni-app',
                    'age': 1
                })

                uni.showToast({
                    title:'調(diào)用同步方法 ' + ret,
                    icon: "none"
                })
            }
        }
    }
</script>

寫(xiě)好代碼后,生成本地 App 包資源導(dǎo)入到插件開(kāi)發(fā)工程,選擇 HBuilder Target 運(yùn)行,然后測(cè)試一下功能是否正常。

提示 3: 前端代碼修改后重新導(dǎo)入資源時(shí),需要在插件開(kāi)發(fā)工程中刪除之前導(dǎo)入的資源,同時(shí)將設(shè)備上的 App 刪除,避免因?yàn)榫彺鎲?wèn)題導(dǎo)致加載的還是舊的資源。

6. 插件市場(chǎng)

原生插件發(fā)布到插件市場(chǎng),本篇文章不涉及這部分內(nèi)容,具體見(jiàn)提交原生插件到Dcloud插件市場(chǎng)


  1. Dcloud SDK ?

  2. 自定義基座 ?

  3. 打包工程是指 Dcloud SDK 目錄下的 HBuilder-Hello iOS 原生項(xiàng)目 ?

  4. 插件工程是指 Dcloud SDK 目錄下的 HBuilder-uniPluginDemo iOS 原生項(xiàng)目 ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,310評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,951評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,796評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,566評(píng)論 6 407
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,055評(píng)論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,303評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,799評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,683評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,899評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,135評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,520評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,757評(píng)論 1 282
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,528評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,844評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容