一、项目介绍

在移动应用中,手机振动(Haptic Feedback) 是一种常见且重要的用户体验增强方式。不同场景下的振动反馈能让用户获得更直观、更沉浸的交互提示,例如:

按钮点击反馈:输入框聚焦、按钮按下时轻微震动,让用户感知操作已生效。

重要提醒:闹钟、计时器、来电或消息通知时的持续振动,确保用户不会错过。

复杂游戏震动:游戏中爆炸、碰撞等重击感,通过不同强度与节奏的振动提升沉浸感。

导航指引:使用导航时,转弯提示或危险路况提醒通过短促振动提示驾驶者。

我们希望搭建一个通用的、可扩展的振动管理框架,满足以下需求:

多种振动效果支持

简单的单次振动

持续振动

自定义节奏(pattern)

强度(amplitude)控制

兼容多 Android 版本

API 级别差异(Vibrator vs VibrationEffect)

Android 12+ 平台 Haptic API 新特性

易于集成

只需在 Application 或 Activity 中初始化一次

对外暴露简单的振动接口,可在业务层随时调用

可扩展高级能力

混合式振动:结合音频、灯光,形成更丰富的感官反馈

远程配置振动资源 JSON 文件,动态下发节奏和强度

与 Jetpack Compose 结合的响应式振动触发

本文将深度剖析 Android 振动底层原理,全面展示从最基础单次振动,到复杂 Haptic Pattern,再到新版 HapticFeedback 兼容方案的完整实现,最终提供一个模块化、可扩展的振动管理框架示例,并附上完整整合代码、方法功能解读及项目总结,全文超过 10,000 字,适合作为博客或文档直接发布。

二、相关知识与术语

在动手编码之前,需要先了解振动在 Android 中的相关概念、API 演进及硬件支持细节。

2.1 振动驱动与硬件支持

Vibrator Service

Android 系统提供了 Vibrator 服务,底层通过 HAL(硬件抽象层)与设备真实的振动电机通信。

不同设备的振动器(ER: Eccentric Rotating Mass vs LRA: Linear Resonant Actuator)性能和特性各异:

ER 振动器:经典偏心轮式,振幅大、能耗高、响应慢。

LRA 振动器:线性谐振器,振幅相对小、能耗低、响应快,常用于触觉反馈。

Vibration HAL 与音频子系统

从 Android 8.0(O)起,振动信号被纳入音频接口,由 AudioFlinger 调度,更易与音频同步。

Vibrator HAL 通过 IVibrator 接口向上暴露 perform()、on()、off()、setAmplitude() 等方法。

2.2 Android 振动 API 演进

API ≤ 25

调用 Vibrator.vibrate(long milliseconds) 或 Vibrator.vibrate(long[] pattern, int repeat)。

无法控制振动强度,且 pattern 的精度与硬件响应有限。

API ≥ 26

引入 VibrationEffect 类:

java

复制编辑

VibrationEffect.createOneShot(long milliseconds, int amplitude); VibrationEffect.createWaveform(long[] timings, int[] amplitudes, int repeat);

amplitude 范围 1–255,0 表示静音模式,可精细控制振动强度。

API ≥ 29 (Android 10)

支持触觉(Haptic)调用优先级控制,避免与媒体音频冲突。

API ≥ 31 (Android 12)

引入 FeedbackEffect 枚举,如 EFFECT_CLICK、EFFECT_TICK、EFFECT_POP,统一触觉体验。

通过 Vibrator.vibrate(VibrationEffect.createPredefined(...)) 直接调用预定义触觉效果。

2.3 权限与兼容性

Permission

普通振动无需申请任何危险权限, 即可。

该权限在运行期不需动态申请。

兼容性注意

不同厂商对振动 amplitude 支持度不同,部分老设备 amplitude 参数会被忽略并退回到全强度。

某些系统(如 MIUI、Flyme)会对短促振动进行合并或限频,需要在业务层做延迟或 pattern 调整。

三、实现思路与架构设计

针对上述需求,我们设计一个 VibrationManager 模块,架构如下:

+-------------------+ 调用 +----------------+

| Business Layer | ----------> | VibrationManager |

| (UI / ViewModel) | +----------------+

+-------------------+ | | |

| | |

+-----------+ | +--------------+

| | |

+--------------+ +---------------+ +---------------+

| BaseHelper | | PatternHelper | | PredefinedHelper |

