uniapp UTS插件开发实录:把手机震动与加速度计打包成跨端API

2026-06-19 0 352

最近一个项目中需要在App端做摇一摇交互,同时要求支持自定义震动模式。起初找了几个现成的插件,要么只支持安卓,要么没有震动时长控制,而且代码混杂着原生语言,改一个参数都得重新编译。后来趁着uniapp新推出UTS(Universal TypeScript)插件的机会,干脆自己写了一个插件——用同一套TypeScript风格的语法,直接调起安卓和iOS底层的振动器与加速度计,运行效率跟原生一样,还能完美融入uniapp的跨端工程。

这篇文章就把打包过程中踩过的坑和最终落地的完整代码整理出来,让你读完就能上手开发自己的原生插件

UTS插件是什么,能做什么

UTS全称是“统一类型系统”,它是DCloud在uniapp中推出的一个插件开发方案。简单说,就是用类似TypeScript的语法来写原生代码,编译后直接生成安卓的Kotlin/Java或者iOS的Swift代码,没有额外的桥接层,所以性能等同于原生模块。你可以在UTS插件里调用系统的震动服务、传感器、文件系统,还能集成第三方原生SDK。

对我们来说,UTS最大的好处是学习成本低——不需要重新学Kotlin或Swift,只要会TypeScript基本语法,再了解一点平台API函数就能开写。而且同一个插件可以同时编译出安卓和iOS两个版本,省去双份代码的维护成本。

环境准备与插件目录结构

首先确认HBuilderX版本在3.7.0以上。然后在项目根目录下右键新建“UTS插件”,选择模板“原生能力扩展”,取名为sensor-utils。创建完成后会得到如下结构:

uni_modules/sensor-utils/
├── utssdk/
│   ├── app-android/
│   │   └── index.uts        // 安卓侧实现
│   ├── app-ios/
│   │   └── index.uts        // iOS侧实现
│   └── interface.uts        // 公共接口定义(可选)
├── package.json
└── ...

公共的类型声明放在interface.uts里,两边共享。接口里定义震动和加速度计的方法签名,这样我们在uniapp页面里获得的自动补全就会很准确。

第一步:定义公共接口

打开utssdk/interface.uts,写入要暴露给JavaScript的方法:

export class VibrationUtils {
  /**
   * 触发指定时长的震动(毫秒)
   */
  static vibrate(duration: number): void;
  /**
   * 触发预设的震动模式(数组元素为振动和停歇交替的时长)
   */
  static vibratePattern(pattern: number[], repeat: number): void;
}

export type AccelerometerData = {
  x: number;
  y: number;
  z: number;
  timestamp: number;
};

export type AccelerometerCallback = (data: AccelerometerData) => void;

export class AccelerometerUtils {
  /**
   * 开始加速度计监听
   */
  static startListening(callback: AccelerometerCallback, interval: string): void;
  /**
   * 停止监听
   */
  static stopListening(): void;
}

这里我们将震动和加速度计分别封装成两个静态类,方便在页面里直接VibrationUtils.vibrate(500),非常直觉。

第二步:安卓侧实现

安卓的震动服务来自Vibrator类,加速度计使用SensorManager。在app-android/index.uts中填充:

import { Context, Vibrator, SensorManager, Sensor, SensorEvent, SystemClock } from 'android';
import { VibrationUtils, AccelerometerUtils, AccelerometerCallback } from '../interface.uts';

let vibrator: Vibrator | null = null;
let sensorManager: SensorManager | null = null;
let accelerometer: Sensor | null = null;
let currentCallback: AccelerometerCallback | null = null;

// 振动实现
VibrationUtils.vibrate = (duration: number) => {
  if (vibrator == null) {
    vibrator = UTSAndroid.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator;
  }
  vibrator!.vibrate(duration);
};

VibrationUtils.vibratePattern = (pattern: number[], repeat: number) => {
  if (vibrator == null) {
    vibrator = UTSAndroid.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator;
  }
  const longArray: number[] = pattern;
  vibrator!.vibrate(longArray, repeat);
};

// 加速度计实现
AccelerometerUtils.startListening = (callback: AccelerometerCallback, interval: string) => {
  if (sensorManager == null) {
    sensorManager = UTSAndroid.getSystemService(Context.SENSOR_SERVICE) as SensorManager;
    accelerometer = sensorManager!.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
  }
  if (accelerometer == null) {
    console.error('设备不支持加速度计');
    return;
  }
  currentCallback = callback;
  const samplingPeriodUs = interval === 'game' ? SensorManager.SENSOR_DELAY_GAME : SensorManager.SENSOR_DELAY_UI;
  sensorManager!.registerListener({
    onSensorChanged(event: SensorEvent) {
      if (currentCallback != null) {
        currentCallback({
          x: event.values[0],
          y: event.values[1],
          z: event.values[2],
          timestamp: SystemClock.uptimeMillis()
        });
      }
    },
    onAccuracyChanged(sensor: Sensor, accuracy: number) {}
  }, accelerometer, samplingPeriodUs);
};

AccelerometerUtils.stopListening = () => {
  if (sensorManager != null && accelerometer != null) {
    sensorManager.unregisterListener(accelerometer);
    currentCallback = null;
  }
};

这里的UTSAndroid是UTS环境提供的全局对象,可以直接获取系统服务。SystemClock.uptimeMillis()提供相对开机时间,比Date.now()更准确。

第三步:iOS侧实现

iOS端的震动比较简单,通过UIImpactFeedbackGenerator还可以产生轻中重等不同触感,不过我们这里保持与安卓一致的时长震动,使用AudioServicesPlaySystemSoundTimer来模拟持续震动。加速度计则用CMMotionManager

