第13集:uni-app 原生能力集成

章节概述

原生能力集成是 uni-app 中非常重要的一个特性,它允许开发者在 uni-app 中使用原生平台的能力,从而实现更多高级功能。uni-app 提供了多种原生能力集成方式,包括使用原生插件、混写原生代码、集成第三方 SDK 等。本章节将详细介绍 uni-app 中的原生能力集成功能,包括原生插件使用、原生代码混写、SDK 集成等核心知识点,并通过实用案例帮助开发者掌握原生能力的集成方法。

核心知识点

1. 原生插件使用

uni-app 支持使用原生插件来扩展应用的功能,主要包括:

  • 官方插件:uni-app 官方提供的原生插件
  • 第三方插件:开发者或第三方提供的原生插件
  • 自定义插件:开发者自己开发的原生插件

原生插件的使用步骤:

  1. 在插件市场或本地导入原生插件
  2. manifest.json 中配置插件
  3. 在代码中调用插件的 API

2. 原生代码混写

uni-app 支持在项目中混写原生代码,主要包括:

  • App 端:支持混写 Android(Java/Kotlin)和 iOS(Objective-C/Swift)代码
  • 小程序端:支持使用各平台的原生能力

原生代码混写的使用场景:

  1. 实现 uni-app 未提供的原生能力
  2. 优化应用性能,使用原生代码实现计算密集型任务
  3. 集成第三方原生 SDK

3. SDK 集成

uni-app 支持集成第三方 SDK,主要包括:

  • 地图 SDK:如高德地图、百度地图等
  • 支付 SDK:如微信支付、支付宝支付等
  • 推送 SDK:如极光推送、个推等
  • 社交 SDK:如微信登录、QQ 登录等
  • 其他 SDK:如人脸识别、语音识别等

SDK 集成的步骤:

  1. 下载并导入 SDK
  2. 配置 SDK 相关参数
  3. 编写原生代码调用 SDK
  4. 封装 API 供 uni-app 调用

4. 原生模块开发

uni-app 支持开发自定义原生模块,主要包括:

  • Android 原生模块:使用 Java 或 Kotlin 开发
  • iOS 原生模块:使用 Objective-C 或 Swift 开发
  • 鸿蒙原生模块:使用 ArkTS 开发

原生模块开发的步骤:

  1. 创建原生模块项目
  2. 实现原生功能
  3. 注册原生模块
  4. 封装 API 供 uni-app 调用

实用案例

集成原生地图 SDK

案例描述

开发一个地图应用,集成高德地图 SDK,实现以下功能:

  1. 显示地图
  2. 定位当前位置
  3. 搜索附近 POI
  4. 规划路线

代码示例

  1. 配置原生插件

manifest.json 中配置高德地图插件:

{
  "name": "uni-app-map-demo",
  "appid": "__UNI__XXXXXX",
  "description": "uni-app 原生地图集成示例",
  "versionName": "1.0.0",
  "versionCode": "100",
  "transformPx": true,
  "app-plus": {
    "modules": {
      "Maps": {
        "description": "地图"
      }
    },
    "distribute": {
      "android": {
        "permissions": [
          "android.permission.ACCESS_COARSE_LOCATION",
          "android.permission.ACCESS_FINE_LOCATION",
          "android.permission.ACCESS_NETWORK_STATE",
          "android.permission.ACCESS_WIFI_STATE",
          "android.permission.INTERNET"
        ]
      },
      "ios": {
        "plist": {
          "NSLocationWhenInUseUsageDescription": "需要获取您的位置信息",
          "NSLocationAlwaysAndWhenInUseUsageDescription": "需要获取您的位置信息"
        }
      }
    }
  }
}
  1. 使用地图组件
<template>
  <view class="container">
    <map 
      class="map" 
      :latitude="latitude" 
      :longitude="longitude" 
      :markers="markers" 
      :polyline="polyline"
      @markertap="markertap"
      @regionchange="regionchange"
    ></map>
    <view class="controls">
      <button class="btn" @click="locate">定位</button>
      <button class="btn" @click="searchNearby">搜索附近</button>
      <button class="btn" @click="calculateRoute">规划路线</button>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      latitude: 39.9042,
      longitude: 116.4074,
      markers: [],
      polyline: []
    };
  },
  methods: {
    // 定位
    locate() {
      uni.getLocation({
        type: 'gcj02',
        success: (res) => {
          this.latitude = res.latitude;
          this.longitude = res.longitude;
          this.markers = [{
            id: 1,
            latitude: res.latitude,
            longitude: res.longitude,
            title: '当前位置'
          }];
        },
        fail: (err) => {
          console.log('定位失败:', err);
        }
      });
    },
    
    // 搜索附近
    searchNearby() {
      // 调用高德地图 SDK 搜索附近 POI
      // 这里需要使用原生插件或原生代码实现
      console.log('搜索附近 POI');
    },
    
    // 规划路线
    calculateRoute() {
      // 调用高德地图 SDK 规划路线
      // 这里需要使用原生插件或原生代码实现
      console.log('规划路线');
    },
    
    // 点击标记
    markertap(e) {
      console.log('点击标记:', e);
    },
    
    // 地图区域变化
    regionchange(e) {
      console.log('地图区域变化:', e);
    }
  },
  onLoad() {
    this.locate();
  }
};
</script>

