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