如何利用 `uni-app-plus` 模式,在 `App` 端调用原生 `SDK`,并处理 `iOS` 和 `Android` 的平台差异?

各位靓仔靓女,晚上好!我是今晚的讲师,大家可以叫我老司机。今天给大家分享一下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.xmlInfo.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是你的盔甲,合理搭配,才能战无不胜!

如果大家还有什么问题,可以在评论区留言,我会尽力解答。

祝大家开发顺利,早日成为技术大牛! 下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注