Android GPS 初涉

GPS(Global Positioning System,全局定位系统)是 Android 设备中用于获取地理位置信息的重要功能,通过卫星信号确定设备的经度、纬度、海拔等数据。结合 Android 动画合集的背景,GPS 数据可以驱动动态动画效果,例如在地图上显示用户位置的移动动画,或根据位置变化触发 UI 交互。本文将介绍 Android GPS 的基本概念、工作原理、使用方法及与动画结合的示例,适合初学者快速入门。

GPS 的作用与原理

  • 作用:Android GPS 提供设备的位置信息(经纬度、海拔、速度等),用于导航、定位、位置分享或基于位置的服务(LBS)。它通过设备内置的 GPS 芯片接收卫星信号,结合其他定位技术(如 Wi-Fi、蜂窝网络)提高精度。
  • 原理:GPS 模块与多颗卫星通信,基于三角测量计算设备位置。Android 通过 LocationManager 或 Google Play Services 的 FusedLocationProviderClient 提供位置数据,融合 GPS、Wi-Fi 和基站定位以优化性能。
  • 应用场景
  • 导航应用:实时显示用户位置(如 Google Maps)。
  • 动画驱动:根据位置变化触发视图移动或缩放动画。
  • 位置记录:跟踪运动轨迹(如跑步应用)。
  • 地理围栏:检测用户进入/离开特定区域,触发动画或通知。
  • AR 应用:结合位置和方向传感器实现增强现实效果。

权限要求

使用 GPS 需要以下权限,在 AndroidManifest.xml 中声明:

<!-- 粗略定位(Wi-Fi/基站) -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 精确定位(GPS) -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- 后台定位(API 29+) -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
  • 动态权限(API 23+,Android 6.0+):
  • ACCESS_FINE_LOCATION:精确 GPS 定位。
  • ACCESS_COARSE_LOCATION:粗略定位。
  • ACCESS_BACKGROUND_LOCATION:后台定位(API 29+)。
  • 使用 ActivityCompat.requestPermissions 请求权限。
  • 注意:API 30+(Android 11+)需明确声明定位类型(前台/后台),并可能需要用户手动授权。

获取 GPS 数据

Android 提供两种主要方式获取位置数据:

  1. LocationManager(传统方式):
  • 系统服务,直接访问 GPS 或网络定位。
  • 适合简单场景,但功能较基础。
  1. FusedLocationProviderClient(推荐,Google Play Services):
  • 融合 GPS、Wi-Fi 和基站定位,精度更高,功耗更低。
  • 需要 Google Play Services 支持。

常用 API

以下是 GPS 相关的核心类和方法:

类/方法描述用法示例
LocationManager系统服务,管理位置提供者(GPS、网络)。LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
LocationManager.requestLocationUpdates()请求位置更新。lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 10, listener);
FusedLocationProviderClientGoogle Play Services 提供的高级定位 API。FusedLocationProviderClient client = LocationServices.getFusedLocationProviderClient(context);
FusedLocationProviderClient.getLastLocation()获取最后已知位置。client.getLastLocation().addOnSuccessListener(location -> {});
FusedLocationProviderClient.requestLocationUpdates()请求实时位置更新。client.requestLocationUpdates(request, callback, null);
  • 定位提供者LocationManager):
  • GPS_PROVIDER:GPS 定位,精度高,耗电高。
  • NETWORK_PROVIDER:Wi-Fi/基站定位,精度较低,耗电低。
  • PASSIVE_PROVIDER:被动接收其他应用的位置更新。

完整示例(FusedLocationProviderClient + 动画)

以下是一个使用 FusedLocationProviderClient 获取 GPS 位置并驱动 ImageView 移动动画的示例,显示用户位置变化。

import android.Manifest;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.animation.ObjectAnimator;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;

