uni-app Android 原生插件开发

核心知识点

1. Android 开发环境搭建

1.1 开发工具准备

  • Android Studio:官方推荐的 Android 开发 IDE,下载地址:https://developer.android.com/studio
  • JDK:Java 开发工具包,推荐使用 JDK 8 或以上版本
  • Gradle:Android 构建工具,Android Studio 会自动安装
  • SDK:Android SDK,包括不同版本的 Android 平台

1.2 环境配置

  • 配置 JAVA_HOME:设置 JDK 安装路径
  • 配置 ANDROID_HOME:设置 Android SDK 安装路径
  • 配置 Gradle 镜像:使用国内镜像加速构建

2. Android 插件项目结构

2.1 基本目录结构

  • 标准目录结构
    android/
      ├── build.gradle        # 项目构建配置
      ├── gradle.properties   # Gradle 属性配置
      ├── gradlew             # Gradle 包装脚本
      ├── gradlew.bat         # Windows 下的 Gradle 包装脚本
      ├── local.properties    # 本地属性配置
      ├── settings.gradle     # 项目设置
      └── src/
          └── main/
              ├── AndroidManifest.xml  # 应用清单文件
              ├── java/                # Java 源代码目录
              │   └── com/
              │       └── example/
              │           └── plugin/
              │               ├── Module.java  # Module 插件实现
              │               └── Component.java  # Component 插件实现
              └── res/                # 资源目录
                  ├── drawable/        # 图片资源
                  ├── layout/          # 布局文件
                  └── values/          # 字符串、样式等资源

2.2 关键文件说明

  • build.gradle:定义插件的依赖、构建配置等
  • AndroidManifest.xml:声明插件的权限、组件等
  • Module.java:实现 Module 接口,提供 JavaScript 调用的方法
  • Component.java:实现 Component 接口,提供自定义 UI 组件

3. Android 插件开发基础

3.1 Module 插件开发

  • 继承 UniModule:自定义 Module 类需要继承 io.dcloud.feature.uniapp.common.UniModule
  • 导出方法:使用 @UniJSMethod 注解导出方法,供 JavaScript 调用
  • 异步回调:使用 UniJSCallback 实现异步回调
  • 上下文获取:通过 mWXSDKInstance.getContext() 获取 Android 上下文

3.2 Component 插件开发

  • 继承 UniComponent:自定义 Component 类需要继承 io.dcloud.feature.uniapp.ui.component.UniComponent
  • 视图创建:在 createView 方法中创建 Android 视图
  • 属性设置:在 updateAttrs 方法中处理组件属性更新
  • 事件处理:使用 fireEvent 方法触发自定义事件

3.3 权限管理

  • 声明权限:在 AndroidManifest.xml 中声明插件需要的权限
  • 运行时权限:对于危险权限,需要在运行时动态申请
  • 权限检查:在使用需要权限的功能前,检查是否已获得权限

4. 插件打包与集成

4.1 插件打包

  • 构建 AAR:在 Android Studio 中构建 AAR 文件
  • 配置 package.json:在插件根目录创建 package.json 文件,配置插件信息
  • 创建 zip 包:将 AAR 文件和 package.json 等文件打包成 zip 文件

4.2 插件集成

  • 本地集成:将插件 zip 包导入到 uni-app 项目中
  • 云端集成:将插件发布到 uni-app 插件市场,然后在项目中引用
  • 配置 manifest.json:在 uni-app 项目的 manifest.json 文件中配置插件

5. 常见问题与解决方案

5.1 依赖冲突

  • 原因:插件依赖的库与 uni-app 或其他插件依赖的库版本冲突
  • 解决方案:使用 exclude 排除冲突的依赖,或使用 force 强制使用特定版本

5.2 混淆问题

  • 原因:代码混淆导致插件的类或方法被重命名,无法被 uni-app 调用
  • 解决方案:在 proguard-rules.pro 文件中添加混淆规则,保留插件的类和方法

5.3 版本兼容

  • 原因:不同版本的 Android 系统 API 不同,导致插件在某些设备上无法正常工作
  • 解决方案:使用 Build.VERSION.SDK_INT 检查 Android 版本,根据版本使用不同的 API

实用案例分析

案例一:开发一个 Android 原生 Module 插件

问题描述

需要开发一个 Android 原生插件,提供震动功能。

解决方案

创建一个 Android Module 插件,实现震动功能的方法。