import { CMMotionManager, NSOperationQueue } from 'ios';
import { VibrationUtils, AccelerometerUtils, AccelerometerCallback } from '../interface.uts';

let motionManager: CMMotionManager | null = null;

// 震动:简易版,复杂模式可用UIFeedbackGenerator
VibrationUtils.vibrate = (duration: number) => {
  // iOS没有简单持续震动API,这里用周期性短震模拟
  // 仅示意,生产环境建议使用 UIImpactFeedbackGenerator
  const timer = new NSTimer.scheduledTimerWithTimeInterval(0.5, true, () => {
    AudioServicesPlaySystemSound(1519); // 轻震
  });
  setTimeout(() => {
    timer.invalidate();
  }, duration);
};

VibrationUtils.vibratePattern = (pattern: number[], repeat: number) => {
  // 按pattern依次播放短震,循环repeat次,实现略
};

// 加速度计
AccelerometerUtils.startListening = (callback: AccelerometerCallback, interval: string) => {
  if (motionManager == null) {
    motionManager = new CMMotionManager();
  }
  if (motionManager!.isAccelerometerAvailable) {
    const updateInterval = interval === 'game' ? 0.02 : 0.1;
    motionManager!.accelerometerUpdateInterval = updateInterval;
    motionManager!.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue, (data, error) => {
      if (data != null) {
        callback({
          x: data.acceleration.x,
          y: data.acceleration.y,
          z: data.acceleration.z,
          timestamp: Date.now()
        });
      }
    });
  }
};

AccelerometerUtils.stopListening = () => {
  motionManager?.stopAccelerometerUpdates();
};

这里模拟震动用了旧式API,如需现代触感可以引入UIKitUISelectionFeedbackGenerator等,但代码量会稍多,核心思路不变。

第四步:在uniapp页面中使用插件

插件编译后,我们在页面里直接导入即可,跟使用JavaScript模块完全一样:

<template>
  <view class="container">
    <button @click="doVibrate">重震一下</button>
    <button @click="startAccel">开始摇一摇监看</button>
    <text>X: {{ accel.x }}, Y: {{ accel.y }}, Z: {{ accel.z }}</text>
  </template>

<script setup>
import { VibrationUtils, AccelerometerUtils } from '@/uni_modules/sensor-utils';
import { reactive } from 'vue';

const accel = reactive({ x: 0, y: 0, z: 0 });
let shakeWatching = false;

function doVibrate() {
  VibrationUtils.vibrate(600); // 震动600毫秒
}

function startAccel() {
  if (shakeWatching) {
    AccelerometerUtils.stopListening();
    shakeWatching = false;
    return;
  }
  AccelerometerUtils.startListening((data) => {
    accel.x = data.x;
    accel.y = data.y;
    accel.z = data.z;
    // 简易摇一摇判断:加速度向量模长超过阈值
    const magnitude = Math.sqrt(data.x ** 2 + data.y ** 2 + data.z ** 2);
    if (magnitude > 15) {
      uni.showToast({ title: '摇一摇触发' });
      // 可以配合震动
      VibrationUtils.vibrate(100);
    }
  }, 'game');
  shakeWatching = true;
}
</script>

上面代码直接拿到了设备传感器数据,并且实现了摇一摇交互。全程没有离开过TypeScript的语法体系,安卓和iOS只是编译目标。

调试与真机测试

在HBuilderX中选择运行到真实iOS或安卓设备,UTS插件会自动编译为对应平台的代码并集成到安装包里。如果编译报错,通常是指定的原生类型名不对,可以在uts-sdk文档中查内置的声明列表。另外注意震动和加速度计的权限:安卓需要VIBRATE权限,HBuilderX默认已添加;iOS的加速度计权限不需要特别授权。

还有一个常见的坑:iOS模拟器上没有加速度计,真机调试时必须选择实体设备。

几种典型的应用扩展

掌握了基础框架后,可以很容易地扩展这个插件:

  • 增加陀螺仪数据,返回{ pitch, roll, yaw }
  • 封装更高阶的触感反馈,利用iOS的UIImpactFeedbackGenerator产生轻、中、重三种力度。
  • 在安卓端读取步数传感器,做计步功能。

所有新增能力仍然写在interface.uts里,然后分别在两端实现,JavaScript调用层完全不变。

为什么用UTS而不是cordova或者webview桥接

之前我们用过cordova插件和webview内置桥,它们的问题在于每一个调用都要序列化参数到字符串再解析,高频传感器数据(每秒几十次回调)性能很差,经常卡断。UTS编译后直接跑在原生线程里,数据是二进制传递,不存在这种瓶颈,这也是加速度计这类场景最适合UTS的原因。

总结

打包这个震动与加速度计插件的过程中,最让我意外的是代码量之少——安卓和iOS两侧加起来都没超过150行,却实现了一个可以直接跨端复用的原生能力模块。UTS把以前必须分开维护的Kotlin和Swift工程,拉进了同一个TypeScript世界里,又保留了原生性能,这几乎是目前跨端插件开发中最省力的方案。

如果你也在项目中碰到“官方API不够用,又不想离开uniapp生态”的时刻,不妨试试UTS,自己动手封装一个插件,那种“写一次代码两端跑”的畅快感,比搜索三天得到的半成品插件要可靠得多。

uniapp UTS插件开发实录:把手机震动与加速度计打包成跨端API
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请联系本站我们会及时删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 uniapp uniapp UTS插件开发实录:把手机震动与加速度计打包成跨端API https://www.taomawang.com/web/uniapp/2239.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务