PowerManager(电源服务)

使用 PowerManager (电源服务)

PowerManager 是 Android 提供的一个系统服务类,用于管理和查询设备的电源状态,如屏幕唤醒、锁屏、电池优化等。它允许开发者控制设备的电源行为(如保持屏幕常亮)或检测电源状态(如是否充电)。结合 Android 动画合集的背景,PowerManager 可用于在动画运行时保持屏幕开启,或根据电源状态调整动画行为(如低电量时降低动画频率)。本文将详细介绍 PowerManager 的功能、使用方法、权限要求及注意事项,重点展示如何使用 WakeLock 和电源状态检测,并结合动画实现交互效果。

PowerManager 的作用与原理

  • 作用PowerManager 提供对设备电源管理的访问,包括屏幕唤醒锁(WakeLock)、CPU 控制、电源模式(如低电量模式)以及电池状态查询。它适合需要控制设备休眠或响应电源变化的场景。
  • 原理PowerManager 通过 Android 的电源管理框架与底层硬件交互,管理屏幕、CPU 和系统的休眠状态。WakeLock 是其核心功能,用于防止设备进入休眠状态,确保任务(如动画、后台服务)持续运行。
  • 应用场景
  • 保持屏幕常亮(如播放动画或视频时)。
  • 检测电池状态,调整动画效果(如低电量时简化动画)。
  • 结合 AlarmManagerVibrator 实现唤醒设备的效果。
  • 响应充电状态变化,触发特定动画(如充电时的波纹效果)。

权限要求

使用 PowerManager 的某些功能需要以下权限,在 AndroidManifest.xml 中声明:

<!-- 使用 WakeLock -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- 查询电池状态(可选) -->
<uses-permission android:name="android.permission.BATTERY_STATS" />
<!-- 检测设备重启(可选) -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
  • 动态权限WAKE_LOCKBATTERY_STATS 是普通权限,无需动态请求,用户安装应用时自动授予。
  • 注意:API 23+(Android 6.0)引入 Doze 模式和 App Standby,可能限制 WakeLock 的行为;API 26+ 推荐使用前台服务替代长时间 WakeLock

获取 PowerManager

通过 Context 获取 PowerManager 实例:

import android.content.Context;
import android.os.PowerManager;

PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);

常用功能与方法

以下是 PowerManager 的核心方法:

方法/功能描述权限要求API 级别用法示例
newWakeLock(int levelAndFlags, String tag)创建唤醒锁,控制屏幕或 CPU 状态。WAKE_LOCK1+PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "MyTag");
isInteractive()检查屏幕是否开启(API 20+)。20+boolean isScreenOn = powerManager.isInteractive();
isPowerSaveMode()检查是否处于省电模式。21+boolean isPowerSave = powerManager.isPowerSaveMode();
isDeviceIdleMode()检查是否处于 Doze 模式(API 23+)。23+boolean isDoze = powerManager.isDeviceIdleMode();
reboot(String reason)重启设备(需系统权限)。系统权限8+powerManager.reboot("user");
isIgnoringBatteryOptimizations(String packageName)检查应用是否被电池优化忽略。23+boolean ignored = powerManager.isIgnoringBatteryOptimizations(getPackageName());
  • WakeLock 类型levelAndFlags):
  • PARTIAL_WAKE_LOCK:保持 CPU 运行,屏幕和键盘可关闭。
  • SCREEN_DIM_WAKE_LOCK(API 17 弃用):屏幕保持低亮。
  • SCREEN_BRIGHT_WAKE_LOCK(API 17 弃用):屏幕保持高亮。
  • FULL_WAKE_LOCK(API 17 弃用):屏幕和键盘保持高亮。
  • PROXIMITY_SCREEN_OFF_WAKE_LOCK(API 21+):靠近传感器关闭屏幕。
  • ACQUIRE_CAUSES_WAKEUP:强制唤醒屏幕(与上述标志组合)。
  • ON_AFTER_RELEASE:释放后保持屏幕短暂开启。
  • 注意:API 17+ 推荐使用 FLAG_KEEP_SCREEN_ON(通过 View 或 Window)替代旧版 WakeLock 类型。