<style>
.container {
  width: 100%;
  height: 100vh;
  display: flex;
  flex-direction: column;
}

.map {
  flex: 1;
  width: 100%;
}

.controls {
  padding: 10px;
  background-color: #ffffff;
  display: flex;
  justify-content: space-around;
}

.btn {
  flex: 1;
  margin: 0 5px;
  height: 40px;
  background-color: #007AFF;
  color: #ffffff;
  border-radius: 4px;
}
</style>
  1. 自定义原生模块

Android 原生模块示例

创建 MyMapModule.java 文件:

package com.example.mymapmodule;

import android.content.Context;
import android.util.Log;

import com.amap.api.services.core.AMapException;
import com.amap.api.services.core.LatLonPoint;
import com.amap.api.services.poisearch.PoiResult;
import com.amap.api.services.poisearch.PoiSearch;

import org.json.JSONArray;
import org.json.JSONObject;

import io.dcloud.feature.uniapp.annotation.UniJSMethod;
import io.dcloud.feature.uniapp.bridge.UniJSCallback;
import io.dcloud.feature.uniapp.common.UniModule;

public class MyMapModule extends UniModule implements PoiSearch.OnPoiSearchListener {
    private Context mContext;
    private UniJSCallback mSearchCallback;
    
    public MyMapModule(Context context) {
        mContext = context;
    }
    
    @UniJSMethod(uiThread = true)
    public void searchNearby(double latitude, double longitude, String keyword, UniJSCallback callback) {
        mSearchCallback = callback;
        
        // 构建 PoiSearch 对象
        PoiSearch.Query query = new PoiSearch.Query(keyword, "", "");
        query.setPageSize(20);
        query.setPageNum(1);
        
        PoiSearch poiSearch = new PoiSearch(mContext, query);
        poiSearch.setBound(new PoiSearch.SearchBound(new LatLonPoint(latitude, longitude), 1000));
        poiSearch.setOnPoiSearchListener(this);
        poiSearch.searchPOIAsyn();
    }
    