+--------------+ +---------------+ +---------------+

Business Layer:Activity、Fragment 或 ViewModel,直接调用 VibrationManager.vibrateOneShot(...)、vibratePattern(...)、vibratePredefined(...)。

VibrationManager:单例,负责初始化系统 Vibrator 服务、统一调度调用、处理 API 版本差异。

BaseHelper:封装最基础的振动调用,如单次、Waveform,无 amplitude 支持的降级。

PatternHelper:提供常用的 pattern 模式生成器,如 SOS、心跳、节奏节拍等。

PredefinedHelper:封装 Android 12+ 预定义触觉效果。

具体流程:

初始化:在 Application.onCreate() 中调用 VibrationManager.init(context),获取系统 Vibrator。

调用:业务层调用 VibrationManager 的方法,无需关心 API 差异与权限。

执行:VibrationManager 根据当前 SDK 版本选择相应 Helper,并调用对应接口。

扩展:如需支持远程配置,PatternHelper 可从 JSON 文件加载 timing/amplitude 数组并调用 createWaveform。

四、完整项目代码(整合)

提示:以下代码已将所有类与 XML、资源整合到同一文件,通过注释分区。复制时请按实际包路径与文件拆分。

// =======================================================

// 包名:com.example.vibrationdemo

// 文件:VibrationExample.java(整合所有类与布局)

// =======================================================

package com.example.vibrationdemo;

import android.app.Application;

import android.content.Context;

import android.os.*;

import androidx.annotation.RequiresApi;

import java.util.*;

// =======================================================

// Class: VibrationManager

// 说明:振动管理单例,API 兼容调度

// =======================================================

public class VibrationManager {

private static VibrationManager instance;

private Vibrator vibrator;

private Context ctx;

/** 初始化,建议在 Application.onCreate() 调用 */

public static void init(Context context) {

if (instance == null) {

instance = new VibrationManager(context.getApplicationContext());

}

}

public static VibrationManager get() {

if (instance == null) {

throw new IllegalStateException("Must call init() first");

}

return instance;

}

private VibrationManager(Context context) {

this.ctx = context;

vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);

}

/** 单次振动 ms 毫秒,最大强度 */

public void vibrateOneShot(long ms) {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

vibrator.vibrate(VibrationEffect.createOneShot(ms, VibrationEffect.DEFAULT_AMPLITUDE));

} else {

vibrator.vibrate(ms);

}

}

/** 持续振动,直到 cancel() */

public void vibrateLong(long ms) {

vibrateOneShot(ms);

}

/** 按 pattern 执行振动,repeat=-1 不循环 */

public void vibratePattern(long[] pattern, int repeat) {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

int[] amps = new int[pattern.length];

Arrays.fill(amps, VibrationEffect.DEFAULT_AMPLITUDE);

vibrator.vibrate(VibrationEffect.createWaveform(pattern, amps, repeat));

} else {

vibrator.vibrate(pattern, repeat);

}

}

/** Android 12+ 预定义触觉效果 */

@RequiresApi(Build.VERSION_CODES.S)

public void vibratePredefined(int effectId) {

vibrator.vibrate(VibrationEffect.createPredefined(effectId));

}

/** 取消所有振动 */

public void cancel() {

vibrator.cancel();

}

}

// =======================================================

// Class: PatternHelper

// 说明:常用振动节奏生成器

// =======================================================

public class PatternHelper {

/** 心跳节奏:200ms 休息 100ms 震动 200ms 休息 150ms 震动 */

public static long[] heartbeatPattern() {

return new long[]{0, 100, 200, 150};

}

/** SOS 节奏:... --- ... */

public static long[] sosPattern() {

// S: dot dot dot, O: dash dash dash

// dot: 200ms on / 200ms off; dash: 600ms on / 200ms off

return new long[]{

0,

200,200, 200,200, 200,600,

600,200, 600,200, 600,200,

200,200, 200,200, 200

};

}

}

// =======================================================

// Class: PredefinedHelper (API 31+)

// 说明:封装 Sdk31+ 预定义效果

// =======================================================

import android.os.VibrationEffect;

@RequiresApi(Build.VERSION_CODES.S)

public class PredefinedHelper {

/** 点击反馈效果 */

public static int EFFECT_CLICK = VibrationEffect.EFFECT_CLICK;

/** 弹簧效果 */

public static int EFFECT_TICK = VibrationEffect.EFFECT_TICK;

/** 长按效果 */

public static int EFFECT_THUD = VibrationEffect.EFFECT_THUD;

}

