Flutter后台服务插件flutter_background_service的使用

发布于 1周前 作者 yuanlaile 来自 Flutter

Flutter后台服务插件flutter_background_service的使用

flutter_background_service 是一个用于在Flutter应用程序中执行后台任务的插件。它允许开发者在应用处于前台或后台时运行 Dart 代码,这对于需要持续运行的任务(如数据同步、位置跟踪等)非常有用。本文将详细介绍如何配置和使用该插件,并提供完整的示例代码。

目录


支持

如果您觉得这个插件对您有帮助,请考虑通过Buy Me A Coffee来支持作者。


Android配置

必要的Gradle版本

确保您的项目使用以下Gradle版本:

  • classpath 'com.android.tools.build:gradle:7.4.2'
  • ext.kotlin_version = '1.8.10'
  • distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip

前台服务配置 (适用于Android 14及以上)

如果您的应用目标是SDK 34并且使用了前台服务,则需要添加额外的配置以声明使用的前台服务类型。例如:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
  <application ...>
    <service
      android:name="id.flutter.flutter_background_service.BackgroundService"
      android:foregroundServiceType="location" />
  </application>
</manifest>

自定义通知图标

为了更改通知图标,请在res/drawable文件夹下添加名为ic_bg_service_small的图标资源。


iOS配置

对于iOS平台,您可以选择启用background_fetch能力(可选),以便iOS执行IosConfiguration.onBackground回调。此外,在iOS 13及以上版本中,您还需要向Info.plist文件中添加如下内容:

<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
  <string>dev.flutter.background.refresh</string>
</array>

同时,在AppDelegate.swift中添加:

import flutter_background_service_ios

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    SwiftFlutterBackgroundServicePlugin.taskIdentifier = "your.custom.task.identifier"
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

使用自定义通知

您可以创建自定义的通知来增强用户体验。这里我们使用flutter_local_notifications插件作为例子:

