0

0

实时Android速度显示与数据保存应用开发指南

心靈之曲

心靈之曲

发布时间:2025-10-10 13:49:00

|

861人浏览过

|

来源于php中文网

原创

实时Android速度显示与数据保存应用开发指南

本文详细介绍了如何开发一个Android应用,实现实时速度显示和后台数据保存。核心解决方案是利用前台服务(Foreground Service)持续获取GPS位置数据,并通过EventBus库实现服务与UI之间的高效、解耦通信,确保即使应用在后台或屏幕关闭时也能持续更新速度并保存到数据库,同时在UI上实时展示。

1. 应用概述与核心挑战

开发一个能够实时显示当前速度并将其持续保存的应用,主要面临以下挑战:

  • 后台运行能力: 应用需要在用户关闭屏幕或切换到其他应用时,仍能持续获取位置信息并计算速度。
  • UI实时更新: 如何将后台服务获取的速度数据高效、安全地传递给主界面,并实时更新UI。
  • 权限管理: Android系统对位置权限有严格要求,尤其是后台位置访问权限。
  • 数据持久化: 将实时速度数据保存到数据库中。

为了解决这些问题,我们将采用前台服务(Foreground Service)来处理位置更新,并使用EventBus库作为服务与Activity之间通信的桥梁。

2. 权限声明与请求

首先,在 AndroidManifest.xml 中声明必要的权限:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Android 10 (API level 29) 及以上版本需要此权限用于后台位置访问 -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

在 MainActivity 中,需要动态请求这些权限。特别是 ACCESS_BACKGROUND_LOCATION 权限,在 Android 10 (API level 29) 及更高版本上是必需的。

public class MainActivity extends AppCompatActivity {

    private static final int MY_FINE_LOCATION_REQUEST = 99;
    private static final int MY_BACKGROUND_LOCATION_REQUEST = 100;

    // ... 其他成员变量和方法

    private void requestFineLocationPermission() {
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                MY_FINE_LOCATION_REQUEST);
    }

    private void requestBackgroundLocationPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION},
                    MY_BACKGROUND_LOCATION_REQUEST);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == MY_FINE_LOCATION_REQUEST) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 精确位置权限已授予,尝试请求后台位置权限
                requestBackgroundLocationPermission();
            } else {
                Toast.makeText(this, "精确位置权限被拒绝", Toast.LENGTH_LONG).show();
                // 引导用户到应用设置页面
            }
        } else if (requestCode == MY_BACKGROUND_LOCATION_REQUEST) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this, "后台位置权限已授予", Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(this, "后台位置权限被拒绝", Toast.LENGTH_LONG).show();
            }
        }
    }

    // ... onCreate() 中调用权限请求逻辑
}

3. 位置服务(LocationService)的实现

LocationService 将作为前台服务运行,负责持续获取位置更新、计算速度,并将数据发送到UI和保存到数据库。

3.1 前台服务配置

为了使服务在后台持续运行,需要将其提升为前台服务。这意味着系统会显示一个持续的通知,告知用户有服务正在运行。

public class LocationService extends Service {

    // ... 其他成员变量

    @Override
    public void onCreate() {
        super.onCreate();
        // ... 初始化 FusedLocationProviderClient 等
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createNotificationChannelAndStartForeground();
        } else {
            // Android O 以下版本直接启动前台服务
            startForeground(1, new Notification());
        }
        // ... 启动位置更新
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    private void createNotificationChannelAndStartForeground() {
        String notificationChannelId = "location_service_channel";
        String channelName = "后台位置服务";
        NotificationChannel chan = new NotificationChannel(
                notificationChannelId,
                channelName,
                NotificationManager.IMPORTANCE_LOW // 低优先级通知,减少打扰
        );
        chan.setLightColor(Color.BLUE);
        chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
        NotificationManager manager = getSystemService(NotificationManager.class);
        manager.createNotificationChannel(chan);

        NotificationCompat.Builder notificationBuilder =
                new NotificationCompat.Builder(this, notificationChannelId);
        Notification notification = notificationBuilder.setOngoing(true)
                .setContentTitle("正在获取位置信息")
                .setSmallIcon(R.drawable.ic_launcher_foreground) // 替换为你的应用图标
                .setPriority(NotificationManager.IMPORTANCE_LOW)
                .setCategory(Notification.CATEGORY_SERVICE)
                .build();
        startForeground(2, notification);
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        // 当应用从最近任务列表中移除时停止服务
        super.onTaskRemoved(rootIntent);
        stopSelf();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 停止位置更新
        fusedLocationClient.removeLocationUpdates(locationCallback);
    }
}

3.2 位置更新与速度计算

使用 FusedLocationProviderClient 获取高精度的位置更新。

public class LocationService extends Service {

    private FusedLocationProviderClient fusedLocationClient;
    private LocationRequest locationRequest;
    private LocationCallback locationCallback;
    private float currentSpeed = 0f; // 存储当前速度

