文章目录
 问题描述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使用通知创建前台服务