    @Override
    public void onPoiSearched(PoiResult poiResult, int i) {
        if (i == AMapException.CODE_AMAP_SUCCESS && poiResult != null) {
            try {
                JSONArray jsonArray = new JSONArray();
                for (PoiResult.PoiItem item : poiResult.getPois()) {
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put("title", item.getTitle());
                    jsonObject.put("latitude", item.getLatLonPoint().getLatitude());
                    jsonObject.put("longitude", item.getLatLonPoint().getLongitude());
                    jsonObject.put("address", item.getSnippet());
                    jsonArray.put(jsonObject);
                }
                
                JSONObject result = new JSONObject();
                result.put("success", true);
                result.put("data", jsonArray);
                
                if (mSearchCallback != null) {
                    mSearchCallback.invoke(result);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            if (mSearchCallback != null) {
                JSONObject result = new JSONObject();
                try {
                    result.put("success", false);
                    result.put("message", "搜索失败");
                    mSearchCallback.invoke(result);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    @Override
    public void onPoiItemSearched(com.amap.api.services.core.PoiItem poiItem, int i) {
        // 不需要实现
    }
}

iOS 原生模块示例

创建 MyMapModule.m 文件:

#import <Foundation/Foundation.h>
#import <AMapSearchKit/AMapSearchKit.h>

#import <DCUniverse/DCUniJSCallback.h>
#import <DCUniverse/DCUniModule.h>

@interface MyMapModule : DCUniModule <AMapSearchDelegate>

@property (nonatomic, strong) AMapSearchAPI *searchAPI;
@property (nonatomic, strong) DCUniJSCallback searchCallback;

@end

@implementation MyMapModule

- (instancetype)init {
    self = [super init];
    if (self) {
        self.searchAPI = [[AMapSearchAPI alloc] init];
        self.searchAPI.delegate = self;
    }
    return self;
}

// 注册方法
UNI_EXPORT_METHOD(@selector(searchNearby:longitude:keyword:callback:))

- (void)searchNearby:(CGFloat)latitude longitude:(CGFloat)longitude keyword:(NSString *)keyword callback:(DCUniJSCallback)callback {
    self.searchCallback = callback;
    
    // 构建 POI 搜索请求
    AMapPOIKeywordsSearchRequest *request = [[AMapPOIKeywordsSearchRequest alloc] init];
    request.keywords = keyword;
    request.location = [AMapGeoPoint locationWithLatitude:latitude longitude:longitude];
    request.radius = 1000;
    request.requireExtension = YES;
    
    // 发起搜索
    [self.searchAPI AMapPOIKeywordsSearch:request];
}

// 实现 AMapSearchDelegate 方法
- (void)onPOISearchDone:(AMapPOISearchResponse *)response {
    if (response.pois.count > 0) {
        NSMutableArray *resultArray = [NSMutableArray array];
        for (AMapPOI *poi in response.pois) {
            NSMutableDictionary *poiDict = [NSMutableDictionary dictionary];
            poiDict[@"title"] = poi.name;
            poiDict[@"latitude"] = @(poi.location.latitude);
            poiDict[@"longitude"] = @(poi.location.longitude);
            poiDict[@"address"] = poi.address;
            [resultArray addObject:poiDict];
        }
        
        NSDictionary *result = @{
            @"success": @YES,
            @"data": resultArray
        };
        
        if (self.searchCallback) {
            self.searchCallback(result);
        }
    } else {
        NSDictionary *result = @{
            @"success": @NO,
            @"message": @"搜索失败"
        };
        
        if (self.searchCallback) {
            self.searchCallback(result);
        }
    }
}

- (void)AMapSearchRequest:(id)request didFailWithError:(NSError *)error {
    NSDictionary *result = @{
        @"success": @NO,
        @"message": error.localizedDescription
    };
    
    if (self.searchCallback) {
        self.searchCallback(result);
    }
}

@end
  1. 调用原生模块
// 调用自定义地图模块
const myMapModule = uni.requireNativePlugin('MyMapModule');

// 搜索附近 POI
myMapModule.searchNearby({
  latitude: this.latitude,
  longitude: this.longitude,
  keyword: '餐厅'
}, (res) => {
  if (res.success) {
    console.log('搜索成功:', res.data);
    // 处理搜索结果
    this.markers = res.data.map((item, index) => ({
      id: index + 1,
      latitude: item.latitude,
      longitude: item.longitude,
      title: item.title
    }));
  } else {
    console.log('搜索失败:', res.message);
  }
});

常见问题与解决方案

1. 原生插件导入失败

问题:导入原生插件失败,无法在代码中调用。

解决方案

  • 检查插件是否与当前 uni-app 版本兼容
  • 检查 manifest.json 中的插件配置是否正确
  • 确保插件的依赖项已正确安装
  • 对于本地插件,检查插件路径是否正确

2. 原生代码编译失败

问题:混写原生代码时,编译失败。

解决方案

  • 检查原生代码语法是否正确
  • 确保已添加必要的依赖库
  • 检查 SDK 版本是否与项目兼容
  • 对于 Android,检查 Gradle 配置是否正确
  • 对于 iOS,检查 Xcode 配置是否正确

3. SDK 集成失败

问题:集成第三方 SDK 时,无法正常使用。

解决方案

  • 检查 SDK 的 AppKey 和 Secret 是否正确配置
  • 确保已添加必要的权限配置
  • 检查 SDK 的初始化代码是否正确
  • 对于需要签名的 SDK,确保应用签名正确
  • 查看 SDK 的日志,定位具体错误

4. 原生模块调用失败

问题:调用自定义原生模块时,无法正常执行。

解决方案

  • 检查原生模块的注册是否正确
  • 确保调用的方法名与原生模块中定义的方法名一致
  • 检查参数类型是否匹配
  • 查看控制台日志,定位具体错误
  • 对于异步操作,确保回调函数正确处理

学习总结

本章节详细介绍了 uni-app 中的原生能力集成功能,包括原生插件使用、原生代码混写、SDK 集成等核心知识点,并通过实用案例帮助开发者掌握原生能力的集成方法。通过本章节的学习,开发者应该能够:

  1. 了解 uni-app 原生能力集成的基本概念和原理
  2. 掌握原生插件的使用方法
  3. 学会混写原生代码实现高级功能
  4. 集成第三方 SDK 扩展应用能力
  5. 开发自定义原生模块满足特定需求

原生能力集成是 uni-app 中实现高级功能的重要手段,它允许开发者在 uni-app 中使用原生平台的能力,从而实现更多高级功能。开发者应该根据应用的实际需求,合理使用原生能力集成,在保证开发效率的同时,实现最佳的应用效果。

练习与思考

  1. 实现一个地图应用,集成高德地图 SDK,实现附近 POI 搜索功能。
  2. 开发一个自定义原生模块,实现 uni-app 未提供的原生能力。
  3. 集成第三方社交 SDK,实现微信登录和 QQ 登录功能。
  4. 分析一个复杂的原生能力需求,设计集成方案。
  5. 思考如何平衡原生能力集成和跨平台开发的关系。
« 上一篇 uni-app 条件编译 下一篇 » uni-app 性能优化