各位靓仔靓女,晚上好!我是今晚的讲师,大家可以叫我老司机。今天给大家分享一下uni-app plus模式下,如何在App端调用原生SDK,以及如何优雅地处理iOS和Android平台的差异。
咱们直接上干货,争取把大家的时间都花在刀刃上。
一、为啥要用原生SDK?
首先,咱们得搞清楚一个问题:为啥要费劲巴拉地用原生SDK?uni-app不是已经很香了吗?
确实,uni-app已经很强大了,大部分需求都能满足。但有些时候,它还是力不从心。比如:
- 性能极致优化: 某些对性能要求极高的场景,比如音视频处理、AR/VR等,原生代码的效率更高。
- 特殊硬件能力: 某些硬件能力,uni-app的封装可能不够完善,或者根本就没有封装,只能通过原生SDK来调用。
- 第三方服务集成: 有些第三方服务,只提供了原生SDK,没有提供uni-app插件。
- 系统底层功能: 访问系统底层功能,例如蓝牙、NFC等,原生SDK更加直接。
总而言之,当uni-app满足不了你的野心时,原生SDK就是你手中的利剑。
二、uni-app plus模式的优势
uni-app plus模式,就是uni-app提供的原生扩展能力。它允许你在uni-app项目中,直接调用原生代码,从而弥补uni-app的不足。
plus模式的优势在于:
- 一套代码,多端运行: 仍然可以使用uni-app的语法编写大部分业务逻辑,减少开发成本。
- 原生能力扩展: 可以自由地调用原生SDK,实现更加复杂的功能。
- 灵活可控: 可以根据实际需求,选择性地使用原生代码,避免过度依赖。
三、如何调用原生SDK?(以Android为例)
接下来,咱们以Android为例,手把手地教大家如何在uni-app plus模式下调用原生SDK。
1. 创建uni-app项目
首先,创建一个uni-app项目。这个步骤我就省略了,相信大家都会。
2. 创建Android原生插件
在项目的nativeplugins
目录下,创建一个Android原生插件。目录结构如下:
nativeplugins/
└── myplugin/
├── android/
│ ├── AndroidManifest.xml
│ ├── libs/
│ │ └── your_sdk.aar //你的原生SDK
│ └── src/
│ └── main/
│ └── java/
│ └── com/example/myplugin/MyPlugin.java //插件类
└── package.json
package.json
: 插件的配置文件,包含插件的名称、版本、描述等信息。android/AndroidManifest.xml
: Android插件的清单文件,用于声明插件的权限和服务。android/libs/your_sdk.aar
: 你的原生SDK的AAR文件。android/src/main/java/com/example/myplugin/MyPlugin.java
: 插件的Java类,用于实现插件的功能。
3. 编写package.json
package.json
的内容如下:
{
"id": "myplugin",
"name": "MyPlugin",
"version": "1.0.0",
"description": "My awesome plugin",
"platforms": {
"android": {
"minSdkVersion": 21,
"plugins": [
{
"type": "module",
"name": "MyPlugin",
"class": "com.example.myplugin.MyPlugin"
}
],
"dependencies": [
// 这里可以添加需要的依赖,比如compileOnly "androidx.appcompat:appcompat:1.4.1"
]
}
}
}
id
: 插件的唯一标识符,必须全局唯一。name
: 插件的名称,用于在uni-app项目中引用。version
: 插件的版本号。description
: 插件的描述信息。platforms.android.plugins
: 声明Android平台的插件信息,包括插件的类型、名称和类名。platforms.android.dependencies
: 声明Android平台需要的依赖库。 compileOnly表示只在编译时使用,运行时不包含。
4. 编写MyPlugin.java
MyPlugin.java
的内容如下:
package com.example.myplugin;
import android.content.Context;
import io.dcloud.feature.uniapp.annotation.UniJSMethod;
import io.dcloud.feature.uniapp.bridge.UniJSCallback;
import io.dcloud.feature.uniapp.common.UniModule;
public class MyPlugin extends UniModule {
private Context mContext;
@Override
public void onCreate(Context context) {
super.onCreate(context);
mContext = context;
}
@UniJSMethod(uiThread = false) // js同步调用
public String syncMethod() {
return "This is a sync method from MyPlugin";
}
@UniJSMethod(uiThread = true) // js异步调用
public void asyncMethod(String options, UniJSCallback callback) {
// 模拟耗时操作
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String result = "This is an async method from MyPlugin with options: " + options;
callback.invoke(result);
}
}).start();
}
@UniJSMethod(uiThread = false)
public void callNativeSDK(String param, UniJSCallback callback) {
// 在这里调用你的原生SDK
String sdkResult = "Result from your SDK with param: " + param;
callback.invoke(sdkResult);
}
}
@UniJSMethod
: 这个注解用于声明可以被JavaScript调用的方法。uiThread = true
表示该方法在UI线程中执行,适合处理UI相关的操作。uiThread = false
表示该方法在非UI线程中执行,适合处理耗时操作。
UniJSCallback
: 用于将结果返回给JavaScript。
5. 修改 AndroidManifest.xml
确保你的 AndroidManifest.xml
文件中包含必要的权限声明,以及插件的声明。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myplugin">
<application>
<!-- 这里可以添加你的application配置 -->
</application>
</manifest>
6. 打包原生插件
在HBuilderX中,右键点击nativeplugins/myplugin
目录,选择“生成本地插件”,将插件打包成ZIP文件。
7. 导入原生插件
在uni-app项目中,打开manifest.json
文件,在“App原生插件配置”中,选择“本地插件”,将刚才打包的ZIP文件导入。
8. 使用原生插件
在uni-app的JavaScript代码中,可以使用uni.requireNativePlugin
方法来获取原生插件的实例,然后调用插件的方法。
const myPlugin = uni.requireNativePlugin('MyPlugin');
// 调用同步方法
const syncResult = myPlugin.syncMethod();
console.log('Sync result:', syncResult);
// 调用异步方法
myPlugin.asyncMethod('hello', result => {
console.log('Async result:', result);
});
// 调用原生SDK方法
myPlugin.callNativeSDK('world', result => {
console.log('SDK Result:', result);
});
四、iOS平台的处理
iOS平台的处理方式与Android类似,但是有一些细节需要注意。
1. 创建iOS原生插件
在项目的nativeplugins
目录下,创建一个iOS原生插件。目录结构如下:
nativeplugins/
└── myplugin/
├── ios/
│ ├── MyPlugin.h
│ ├── MyPlugin.m
│ └── your_sdk.framework //你的原生SDK
└── package.json
package.json
: 插件的配置文件。ios/MyPlugin.h
: 插件的头文件,用于声明插件的接口。ios/MyPlugin.m
: 插件的实现文件,用于实现插件的功能。ios/your_sdk.framework
: 你的原生SDK的Framework文件。
2. 编写package.json
package.json
的内容如下:
{
"id": "myplugin",
"name": "MyPlugin",
"version": "1.0.0",
"description": "My awesome plugin",
"platforms": {
"ios": {
"plugins": [
{
"type": "module",
"name": "MyPlugin",
"class": "MyPlugin"
}
],
"deploymentTarget": "11.0", // 适配的最低系统版本
"frameworks": [
"CoreLocation.framework", // 如果你的SDK依赖framework,在这里添加
"SystemConfiguration.framework"
]
}
}
}
platforms.ios.plugins
: 声明iOS平台的插件信息。platforms.ios.deploymentTarget
: 声明iOS平台适配的最低系统版本。platforms.ios.frameworks
: 声明iOS平台需要链接的Framework。
3. 编写MyPlugin.h
MyPlugin.h
的内容如下:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <DCUniPlugin.h>
NS_ASSUME_NONNULL_BEGIN
@interface MyPlugin : DCUniPlugin
/**
* 同步方法
*/
- (NSString *)syncMethod;
/**
* 异步方法
* @param options 参数
* @param callback 回调
*/
- (void)asyncMethod:(NSDictionary *)options callback:(UniModuleKeepAliveCallback)callback;
/**
* 调用原生SDK的方法
* @param param 参数
* @param callback 回调
*/
- (void)callNativeSDK:(NSString *)param callback:(UniModuleKeepAliveCallback)callback;
@end
NS_ASSUME_NONNULL_END
4. 编写MyPlugin.m
MyPlugin.m
的内容如下:
#import "MyPlugin.h"
@implementation MyPlugin
- (void)onCreateUniContext:(DCUniSDKInstance *)uniInstance {
[super onCreateUniContext:uniInstance];
NSLog(@"MyPlugin 初始化");
}
- (NSString *)syncMethod {
return @"This is a sync method from MyPlugin";
}
- (void)asyncMethod:(NSDictionary *)options callback:(UniModuleKeepAliveCallback)callback {
// 模拟耗时操作
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:2];
NSString *result = [NSString stringWithFormat:@"This is an async method from MyPlugin with options: %@", options];
callback(result, NO); // NO表示执行完毕,不再保持回调
});
}
- (void)callNativeSDK:(NSString *)param callback:(UniModuleKeepAliveCallback)callback {
// 在这里调用你的原生SDK
NSString *sdkResult = [NSString stringWithFormat:@"Result from your SDK with param: %@", param];
callback(sdkResult, NO);
}
@end
DCUniPlugin
: iOS插件需要继承DCUniPlugin
类。UniModuleKeepAliveCallback
: 用于将结果返回给JavaScript。onCreateUniContext
: 用于插件的初始化。
5. 添加Framework
将你的原生SDK的Framework文件添加到插件的ios
目录下,并在package.json
中声明需要链接的Framework。
6. 打包原生插件
在HBuilderX中,右键点击nativeplugins/myplugin
目录,选择“生成本地插件”,将插件打包成ZIP文件。
7. 导入原生插件
在uni-app项目中,打开manifest.json
文件,在“App原生插件配置”中,选择“本地插件”,将刚才打包的ZIP文件导入。
8. 使用原生插件
在uni-app的JavaScript代码中,使用uni.requireNativePlugin
方法来获取原生插件的实例,然后调用插件的方法。
const myPlugin = uni.requireNativePlugin('MyPlugin');
// 调用同步方法
const syncResult = myPlugin.syncMethod();
console.log('Sync result:', syncResult);
// 调用异步方法
myPlugin.asyncMethod({message: 'hello'}, result => {
console.log('Async result:', result);
});
// 调用原生SDK方法
myPlugin.callNativeSDK('world', result => {
console.log('SDK Result:', result);
});
五、平台差异处理
重点来了!如何处理iOS和Android平台的差异?毕竟,代码不能一套梭哈,总得有点区分。
1. 条件编译
uni-app提供了条件编译的语法,可以根据不同的平台,编译不同的代码。
// #ifdef APP-PLUS-ANDROID
// Android平台代码
const myPlugin = uni.requireNativePlugin('MyPlugin');
// #endif
// #ifdef APP-PLUS-IOS
// iOS平台代码
const myPlugin = uni.requireNativePlugin('MyPlugin');
// #endif
2. 平台判断
可以使用uni.getSystemInfoSync()
方法获取设备信息,然后判断当前平台。
const systemInfo = uni.getSystemInfoSync();
if (systemInfo.platform === 'android') {
// Android平台代码
const myPlugin = uni.requireNativePlugin('MyPlugin');
} else if (systemInfo.platform === 'ios') {
// iOS平台代码
const myPlugin = uni.requireNativePlugin('MyPlugin');
}
3. 封装统一接口
可以将不同平台的代码封装成统一的接口,然后在JavaScript代码中调用统一的接口。
// common.js
export function callNativeSDK(param, callback) {
// #ifdef APP-PLUS-ANDROID
const myPlugin = uni.requireNativePlugin('MyPlugin');
myPlugin.callNativeSDK(param, callback);
// #endif
// #ifdef APP-PLUS-IOS
const myPlugin = uni.requireNativePlugin('MyPlugin');
myPlugin.callNativeSDK(param, callback);
// #endif
}
// 在uni-app页面中
import { callNativeSDK } from './common.js';
callNativeSDK('world', result => {
console.log('SDK Result:', result);
});
4. 使用plus.os.name
plus.os.name
可以更方便的获取当前操作系统名称。
if (plus.os.name == "Android") {
//Android平台
const myPlugin = uni.requireNativePlugin('MyPlugin');
} else if (plus.os.name == "iOS") {
//iOS平台
const myPlugin = uni.requireNativePlugin('MyPlugin');
}
六、注意事项
- 插件ID必须唯一: 插件的ID必须全局唯一,否则可能会导致冲突。
- 权限声明: 确保在
AndroidManifest.xml
和Info.plist
文件中声明了必要的权限。 - 异常处理: 在原生代码中,要做好异常处理,避免程序崩溃。
- 线程安全: 注意线程安全问题,避免在UI线程中执行耗时操作。
- 内存管理: 注意内存管理,避免内存泄漏。
- 异步回调: 使用异步回调时,要确保回调函数能够正确地执行。
- 插件更新: 更新插件时,需要重新打包uni-app项目。
- uni-app版本: 确保uni-app版本支持原生插件功能。 建议使用最新版本。
- HBuilderX版本: 确保HBuilderX版本支持原生插件开发。
七、示例代码:获取设备唯一标识
这是一个更完整的示例,演示如何获取设备的唯一标识,并处理iOS和Android平台的差异。
1. Android原生插件 (MyPlugin.java
)
package com.example.myplugin;
import android.content.Context;
import android.provider.Settings;
import io.dcloud.feature.uniapp.annotation.UniJSMethod;
import io.dcloud.feature.uniapp.bridge.UniJSCallback;
import io.dcloud.feature.uniapp.common.UniModule;
public class MyPlugin extends UniModule {
private Context mContext;
@Override
public void onCreate(Context context) {
super.onCreate(context);
mContext = context;
}
@UniJSMethod(uiThread = false)
public void getDeviceId(UniJSCallback callback) {
String deviceId = Settings.Secure.getString(mContext.getContentResolver(), Settings.Secure.ANDROID_ID);
callback.invoke(deviceId);
}
}
2. iOS原生插件 (MyPlugin.h
and MyPlugin.m
)
MyPlugin.h
:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <DCUniPlugin.h>
NS_ASSUME_NONNULL_BEGIN
@interface MyPlugin : DCUniPlugin
- (void)getDeviceId:(UniModuleKeepAliveCallback)callback;
@end
NS_ASSUME_NONNULL_END
MyPlugin.m
:
#import "MyPlugin.h"
#import <UIKit/UIKit.h>
@implementation MyPlugin
- (void)getDeviceId:(UniModuleKeepAliveCallback)callback {
NSString *deviceId = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
callback(deviceId, NO);
}
@end
3. JavaScript 代码 (在 uni-app 页面中)
function getDeviceId() {
return new Promise((resolve, reject) => {
const myPlugin = uni.requireNativePlugin('MyPlugin');
if (myPlugin) {
myPlugin.getDeviceId(deviceId => {
resolve(deviceId);
});
} else {
reject('Plugin not found');
}
});
}
async function init() {
try {
const deviceId = await getDeviceId();
console.log('Device ID:', deviceId);
// 在这里使用 deviceId
} catch (error) {
console.error('Error getting device ID:', error);
}
}
init();
八、总结
好了,今天的讲座就到这里。希望通过今天的分享,大家能够掌握uni-app plus模式下调用原生SDK的技巧,以及处理平台差异的方法。
记住,原生SDK是你的武器,uni-app是你的盔甲,合理搭配,才能战无不胜!
如果大家还有什么问题,可以在评论区留言,我会尽力解答。
祝大家开发顺利,早日成为技术大牛! 下课!