通过Notification解决Android8.0下后台无定位的问题,兼容Android 9、Android 10

mac2025-05-15  14

文章目录

问题描述MainActivity.java 解决方法NotificationUtil.javaServiceNotification.javaAndroidManifest.xmlMainActivity.java Android 8.0 新特性后台服务限制广播限制 Android 9 兼容Android 10 兼容参考资料

问题描述

在Android 8.0上获取定位数据,在程序切换到后台时,系统无定位数据输出。

$GNGGA,080930.174,2259.0118,N,11322.0587,E,1,24,0.56,39.0,M,-6.0,M,,*5B $GNGGA,080931.174,2259.0118,N,11322.0587,E,1,24,0.56,38.8,M,-6.0,M,,*53 $GNGGA,080932.175,2259.0119,N,11322.0589,E,1,24,0.56,38.8,M,-6.0,M,,*5E $GNGGA,080933.174,2259.0120,N,11322.0589,E,1,24,0.56,38.5,M,-6.0,M,,*59 $GNGGA,080934.175,2259.0120,N,11322.0589,E,1,24,0.56,38.4,M,-6.0,M,,*5E $GNGGA,080949.377,2259.0120,N,11322.0589,E,0,0,,38.4,M,-6.0,M,,*7E $GNGGA,080950.378,2259.0120,N,11322.0589,E,1,21,0.59,38.3,M,-6.0,M,,*5E $GNGGA,080951.878,2259.0123,N,11322.0588,E,1,23,0.57,37.3,M,-6.0,M,,*55 $GNGGA,080952.879,2259.0125,N,11322.0588,E,1,23,0.57,36.9,M,-6.0,M,,*5A $GNGGA,080953.879,2259.0126,N,11322.0589,E,1,24,0.54,36.6,M,-6.0,M,,*52

在9分34秒后开始丢失数据,到9分49秒重新切换程序到前台时恢复。

MainActivity.java

public class MainActivity extends AppCompatActivity { /** * 位置管理器 */ private LocationManager locationManager = null; /** * 保存NMEA */ private FileOutputStream fos = null; private TextView tvProvider = null; private TextView tvTime = null; private TextView tvLatitude = null; private TextView tvLongitude = null; private TextView tvAltitude = null; private TextView tvBearing = null; private TextView tvSpeed = null; private TextView tvAccuracy = null; private CheckBox cbSaveNmea = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); App.getInstance().setMainActivity(this); tvProvider = findViewById(R.id.tv_provider); tvTime = findViewById(R.id.tv_time); tvLatitude = findViewById(R.id.tv_latitude); tvLongitude = findViewById(R.id.tv_longitude); tvAltitude = findViewById(R.id.tv_altitude); tvBearing = findViewById(R.id.tv_bearing); tvSpeed = findViewById(R.id.tv_speed); tvAccuracy = findViewById(R.id.tv_accuracy); cbSaveNmea = findViewById(R.id.cb_save); locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); // 注册位置服务,获取系统位置 locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, locationListener); // 监听NMEA locationManager.addNmeaListener(nmeaListener); } @Override protected void onDestroy() { if (fos != null) { try { fos.flush(); fos.close(); } catch (IOException e) { e.printStackTrace(); } fos = null; } locationManager.removeNmeaListener(nmeaListener); locationManager.removeUpdates(locationListener); super.onDestroy(); } /** * 位置监听 */ private LocationListener locationListener = new LocationListener() { @Override public void onLocationChanged(final Location location) { try { runOnUiThread(new Runnable() { @Override public void run() { tvProvider.setText(location.getProvider()); tvTime.setText(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(location.getTime()))); tvLatitude.setText(location.getLatitude() + " °"); tvLongitude.setText(location.getLongitude() + " °"); tvAltitude.setText(location.getAltitude() + " m"); tvBearing.setText(location.getBearing() + " °"); tvSpeed.setText(location.getSpeed() + " m/s"); tvAccuracy.setText(location.getAccuracy() + " m"); } }); } catch (Exception ex) { ex.printStackTrace(); } } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public void onProviderEnabled(String provider) { } @Override public void onProviderDisabled(String provider) { } }; /** * NMEA监听 */ private GpsStatus.NmeaListener nmeaListener = new GpsStatus.NmeaListener() { @Override public void onNmeaReceived(long timestamp, String nmea) { if (cbSaveNmea.isChecked() == false || nmea.contains("GGA") == false) { return; } if (fos == null) { String file = Environment.getExternalStorageDirectory() + File.separator + "nmea.txt"; try { fos = new FileOutputStream(file); } catch (FileNotFoundException e) { e.printStackTrace(); } } if (fos != null) { try { fos.write(nmea.getBytes()); } catch (IOException e) { e.printStackTrace(); } } } }; }

解决方法

在程序切换到后台执行时,使用通知创建前台服务,以便强制程序在前台运行。

添加通知辅助类 NotificationUtil添加创建通知的服务类 ServiceNotificationAndroidManifest 中注册服务MainActivity 中启动服务

NotificationUtil.java