public class GPSActivity extends AppCompatActivity {
    private FusedLocationProviderClient fusedLocationClient;
    private ImageView locationMarker;
    private TextView locationText;
    private ObjectAnimator translateXAnimator;
    private ObjectAnimator translateYAnimator;
    private LocationCallback locationCallback;
    private static final int PERMISSION_REQUEST_CODE = 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gps);

        locationMarker = findViewById(R.id.location_marker);
        locationText = findViewById(R.id.location_text);
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);

        // 初始化动画
        translateXAnimator = ObjectAnimator.ofFloat(locationMarker, "translationX", 0f);
        translateXAnimator.setDuration(500);
        translateYAnimator = ObjectAnimator.ofFloat(locationMarker, "translationY", 0f);
        translateYAnimator.setDuration(500);

        // 检查权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityResultLauncher<String> permissionLauncher = registerForActivityResult(
                new ActivityResultContracts.RequestPermission(),
                isGranted -> {
                    if (isGranted) {
                        startLocationUpdates();
                    } else {
                        Toast.makeText(this, "Location permission denied", Toast.LENGTH_SHORT).show();
                    }
                }
            );
            permissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION);
        } else {
            startLocationUpdates();
        }
    }

    private void startLocationUpdates() {
        LocationRequest locationRequest = LocationRequest.create()
            .setInterval(5000) // 更新间隔 5 秒
            .setFastestInterval(2000) // 最快更新间隔
            .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); // 高精度(GPS)

        locationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(LocationResult locationResult) {
                if (locationResult == null) return;
                Location location = locationResult.getLastLocation();
                if (location != null) {
                    // 更新 UI
                    locationText.setText(String.format("Lat: %.4f, Lon: %.4f", location.getLatitude(), location.getLongitude()));

                    // 模拟动画:将经纬度映射到屏幕坐标
                    float x = (float) (location.getLongitude() * 10); // 简单映射
                    float y = (float) (location.getLatitude() * 10);
                    translateXAnimator.setFloatValues(locationMarker.getTranslationX(), x);
                    translateYAnimator.setFloatValues(locationMarker.getTranslationY(), -y);
                    translateXAnimator.start();
                    translateYAnimator.start();
                }
            }
        };

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (locationCallback != null) {
            fusedLocationClient.removeLocationUpdates(locationCallback);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        translateXAnimator.cancel();
        translateYAnimator.cancel();
    }
}
  • 布局 XML(res/layout/activity_gps.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    android:padding="16dp">
    <ImageView
        android:id="@+id/location_marker"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/location_icon" />
    <TextView
        android:id="@+id/location_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Lat: 0.0, Lon: 0.0"
        android:textSize="18sp" />
</LinearLayout>
  • AndroidManifest.xml(添加权限):
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  • 说明
  • 权限:动态请求 ACCESS_FINE_LOCATION
  • 定位:使用 FusedLocationProviderClient 请求高精度位置更新(每 5 秒)。
  • 动画:根据经纬度变化驱动 ImageView 的平移动画(translationXtranslationY)。
  • UI 更新:实时显示经纬度。
  • 生命周期:在 onPause 停止位置更新,onDestroy 取消动画。
  • 资源:需要一个位置图标(如 res/drawable/location_icon.png)。

使用 LocationManager(传统方式)

以下是使用 LocationManager 获取 GPS 数据的简单示例:

LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
LocationListener locationListener = new LocationListener() {
    @Override
    public void onLocationChanged(Location location) {
        locationText.setText(String.format("Lat: %.4f, Lon: %.4f", location.getLatitude(), location.getLongitude()));
        float x = (float) (location.getLongitude() * 10);
        float y = (float) (location.getLatitude() * 10);
        translateXAnimator.setFloatValues(locationMarker.getTranslationX(), x);
        translateYAnimator.setFloatValues(locationMarker.getTranslationY(), -y);
        translateXAnimator.start();
        translateYAnimator.start();
    }
};

if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
    locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 10, locationListener);
}
  • 注销监听
@Override
protected void onPause() {
    super.onPause();
    locationManager.removeUpdates(locationListener);
}

结合振动

结合 Vibrator(前文介绍),当位置显著变化时触发振动:

private Location lastLocation;

@Override
public void onLocationChanged(Location location) {
    if (lastLocation != null && location.distanceTo(lastLocation) > 50) { // 移动超过 50 米
        Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
        } else {
            vibrator.vibrate(100);
        }
    }
    lastLocation = location;
}