完整示例(WakeLock + 动画)

以下是一个使用 PowerManager 保持屏幕常亮并结合按钮动画的示例,触发 SoundPool 音效和振动。

import android.content.Context;
import android.media.AudioAttributes;
import android.media.SoundPool;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.widget.Button;
import android.widget.Toast;
import android.animation.ObjectAnimator;
import androidx.appcompat.app.AppCompatActivity;

public class PowerManagerActivity extends AppCompatActivity {
    private PowerManager powerManager;
    private PowerManager.WakeLock wakeLock;
    private Vibrator vibrator;
    private SoundPool soundPool;
    private int soundId;
    private Button actionButton;
    private ObjectAnimator scaleAnimator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_power_manager);

        actionButton = findViewById(R.id.action_button);
        powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);

        // 初始化 SoundPool
        soundPool = new SoundPool.Builder()
            .setMaxStreams(1)
            .setAudioAttributes(new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                .build())
            .build();
        soundId = soundPool.load(this, R.raw.click_sound, 1);

        // 初始化 WakeLock
        wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::WakeLock");

        // 初始化动画
        scaleAnimator = ObjectAnimator.ofFloat(actionButton, "scaleX", 1f, 1.2f, 1f);
        scaleAnimator.setDuration(200);

        // 按钮点击
        actionButton.setOnClickListener(v -> {
            // 获取 WakeLock
            if (!wakeLock.isHeld()) {
                wakeLock.acquire(10 * 60 * 1000L /* 10 分钟 */);
                Toast.makeText(this, "WakeLock acquired", Toast.LENGTH_SHORT).show();
            }

            // 播放音效
            soundPool.play(soundId, 1.0f, 1.0f, 1, 0, 1.0f);

            // 触发振动
            if (vibrator.hasVibrator()) {
                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                    vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
                } else {
                    vibrator.vibrate(100);
                }
            }

            // 触发动画
            scaleAnimator.start();

            // 检查电源状态
            boolean isPowerSave = powerManager.isPowerSaveMode();
            Toast.makeText(this, "Power Save Mode: " + isPowerSave, Toast.LENGTH_SHORT).show();
        });
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (wakeLock.isHeld()) {
            wakeLock.release();
        }
        soundPool.autoPause();
        scaleAnimator.cancel();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        soundPool.release();
        vibrator.cancel();
        scaleAnimator.cancel();
    }
}
  • 布局 XML(res/layout/activity_power_manager.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">
    <Button
        android:id="@+id/action_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Trigger Action" />
</LinearLayout>
  • AndroidManifest.xml(添加权限):
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
  • 说明
  • 权限:声明 WAKE_LOCKVIBRATE 权限,无需动态请求。
  • WakeLock:使用 PARTIAL_WAKE_LOCK 保持 CPU 运行 10 分钟。
  • 音效和振动:结合 SoundPoolVibrator(前文介绍)增强交互。
  • 动画:按钮点击时触发缩放动画(scaleX)。
  • 电源状态:检查是否处于省电模式,显示提示。
  • 生命周期onPause 释放 WakeLock 和暂停音效,onDestroy 释放所有资源。

使用 FLAG_KEEP_SCREEN_ON

替代 WakeLock,通过 Window 保持屏幕常亮:

getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  • 移除屏幕常亮:
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  • 优点:无需 WAKE_LOCK 权限,简单易用。
  • 缺点:仅限 Activity 界面,无法控制 CPU。

检测电池状态

结合 BroadcastReceiver 监听电池状态:

BroadcastReceiver batteryReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
        int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
        float batteryPct = level * 100 / (float) scale;
        if (batteryPct < 20) {
            scaleAnimator.cancel(); // 低电量停止动画
            Toast.makeText(context, "Low battery: " + batteryPct + "%", Toast.LENGTH_SHORT).show();
        }
    }
};
registerReceiver(batteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));