    // Firebase 数据库引用(示例,可替换为其他数据存储方式)
    FirebaseDatabase database = FirebaseDatabase.getInstance();
    DatabaseReference myRef = database.getReference(Build.MANUFACTURER + " " + Build.DEVICE);
    DatabaseReference myLiveRef = myRef.child("LiveSpeed");
    DatabaseReference myPastsRef = myRef.child("PastSpeeds");

    @Override
    public void onCreate() {
        super.onCreate();
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
        // ... 前台服务启动逻辑

        locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000) // 每秒更新一次
                .setWaitForAccurateLocation(false)
                .setMinUpdateIntervalMillis(500) // 最快每0.5秒更新
                .setMaxUpdateDelayMillis(1500) // 最慢1.5秒延迟
                .build();

        locationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(@NonNull LocationResult locationResult) {
                Location location = locationResult.getLastLocation();
                if (location != null && location.hasSpeed()) {
                    // 速度单位为米/秒,转换为公里/小时 (m/s * 3.6 = km/h)
                    currentSpeed = location.getSpeed() * 3.6f;

                    // 1. 保存实时速度到数据库
                    myLiveRef.setValue(currentSpeed);

                    // 2. 保存历史速度到数据库
                    DatabaseReference newPastRef = myPastsRef.push();
                    newPastRef.setValue(String.valueOf(Calendar.getInstance().getTime()) + " ||||||||  " + currentSpeed + " KM/H");

                    // 3. 通过 EventBus 发送速度更新事件到 UI
                    EventBus.getDefault().post(new MessageEvents.NewGPSCoordinates(location));
                }
            }
        };
        startLocationUpdates();
    }

    private void startLocationUpdates() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
            ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // 权限未授予,通常在 Activity 中处理,这里只做检查
            return;
        }
        fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper());
    }

    // ... onBind(), onStartCommand() 等方法
}

4. EventBus 集成与通信

EventBus 是一个用于 Android 的发布/订阅事件总线,可以简化组件之间的通信。

绘蛙
绘蛙

电商场景的AI创作平台,无需高薪聘请商拍和文案团队,使用绘蛙即可低成本、批量创作优质的商拍图、种草文案

下载

4.1 添加 EventBus 依赖

在 build.gradle (app) 文件中添加 EventBus 依赖:

dependencies {
    implementation 'org.greenrobot:eventbus:3.3.1' // 使用最新版本
}

4.2 定义事件类

创建一个简单的事件类,用于封装要传递的数据(例如,位置信息)。

// 在一个单独的文件,例如 MessageEvents.java 中定义
public class MessageEvents {
    public static class NewGPSCoordinates {
        public final Location location;

        public NewGPSCoordinates(Location location) {
            this.location = location;
        }
    }
    // 可以定义其他事件
}

4.3 在服务中发布事件

在 LocationService 的 onLocationResult 方法中,当获取到新的位置数据时,发布一个 NewGPSCoordinates 事件。

// LocationService.java -> onLocationResult()
if (location != null && location.hasSpeed()) {
    currentSpeed = location.getSpeed() * 3.6f;
    // ... 保存数据到 Firebase

    // 发布事件
    EventBus.getDefault().post(new MessageEvents.NewGPSCoordinates(location));
}

4.4 在 Activity 中订阅和处理事件

在 MainActivity 中,订阅 NewGPSCoordinates 事件,并在收到事件时更新UI。

public class MainActivity extends AppCompatActivity {

    TextView textView; // 用于显示速度的 TextView

    // ... 其他成员变量和方法

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textView); // 假设你的布局文件中有 ID 为 textView 的 TextView
        // ... 权限请求和服务启动/停止按钮逻辑
    }

    @Override
    public void onStart() {
        super.onStart();
        EventBus.getDefault().register(this); // 注册 EventBus 订阅者
    }

    @Override
    public void onStop() {
        super.onStop();
        EventBus.getDefault().unregister(this); // 取消注册 EventBus 订阅者
    }

    @Subscribe(threadMode = ThreadMode.MAIN) // 确保在主线程更新 UI
    public void onNewGPSCoordinatesEvent(MessageEvents.NewGPSCoordinates event) {
        Location location = event.location;
        if (location != null && location.hasSpeed()) {
            float speed = location.getSpeed() * 3.6f; // 转换为 km/h
            textView.setText(String.format(Locale.getDefault(), "当前速度: %.2f KM/H", speed));
            Log.i("MainActivity", "接收到新速度: " + speed);
        }
    }

    // ... 服务绑定/解绑逻辑
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 服务连接成功,可以获取服务实例,但对于 EventBus 通信,通常不需要直接调用服务方法
            // LocationService.MyBinder binder = (LocationService.MyBinder) service;
            // mLocationService = binder.getService();
            // mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // mBound = false;
        }
    };

    private void starServiceFunc(){
        // 启动服务并绑定
        mServiceIntent = new Intent(this, LocationService.class); // 直接使用类名
        if (!Util.isMyServiceRunning(LocationService.class, this)) {
            startService(mServiceIntent);
            bindService(mServiceIntent, connection, Context.BIND_AUTO_CREATE);
            Toast.makeText(this, getString(R.string.service_start_successfully), Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, getString(R.string.service_already_running), Toast.LENGTH_SHORT).show();
        }
    }

    private void stopServiceFunc(){
        // 停止服务并解绑
        if (Util.isMyServiceRunning(LocationService.class, this)) {
            unbindService(connection); // 先解绑
            stopService(mServiceIntent); // 再停止
            Toast.makeText(this, "服务已停止!!", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "服务已停止!!", Toast.LENGTH_SHORT).show();
        }
    }
}

