[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 用打包工程生成自定義基座
在打包原生工程[3]里找到 control.xml 文件,在 HBuilder 節(jié)點(diǎn)里查看是否有這2個(gè):
debug="true" syncDebug="true"
配置,沒(méi)有的話加上。修改打包工程的 Bundle identifier 為你 App 的 Bundle identifier
在原生打包工程里找到 info.plist 文件并增加一項(xiàng):
Application supports iTunes file sharing
, 值設(shè)為yes
。確保原生工程里
Pandora
文件夾下的apps文件夾里只有一個(gè)文件夾,并且文件夾的名稱和里面的manifest
的id
值相同。確保 control.xml 文件里的 appid 的值和 apps 目錄下的第一個(gè)文件夾的名稱一致。
確保 HBuilderX 里要調(diào)試的代碼的 appid 和 control.xml 的appid值一致。
使用 Xcode 的 Product 下的 archive 打包,然后生成ipa,并把 ipa 名稱命名為:
iOS_debug.ipa
。在 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 打包工程,按照下面的步驟操作:
這里主要是配置工程,諸如 App 名字,版本,icon,國(guó)際化等的一些信息。照著教程配置就好。
配置好之后,把從 HBuilderX 打包出來(lái)的 App 包資源放到 Pandora 的 apps 文件夾下去。
最后一步是打包,上面都配置好的話,就跟 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)。
- H5+ 插件:不過(guò)這種插件已經(jīng)成為過(guò)去式了;
- uni-app 插件:這種是目前 DCloud官方推薦的。
插件從形式上區(qū)分,又有兩種形式,比如Dcloud SDK 里的插件工程[4]里有個(gè)DCTestUniPlugin
插件,這個(gè)插件,提供了 module
和 component
兩種形式。
- module: 是純功能性的插件,沒(méi)有具體界面的,比如獲取手機(jī)電量,地理位置等功能。
- 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.framework
copy 到 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)。