下面我将从核心问题、解决方案、代码示例和完整适配步骤四个方面,为您详细说明如何进行GPS工具箱的安卓7.0适配。

核心问题与挑战
安卓7.0主要有三个变更会直接影响GPS工具箱的开发:
- HTTP明文流量的限制:这是最常见也最容易出问题的地方,如果你的应用在安卓7.0及以上的设备上,尝试通过非加密的HTTP协议(
http://)去访问网络(下载离线地图、获取NMEA数据流、连接RTK服务器等),系统会直接抛出NetworkOnMainThreadException或类似的安全异常,导致功能失效。 - 后台执行限制:安卓7.0进一步收紧了后台应用的权限,如果你的GPS工具箱在后台持续进行高精度的定位或数据记录,系统可能会为了省电而限制其后台活动,导致定位精度下降或数据记录中断。
- 运行时权限:虽然安卓6.0已经引入了运行时权限,但很多开发者在适配时仍会忽略或处理不当,GPS工具箱通常需要
ACCESS_FINE_LOCATION(精确位置)权限,在安卓7.0上,这个权限的申请和处理逻辑必须正确,否则应用根本无法获取任何位置信息。
核心解决方案
解决HTTP明文流量问题(最重要)
这是安卓7.0适配的首要任务,解决方案有两种,推荐第一种。
全面启用HTTPS(推荐)
这是最安全、最彻底的解决方案,你需要将所有网络请求从http://改为https://。

- 服务器端:确保你的服务器支持HTTPS,并拥有有效的SSL/TLS证书(可以是免费的Let's Encrypt证书)。
- 客户端:修改所有网络请求的URL。
- 下载离线地图:
http://yourserver.com/map.zip->https://yourserver.com/map.zip - 连接NMEA/RTK服务:
tcp://yourserver.com:5000(TCP不受影响) 或http://yourserver.com/nmea->https://yourserver.com/nmea
- 下载离线地图:
在network_security_config.xml中允许特定HTTP(不推荐,仅作临时方案)
如果因为某些原因(连接的是老旧的、不支持HTTPS的硬件设备),你无法立即启用HTTPS,可以在应用中临时允许特定的HTTP域名。
操作步骤:
-
在
res/xml/目录下创建一个文件network_security_config.xml。
(图片来源网络,侵删)<!-- res/xml/network_security_config.xml --> <network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">yourserver.com</domain> <!-- 可以添加多个需要允许HTTP的域名 --> <domain includeSubdomains="true">192.168.1.100</domain> <!-- 允许内网IP --> </domain-config> </network-security-config> -
在
AndroidManifest.xml中为<application>标签添加android:networkSecurityConfig属性。<!-- AndroidManifest.xml --> <application ... android:networkSecurityConfig="@xml/network_security_config" android:usesCleartextTraffic="true" ...> ... </application>android:usesCleartextTraffic="true":这个属性告诉系统,你的应用允许使用明文流量(HTTP),在安卓9(Pie, API 28)及更高版本中,这是必需的。- 注意:此方法会降低应用的安全性,应仅作为过渡方案,并尽快迁移到HTTPS。
解决后台执行限制
GPS工具箱通常需要在后台持续工作,适配策略如下:
使用前台服务
这是最标准、最可靠的方式,前台服务会在状态栏显示一个持续的通知,告诉用户应用正在后台执行任务,从而有效避免系统被杀死。
操作步骤:
-
在
AndroidManifest.xml中声明前台服务权限:<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-
启动服务时,将服务设置为前台服务: 在你的
Service的onStartCommand或onStart方法中,调用startForeground()。// 在你的Service类中 private static final int NOTIFICATION_ID = 1; private static final String CHANNEL_ID = "GPS_Toolbox_Channel"; @Override public int onStartCommand(Intent intent, int flags, int startId) { // 1. 创建通知渠道 (Android 8.0+ 需要) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "GPS Service", NotificationManager.IMPORTANCE_DEFAULT); NotificationManager manager = getSystemService(NotificationManager.class); if (manager != null) { manager.createNotificationChannel(channel); } } // 2. 创建通知 Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("GPS工具箱") .setContentText("正在记录轨迹...") .setSmallIcon(R.drawable.ic_gps_notification) // 必须设置一个图标 .build(); // 3. 启动为前台服务 startForeground(NOTIFICATION_ID, notification); // ... 执行你的GPS记录任务 return START_STICKY; }
优化定位策略
- 降低更新频率:在后台时,可以适当降低位置更新的频率,例如从每秒一次改为每5秒一次,以节省电量。
- 使用
setPriority():在LocationRequest中设置较高的优先级,虽然不能完全保证后台定位,但能提高被系统选中的概率。locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); locationRequest.setInterval(1000); // 1秒 locationRequest.setFastestInterval(500); // 最快0.5秒
处理运行时权限
确保你的权限请求逻辑是健壮的。
标准流程:
-
检查权限:在需要定位功能时,首先检查是否已授权。
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { // 权限未授予,请求权限 } else { // 权限已授予,开始定位 } -
请求权限:使用
ActivityCompat.requestPermissions()。ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, MY_PERMISSIONS_REQUEST_LOCATION); -
处理回调:重写
onRequestPermissionsResult方法处理用户的授权结果。@Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == MY_PERMISSIONS_REQUEST_LOCATION) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 用户授权成功,可以开始定位 startLocationUpdates(); } else { // 用户拒绝授权,可以显示一个解释对话框,并引导用户去设置中手动开启 showPermissionRationale(); } } } -
处理“不再询问”:当用户拒绝权限时,最好检查一下是否勾选了“不再询问”,如果是,则应引导用户到应用设置页面手动开启权限。
// 在用户拒绝权限后调用 private void showPermissionRationale() { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) { // 用户拒绝了权限,但没有勾选“不再询问”,可以再次解释并请求 new AlertDialog.Builder(this) .setTitle("权限需要") .setMessage("此应用需要位置权限才能提供GPS功能。") .setPositiveButton("确定", (dialog, which) -> { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, MY_PERMISSIONS_REQUEST_LOCATION); }) .setNegativeButton("取消", null) .show(); } else { // 用户勾选了“不再询问”,直接跳转到设置页面 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivity(intent); } }
代码示例:一个简化的后台GPS服务
这是一个结合了前台服务和定位权限的简化版服务,可以作为你GPS工具箱的核心。
// GpsService.java
public class GpsService extends Service implements LocationListener {
private LocationManager locationManager;
private Location currentLocation;
private NotificationManager notificationManager;
@Override
public void onCreate() {
super.onCreate();
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForegroundService();
startLocationUpdates();
return START_STICKY;
}
private void startForegroundService() {
// 创建通知渠道 (Android 8.0+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("gps_channel",
"GPS Service",
NotificationManager.IMPORTANCE_LOW);
notificationManager.createNotificationChannel(channel);
}
Notification notification = new NotificationCompat.Builder(this, "gps_channel")
.setContentTitle("GPS工具箱")
.setContentText("正在后台定位...")
.setSmallIcon(R.drawable.ic_gps) // 请确保有这个图标
.build();
startForeground(1, notification);
}
private void startLocationUpdates() {
// 检查权限
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// 权限不足,停止服务
stopSelf();
return;
}
LocationRequest locationRequest = LocationRequest.create();
locationRequest.setInterval(1000);
locationRequest.setFastestInterval(500);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
try {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
locationRequest.getInterval(),
locationRequest.getSmallestDisplacement(),
this,
Looper.getMainLooper()); // 使用主线程的Looper处理回调
} catch (SecurityException e) {
e.printStackTrace();
}
}
@Override
public void onLocationChanged(Location location) {
this.currentLocation = location;
// 在这里处理位置数据,例如保存到文件或发送到服务器
Log.d("GpsService", "Location: " + location.getLatitude() + ", " + location.getLongitude());
}
@Override
public void onDestroy() {
super.onDestroy();
if (locationManager != null) {
locationManager.removeUpdates(this);
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
完整的安卓7.0适配清单
-
检查
targetSdkVersion:确保你的build.gradle文件中的targetSdkVersion已经设置为24或更高。android { compileSdkVersion 34 defaultConfig { targetSdkVersion 24 // 至少为24 ... } } -
权限处理:
- 在
AndroidManifest.xml中声明所有需要的权限,特别是ACCESS_FINE_LOCATION和FOREGROUND_SERVICE。 - 在运行时动态请求
ACCESS_FINE_LOCATION,并妥善处理用户拒绝和“不再询问”的情况。
- 在
-
网络配置:
- 强烈建议:将所有HTTP链接改为HTTPS。
- 临时方案:如果必须使用HTTP,创建
res/xml/network_security_config.xml并在AndroidManifest.xml中引用它,同时设置android:usesCleartextTraffic="true"。
-
后台服务:
- 如果应用需要在后台持续定位,请务必使用前台服务。
- 在
AndroidManifest.xml中声明FOREGEND_SERVICE权限。 - 在服务启动时调用
startForeground()并提供一个合适的通知。
-
测试:
- 在一台运行安卓7.0的真机上进行全面测试,模拟器有时无法完全模拟真实环境。
- 重点测试以下场景:
- 首次启动,权限申请流程是否顺畅?
- 切换到桌面,GPS记录是否继续?通知是否正常显示?
- 尝试通过HTTP下载文件,是否成功?(如果使用了HTTPS,则测试HTTPS链接)
- 在设置中手动关闭你的应用的定位权限,应用行为是否正确?
通过以上步骤,你的GPS工具箱就能很好地适配安卓7.0系统了。