优缺点

  • 优点
  • GPS 提供高精度位置数据,适合导航和 LBS 应用。
  • FusedLocationProviderClient 优化了功耗和精度。
  • 易于与动画结合,增强交互性。
  • 缺点
  • GPS 定位耗电量高,室内信号弱。
  • 需动态权限,增加开发复杂性。
  • 首次定位可能较慢(冷启动)。
  • 替代方案
  • Network Provider:Wi-Fi/基站定位,功耗低但精度较低。
  • Google Play Services Geofencing:地理围栏功能。
  • Jetpack Compose:声明式 UI 显示位置数据。
  • Third-party Libraries:如 Mapbox 或 OpenStreetMap。

注意事项

  • 权限:API 23+ 需动态请求 ACCESS_FINE_LOCATION
  • GPS 可用性:检查 GPS 是否启用(locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER))。
  • 功耗:设置合理的更新间隔(如 5 秒),避免高频请求。
  • 生命周期:在 onPause 停止位置更新,避免资源泄漏。
  • 信号问题:室内或信号弱时,结合网络定位提高可靠性。
  • 调试:通过 Log 检查位置数据和动画效果。

扩展:Jetpack Compose 中的实现

在 Jetpack Compose 中,结合 GPS 数据驱动动画:

import android.Manifest
import android.content.Context
import android.location.Location
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts

@Composable
fun GPSScreen() {
    val context = LocalContext.current
    val fusedLocationClient = remember { LocationServices.getFusedLocationProviderClient(context) }
    var location by remember { mutableStateOf<Location?>(null) }
    val xTranslation by animateFloatAsState(
        targetValue = location?.longitude?.toFloat()?.times(10) ?: 0f,
        animationSpec = tween(500)
    )
    val yTranslation by animateFloatAsState(
        targetValue = location?.latitude?.toFloat()?.times(-10) ?: 0f,
        animationSpec = tween(500)
    )

    val permissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
        if (isGranted) {
            startLocationUpdates(context, fusedLocationClient) { loc -> location = loc }
        }
    }

    LaunchedEffect(Unit) {
        if (androidx.core.content.ContextCompat.checkSelfPermission(
                context,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) == android.content.pm.PackageManager.PERMISSION_GRANTED
        ) {
            startLocationUpdates(context, fusedLocationClient) { loc -> location = loc }
        } else {
            permissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
        }
    }

    Column(
        modifier = Modifier.fillMaxSize().padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Image(
            painter = painterResource(id = R.drawable.location_icon),
            contentDescription = "Location Marker",
            modifier = Modifier
                .size(50.dp)
                .offset(x = xTranslation.dp, y = yTranslation.dp)
        )
        Text(
            text = location?.let { String.format("Lat: %.4f, Lon: %.4f", it.latitude, it.longitude) } ?: "Waiting for location...",
            fontSize = 18.sp
        )
    }
}

private fun startLocationUpdates(
    context: Context,
    client: FusedLocationProviderClient,
    onLocationUpdate: (Location) -> Unit
) {
    val locationRequest = LocationRequest.create()
        .setInterval(5000)
        .setFastestInterval(2000)
        .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)

    val locationCallback = object : LocationCallback() {
        override fun onLocationResult(result: LocationResult) {
            result.lastLocation?.let { onLocationUpdate(it) }
        }
    }

    if (androidx.core.content.ContextCompat.checkSelfPermission(
            context,
            Manifest.permission.ACCESS_FINE_LOCATION
        ) == android.content.pm.PackageManager.PERMISSION_GRANTED
    ) {
        client.requestLocationUpdates(locationRequest, locationCallback, null)
    }
}
  • 说明
  • 使用 rememberLauncherForActivityResult 请求定位权限。
  • FusedLocationProviderClient 获取实时位置更新。
  • animateFloatAsState 驱动 Image 的平移动画。
  • 显示实时经纬度。

如果需要更复杂的功能(如地理围栏、轨迹绘制、结合 WindowManager 创建悬浮定位 UI、或与地图 SDK 集成),或想探讨其他传感器或动画效果,请告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注