代码示例

  1. 创建插件目录结构
vibrate-plugin/
  ├── android/
  │   ├── build.gradle
  │   └── src/
  │       └── main/
  │           ├── AndroidManifest.xml
  │           └── java/
  │               └── com/
  │                   └── example/
  │                       └── vibrate/
  │                           └── VibrateModule.java
  └── package.json
  1. 配置 build.gradle
apply plugin: 'com.android.library'

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.3.0'
    // 引入 uni-app 插件开发库
    implementation 'com.uniplugin:uniplugin-module:1.0.0'
}
  1. 配置 AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.vibrate">

    <!-- 声明震动权限 -->
    <uses-permission android:name="android.permission.VIBRATE" />

</manifest>
  1. 实现 VibrateModule.java
package com.example.vibrate;

import android.content.Context;
import android.os.Vibrator;
import io.dcloud.feature.uniapp.common.UniModule;
import io.dcloud.feature.uniapp.bridge.UniJSCallback;

public class VibrateModule extends UniModule {

    // 震动方法
    @UniJSMethod
    public void vibrate(int duration, UniJSCallback callback) {
        Context context = mWXSDKInstance.getContext();
        Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
        
        if (vibrator != null) {
            // 检查设备是否支持震动
            if (vibrator.hasVibrator()) {
                // 震动指定时长
                vibrator.vibrate(duration);
                
                if (callback != null) {
                    callback.invoke("success");
                }
            } else {
                if (callback != null) {
                    callback.invoke("device not support vibrate");
                }
            }
        } else {
            if (callback != null) {
                callback.invoke("vibrator service not available");
            }
        }
    }

    // 自定义震动模式
    @UniJSMethod
    public void vibratePattern(int[] pattern, int repeat, UniJSCallback callback) {
        Context context = mWXSDKInstance.getContext();
        Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
        
        if (vibrator != null) {
            // 检查设备是否支持震动
            if (vibrator.hasVibrator()) {
                // 按照指定模式震动
                // pattern: 震动和暂停的时间序列,单位为毫秒
                // repeat: 重复次数,-1 表示不重复
                vibrator.vibrate(pattern, repeat);
                
                if (callback != null) {
                    callback.invoke("success");
                }
            } else {
                if (callback != null) {
                    callback.invoke("device not support vibrate");
                }
            }
        } else {
            if (callback != null) {
                callback.invoke("vibrator service not available");
            }
        }
    }

    // 取消震动
    @UniJSMethod
    public void cancelVibrate(UniJSCallback callback) {
        Context context = mWXSDKInstance.getContext();
        Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
        
        if (vibrator != null) {
            vibrator.cancel();
            
            if (callback != null) {
                callback.invoke("success");
            }
        } else {
            if (callback != null) {
                callback.invoke("vibrator service not available");
            }
        }
    }
}
  1. 配置 package.json
{
  "name": "vibrate-plugin",
  "version": "1.0.0",
  "description": "提供设备震动功能的 Android 原生插件",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "uni-app",
    "plugin",
    "android",
    "vibrate"
  ],
  "author": "example",
  "license": "MIT",
  "uni-app": {
    "plugins": {
      "Vibrate": {
        "version": "1.0.0",
        "provider": "com.example.vibrate"
      }
    }
  },
  "dcloudPlugins": {
    "android": {
      "plugins": [
        {
          "type": "module",
          "name": "Vibrate",
          "class": "com.example.vibrate.VibrateModule"
        }
      ]
    }
  }
}
  1. 创建 index.js
const vibrate = uni.requireNativePlugin('Vibrate');

export default {
  // 震动指定时长
  vibrate(duration, callback) {
    vibrate.vibrate(duration, callback);
  },
  // 自定义震动模式
  vibratePattern(pattern, repeat, callback) {
    vibrate.vibratePattern(pattern, repeat, callback);
  },
  // 取消震动
  cancelVibrate(callback) {
    vibrate.cancelVibrate(callback);
  }
};

案例二:开发一个 Android 原生 Component 插件

问题描述

需要开发一个 Android 原生插件,提供一个自定义的圆形进度条组件。

解决方案

创建一个 Android Component 插件,实现一个圆形进度条组件。

代码示例

  1. 创建插件目录结构
circle-progress-plugin/
  ├── android/
  │   ├── build.gradle
  │   └── src/
  │       └── main/
  │           ├── AndroidManifest.xml
  │           └── java/
  │               └── com/
  │                   └── example/
  │                       └── circleprogress/
  │                           └── CircleProgressComponent.java
  └── package.json
  1. 配置 build.gradle