Future<void> initializeService() async {
  final service = FlutterBackgroundService();
  
  const AndroidNotificationChannel channel = AndroidNotificationChannel(
    'my_foreground', // id
    'MY FOREGROUND SERVICE', // title
    description: 'This channel is used for important notifications.', // description
    importance: Importance.low, // importance must be at low or higher level
  );

  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

  await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()?.createNotificationChannel(channel);

  await service.configure(
    androidConfiguration: AndroidConfiguration(
      onStart: onStart,
      autoStart: true,
      isForegroundMode: true,
      notificationChannelId: 'my_foreground',
      initialNotificationTitle: 'AWESOME SERVICE',
      initialNotificationContent: 'Initializing',
      foregroundServiceNotificationId: 888,
    ),
    ...

保持后台服务运行

为了让后台服务即使在应用关闭后也能继续运行,您可以在onStart方法中设置定时器来定期发送心跳信号或其他操作。另外,请注意对于某些设备制造商(如小米的MIUI),可能需要用户手动禁用电池优化才能保证服务不被杀死。


Socket.IO示例

下面是一个简单的Socket.IO实现,它可以在后台监听服务器事件并作出响应:

void onStart(ServiceInstance service) async {
  final socket = io.io("your-server-url", <String, dynamic>{
    'transports': ['websocket'],
    'autoConnect': true,
  });
  
  socket.onConnect((_) {
    print('Connected. Socket ID: ${socket.id}');
  });

  socket.onDisconnect((_) {
    print('Disconnected');
  });
  
  socket.on("event-name", (data) {
    // 处理接收到的数据,比如推送通知
  });
  
  Timer.periodic(const Duration(seconds: 1), (timer) {
    socket.emit("event-name", "your-message");
    print("service is successfully running ${DateTime.now().second}");
  });
}

完整示例代码

以下是基于上述内容整理的一个完整的示例应用代码:

import 'dart:async';
import 'dart:io';
import 'dart:ui';

import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:shared_preferences/shared_preferences.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await initializeService();
  runApp(const MyApp());
}

Future<void> initializeService() async {
  final service = FlutterBackgroundService();

  const AndroidNotificationChannel channel = AndroidNotificationChannel(
    'my_foreground', 
    'MY FOREGROUND SERVICE', 
    description: 'This channel is used for important notifications.', 
    importance: Importance.low, 
  );

  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

  if (Platform.isIOS || Platform.isAndroid) {
    await flutterLocalNotificationsPlugin.initialize(
      const InitializationSettings(
        iOS: DarwinInitializationSettings(),
        android: AndroidInitializationSettings('ic_bg_service_small'),
      ),
    );
  }

  await flutterLocalNotificationsPlugin
      .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
      ?.createNotificationChannel(channel);

  await service.configure(
    androidConfiguration: AndroidConfiguration(
      onStart: onStart,
      autoStart: true,
      isForegroundMode: true,
      notificationChannelId: 'my_foreground',
      initialNotificationTitle: 'AWESOME SERVICE',
      initialNotificationContent: 'Initializing',
      foregroundServiceNotificationId: 888,
      foregroundServiceTypes: [AndroidForegroundType.location],
    ),
    iosConfiguration: IosConfiguration(
      autoStart: true,
      onForeground: onStart,
      onBackground: onIosBackground,
    ),
  );
}

@pragma('vm:entry-point')
Future<bool> onIosBackground(ServiceInstance service) async {
  WidgetsFlutterBinding.ensureInitialized();
  DartPluginRegistrant.ensureInitialized();

  SharedPreferences preferences = await SharedPreferences.getInstance();
  await preferences.reload();
  final log = preferences.getStringList('log') ?? <String>[];
  log.add(DateTime.now().toIso8601String());
  await preferences.setStringList('log', log);

  return true;
}

@pragma('vm:entry-point')
void onStart(ServiceInstance service) async {
  DartPluginRegistrant.ensureInitialized();

  SharedPreferences preferences = await SharedPreferences.getInstance();
  await preferences.setString("hello", "world");

  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

  if (service is AndroidServiceInstance) {
    service.on('setAsForeground').listen((event) {
      service.setAsForegroundService();
    });

    service.on('setAsBackground').listen((event) {
      service.setAsBackgroundService();
    });
  }

  service.on('stopService').listen((event) {
    service.stopSelf();
  });

  Timer.periodic(const Duration(seconds: 1), (timer) async {
    if (service is AndroidServiceInstance) {
      if (await service.isForegroundService()) {
        flutterLocalNotificationsPlugin.show(
          888,
          'COOL SERVICE',
          'Awesome ${DateTime.now()}',
          const NotificationDetails(
            android: AndroidNotificationDetails(
              'my_foreground',
              'MY FOREGROUND SERVICE',
              icon: 'ic_bg_service_small',
              ongoing: true,
            ),
          ),
        );

        service.setForegroundNotificationInfo(
          title: "My App Service",
          content: "Updated at ${DateTime.now()}",
        );
      }
    }

    debugPrint('FLUTTER BACKGROUND SERVICE: ${DateTime.now()}');

    final deviceInfo = DeviceInfoPlugin();
    String? device;
    if (Platform.isAndroid) {
      final androidInfo = await deviceInfo.androidInfo;
      device = androidInfo.model;
    } else if (Platform.isIOS) {
      final iosInfo = await deviceInfo.iosInfo;
      device = iosInfo.model;
    }

    service.invoke(
      'update',
      {
        "current_date": DateTime.now().toIso8601String(),
        "device": device,
      },
    );
  });
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String text = "Stop Service";

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Service App'),
        ),
        body: Column(
          children: [
            StreamBuilder<Map<String, dynamic>?>(
              stream: FlutterBackgroundService().on('update'),
              builder: (context, snapshot) {
                if (!snapshot.hasData) {
                  return const Center(
                    child: CircularProgressIndicator(),
                  );
                }

                final data = snapshot.data!;
                String? device = data["device"];
                DateTime? date = DateTime.tryParse(data["current_date"]);
                return Column(
                  children: [
                    Text(device ?? 'Unknown'),
                    Text(date.toString()),
                  ],
                );
              },
            ),
            ElevatedButton(
              child: const Text("Foreground Mode"),
              onPressed: () => FlutterBackgroundService().invoke("setAsForeground"),
            ),
            ElevatedButton(
              child: const Text("Background Mode"),
              onPressed: () => FlutterBackgroundService().invoke("setAsBackground"),
            ),
            ElevatedButton(
              child: Text(text),
              onPressed: () async {
                final service = FlutterBackgroundService();
                var isRunning = await service.isRunning();
                isRunning ? service.invoke("stopService") : service.startService();

                setState(() {
                  text = isRunning ? 'Start Service' : 'Stop Service';
                });
              },
            ),
            const Expanded(
              child: LogView(),
            ),
          ],
        ),
      ),
    );
  }
}

class LogView extends StatefulWidget {
  const LogView({Key? key}) : super(key: key);

  @override
  State<LogView> createState() => _LogViewState();
}

class _LogViewState extends State<LogView> {
  late final Timer timer;
  List<String> logs = [];

  @override
  void initState() {
    super.initState();
    timer = Timer.periodic(const Duration(seconds: 1), (timer) async {
      final SharedPreferences sp = await SharedPreferences.getInstance();
      await sp.reload();
      logs = sp.getStringList('log') ?? [];
      if (mounted) {
        setState(() {});
      }
    });
  }

  @override
  void dispose() {
    timer.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: logs.length,
      itemBuilder: (context, index) {
        final log = logs.elementAt(index);
        return Text(log);
      },
    );
  }
}

此示例展示了如何配置和使用flutter_background_service插件来实现前后台模式切换、与主程序通信以及展示日志等功能。希望这些信息能帮助您更好地理解和使用该插件。


更多关于Flutter后台服务插件flutter_background_service的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter后台服务插件flutter_background_service的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用flutter_background_service插件来实现后台服务的示例代码。这个插件允许你在Flutter应用中运行后台任务,即使应用不在前台也能执行任务。

