
开发一个能够实时显示当前速度并将其持续保存的应用,主要面临以下挑战:
为了解决这些问题,我们将采用前台服务(Foreground Service)来处理位置更新,并使用EventBus库作为服务与Activity之间通信的桥梁。
首先,在 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() 中调用权限请求逻辑
}LocationService 将作为前台服务运行,负责持续获取位置更新、计算速度,并将数据发送到UI和保存到数据库。
为了使服务在后台持续运行,需要将其提升为前台服务。这意味着系统会显示一个持续的通知,告知用户有服务正在运行。
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);
}
}使用 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() 等方法
}EventBus 是一个用于 Android 的发布/订阅事件总线,可以简化组件之间的通信。
在 build.gradle (app) 文件中添加 EventBus 依赖:
dependencies {
implementation 'org.greenrobot:eventbus:3.3.1' // 使用最新版本
}创建一个简单的事件类,用于封装要传递的数据(例如,位置信息)。
// 在一个单独的文件,例如 MessageEvents.java 中定义
public class MessageEvents {
public static class NewGPSCoordinates {
public final Location location;
public NewGPSCoordinates(Location location) {
this.location = location;
}
}
// 可以定义其他事件
}在 LocationService 的 onLocationResult 方法中,当获取到新的位置数据时,发布一个 NewGPSCoordinates 事件。
// LocationService.java -> onLocationResult()
if (location != null && location.hasSpeed()) {
currentSpeed = location.getSpeed() * 3.6f;
// ... 保存数据到 Firebase
// 发布事件
EventBus.getDefault().post(new MessageEvents.NewGPSCoordinates(location));
}在 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();
}
}
}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;
}
}通过结合 Android 前台服务、FusedLocationProviderClient 和 EventBus,我们可以构建一个健壮的 Android 应用,实现实时速度的获取、显示和后台持久化。前台服务确保了位置更新的持续性,而 EventBus 则提供了一种高效、解耦的方式将后台数据实时同步到用户界面,极大地提升了应用的用户体验和功能稳定性。
以上就是实时Android速度显示与数据保存应用开发指南的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号