// =======================================================

// Application: MyApp

// 说明:初始化 VibrationManager

// =======================================================

public class MyApp extends Application {

@Override

public void onCreate() {

super.onCreate();

VibrationManager.init(this);

}

}

// =======================================================

// Activity: MainActivity

// 说明:演示各种振动调用

// =======================================================

import android.app.Activity;

import android.os.Build;

import android.os.Bundle;

import android.view.View;

import android.widget.*;

public class MainActivity extends Activity {

private Button btnOneShot, btnPattern, btnHeartbeat, btnSOS, btnCancel, btnPredef;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

btnOneShot = findViewById(R.id.btn_one_shot);

btnPattern = findViewById(R.id.btn_pattern);

btnHeartbeat= findViewById(R.id.btn_heartbeat);

btnSOS = findViewById(R.id.btn_sos);

btnCancel = findViewById(R.id.btn_cancel);

btnPredef = findViewById(R.id.btn_predef);

btnOneShot.setOnClickListener(v ->

VibrationManager.get().vibrateOneShot(500));

btnPattern.setOnClickListener(v ->

VibrationManager.get().vibratePattern(new long[]{0,300,150,300}, -1));

btnHeartbeat.setOnClickListener(v ->

VibrationManager.get().vibratePattern(PatternHelper.heartbeatPattern(), 0));

btnSOS.setOnClickListener(v ->

VibrationManager.get().vibratePattern(PatternHelper.sosPattern(), -1));

btnCancel.setOnClickListener(v ->

VibrationManager.get().cancel());

btnPredef.setOnClickListener(v -> {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {

VibrationManager.get()

.vibratePredefined(PredefinedHelper.EFFECT_CLICK);

} else {

Toast.makeText(this, "仅 Android 12+ 支持预定义效果", Toast.LENGTH_SHORT).show();

}

});

}

}

/*

=======================================

XML: res/layout/activity_main.xml

=======================================

android:orientation="vertical" android:padding="16dp"

android:layout_width="match_parent" android:layout_height="match_parent">

android:id="@+id/btn_one_shot"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="单次振动 500ms"/>

android:id="@+id/btn_pattern"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="Pattern 300/150/300"

android:layout_marginTop="8dp"/>

android:id="@+id/btn_heartbeat"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="心跳节奏"

android:layout_marginTop="8dp"/>

android:id="@+id/btn_sos"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="SOS 节奏"

android:layout_marginTop="8dp"/>

android:id="@+id/btn_predef"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="预定义效果 (Android 12+)"

android:layout_marginTop="8dp"/>

android:id="@+id/btn_cancel"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="取消振动"

android:layout_marginTop="8dp"/>

*/

五、代码解读(方法功能说明)

VibrationManager.init(Context)

在 Application.onCreate() 中调用,获取系统 Vibrator 服务并缓存。

vibrateOneShot(long ms)

API 26+:VibrationEffect.createOneShot(ms, DEFAULT_AMPLITUDE);

API 25 及以下:退回 vibrator.vibrate(ms)。

vibratePattern(long[] pattern, int repeat)

API 26+:使用 createWaveform(pattern, amplitudes, repeat),默认全强度;

否则:调用 vibrator.vibrate(pattern, repeat)。

vibratePredefined(int effectId)

API 31+:调用 createPredefined(effectId),支持系统预设触觉效果;

通过 PredefinedHelper 封装常用效果常量。

cancel()

取消所有正在进行的振动。

PatternHelper.heartbeatPattern() / sosPattern()

提供常用振动节奏的 long[] timings 数组,便于业务层直接调用。

MainActivity 中的按钮点击

分别演示各种振动类型的调用,无需关心具体 API 差异。

六、基础篇小结

核心功能:单次振动、Pattern 振动、取消振动、API 兼容处理、Android 12+ 预定义触觉。

模块化设计:VibrationManager 统一入口,PatternHelper/PredefinedHelper 拆分业务。

扩展思考:

可在 PatternHelper 中加载远程配置 JSON,动态下发节奏。

可为不同 UI 组件封装 haptic-click 类,自动调用预定义 EFFECT_TICK。

在 Jetpack Compose 中,使用 remember + LaunchedEffect 在状态变化时触发振动。