Flutter电池信息:battery_plus检测全攻略
你是否还在为跨平台获取设备电池状态而编写大量原生代码?是否在寻找一种统一的方式处理Android、iOS、Web和桌面平台的电池信息?本文将带你深入了解如何使用Flutter的platform channel机制实现电池信息检测,并对比第三方插件battery_plus的使用方法,帮助你快速掌握跨平台电池状态监控方案。
读完本文你将获得:
- 理解Flutter平台通道(Platform Channel)工作原理
- 掌握原生电池信息获取的实现方法
- 学会使用battery_plus插件简化开发流程
- 解决跨平台电池检测的常见问题
- 获取完整的代码示例和最佳实践
平台通道工作原理
Flutter通过平台通道(Platform Channel)实现与原生平台的通信,这是实现电池信息检测的基础。平台通道允许Flutter应用调用原生平台的API,同时也能接收来自原生平台的事件通知。
平台通道架构
平台通道主要有三种类型:
- MethodChannel:用于方法调用,支持双向通信
- EventChannel:用于事件流通信,通常用于原生平台向Flutter发送事件
- BasicMessageChannel:用于传递字符串和半结构化消息
在电池信息检测场景中,我们主要使用MethodChannel获取电池电量,使用EventChannel监听电池状态变化。
原生实现方案
Flutter官方提供了一个平台通道示例项目,展示了如何通过原生代码获取电池信息。该示例位于examples/platform_channel/目录下,实现了电池电量获取和充电状态监听功能。
Dart代码实现
以下是Dart端核心代码,位于examples/platform_channel/lib/main.dart:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class PlatformChannel extends StatefulWidget {
const PlatformChannel({super.key});
@override
State<PlatformChannel> createState() => _PlatformChannelState();
}
class _PlatformChannelState extends State<PlatformChannel> {
static const MethodChannel methodChannel = MethodChannel('samples.flutter.io/battery');
static const EventChannel eventChannel = EventChannel('samples.flutter.io/charging');
String _batteryLevel = 'Battery level: unknown.';
String _chargingStatus = 'Battery status: unknown.';
Future<void> _getBatteryLevel() async {
String batteryLevel;
try {
final int? result = await methodChannel.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level: $result%.';
} on PlatformException catch (e) {
if (e.code == 'NO_BATTERY') {
batteryLevel = 'No battery.';
} else {
batteryLevel = 'Failed to get battery level.';
}
}
setState(() {
_batteryLevel = batteryLevel;
});
}
@override
void initState() {
super.initState();
eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
}
void _onEvent(Object? event) {
setState(() {
_chargingStatus = "Battery status: ${event == 'charging' ? '' : 'dis'}charging.";
});
}
void _onError(Object error) {
setState(() {
_chargingStatus = 'Battery status: unknown.';
});
}
@override
Widget build(BuildContext context) {
return Material(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(_batteryLevel, key: const Key('Battery level label')),
Padding(
padding: const EdgeInsets.all(16.0),
child: ElevatedButton(onPressed: _getBatteryLevel, child: const Text('Refresh')),
),
],
),
Text(_chargingStatus),
],
),
);
}
}
void main() {
runApp(const MaterialApp(home: PlatformChannel()));
}
各平台原生实现
Android实现
在Android平台,我们需要创建一个MethodChannel处理电池信息请求,并注册一个BroadcastReceiver监听电池状态变化。
// MainActivity.kt
package io.flutter.example.platformchannel
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
private val BATTERY_CHANNEL = "samples.flutter.io/battery"
private val CHARGING_CHANNEL = "samples.flutter.io/charging"
private lateinit var chargingChannel: EventChannel
private lateinit var batteryChannel: MethodChannel
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// 方法通道 - 获取电池电量
batteryChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, BATTERY_CHANNEL)
batteryChannel.setMethodCallHandler { call, result ->
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
} else {
result.notImplemented()
}
}
// 事件通道 - 监听充电状态
chargingChannel = EventChannel(flutterEngine.dartExecutor.binaryMessenger, CHARGING_CHANNEL)
chargingChannel.setStreamHandler(object : EventChannel.StreamHandler {
private var chargingStateChangeReceiver: BroadcastReceiver? = null
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
chargingStateChangeReceiver = createChargingStateChangeReceiver(events)
registerReceiver(
chargingStateChangeReceiver,
IntentFilter(Intent.ACTION_BATTERY_CHANGED)
)
}
override fun onCancel(arguments: Any?) {
unregisterReceiver(chargingStateChangeReceiver)
chargingStateChangeReceiver = null
}
})
}
private fun getBatteryLevel(): Int {
return if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = ContextWrapper(applicationContext).registerReceiver(
null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)
)
intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 /
intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
}
}
private fun createChargingStateChangeReceiver(events: EventChannel.EventSink): BroadcastReceiver {
return object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
if (status == BatteryManager.BATTERY_STATUS_UNKNOWN) {
events.error("UNAVAILABLE", "Charging status unavailable", null)
} else {
val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL
events.success(if (isCharging) "charging" else "discharging")
}
}
}
}
}
iOS实现
在iOS平台,我们需要使用UIDevice类获取电池信息,并通过NotificationCenter监听电池状态变化。
// AppDelegate.swift
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
private let batteryChannel = "samples.flutter.io/battery"
private let chargingChannel = "samples.flutter.io/charging"
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
// 方法通道 - 获取电池电量
let batteryChannel = FlutterMethodChannel(name: batteryChannel, binaryMessenger: controller.binaryMessenger)
batteryChannel.setMethodCallHandler { [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) in
guard call.method == "getBatteryLevel" else {
result(FlutterMethodNotImplemented)
return
}
self?.receiveBatteryLevel(result: result)
}
// 事件通道 - 监听充电状态
let chargingChannel = FlutterEventChannel(name: chargingChannel, binaryMessenger: controller.binaryMessenger)
chargingChannel.setStreamHandler(ChargingStreamHandler())
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func receiveBatteryLevel(result: FlutterResult) {
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
if device.batteryState == .unknown {
result(FlutterError(code: "UNAVAILABLE", message: "Battery level not available", details: nil))
} else {
result(Int(device.batteryLevel * 100))
}
}
}
class ChargingStreamHandler: NSObject, FlutterStreamHandler {
private var eventSink: FlutterEventSink?
private var observation: NSKeyValueObservation?
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
eventSink = events
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
// 发送当前充电状态
sendChargingStatus(device: device)
// 监听充电状态变化
observation = device.observe(\.batteryState, options: [.new]) { device, change in
self.sendChargingStatus(device: device)
}
return nil
}
private func sendChargingStatus(device: UIDevice) {
switch device.batteryState {
case .charging, .full:
eventSink?("charging")
case .unplugged, .unknown:
eventSink?("discharging")
@unknown default:
eventSink?("discharging")
}
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
eventSink = nil
observation = nil
return nil
}
}
Windows实现
Windows平台实现相对复杂,需要使用Windows API获取电池信息。以下是简化的实现示例:
// battery_info.cpp
#include "battery_info.h"
#include <windows.h>
#include <powrprof.h>
#include <iostream>
#pragma comment(lib, "powrprof.lib")
int getBatteryLevel() {
SYSTEM_POWER_STATUS status;
if (GetSystemPowerStatus(&status)) {
if (status.BatteryFlag == 128) { // 电池不存在
return -1;
}
return status.BatteryLifePercent * 100;
}
return -1;
}
bool isCharging() {
SYSTEM_POWER_STATUS status;
if (GetSystemPowerStatus(&status)) {
return status.ACLineStatus == 1; // 1表示交流电供电,即充电中
}
return false;
}
然后在Flutter的Windows平台通道实现中调用这些函数:
// flutter_window.cpp
#include "flutter_window.h"
#include <flutter/event_channel.h>
#include <flutter/method_channel.h>
#include <flutter/standard_method_codec.h>
#include "battery_info.h"
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
: project_(project) {}
FlutterWindow::~FlutterWindow() {}
bool FlutterWindow::OnCreate() {
if (!Win32Window::OnCreate()) {
return false;
}
// 创建Flutter引擎
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
view()->GetClientAreaRect().size, project_);
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
return false;
}
// 设置方法通道 - 获取电池电量
auto battery_channel = std::make_unique<flutter::MethodChannel<>>(
flutter_controller_->engine()->messenger(), "samples.flutter.io/battery",
&flutter::StandardMethodCodec::GetInstance());
battery_channel->SetMethodCallHandler(
[](const flutter::MethodCall<>& call,
std::unique_ptr<flutter::MethodResult<>> result) {
if (call.method_name() == "getBatteryLevel") {
int battery_level = getBatteryLevel();
if (battery_level != -1) {
result->Success(battery_level);
} else {
result->Error("UNAVAILABLE", "Battery level not available.");
}
} else {
result->NotImplemented();
}
});
// 设置事件通道 - 监听充电状态
auto charging_channel = std::make_unique<flutter::EventChannel<>>(
flutter_controller_->engine()->messenger(), "samples.flutter.io/charging",
&flutter::StandardMethodCodec::GetInstance());
charging_channel->SetStreamHandler(
std::make_unique<ChargingStreamHandler>());
SetChildContent(flutter_controller_->view()->GetNativeWindow());
return true;
}
battery_plus插件方案
虽然通过原生代码实现电池信息获取是可行的,但需要为每个平台编写大量原生代码,这违背了Flutter"一次编写,到处运行"的理念。幸运的是,Flutter社区提供了battery_plus插件,它封装了所有平台的电池信息获取逻辑,让我们可以用纯Dart代码实现跨平台电池信息检测。
battery_plus简介
battery_plus是Flutter Community开发的一个跨平台插件,提供了统一的API来获取设备电池信息,支持以下平台:
- Android
- iOS
- Web
- Windows
- macOS
- Linux
该插件的主要功能包括:
- 获取当前电池电量
- 监听电池电量变化
- 获取电池充电状态
- 监听充电状态变化
- 获取电池健康状态(部分平台)
安装与配置
在pubspec.yaml文件中添加battery_plus依赖:
dependencies:
flutter:
sdk: flutter
battery_plus: ^4.0.0
然后运行以下命令安装依赖:
flutter pub get
不同平台可能需要额外的配置:
Android配置
无需额外配置,插件会自动处理权限请求。
iOS配置
在Info.plist中添加以下权限描述(如果需要):
<key>NSBatteryUsageDescription</key>
<string>需要访问电池信息以显示设备电量</string>
Web配置
无需额外配置,插件使用Web Battery API实现功能。
基本使用示例
以下是使用battery_plus插件获取电池信息的基本示例:
import 'package:flutter/material.dart';
import 'package:battery_plus/battery_plus.dart';
class BatteryPlusExample extends StatefulWidget {
const BatteryPlusExample({super.key});
@override
State<BatteryPlusExample> createState() => _BatteryPlusExampleState();
}
class _BatteryPlusExampleState extends State<BatteryPlusExample> {
final Battery _battery = Battery();
BatteryState? _batteryState;
int? _batteryLevel;
late StreamSubscription<BatteryState> _batteryStateSubscription;
@override
void initState() {
super.initState();
_initBatteryMonitoring();
}
Future<void> _initBatteryMonitoring() async {
// 获取当前电池电量
_getBatteryLevel();
// 获取当前电池状态
_batteryState = await _battery.batteryState;
// 监听电池状态变化
_batteryStateSubscription = _battery.onBatteryStateChanged.listen((BatteryState state) {
setState(() {
_batteryState = state;
});
// 电池状态变化时更新电量
if (state == BatteryState.charging || state == BatteryState.full) {
_getBatteryLevel();
}
});
}
Future<void> _getBatteryLevel() async {
try {
final level = await _battery.batteryLevel;
setState(() {
_batteryLevel = level;
});
} catch (e) {
debugPrint('获取电池电量失败: $e');
setState(() {
_batteryLevel = null;
});
}
}
@override
void dispose() {
// 取消订阅,防止内存泄漏
_batteryStateSubscription.cancel();
super.dispose();
}
String _getBatteryStateText() {
switch (_batteryState) {
case BatteryState.charging:
return '充电中';
case BatteryState.discharging:
return '放电中';
case BatteryState.full:
return '已充满';
case BatteryState.unknown:
return '未知状态';
default:
return '获取中...';
}
}
IconData _getBatteryIcon() {
if (_batteryLevel == null || _batteryState == null) {
return Icons.battery_unknown;
}
switch (_batteryState!) {
case BatteryState.charging:
return Icons.battery_charging_full;
case BatteryState.full:
return Icons.battery_full;
case BatteryState.discharging:
if (_batteryLevel! <= 10) return Icons.battery_1_bar;
if (_batteryLevel! <= 25) return Icons.battery_2_bar;
if (_batteryLevel! <= 50) return Icons.battery_3_bar;
if (_batteryLevel! <= 75) return Icons.battery_5_bar;
return Icons.battery_full;
default:
return Icons.battery_unknown;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Battery Plus 示例'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
_getBatteryIcon(),
size: 100,
color: _batteryLevel != null && _batteryLevel! <= 15
? Colors.red
: Theme.of(context).primaryColor,
),
const SizedBox(height: 20),
Text(
_batteryLevel != null
? '当前电量: $_batteryLevel%'
: '无法获取电池电量',
style: const TextStyle(fontSize: 20),
),
const SizedBox(height: 10),
Text(
'电池状态: ${_getBatteryStateText()}',
style: const TextStyle(fontSize: 18),
),
const SizedBox(height: 30),
ElevatedButton(
onPressed: _getBatteryLevel,
child: const Text('刷新电量'),
),
],
),
),
);
}
}
void main() {
runApp(const MaterialApp(
home: BatteryPlusExample(),
));
}
电池状态管理
battery_plus插件定义了四种电池状态:
enum BatteryState {
/// 电池正在充电
charging,
/// 电池正在放电(未充电)
discharging,
/// 电池已充满
full,
/// 无法确定电池状态
unknown
}
通过onBatteryStateChanged流可以实时监听电池状态变化:
// 监听电池状态变化
_batteryStateSubscription = _battery.onBatteryStateChanged.listen((BatteryState state) {
setState(() {
_batteryState = state;
});
// 根据状态变化执行不同操作
switch (state) {
case BatteryState.charging:
// 开始充电时的处理
break;
case BatteryState.discharging:
// 停止充电时的处理
break;
case BatteryState.full:
// 电池充满时的处理
break;
case BatteryState.unknown:
// 未知状态处理
break;
}
});
两种方案对比分析
| 特性 | 原生平台通道实现 | battery_plus插件 |
|---|---|---|
| 实现复杂度 | 高(需编写各平台原生代码) | 低(纯Dart代码) |
| 平台支持 | 需手动实现各平台 | 支持Android、iOS、Web、Windows、macOS、Linux |
| 代码量 | 大量(各平台数百行代码) | 少量(几十行Dart代码) |
| 维护成本 | 高(需跟踪各平台API变化) | 低(插件维护者负责更新) |
| 灵活性 | 高(可定制任意原生功能) | 中等(受限于插件API) |
| 开发速度 | 慢 | 快 |
| 学习曲线 | 陡峭(需了解各平台原生开发) | 平缓(只需学习Dart API) |
| 社区支持 | 官方示例有限 | 活跃的社区支持和更新 |
| 测试覆盖 | 需为各平台编写测试 | 插件已包含多平台测试 |
选择建议
- 快速开发:选择battery_plus插件,节省大量开发时间
- 深度定制:选择原生平台通道实现,可以实现插件不支持的特殊功能
- 学习目的:了解平台通道工作原理,推荐先研究原生实现
- 生产环境:优先考虑battery_plus插件,有更好的维护和兼容性
常见问题与解决方案
权限问题
问题:在某些平台上无法获取电池信息,通常是由于权限不足。
解决方案:
-
Android:确保在AndroidManifest.xml中添加了必要权限
<uses-permission android:name="android.permission.BATTERY_STATS"/> -
iOS:在Info.plist中添加电池使用描述
<key>NSBatteryUsageDescription</key> <string>需要访问电池信息以显示设备电量</string> -
Web:在HTTPS环境下使用,浏览器可能限制非安全上下文的电池API访问
电池信息获取失败
问题:调用batteryLevel时返回异常或null。
解决方案:
Future<int?> getBatteryLevelSafely() async {
try {
// 检查平台是否支持
if (!await Battery().isBatteryAvailable()) {
debugPrint('当前平台不支持电池信息获取');
return null;
}
// 获取电池电量
return await Battery().batteryLevel;
} catch (e) {
debugPrint('获取电池电量失败: $e');
// 针对特定异常的处理
if (e is PlatformException) {
if (e.code == 'PERMISSION_DENIED') {
// 请求权限
} else if (e.code == 'NOT_AVAILABLE') {
// 设备不支持
}
}
return null;
}
}
内存泄漏问题
问题:EventChannel或插件的事件流未正确取消订阅,导致内存泄漏。
解决方案:
// 正确管理订阅生命周期
late StreamSubscription<BatteryState> _batterySubscription;
@override
void initState() {
super.initState();
// 保存订阅对象
_batterySubscription = Battery().onBatteryStateChanged.listen((state) {
// 处理电池状态变化
});
}
@override
void dispose() {
// 取消订阅,防止内存泄漏
_batterySubscription.cancel();
super.dispose();
}
Web平台兼容性
问题:Web平台上电池API行为与其他平台不同。
解决方案:
// Web平台特殊处理
if (kIsWeb) {
// Web Battery API返回的是0-1之间的小数
final battery = await window.navigator.getBattery();
final level = (battery.level * 100).round();
// 处理Web平台电池状态
} else {
// 其他平台处理方式
}
最佳实践
状态管理集成
将电池信息集成到应用状态管理中,方便全局访问:
// 使用Provider管理电池状态
class BatteryProvider with ChangeNotifier {
final Battery _battery = Battery();
int? _batteryLevel;
BatteryState? _batteryState;
late StreamSubscription<BatteryState> _subscription;
int? get batteryLevel => _batteryLevel;
BatteryState? get batteryState => _batteryState;
BatteryProvider() {
_init();
}
Future<void> _init() async {
await _getBatteryLevel();
_subscribeToBatteryState();
}
Future<void> _getBatteryLevel() async {
try {
_batteryLevel = await _battery.batteryLevel;
notifyListeners();
} catch (e) {
debugPrint('获取电池电量失败: $e');
}
}
void _subscribeToBatteryState() {
_subscription = _battery.onBatteryStateChanged.listen((state) {
_batteryState = state;
// 充电状态变化时更新电量
if (state == BatteryState.charging || state == BatteryState.full) {
_getBatteryLevel();
}
notifyListeners();
});
}
@override
void dispose() {
_subscription.cancel();
super.dispose();
}
Future<void> refresh() async {
await _getBatteryLevel();
}
}
// 在应用中使用
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => BatteryProvider(),
child: const MyApp(),
),
);
}
低电量提醒功能
实现低电量自动提醒功能:
void _checkLowBattery(int level) {
if (level <= 15) {
// 显示低电量对话框
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('低电量警告'),
content: const Text('电池电量过低,请及时充电。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('确定'),
),
],
),
);
// 可以在这里触发其他操作,如保存数据、降低应用性能等
_saveAppData();
_reducePerformanceMode();
}
}
电量优化策略
根据电池状态调整应用行为,优化电量消耗:
void adjustAppBehaviorBasedOnBatteryState(BatteryState state) {
switch (state) {
case BatteryState.charging:
// 充电时可以启用更多功能
enableHighPerformanceMode();
startBackgroundSync();
break;
case BatteryState.discharging:
// 放电时优化电量消耗
if (_batteryLevel != null && _batteryLevel! < 30) {
enablePowerSavingMode();
stopBackgroundActivities();
reduceRefreshRate();
}
break;
case BatteryState.full:
// 电池充满时可以执行一些维护任务
performDatabaseMaintenance();
break;
default:
break;
}
}
总结与展望
本文详细介绍了两种在Flutter中获取电池信息的方案:原生平台通道实现和battery_plus插件使用。通过对比分析,我们可以看到battery_plus插件极大地简化了跨平台电池信息获取的开发流程,同时保持了良好的性能和兼容性。
随着Flutter生态系统的不断发展,我们有理由相信电池信息获取等基础功能将更加完善。未来可能会看到:
- 更多平台的支持(如嵌入式系统)
- 更丰富的电池健康相关信息
- 更低功耗的监测方案
- 与系统电源管理更深度的集成
无论你选择哪种方案,关键是理解Flutter与原生平台通信的基本原理,这将帮助你解决更复杂的跨平台开发问题。希望本文能为你的Flutter项目提供有价值的参考,让你在跨平台电池信息检测方面事半功倍。
如果你觉得本文对你有帮助,请点赞、收藏并关注,以便获取更多Flutter开发技巧和最佳实践。下期我们将探讨Flutter中的电量优化策略,敬请期待!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



