上一篇给的示例确实太简略了,连依赖怎么配、类型怎么转换、回调怎么写都没交代清楚。这里用一个完整可跑通的示例重新来:获取 Android/iOS 设备的电量水平。
选这个场景的原因:它足够简单,但覆盖了 uts 插件开发的核心要点------原生 API 调用、数据类型转换、异步回调设计、config.json 配置。
插件目录结构
先在项目的 uni_modules 下新建插件,命名为 uts-getbatteryinfo。完整目录如下:
text
bash
uni_modules/uts-getbatteryinfo/
├── utssdk/
│ ├── app-android/
│ │ ├── index.uts # Android 实现
│ │ └── config.json # Android 原生配置(可选)
│ ├── app-ios/
│ │ ├── index.uts # iOS 实现
│ │ └── config.json # iOS 原生配置(可选)
│ └── interface.uts # 跨端接口类型定义
└── package.json # 插件清单
关键点:interface.uts 定义的是跨端统一的类型契约 ,app-android 和 app-ios 各自实现。前端只 import 接口,不关心底层是 Kotlin 还是 Swift。
第一步:定义接口类型
utssdk/interface.uts:
uts
typescript
// 成功回调的数据结构
export type GetBatteryInfoSuccess = {
errCode: number
errMsg: string
level: number // 电量百分比,0-100
isCharging: boolean // 是否正在充电
}
// 失败回调
export type GetBatteryInfoFail = {
errCode: number
errMsg: string
}
// 调用参数
export type GetBatteryInfoOptions = {
success?: (res: GetBatteryInfoSuccess) => void
fail?: (res: GetBatteryInfoFail) => void
complete?: (res: any) => void
}
// 对外暴露的函数签名
export type GetBatteryInfo = (options: GetBatteryInfoOptions) => void
这里用的是回调风格(success/fail/complete) ,而不是 Promise。uni-app 的传统 API 都是这个模式,保持一致,调用方不用额外学习。
第二步:Android 实现
utssdk/app-android/index.uts:
uts
javascript
import Context from "android.content.Context"
import BatteryManager from "android.os.BatteryManager"
import { UTSAndroid } from "io.dcloud.uts"
import {
GetBatteryInfo,
GetBatteryInfoOptions
} from "../interface.uts"
export const getBatteryInfo: GetBatteryInfo = function (options: GetBatteryInfoOptions) {
// 获取 Android 应用上下文
const context = UTSAndroid.getAppContext()
// 上下文为空时直接走 fail 回调
if (context == null) {
const res = {
errCode: 1001,
errMsg: "getBatteryInfo:fail getAppContext is null"
}
options.fail?.(res)
options.complete?.(res)
return
}
// 获取电池管理服务
const manager = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
// 读取电量百分比(0-100)
// getIntProperty 返回的是 Int,UTS 里 Number 可以覆盖 Int 场景
const level = manager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
// 判断是否在充电
const isCharging = manager.isCharging()
const res = {
errCode: 0,
errMsg: "getBatteryInfo:ok",
level: level,
isCharging: isCharging
}
options.success?.(res)
options.complete?.(res)
}
几个值得注意的地方:
UTSAndroid.getAppContext() 是 uts 内置的 Android 上下文获取方式,不需要自己传 Context。as BatteryManager 是类型断言,因为 getSystemService 返回的是 Object,编译器需要知道具体类型才能调 getIntProperty。
关于 Int 和 Number:UTS 本身只有 Number,但 Android 原生 API 的 getIntProperty 返回 Int。当原生 API 明确要求或返回原生类型时,UTS 允许直接使用 Int ,编译器会做适配。这里 level 拿到的是 Int,赋给 Number 类型的字段是安全的。
第三步:iOS 实现
utssdk/app-ios/index.uts:
uts
javascript
import { UIDevice } from "UIKit"
import { GetBatteryInfo, GetBatteryInfoOptions } from "../interface.uts"
export const getBatteryInfo: GetBatteryInfo = function (options: GetBatteryInfoOptions) {
// 开启电池监控,否则 batteryLevel 返回 -1
UIDevice.current.isBatteryMonitoringEnabled = true
const level = UIDevice.current.batteryLevel // 0.0 ~ 1.0
const state = UIDevice.current.batteryState
// batteryLevel 为 -1 表示监控未开启或未知
if (level < 0) {
const res = {
errCode: 1002,
errMsg: "getBatteryInfo:fail battery level unavailable"
}
options.fail?.(res)
options.complete?.(res)
return
}
// 转换为百分比,取整
const levelPercent = Math.round(Number.from(level) * 100)
// 充电状态判断
const isCharging = state == UIDeviceBatteryState.charging ||
state == UIDeviceBatteryState.full
const res = {
errCode: 0,
errMsg: "getBatteryInfo:ok",
level: levelPercent,
isCharging: isCharging
}
options.success?.(res)
options.complete?.(res)
}
iOS 侧的关键差异:
batteryLevel 返回的是 Float(0.0 到 1.0),需要手动转成百分比。Number.from(level) 把原生 Float 转成 UTS 的 Number,这是跨类型转换的标准做法。
isBatteryMonitoringEnabled 必须设为 true,否则 batteryLevel 返回 -1。这是个容易踩的坑。
充电状态用的是 UIDeviceBatteryState 枚举,UTS 可以直接引用。
第四步:前端调用
在页面里:
javascript
javascript
import { getBatteryInfo } from '@/uni_modules/uts-getbatteryinfo'
getBatteryInfo({
success(res) {
console.log('电量:', res.level + '%')
console.log('充电中:', res.isCharging)
uni.showToast({
title: `电量 ${res.level}%`,
icon: 'none'
})
},
fail(err) {
console.error('获取失败:', err.errMsg)
}
})
注意:直接 import 函数名调用 ,不是 uni.requireNativePlugin。这是 uts 插件和旧原生语言插件最直观的区别------调用方式就是普通的 ES module import,有类型提示,支持摇树优化。
第五步:如果要引入三方库
假设你想用某个 Java 工具库(比如 Hutool 的 StrUtil)来做字符串处理,需要在 app-android/config.json 里声明依赖:
json
json
{
"minSdkVersion": 21,
"dependencies": [
"cn.hutool:hutool-all:5.8.26"
]
}
然后在 index.uts 里直接 import:
uts
typescript
import StrUtil from 'cn.hutool.core.util.StrUtil'
export const isNotBlank = function (str: string): boolean {
return StrUtil.isNotBlank(str)
}
HBuilderX 会在真机运行时通过 Gradle 自动下载依赖。
什么时候需要自定义基座
只要你的 uts 插件涉及原生依赖、config.json 配置、或隐私合规相关代码,就必须制作自定义调试基座 。标准基座只包含官方内置的原生能力,不包含你新写的插件。
流程:HBuilderX → 运行 → 运行到手机或模拟器 → 制作自定义调试基座 → 云打包完成后,再用自定义基座运行。
这个示例覆盖了什么
回头看,这个电量插件虽然简单,但把 uts 插件开发的核心模式都走了一遍:interface 定义契约 → 各平台原生实现 → 类型转换处理 → 回调风格 API 设计 → config.json 依赖声明 → import 调用。
换一个更复杂的场景------比如封装一个蓝牙扫描、或者接入一个音视频 SDK------骨架是完全一样的,只是原生 API 调用和类型处理更多而已。