我在Android O操作系统上使用服务类。
我计划在后台使用服务。
Android文档指出
如果你的应用程序的API级别为26或更高,系统会对使用或创建后台服务施加限制,除非应用程序本身在前台。如果应用程序需要创建前台服务,应用程序应该调用startForegroundService()。
如果使用startForegroundService(),服务抛出以下错误。
Context.startForegroundService() did not then call
Service.startForeground()
这有什么问题?
我有一个解决这个问题的办法。我已经在自己的应用(DAU超过30万)中验证了这一修复方法,它至少可以减少95%的这种崩溃,但仍然不能100%避免这个问题。
即使您确保在服务启动后调用start前台(),也会发生此问题。这可能是因为在很多情况下,服务创建和初始化过程已经花费了超过5秒的时间,那么无论何时何地调用startForeground()方法,这个崩溃都是不可避免的。
我的解决方案是确保start前台()将在startForegroundService()方法后的5秒内执行,无论您的服务需要创建和初始化多长时间。下面是详细的解决方案。
Do not use startForegroundService at the first place, use bindService() with auto_create flag. It will wait for the service initialization. Here is the code, my sample service is MusicService:
final Context applicationContext = context.getApplicationContext();
Intent intent = new Intent(context, MusicService.class);
applicationContext.bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
if (binder instanceof MusicBinder) {
MusicBinder musicBinder = (MusicBinder) binder;
MusicService service = musicBinder.getService();
if (service != null) {
// start a command such as music play or pause.
service.startCommand(command);
// force the service to run in foreground here.
// the service is already initialized when bind and auto_create.
service.forceForeground();
}
}
applicationContext.unbindService(this);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, Context.BIND_AUTO_CREATE);
Then here is MusicBinder implementation:
/**
* Use weak reference to avoid binder service leak.
*/
public class MusicBinder extends Binder {
private WeakReference<MusicService> weakService;
/**
* Inject service instance to weak reference.
*/
public void onBind(MusicService service) {
this.weakService = new WeakReference<>(service);
}
public MusicService getService() {
return weakService == null ? null : weakService.get();
}
}
The most important part, MusicService implementation, forceForeground() method will ensure that startForeground() method is called just after service start:
public class MusicService extends MediaBrowserServiceCompat {
...
private final MusicBinder musicBind = new MusicBinder();
...
@Override
public IBinder onBind(Intent intent) {
musicBind.onBind(this);
return musicBind;
}
...
public void forceForeground() {
// API lower than 26 do not need this work around.
if (Build.VERSION.SDK_INT >= 26) {
Notification notification = mNotificationHandler.createNotification(this);
// call startForeground just after service start.
startForeground(Constants.NOTIFICATION_ID, notification);
}
}
}
If you want to run the step 1 code snippet in a pending intent, such as if you want to start a foreground service in a widget (a click on widget button) without opening your app, you can wrap the code snippet in a broadcast receiver, and fire a broadcast event instead of start service command.
仅此而已。希望能有所帮助。祝你好运。
我有一个小部件,当设备处于清醒状态时,它会相对频繁地进行更新,我在短短几天内就看到了数千次崩溃。
问题触发
我甚至在我的Pixel 3 XL上也注意到了这个问题,而我本以为这款设备负载并不大。所有代码路径都被startForeground()覆盖。但后来我意识到,在很多情况下,我的服务可以很快地完成工作。我认为触发我的应用程序的原因是,在系统真正抽出时间显示通知之前,服务已经结束。
解决方案/解决方案
我摆脱了所有的崩溃。我所做的就是删除对stopSelf()的调用。(我正在考虑延迟停止,直到我非常确定通知被显示,但我不希望用户看到通知,如果它不是必要的。)当服务空闲一分钟或系统正常销毁它而不抛出任何异常时。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(true);
} else {
stopSelf();
}
服务
class TestService : Service() {
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate")
val nBuilder = NotificationCompat.Builder(this, "all")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("TestService")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
startForeground(1337, nBuilder.build())
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val rtn = super.onStartCommand(intent, flags, startId)
if (intent?.action == STOP_ACTION) {
Log.d(TAG, "onStartCommand -> STOP")
stopForeground(true)
stopSelf()
} else {
Log.d(TAG, "onStartCommand -> START")
}
return rtn
}
override fun onDestroy() {
Log.d(TAG, "onDestroy")
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? = null
companion object {
private val TAG = "TestService"
private val STOP_ACTION = "ly.zen.test.TestService.ACTION_STOP"
fun start(context: Context) {
ContextCompat.startForegroundService(context, Intent(context, TestService::class.java))
}
fun stop(context: Context) {
val intent = Intent(context, TestService::class.java)
intent.action = STOP_ACTION
ContextCompat.startForegroundService(context, intent)
}
}
}
测试人员
val nChannel = NotificationChannel("all", "All", NotificationManager.IMPORTANCE_NONE)
val nManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nManager.createNotificationChannel(nChannel)
start_test_service.setOnClickListener {
TestService.start(this@MainActivity)
TestService.stop(this@MainActivity)
}
结果
D/TestService: onCreate
D/TestService: onStartCommand -> START
D/TestService: onStartCommand -> STOP
D/TestService: onDestroy
请不要在onCreate()方法中调用任何StartForgroundServices,你必须在onStartCommand()中调用StartForground services,否则你将总是得到ANR,所以请不要在onStartCommand()的主线程中编写复杂的登录;
public class Services extends Service {
private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
.setContentTitle(getString(R.string.app_name))
.setContentText("SmartTracker Running")
.setAutoCancel(true);
Notification notification = builder.build();
startForeground(1, notification);
Log.e("home_button","home button");
} else {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setContentTitle(getString(R.string.app_name))
.setContentText("SmartTracker is Running...")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true);
Notification notification = builder.build();
startForeground(1, notification);
Log.e("home_button_value","home_button_value");
}
return super.onStartCommand(intent, flags, startId);
}
}
编辑:小心!start前台函数不能以0作为第一个参数,它将引发异常!这个例子包含错误的函数调用,将0更改为你自己的const,它不能为0或大于Max(Int32)
我也面临着同样的问题,花时间找到了一个解决方案,你可以尝试下面的代码。如果你使用服务,然后把这段代码放在onCreate,否则你使用意图服务,然后把这段代码放在onHandleIntent。
if (Build.VERSION.SDK_INT >= 26) {
String CHANNEL_ID = "my_app";
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
"MyApp", NotificationManager.IMPORTANCE_DEFAULT);
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("")
.setContentText("").build();
startForeground(1, notification);
}