1. 添加依赖

首先,在你的pubspec.yaml文件中添加flutter_background_service依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_background_service: ^0.9.0  # 请检查最新版本号

然后运行flutter pub get来安装依赖。

2. 配置Android

android/app/src/main/AndroidManifest.xml中添加必要的权限和声明服务:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.yourapp">

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <application
        ... >
        
        <service
            android:name=".MyBackgroundService"
            android:enabled="true"
            android:exported="true"
            android:foregroundServiceType="location" />
            
        ...
    </application>
</manifest>

3. 创建后台服务类

android/app/src/main/kotlin/com/example/yourapp/(或java/com/example/yourapp/如果你使用Java)目录下创建一个新的Kotlin/Java类,例如MyBackgroundService.kt(或MyBackgroundService.java):

Kotlin:

package com.example.yourapp

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.plugin.common.MethodChannel
import com.transistorsoft.flutter_background_geolocation.FlutterBackgroundService

class MyBackgroundService : FlutterBackgroundService() {

    override fun onCreate() {
        super.onCreate()
        
        // Initialize FlutterEngine
        val flutterEngine = FlutterEngine(this)
        flutterEngine.dartExecutor.executeDartEntrypoint(
            DartExecutor.DartEntrypoint.createDefault()
        )

        // Setup MethodChannel
        val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.yourapp/background_service")
        channel.setMethodCallHandler { call, result ->
            if (call.method == "startTask") {
                // Handle start task logic here
                result.success("Task started")
            } else {
                result.notImplemented()
            }
        }

        // Keep FlutterEngine alive
        flutterEngineCache
            .put("my_engine_id", flutterEngine)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // Handle command to start service
        return START_STICKY
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    override fun onDestroy() {
        super.onDestroy()
        // Cleanup resources if needed
    }

    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        // Handle task removal if needed
    }
}

Java:

package com.example.yourapp;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;

import androidx.core.app.NotificationCompat;

import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.plugin.common.MethodChannel;
import com.transistorsoft.flutter_background_geolocation.FlutterBackgroundService;

public class MyBackgroundService extends FlutterBackgroundService {

    @Override
    public void onCreate() {
        super.onCreate();

        // Initialize FlutterEngine
        FlutterEngine flutterEngine = new FlutterEngine(this);
        flutterEngine.getDartExecutor().executeDartEntrypoint(
            DartExecutor.DartEntrypoint.createDefault()
        );

        // Setup MethodChannel
        MethodChannel channel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), "com.example.yourapp/background_service");
        channel.setMethodCallHandler((call, result) -> {
            if (call.method.equals("startTask")) {
                // Handle start task logic here
                result.success("Task started");
            } else {
                result.notImplemented();
            }
        });

        // Keep FlutterEngine alive
        flutterEngineCache.put("my_engine_id", flutterEngine);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // Handle command to start service
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // Cleanup resources if needed
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        super.onTaskRemoved(rootIntent);
        // Handle task removal if needed
    }
}

4. 在Dart代码中启动后台服务

在你的Flutter Dart代码中,使用MethodChannel与后台服务进行通信:

import 'package:flutter/material.dart';
import 'package:flutter_background_service/flutter_background_service.dart';

void main() {
  runApp(MyApp());

  // Initialize background service
  FlutterBackgroundService.initialize(
    androidNotificationChannel: AndroidNotificationChannel(
      id: 'com.example.yourapp/channel',
      name: 'Background Service Channel',
      description: 'Channel for background service notifications',
      importance: NotificationImportance.High,
      playSound: true,
    ),
    onBackgroundMessage: backgroundMessageHandler,
  );
}

void backgroundMessageHandler(Map<String, dynamic> message) {
  if (message['type'] == 'startTask') {
    // Handle start task logic here
    print('Background task started');
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Background Service Example'),
        ),
        body: Center(
          child: ElevatedButton(
            onPressed: () async {
              // Start background service
              bool isServiceRunning = await FlutterBackgroundService.isServiceRunning();
              if (!isServiceRunning) {
                await FlutterBackgroundService.startService(
                  androidConfig: AndroidConfig(
                    autoStart: true,
                    foregroundService: true,
                  ),
                );

                // Send message to background service
                FlutterBackgroundService.sendData({
                  'type': 'startTask',
                });
              }
            },
            child: Text('Start Background Service'),
          ),
        ),
      ),
    );
  }
}

注意事项

  1. 权限处理:确保在运行时请求必要的权限,如FOREGROUND_SERVICEWAKE_LOCK
  2. 服务生命周期:理解并处理服务的生命周期,确保在不需要时正确停止服务。
  3. 通知处理:在Android 8.0及以上版本中,后台服务需要显示前台通知。

这个示例展示了如何设置和使用flutter_background_service插件来运行后台任务。根据你的具体需求,你可能需要调整代码逻辑和配置。

回到顶部