apply plugin: 'com.android.library'

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.3.0'
    // 引入 uni-app 插件开发库
    implementation 'com.uniplugin:uniplugin-ui:1.0.0'
}
  1. 实现 CircleProgressComponent.java
package com.example.circleprogress;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.view.View;
import io.dcloud.feature.uniapp.ui.component.UniComponent;
import io.dcloud.feature.uniapp.ui.component.UniVContainer;

public class CircleProgressComponent extends UniComponent<View> {

    private CircleProgressView mCircleProgressView;
    private float mProgress = 0;
    private int mProgressColor = Color.BLUE;
    private int mBackgroundColor = Color.GRAY;
    private float mStrokeWidth = 10;
    private float mRadius = 50;

    public CircleProgressComponent(UniVContainer uniVContainer, Context context) {
        super(uniVContainer, context);
    }

    @Override
    protected View createView(Context context) {
        mCircleProgressView = new CircleProgressView(context);
        updateProgress();
        return mCircleProgressView;
    }

    @Override
    public void updateAttrs(Object attrs) {
        super.updateAttrs(attrs);
        if (attrs instanceof org.json.JSONObject) {
            org.json.JSONObject jsonObject = (org.json.JSONObject) attrs;
            try {
                // 处理进度属性
                if (jsonObject.has("progress")) {
                    mProgress = (float) jsonObject.getDouble("progress");
                    updateProgress();
                }
                // 处理进度颜色属性
                if (jsonObject.has("progressColor")) {
                    String colorStr = jsonObject.getString("progressColor");
                    mProgressColor = Color.parseColor(colorStr);
                    updateProgress();
                }
                // 处理背景颜色属性
                if (jsonObject.has("backgroundColor")) {
                    String colorStr = jsonObject.getString("backgroundColor");
                    mBackgroundColor = Color.parseColor(colorStr);
                    updateProgress();
                }
                // 处理线宽属性
                if (jsonObject.has("strokeWidth")) {
                    mStrokeWidth = (float) jsonObject.getDouble("strokeWidth");
                    updateProgress();
                }
                // 处理半径属性
                if (jsonObject.has("radius")) {
                    mRadius = (float) jsonObject.getDouble("radius");
                    updateProgress();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void updateProgress() {
        if (mCircleProgressView != null) {
            mCircleProgressView.setProgress(mProgress);
            mCircleProgressView.setProgressColor(mProgressColor);
            mCircleProgressView.setBackgroundColor(mBackgroundColor);
            mCircleProgressView.setStrokeWidth(mStrokeWidth);
            mCircleProgressView.setRadius(mRadius);
            mCircleProgressView.invalidate();
        }
    }

    // 自定义圆形进度条视图
    private class CircleProgressView extends View {

        private Paint mProgressPaint;
        private Paint mBackgroundPaint;
        private RectF mRectF;

        public CircleProgressView(Context context) {
            super(context);
            init();
        }

        private void init() {
            // 初始化进度画笔
            mProgressPaint = new Paint();
            mProgressPaint.setAntiAlias(true);
            mProgressPaint.setStyle(Paint.Style.STROKE);
            mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
            
            // 初始化背景画笔
            mBackgroundPaint = new Paint();
            mBackgroundPaint.setAntiAlias(true);
            mBackgroundPaint.setStyle(Paint.Style.STROKE);
            mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
            
            // 初始化矩形
            mRectF = new RectF();
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            
            int width = getWidth();
            int height = getHeight();
            int centerX = width / 2;
            int centerY = height / 2;
            
            // 计算实际半径
            float actualRadius = Math.min(centerX, centerY) - mStrokeWidth / 2;
            
            // 设置矩形范围
            mRectF.set(centerX - actualRadius, centerY - actualRadius, 
                      centerX + actualRadius, centerY + actualRadius);
            
            // 绘制背景圆环
            mBackgroundPaint.setColor(mBackgroundColor);
            mBackgroundPaint.setStrokeWidth(mStrokeWidth);
            canvas.drawCircle(centerX, centerY, actualRadius, mBackgroundPaint);
            
            // 绘制进度圆环
            mProgressPaint.setColor(mProgressColor);
            mProgressPaint.setStrokeWidth(mStrokeWidth);
            float sweepAngle = 360 * mProgress / 100;
            canvas.drawArc(mRectF, -90, sweepAngle, false, mProgressPaint);
        }

        public void setProgress(float progress) {
            mProgress = progress;
        }

        public void setProgressColor(int color) {
            mProgressColor = color;
        }

        public void setBackgroundColor(int color) {
            mBackgroundColor = color;
        }

        public void setStrokeWidth(float width) {
            mStrokeWidth = width;
        }

        public void setRadius(float radius) {
            mRadius = radius;
        }
    }
}
  1. 配置 package.json
{
  "name": "circle-progress-plugin",
  "version": "1.0.0",
  "description": "提供圆形进度条组件的 Android 原生插件",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "uni-app",
    "plugin",
    "android",
    "circle-progress"
  ],
  "author": "example",
  "license": "MIT",
  "uni-app": {
    "plugins": {
      "CircleProgress": {
        "version": "1.0.0",
        "provider": "com.example.circleprogress"
      }
    }
  },
  "dcloudPlugins": {
    "android": {
      "plugins": [
        {
          "type": "component",
          "name": "circle-progress",
          "class": "com.example.circleprogress.CircleProgressComponent"
        }
      ]
    }
  }
}
  1. 创建 index.js
export default {
  name: 'circle-progress',
  props: {
    progress: {
      type: Number,
      default: 0
    },
    progressColor: {
      type: String,
      default: '#007AFF'
    },
    backgroundColor: {
      type: String,
      default: '#E0E0E0'
    },
    strokeWidth: {
      type: Number,
      default: 10
    },
    radius: {
      type: Number,
      default: 50
    }
  }
};

案例三:在 uni-app 中使用 Android 原生插件

问题描述

需要在 uni-app 应用中使用刚才开发的震动插件和圆形进度条插件。

解决方案

在 uni-app 项目中引入并使用 Android 原生插件。

代码示例

  1. 在 manifest.json 中配置插件
{
  "name": "uni-app-demo",
  "version": "1.0.0",
  "description": "uni-app 演示应用",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "uni-app"
  ],
  "author": "example",
  "license": "MIT",
  "dependencies": {
    "@dcloudio/uni-app": "^2.0.0"
  },
  "uni-app": {
    "plugins": {
      "Vibrate": {
        "version": "1.0.0",
        "provider": "com.example.vibrate"
      },
      "CircleProgress": {
        "version": "1.0.0",
        "provider": "com.example.circleprogress"
      }
    }
  }
}
  1. 在页面中使用震动插件
<template>
  <view class="vibrate-demo">
    <h2>震动功能演示</h2>
    <button @click="vibrateShort">短震动 (100ms)</button>
    <button @click="vibrateLong">长震动 (500ms)</button>
    <button @click="vibratePattern">自定义震动模式</button>
    <button @click="cancelVibrate">取消震动</button>
  </view>
</template>

<script>
import vibratePlugin from '@/plugins/vibrate-plugin/index.js';

export default {
  methods: {
    vibrateShort() {
      vibratePlugin.vibrate(100, (res) => {
        console.log('震动结果:', res);
        uni.showToast({ title: res, icon: 'none' });
      });
    },
    vibrateLong() {
      vibratePlugin.vibrate(500, (res) => {
        console.log('震动结果:', res);
        uni.showToast({ title: res, icon: 'none' });
      });
    },
    vibratePattern() {
      // 震动模式:[震动时长, 暂停时长, 震动时长, 暂停时长, ...]
      const pattern = [100, 100, 200, 100, 300];
      // 重复次数:-1 表示不重复
      const repeat = -1;
      vibratePlugin.vibratePattern(pattern, repeat, (res) => {
        console.log('震动结果:', res);
        uni.showToast({ title: res, icon: 'none' });
      });
    },
    cancelVibrate() {
      vibratePlugin.cancelVibrate((res) => {
        console.log('取消震动结果:', res);
        uni.showToast({ title: res, icon: 'none' });
      });
    }
  }
};
</script>

<style scoped>
.vibrate-demo {
  padding: 20rpx;
}

h2 {
  margin-bottom: 20rpx;
  font-size: 36rpx;
  font-weight: bold;
}

button {
  width: 100%;
  padding: 20rpx;
  margin-bottom: 20rpx;
  background-color: #1890ff;
  color: #fff;
  border: none;
  border-radius: 8rpx;
}
</style>
  1. 在页面中使用圆形进度条插件
<template>
  <view class="circle-progress-demo">
    <h2>圆形进度条演示</h2>
    <view class="progress-container">
      <circle-progress 
        :progress="progress" 
        :progressColor="progressColor" 
        :backgroundColor="backgroundColor" 
        :strokeWidth="strokeWidth" 
        :radius="radius"
      />
    </view>
    <view class="controls">
      <text>进度: {{ progress }}%</text>
      <slider @change="onProgressChange" :value="progress" :min="0" :max="100" />
      
      <text>线宽: {{ strokeWidth }}</text>
      <slider @change="onStrokeWidthChange" :value="strokeWidth" :min="1" :max="30" />
      
      <text>半径: {{ radius }}</text>
      <slider @change="onRadiusChange" :value="radius" :min="20" :max="100" />
      
      <view class="color-pickers">
        <text>进度颜色:</text>
        <view class="color-buttons">
          <button 
            v-for="color in colors" 
            :key="color"
            :style="{ backgroundColor: color }"
            @click="progressColor = color"
          ></button>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      progress: 50,
      progressColor: '#007AFF',
      backgroundColor: '#E0E0E0',
      strokeWidth: 10,
      radius: 60,
      colors: ['#007AFF', '#FF3B30', '#34C759', '#FF9500', '#AF52DE', '#FF2D55']
    };
  },
  methods: {
    onProgressChange(e) {
      this.progress = e.detail.value;
    },
    onStrokeWidthChange(e) {
      this.strokeWidth = e.detail.value;
    },
    onRadiusChange(e) {
      this.radius = e.detail.value;
    }
  }
};
</script>