public class NotificationUtil { public static final int NOTIFICATION_ID = 1; private Activity ctx = null; /** * 通知管理器 */ private NotificationManager notificationManager = null; /** * 通知 */ private Notification notification = null; public Notification getNotification() { return notification; } /** * 通知的事件消息 */ private PendingIntent pendingIntent = null; public NotificationUtil(Activity context, int icon, String title, String message, String channelId, String channelName) { this.ctx = context; // 创建一个NotificationManager的引用 notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // importance: 通知重要性 // IMPORTANCE_HIGH:紧急级别(发出通知声音并显示为提示通知) // IMPORTANCE_DEFAULT:高级别(发出通知声音并且通知栏有通知) // IMPORTANCE_LOW:中等级别(没有通知声音但通知栏有通知) // IMPORTANCE_MIN:低级别(没有通知声音也不会出现在状态栏) NotificationChannel channel = new NotificationChannel( channelId, channelName, NotificationManager.IMPORTANCE_LOW); //是否绕过请勿打扰模式 channel.canBypassDnd(); //设置可绕过请勿打扰模式 channel.setBypassDnd(true); //锁屏显示通知 channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET); //桌面launcher的消息角标 channel.canShowBadge(); //获取通知取到组 channel.getGroup(); // //闪光灯 // channel.enableLights(true); // //闪关灯的灯光颜色 // channel.setLightColor(Color.RED); // //是否会有灯光 // channel.shouldShowLights(); // // //是否允许震动 // channel.enableVibration(true); // //获取系统通知响铃声音的配置 // channel.getAudioAttributes(); // //设置震动模式 // channel.setVibrationPattern(new long[]{100, 100, 200}); notificationManager.createNotificationChannel(channel); } // 定义Notification的各种属性 NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId) .setContentTitle(title) .setContentText(message) .setSmallIcon(icon) .setAutoCancel(true)//用户触摸时,自动关闭 .setOngoing(true);//设置处于运行状态 // 设置通知的事件消息 Intent notificationIntent = new Intent(ctx, ctx.getClass()); notificationIntent.setAction(Intent.ACTION_MAIN); notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER); pendingIntent = PendingIntent.getActivity( ctx, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent(pendingIntent); notification = builder.build(); } /** * 显示Notification */ public void showNotification() { notificationManager.notify(NOTIFICATION_ID, notification); } /** * 取消通知 */ public void cancelNotification(){ notificationManager.cancel(NOTIFICATION_ID); } }

ServiceNotification.java

public class ServiceNotification extends Service { public static final String EXTRA_NOTIFICATION_CONTENT = "notification_content"; private static final String CHANNEL_ID = "com.xd.gps"; private static final String CHANNEL_NAME = "Default Channel"; private NotificationUtil notificationUtil; @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent == null) { return START_NOT_STICKY; } String content = intent.getStringExtra(EXTRA_NOTIFICATION_CONTENT); notificationUtil = new NotificationUtil(App.getInstance().getMainActivity(), R.mipmap.ic_launcher, getString(R.string.app_name), content, CHANNEL_ID, CHANNEL_NAME); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForeground(NotificationUtil.NOTIFICATION_ID, notificationUtil.getNotification()); } else { notificationUtil.showNotification(); } return START_STICKY; } @Override public void onDestroy() { if (notificationUtil != null) { notificationUtil.cancelNotification(); notificationUtil = null; } super.onDestroy(); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }

AndroidManifest.xml

<!-- 通知栏 --> <service android:name=".ServiceNotification" />

MainActivity.java

onPause() 中开启通知栏 serviceForegroundIntent = new Intent(this, ServiceNotification.class); serviceForegroundIntent.putExtra(ServiceNotification.EXTRA_NOTIFICATION_CONTENT, "test test test"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(serviceForegroundIntent); } else { startService(serviceForegroundIntent); } onResume() 中销毁通知栏 if (serviceForegroundIntent != null) { stopService(serviceForegroundIntent); serviceForegroundIntent = null; } onDestroy() 中销毁通知栏 if (serviceForegroundIntent != null) { stopService(serviceForegroundIntent); serviceForegroundIntent = null; }

Android 8.0 新特性

Android 8.0(即Android O)对应用在后台运行时可以执行的操作施加了限制,称为后台执行限制(Background Execution Limits),这可以大大减少应用的内存使用和耗电量,提高用户体验。

后台执行限制分为两个部分:后台服务限制(Background Service Limitations)、广播限制(BroadcastLimitations)。

后台服务限制

除了下面情况外都是后台应用:

具有可见的Activity

具有前台服务

另一个前台应用已关联到该应用(通过bindService或者使用该应用的ContentProvider)。

当应用处于后台时:

1.在后台运行的服务在几分钟内会被stop掉(模拟器测试在1分钟左右后被kill掉)。在这段时间内,应用仍可以创建和使用服务。

2.在应用处于后台几分钟后(模拟器测试1分钟左右),应用将不能再通过startService创建后台服务,如果创建则抛出IllegalStateException异常。

广播限制

如果应用监听一些系统广播,当系统发出广播时,很多应用都会被唤醒,这会导致所有应用快速地连续消耗资源,从而降低用户体验。其实,大部分应用都不会处理这个广播,应用只是唤醒一下看看和自己是否有关,为了缓解这一问题,Android N对一些广播做出了限制:

1.targetSdkVersion为 Android N(API level 24)及以上的应用,如果应用在AndroidManifest.xml中静态注册CONNECTIVITY_ACTION这个 receiver,应用将不能收到此广播。如果应用使用Context.registerReceiver()动态注册 receiver,应用仍可以收到这个广播。

2.运行在 Android N 及以上设备的应用,无论是targetSdkVersion是否是 Android N,应用都不能发送或者接收ACTION_NEW_PICTURE和ACTION_NEW_VIDEO这两个广播。

而 Android O 执行了更为严格的限制:

1.动态注册的 receiver,可接收任何显式和隐式广播。

2.targetSdkVersion为 Android O(API level 26)及以上的应用,静态注册的 receiver 将不能收到隐式广播,但可以收到显式广播。


Android 9 兼容

AndroidManifest.xml中需要添加前台服务权限:

<!--前台服务权限,Android 9 新增,调用Service.startForeground必须添加该权限--> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

Android 10 兼容

Android 10 定位问题,获取NMEA(支持5.0~10.0)


参考资料

Android 8.0后台执行限制解决Android 8.0 的Notification不显示问题Android8.0使用通知创建前台服务
最新回复(0)