You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中如何跟踪用户应用使用时长并通过Firebase上报给管理员

Flutter 应用使用时长跟踪与Firebase上报实现

准备工作

首先在pubspec.yaml中添加必要依赖:

dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^2.20.0
  cloud_firestore: ^4.12.0
  shared_preferences: ^2.2.2 # 缓存未上报时长,防止应用被杀丢失数据

核心跟踪逻辑实现

创建单例类AppUsageTracker处理时长统计与生命周期监听:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';

class AppUsageTracker with WidgetsBindingObserver {
  static final AppUsageTracker _instance = AppUsageTracker._internal();
  factory AppUsageTracker() => _instance;
  AppUsageTracker._internal() {
    WidgetsBinding.instance.addObserver(this);
  }

  DateTime? _startTime;
  int _currentSessionDuration = 0; // 单位:秒
  final FirebaseFirestore _firestore = FirebaseFirestore.instance;
  final FirebaseAuth _auth = FirebaseAuth.instance;

  // 初始化加载缓存的未上报时长
  Future<void> init() async {
    final prefs = await SharedPreferences.getInstance();
    _currentSessionDuration = prefs.getInt('unreported_duration') ?? 0;
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    switch (state) {
      case AppLifecycleState.resumed:
        // 应用进入前台,开始计时
        _startTime = DateTime.now();
        break;
      case AppLifecycleState.paused:
        // 应用进入后台,计算本次会话时长并累加
        if (_startTime != null) {
          final duration = DateTime.now().difference(_startTime!).inSeconds;
          _currentSessionDuration += duration;
          _startTime = null;
          _cacheUnreportedDuration();
          _reportUsage();
        }
        break;
      case AppLifecycleState.detached:
        // 应用被销毁,最后一次上报
        if (_startTime != null) {
          final duration = DateTime.now().difference(_startTime!).inSeconds;
          _currentSessionDuration += duration;
          _startTime = null;
          _cacheUnreportedDuration();
          _reportUsage();
        }
        break;
      default:
        break;
    }
  }

  // 缓存未上报时长
  Future<void> _cacheUnreportedDuration() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setInt('unreported_duration', _currentSessionDuration);
  }

  // 上报时长到Firebase
  Future<void> _reportUsage() async {
    final user = _auth.currentUser;
    if (user == null) return; // 未登录用户不上报

    try {
      final today = DateTime.now().toIso8601String().split('T')[0]; // 格式:YYYY-MM-DD
      final userUsageDoc = _firestore.collection('user_app_usage').doc(user.uid);

      await _firestore.runTransaction((transaction) async {
        final snapshot = await transaction.get(userUsageDoc);
        if (snapshot.exists) {
          // 更新今日时长
          final currentDailyUsage = snapshot.data()?['daily_usage'] as Map<String, dynamic>? ?? {};
          final todayUsage = (currentDailyUsage[today] as int? ?? 0) + _currentSessionDuration;
          currentDailyUsage[today] = todayUsage;
          transaction.update(userUsageDoc, {
            'daily_usage': currentDailyUsage,
            'last_updated': FieldValue.serverTimestamp(),
            'user_id': user.uid,
            'email': user.email
          });
        } else {
          // 创建新文档
          transaction.set(userUsageDoc, {
            'daily_usage': {today: _currentSessionDuration},
            'last_updated': FieldValue.serverTimestamp(),
            'user_id': user.uid,
            'email': user.email
          });
        }
      });

      // 上报成功,清空缓存
      _currentSessionDuration = 0;
      await _cacheUnreportedDuration();
    } catch (e) {
      print('上报失败: $e');
      // 上报失败,保留缓存,下次启动自动重试
    }
  }

  // 主动触发上报(比如用户退出登录时)
  Future<void> forceReport() async {
    if (_startTime != null) {
      final duration = DateTime.now().difference(_startTime!).inSeconds;
      _currentSessionDuration += duration;
      _startTime = null;
    }
    await _reportUsage();
  }

  // 销毁时移除监听
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
  }
}

集成到应用中

在main.dart中初始化Firebase和跟踪器:

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

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  // 初始化应用时长跟踪器
  await AppUsageTracker().init();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'App Usage Tracker Demo',
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  void dispose() {
    // 页面销毁时主动上报(可选)
    AppUsageTracker().forceReport();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('首页')),
      body: const Center(child: Text('应用使用时长跟踪中...')),
    );
  }
}

管理员查看数据

管理员可通过Firebase控制台的Firestore查看user_app_usage集合:

  • 每个文档对应一位用户,包含user_id、email、last_updated字段
  • daily_usage字段为键值对,键是日期(YYYY-MM-DD),值是当日累计使用时长(秒)

如需可视化报表,可将数据导出至Excel,或结合Firebase Analytics自定义事件补充统计维度。

关键注意事项

  • 登录校验:仅上报已登录用户数据,避免匿名数据混乱
  • 异常处理:通过本地缓存确保应用被杀时未上报数据不丢失
  • 数据一致性:使用Firestore事务防止多端同时上报时的数据冲突
  • 隐私合规:需根据地区法规(如GDPR)告知用户数据收集行为,并提供关闭选项

内容的提问来源于stack exchange,提问作者SM Huzaifa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 11:50:23