5. 辅助工具类(Util)

Util 类提供了一个检查服务是否正在运行的实用方法。

public class Util {
    public static boolean isMyServiceRunning(Class<?> serviceClass, Context context) {
        ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        if (manager != null) {
            for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
                if (serviceClass.getName().equals(service.service.getClassName())) {
                    return true;
                }
            }
        }
        return false;
    }
}

6. 注意事项与最佳实践

  • 权限管理: 务必在应用启动时妥善处理位置权限请求,包括 ACCESS_FINE_LOCATION 和 ACCESS_BACKGROUND_LOCATION(针对 Android 10+)。
  • 前台服务通知: 前台服务必须伴随一个可见的通知。通知的优先级应合理设置,以免过度打扰用户。
  • 生命周期管理: 在 MainActivity 的 onStart() 和 onStop() 方法中正确注册和取消注册 EventBus 订阅者,以避免内存泄漏和不必要的事件处理。同样,服务在 onDestroy() 中应停止位置更新。
  • 服务绑定与解绑: 启动服务后,如果需要与服务进行双向通信(EventBus 主要是单向从服务到 UI),可以绑定服务。但对于本场景,EventBus 已经足够。在停止服务前,确保先解绑。
  • UI 更新: EventBus 的 @Subscribe(threadMode = ThreadMode.MAIN) 确保事件处理方法在主线程执行,这是更新UI的必要条件。
  • 速度精度: Location.getSpeed() 提供的速度可能受GPS信号质量影响,可能存在误差。
  • Firebase 集成: 示例代码使用了 Firebase 实时数据库进行数据存储。在实际项目中,你可以根据需求选择其他本地或云端数据库(如 Room, SQLite, Realm 等)。
  • 设备兼容性: 考虑不同 Android 版本对后台位置访问和前台服务通知的差异,特别是 Android 8.0 (Oreo) 和 Android 10 (Q) 引入的变更。

总结

通过结合 Android 前台服务、FusedLocationProviderClient 和 EventBus,我们可以构建一个健壮的 Android 应用,实现实时速度的获取、显示和后台持久化。前台服务确保了位置更新的持续性,而 EventBus 则提供了一种高效、解耦的方式将后台数据实时同步到用户界面,极大地提升了应用的用户体验和功能稳定性。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
pdf怎么转换成xml格式
pdf怎么转换成xml格式

将 pdf 转换为 xml 的方法:1. 使用在线转换器;2. 使用桌面软件(如 adobe acrobat、itext);3. 使用命令行工具(如 pdftoxml)。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1948

2024.04.01

xml怎么变成word
xml怎么变成word

步骤:1. 导入 xml 文件;2. 选择 xml 结构;3. 映射 xml 元素到 word 元素;4. 生成 word 文档。提示:确保 xml 文件结构良好,并预览 word 文档以验证转换是否成功。想了解更多xml的相关内容,可以阅读本专题下面的文章。

2119

2024.08.01

xml是什么格式的文件
xml是什么格式的文件

xml是一种纯文本格式的文件。xml指的是可扩展标记语言,标准通用标记语言的子集,是一种用于标记电子文件使其具有结构性的标记语言。想了解更多相关的内容,可阅读本专题下面的相关文章。

1168

2024.11.28

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

765

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

765

2023.08.10

location.assign
location.assign

在前端开发中,我们经常需要使用JavaScript来控制页面的跳转和数据的传递。location.assign就是JavaScript中常用的一个跳转方法。通过location.assign,我们可以在当前窗口或者iframe中加载一个新的URL地址,并且可以保存旧页面的历史记录。php中文网为大家带来了location.assign的相关知识、以及相关文章等内容,供大家免费下载使用。

232

2023.06.27

数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

386

2023.06.29

如何删除数据库
如何删除数据库

删除数据库是指在MySQL中完全移除一个数据库及其所包含的所有数据和结构,作用包括:1、释放存储空间;2、确保数据的安全性;3、提高数据库的整体性能,加速查询和操作的执行速度。尽管删除数据库具有一些好处,但在执行任何删除操作之前,务必谨慎操作,并备份重要的数据。删除数据库将永久性地删除所有相关数据和结构,无法回滚。

2111

2023.08.14

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

76

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 4.3万人学习

C# 教程
C# 教程

共94课时 | 11.2万人学习

Java 教程
Java 教程

共578课时 | 81.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号