优缺点

  • 优点
  • 提供灵活的电源管理(如 WakeLock、屏幕控制)。
  • 可检测省电模式和 Doze 状态,优化应用行为。
  • 与动画、音效、振动结合增强用户体验。
  • 缺点
  • WakeLock 滥用可能增加耗电,API 26+ 推荐前台服务。
  • Doze 模式限制后台任务执行。
  • 设备差异可能影响行为(如无振动硬件)。
  • 替代方案
  • Foreground Service:API 26+ 替代长时间 WakeLock
  • WorkManager:适合低优先级后台任务。
  • BatteryManager:更详细的电池信息。
  • WindowManager.FLAG_KEEP_SCREEN_ON:简单保持屏幕常亮。

注意事项

  • 权限:确保声明 WAKE_LOCK 权限。
  • WakeLock 管理:始终在适当时间释放 WakeLock,避免耗电。
  • Doze 模式:API 23+ 的 Doze 和 App Standby 可能限制 WakeLock,使用前台服务或 AlarmManager
  • 生命周期:在 onPauseonDestroy 释放资源(WakeLock、动画、音效)。
  • 电池优化:引导用户将应用加入电池优化白名单(ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)。
  • 调试:通过 Log 检查 WakeLock 状态和电源模式。

扩展:Jetpack Compose 中的实现

在 Jetpack Compose 中,PowerManager 可结合 Composable 实现电源管理和动画:

import android.content.Context
import android.os.PowerManager
import android.os.VibrationEffect
import android.os.Vibrator
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween

@Composable
fun PowerManagerScreen() {
    val context = LocalContext.current
    val powerManager = remember { context.getSystemService(Context.POWER_SERVICE) as PowerManager }
    val vibrator = remember { context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator }
    var wakeLock by remember { mutableStateOf<PowerManager.WakeLock?>(null) }
    var isActive by remember { mutableStateOf(false) }
    val scale by animateFloatAsState(
        targetValue = if (isActive) 1.2f else 1f,
        animationSpec = tween(200)
    )

    Column(
        modifier = Modifier.fillMaxSize().padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text("Power Save Mode: ${powerManager.isPowerSaveMode()}")
        Spacer(Modifier.height(16.dp))
        Button(
            onClick = {
                if (wakeLock == null || !wakeLock!!.isHeld) {
                    wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::WakeLock")
                    wakeLock?.acquire(10 * 60 * 1000L) // 10 分钟
                    if (vibrator.hasVibrator()) {
                        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                            vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE))
                        } else {
                            vibrator.vibrate(100)
                        }
                    }
                    isActive = true
                } else {
                    wakeLock?.release()
                    wakeLock = null
                    isActive = false
                }
            },
            modifier = Modifier.scale(scale)
        ) {
            Text(if (isActive) "Release WakeLock" else "Acquire WakeLock")
        }
    }

    DisposableEffect(Unit) {
        onDispose {
            wakeLock?.release()
        }
    }
}
  • 说明
  • 使用 remember 缓存 PowerManagerVibrator
  • animateFloatAsState 实现按钮缩放动画。
  • WakeLock 保持 CPU 运行 10 分钟,点击时触发振动。
  • DisposableEffect 确保释放 WakeLock
  • 显示当前省电模式状态。

示例:低电量检测

结合电池状态调整动画:

BroadcastReceiver batteryReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
        if (level < 20) {
            scaleAnimator.cancel(); // 低电量停止动画
            if (wakeLock.isHeld()) {
                wakeLock.release();
            }
        }
    }
};
registerReceiver(batteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));

如果需要更复杂的功能(如 Doze 模式优化、与 AlarmManager 结合定时唤醒、或 Canvas 绘制电源状态动画),或想继续探讨 Android 动画合集(如过渡动画、Lottie),请告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注