<style scoped>
.circle-progress-demo {
  padding: 20rpx;
}

h2 {
  margin-bottom: 20rpx;
  font-size: 36rpx;
  font-weight: bold;
}

.progress-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 300rpx;
  margin-bottom: 40rpx;
}

.controls {
  display: flex;
  flex-direction: column;
  gap: 20rpx;
}

slider {
  width: 100%;
}

.color-pickers {
  margin-top: 20rpx;
}

.color-buttons {
  display: flex;
  gap: 10rpx;
  margin-top: 10rpx;
}

.color-buttons button {
  width: 60rpx;
  height: 60rpx;
  border-radius: 50%;
  border: 1rpx solid #E0E0E0;
}
</style>

学习目标

  1. 了解 Android 开发环境的搭建和配置
  2. 掌握 uni-app Android 原生插件的目录结构和配置方法
  3. 学会开发 Android Module 插件和 Component 插件
  4. 理解 Android 原生插件的生命周期和工作原理
  5. 掌握插件的打包、集成和使用方法
  6. 能够解决 Android 原生插件开发中遇到的常见问题

Android 原生插件开发最佳实践

  1. 遵循 Android 开发规范:按照 Google 推荐的 Android 开发规范编写代码
  2. 注重代码质量:使用清晰的命名、适当的注释、合理的代码结构
  3. 处理异常情况:合理处理各种异常,提供友好的错误信息
  4. 优化性能:注意内存管理,避免内存泄漏,优化 UI 渲染性能
  5. 测试兼容性:在不同版本的 Android 设备上测试插件
  6. 提供详细文档:为插件提供清晰的使用文档和示例代码

总结

Android 原生插件开发是 uni-app 应用扩展功能的重要手段,通过开发 Android 原生插件,可以访问 Android 设备的底层 API,实现更复杂的功能,提升应用性能。

在本教程中,我们学习了 Android 开发环境的搭建、Android 插件项目结构、Module 和 Component 插件的开发方法、插件打包与集成等核心知识点。通过两个实用案例,我们了解了如何开发一个震动功能的 Module 插件和一个圆形进度条的 Component 插件,以及如何在 uni-app 应用中使用这些插件。

要成为一名优秀的 uni-app Android 原生插件开发者,需要掌握以下技能:

  1. 熟悉 Android 原生开发,包括 Java 语言、Android SDK、Android Studio 等
  2. 了解 uni-app 插件开发规范和 API
  3. 具备良好的 API 设计能力
  4. 注重代码质量和性能优化
  5. 具备解决问题的能力

通过不断学习和实践,你可以开发出功能强大、性能优异的 Android 原生插件,为 uni-app 生态系统做出贡献。

« 上一篇 uni-app 原生插件开发基础 下一篇 » uni-app iOS 原生插件开发