Fix ANR in service launch

Some ANRs are reported on Google Play. This is an atempt to remove them, by making sure that `startForeground` is always called when the service starts.
This commit is contained in:
Synced Synapse 2019-05-11 15:52:01 +01:00 committed by Martijn Brekhof
parent c81e553589
commit c7fef43deb
3 changed files with 111 additions and 120 deletions

View File

@ -857,7 +857,8 @@ public class HostConnectionObserver
private boolean getItemResultChanged(ListType.ItemsAll getItemResult) {
return (hostState.lastGetItemResult == null) ||
(hostState.lastGetItemResult.id != getItemResult.id) ||
(!hostState.lastGetItemResult.label.equals(getItemResult.label));
((hostState.lastGetItemResult.label != null &&
!hostState.lastGetItemResult.label.equals(getItemResult.label)));
}
/**

View File

@ -49,13 +49,13 @@ public class ConnectionObserversManagerService extends Service
implements HostConnectionObserver.PlayerEventsObserver {
public static final String TAG = LogUtils.makeLogTag(ConnectionObserversManagerService.class);
private HostConnectionObserver mHostConnectionObserver = null;
private HostConnectionObserver hostConnectionObserver = null;
private List<HostConnectionObserver.PlayerEventsObserver> mConnectionObservers = new ArrayList<>();
private NotificationObserver mNotificationObserver;
private List<HostConnectionObserver.PlayerEventsObserver> observers = new ArrayList<>();
private NotificationObserver notificationObserver;
private boolean somethingPlaying = false;
private Handler mStopHandler = new Handler();
private boolean somethingIsPlaying = false;
private Handler stopHandler = new Handler();
@Override
public void onCreate() {
@ -67,59 +67,39 @@ public class ConnectionObserversManagerService extends Service
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.LOGD(TAG, "onStartCommand");
// Create the observers we are managing
createObservers();
// If no observers created, stop immediately
if (mConnectionObservers.isEmpty()) {
LogUtils.LOGD(TAG, "No observers, stopping observer service.");
stopSelf();
return START_NOT_STICKY;
// Create the observers we are managing and immediatelly call
// startForeground() to avoid ANRs
if (observers.isEmpty()) {
createObservers();
}
startForeground(NotificationObserver.NOTIFICATION_ID,
notificationObserver.getCurrentNotification());
HostConnectionObserver connectionObserver =
HostManager.getInstance(this).getHostConnectionObserver();
if (hostConnectionObserver == null) {
hostConnectionObserver = connectionObserver;
hostConnectionObserver.registerPlayerObserver(this, true);
} else if (hostConnectionObserver != connectionObserver) {
// There has been a change in hosts.
// Unregister the previous one and register the current one
hostConnectionObserver.unregisterPlayerObserver(this);
hostConnectionObserver = connectionObserver;
hostConnectionObserver.registerPlayerObserver(this, true);
}
// Get the connection observer here, not on create to check if
// there has been a change in hosts, and if so unregister the previous one
HostConnectionObserver connectionObserver = HostManager.getInstance(this).getHostConnectionObserver();
// If we are already initialized and the same host, exit
if (mHostConnectionObserver == connectionObserver) {
LogUtils.LOGD(TAG, "Already initialized");
return START_STICKY;
}
// Create the observers we are managing
createObservers();
if (mConnectionObservers.isEmpty()) {
stopForeground(true);
stopSelf();
return START_NOT_STICKY;
}
startForeground(NotificationObserver.NOTIFICATION_ID, mNotificationObserver.getNothingPlayingNotification());
// If there's a change in hosts, unregister from the previous one
if (mHostConnectionObserver != null) {
mHostConnectionObserver.unregisterPlayerObserver(this);
}
// Register us on the connection observer
mHostConnectionObserver = connectionObserver;
mHostConnectionObserver.registerPlayerObserver(this, true);
// If we get killed, after returning from here, don't restart
// If we get killed after returning from here, restart
return START_STICKY;
}
private void createObservers() {
mConnectionObservers = new ArrayList<>();
observers = new ArrayList<>();
// Always show a notification
// boolean showNotification = PreferenceManager
// .getDefaultSharedPreferences(this)
// .getBoolean(Settings.KEY_PREF_SHOW_NOTIFICATION,
// Settings.DEFAULT_PREF_SHOW_NOTIFICATION);
mNotificationObserver = new NotificationObserver(this);
mConnectionObservers.add(mNotificationObserver);
notificationObserver = new NotificationObserver(this);
observers.add(notificationObserver);
// Check whether we should react to phone state changes and wether
// we have permissions to do so
@ -128,9 +108,10 @@ public class ConnectionObserversManagerService extends Service
.getBoolean(Settings.KEY_PREF_PAUSE_DURING_CALLS,
Settings.DEFAULT_PREF_PAUSE_DURING_CALLS);
boolean hasPhonePermission =
ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED;
ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) ==
PackageManager.PERMISSION_GRANTED;
if (shouldPause && hasPhonePermission) {
mConnectionObservers.add(new PauseCallObserver(this));
observers.add(new PauseCallObserver(this));
}
}
@ -143,13 +124,13 @@ public class ConnectionObserversManagerService extends Service
@Override
public void onTaskRemoved (Intent rootIntent) {
// Gracefully stop
for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
for (HostConnectionObserver.PlayerEventsObserver observer : observers) {
observer.playerOnConnectionError(0, "Task removed");
}
LogUtils.LOGD(TAG, "Shutting down observer service - Task removed");
if (mHostConnectionObserver != null) {
mHostConnectionObserver.unregisterPlayerObserver(this);
if (hostConnectionObserver != null) {
hostConnectionObserver.unregisterPlayerObserver(this);
}
stopForeground(true);
stopSelf();
@ -158,12 +139,12 @@ public class ConnectionObserversManagerService extends Service
@Override
public void onDestroy() {
// Gracefully stop
for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
for (HostConnectionObserver.PlayerEventsObserver observer : observers) {
observer.playerOnConnectionError(0, "Service destroyed");
}
LogUtils.LOGD(TAG, "Shutting down observer service - destroyed");
if (mHostConnectionObserver != null) {
mHostConnectionObserver.unregisterPlayerObserver(this);
if (hostConnectionObserver != null) {
hostConnectionObserver.unregisterPlayerObserver(this);
}
}
@ -178,36 +159,36 @@ public class ConnectionObserversManagerService extends Service
public void playerOnPlay(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
PlayerType.PropertyValue getPropertiesResult,
ListType.ItemsAll getItemResult) {
for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
for (HostConnectionObserver.PlayerEventsObserver observer : observers) {
observer.playerOnPlay(getActivePlayerResult, getPropertiesResult, getItemResult);
}
somethingPlaying = true;
somethingIsPlaying = true;
}
public void playerOnPause(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
PlayerType.PropertyValue getPropertiesResult,
ListType.ItemsAll getItemResult) {
for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
for (HostConnectionObserver.PlayerEventsObserver observer : observers) {
observer.playerOnPause(getActivePlayerResult, getPropertiesResult, getItemResult);
}
somethingPlaying = true;
somethingIsPlaying = true;
}
public void playerOnStop() {
for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
for (HostConnectionObserver.PlayerEventsObserver observer : observers) {
observer.playerOnStop();
}
somethingPlaying = false;
somethingIsPlaying = false;
// Stop service if nothing starts in a couple of seconds
mStopHandler.postDelayed(new Runnable() {
stopHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (!somethingPlaying) {
if (!somethingIsPlaying) {
LogUtils.LOGD(TAG, "Stopping service");
if (mHostConnectionObserver != null) {
mHostConnectionObserver.unregisterPlayerObserver(ConnectionObserversManagerService.this);
if (hostConnectionObserver != null) {
hostConnectionObserver.unregisterPlayerObserver(ConnectionObserversManagerService.this);
}
stopForeground(true);
stopSelf();
@ -217,37 +198,37 @@ public class ConnectionObserversManagerService extends Service
}
public void playerNoResultsYet() {
for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
for (HostConnectionObserver.PlayerEventsObserver observer : observers) {
observer.playerNoResultsYet();
}
somethingPlaying = false;
somethingIsPlaying = false;
}
public void playerOnConnectionError(int errorCode, String description) {
for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
for (HostConnectionObserver.PlayerEventsObserver observer : observers) {
observer.playerOnConnectionError(errorCode, description);
}
somethingPlaying = false;
somethingIsPlaying = false;
// Stop service
LogUtils.LOGD(TAG, "Shutting down observer service - Connection error");
if (mHostConnectionObserver != null) {
mHostConnectionObserver.unregisterPlayerObserver(this);
if (hostConnectionObserver != null) {
hostConnectionObserver.unregisterPlayerObserver(this);
}
stopForeground(true);
stopSelf();
}
public void systemOnQuit() {
for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
for (HostConnectionObserver.PlayerEventsObserver observer : observers) {
observer.systemOnQuit();
}
somethingPlaying = false;
somethingIsPlaying = false;
// Stop service
LogUtils.LOGD(TAG, "Shutting down observer service - System quit");
if (mHostConnectionObserver != null) {
mHostConnectionObserver.unregisterPlayerObserver(this);
if (hostConnectionObserver != null) {
hostConnectionObserver.unregisterPlayerObserver(this);
}
stopForeground(true);
stopSelf();
@ -255,13 +236,13 @@ public class ConnectionObserversManagerService extends Service
// Ignore this
public void inputOnInputRequested(String title, String type, String value) {
for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
for (HostConnectionObserver.PlayerEventsObserver observer : observers) {
observer.inputOnInputRequested(title, type, value);
}
}
public void observerOnStopObserving() {
for (HostConnectionObserver.PlayerEventsObserver observer : mConnectionObservers) {
for (HostConnectionObserver.PlayerEventsObserver observer : observers) {
observer.observerOnStopObserving();
}
// Called when the user changes host

View File

@ -58,26 +58,26 @@ public class NotificationObserver
public static final int NOTIFICATION_ID = 1;
private static final String NOTIFICATION_CHANNEL = "KORE";
private PendingIntent mRemoteStartPendingIntent;
private Service mService;
private Notification mNothingPlayingNotification;
private PendingIntent remoteStartPendingIntent;
private Service service;
private Notification nothingPlayingNotification;
private Notification currentNotification = null;
public NotificationObserver(Service service) {
this.mService = service;
this.service = service;
// Create the intent to start the remote when the user taps the notification
TaskStackBuilder stackBuilder = TaskStackBuilder.create(mService);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this.service);
stackBuilder.addParentStack(RemoteActivity.class);
stackBuilder.addNextIntent(new Intent(mService, RemoteActivity.class));
mRemoteStartPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
stackBuilder.addNextIntent(new Intent(this.service, RemoteActivity.class));
remoteStartPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Create the notification channel
if (Utils.isOreoOrLater()) {
buildNotificationChannel();
}
mNothingPlayingNotification = buildNothingPlayingNotification();
nothingPlayingNotification = buildNothingPlayingNotification();
}
@Override
@ -129,14 +129,14 @@ public class NotificationObserver
NotificationChannel channel =
new NotificationChannel(NOTIFICATION_CHANNEL,
mService.getString(R.string.app_name),
service.getString(R.string.app_name),
NotificationManager.IMPORTANCE_LOW);
channel.enableLights(false);
channel.enableVibration(false);
channel.setShowBadge(false);
NotificationManager notificationManager =
(NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
(NotificationManager) service.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null)
notificationManager.createNotificationChannel(channel);
}
@ -147,25 +147,28 @@ public class NotificationObserver
private Notification buildNothingPlayingNotification() {
int smallIcon = R.drawable.ic_devices_white_24dp;
NotificationCompat.Builder builder = new NotificationCompat.Builder(mService, NOTIFICATION_CHANNEL);
NotificationCompat.Builder builder = new NotificationCompat.Builder(service, NOTIFICATION_CHANNEL);
return builder
.setSmallIcon(smallIcon)
.setShowWhen(false)
.setOngoing(true)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setCategory(NotificationCompat.CATEGORY_TRANSPORT)
.setContentIntent(mRemoteStartPendingIntent)
.setContentTitle(String.format(mService.getString(R.string.connected_to),
HostManager.getInstance(mService).getHostInfo().getName()))
.setContentText(mService.getString(R.string.nothing_playing))
.setContentIntent(remoteStartPendingIntent)
.setContentTitle(String.format(service.getString(R.string.connected_to),
HostManager.getInstance(service).getHostInfo().getName()))
.setContentText(service.getString(R.string.nothing_playing))
.build();
}
public Notification getNothingPlayingNotification() {
if (mNothingPlayingNotification == null) {
mNothingPlayingNotification = buildNothingPlayingNotification();
public Notification getCurrentNotification() {
if (currentNotification == null) {
if (nothingPlayingNotification == null) {
nothingPlayingNotification = buildNothingPlayingNotification();
}
currentNotification = nothingPlayingNotification;
}
return mNothingPlayingNotification;
return currentNotification;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@ -187,7 +190,7 @@ public class NotificationObserver
break;
case ListType.ItemsAll.TYPE_EPISODE:
title = getItemResult.title;
String seasonEpisode = String.format(mService.getString(R.string.season_episode_abbrev),
String seasonEpisode = String.format(service.getString(R.string.season_episode_abbrev),
getItemResult.season, getItemResult.episode);
underTitle = String.format("%s | %s", getItemResult.showtitle, seasonEpisode);
poster = getItemResult.art.poster;
@ -232,7 +235,7 @@ public class NotificationObserver
PendingIntent rewindPendingIntent, ffPendingIntent, playPausePendingIntent;
playPausePendingIntent = buildActionPendingIntent(getActivePlayerResult.playerid, IntentActionsService.ACTION_PLAY_PAUSE);
boolean useSeekJump = PreferenceManager
.getDefaultSharedPreferences(this.mService)
.getDefaultSharedPreferences(this.service)
.getBoolean(Settings.KEY_PREF_NOTIFICATION_SEEK_JUMP, Settings.DEFAULT_PREF_NOTIFICATION_SEEK_JUMP);
if (getItemResult.type.equals(ListType.ItemsAll.TYPE_SONG)) {
rewindPendingIntent = buildActionPendingIntent(getActivePlayerResult.playerid, IntentActionsService.ACTION_PREVIOUS);
@ -252,17 +255,17 @@ public class NotificationObserver
}
final NotificationCompat.Builder builder =
new NotificationCompat.Builder(mService, NOTIFICATION_CHANNEL)
new NotificationCompat.Builder(service, NOTIFICATION_CHANNEL)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setSmallIcon(smallIcon)
.setShowWhen(false)
.setOngoing(true)
.addAction(rewindIcon, mService.getString(R.string.rewind), rewindPendingIntent) // #0
.addAction(playPauseIcon, mService.getString(R.string.play), playPausePendingIntent) // #1
.addAction(ffIcon, mService.getString(R.string.fast_forward), ffPendingIntent) // #2
.addAction(rewindIcon, service.getString(R.string.rewind), rewindPendingIntent) // #0
.addAction(playPauseIcon, service.getString(R.string.play), playPausePendingIntent) // #1
.addAction(ffIcon, service.getString(R.string.fast_forward), ffPendingIntent) // #2
.setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle()
.setShowActionsInCompactView(0, 1, 2))
.setContentIntent(mRemoteStartPendingIntent)
.setContentIntent(remoteStartPendingIntent)
.setContentTitle(title)
.setContentText(underTitle);
@ -285,7 +288,7 @@ public class NotificationObserver
//
// 4. We specifically resize the image to the same dimensions used in
// the remote, so that Picasso reuses it in the remote and here from the cache
Resources resources = mService.getResources();
Resources resources = service.getResources();
final int posterWidth = resources.getDimensionPixelOffset(R.dimen.now_playing_poster_width);
final int posterHeight = isVideo?
resources.getDimensionPixelOffset(R.dimen.now_playing_poster_height):
@ -299,7 +302,7 @@ public class NotificationObserver
@Override
public void onBitmapFailed(Drawable errorDrawable) {
CharacterDrawable avatarDrawable = UIUtils.getCharacterAvatar(mService, title);
CharacterDrawable avatarDrawable = UIUtils.getCharacterAvatar(service, title);
showNotification(Utils.drawableToBitmap(avatarDrawable, posterWidth, posterHeight));
}
@ -309,15 +312,17 @@ public class NotificationObserver
private void showNotification(Bitmap bitmap) {
builder.setLargeIcon(bitmap);
NotificationManager notificationManager =
(NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null)
notificationManager.notify(NOTIFICATION_ID, builder.build());
(NotificationManager) service.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null) {
currentNotification = builder.build();
notificationManager.notify(NOTIFICATION_ID, currentNotification);
}
picassoTarget = null;
}
};
// Load the image
HostManager hostManager = HostManager.getInstance(mService);
HostManager hostManager = HostManager.getInstance(service);
hostManager.getPicasso()
.load(hostManager.getHostInfo().getImageUrl(poster))
.resize(posterWidth, posterHeight)
@ -326,24 +331,28 @@ public class NotificationObserver
}
private PendingIntent buildActionPendingIntent(int playerId, String action) {
Intent intent = new Intent(mService, IntentActionsService.class)
Intent intent = new Intent(service, IntentActionsService.class)
.setAction(action)
.putExtra(IntentActionsService.EXTRA_PLAYER_ID, playerId);
return PendingIntent.getService(mService, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return PendingIntent.getService(service, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
private void removeNotification() {
NotificationManager notificationManager =
(NotificationManager)mService.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null)
(NotificationManager) service.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null) {
notificationManager.cancel(NOTIFICATION_ID);
currentNotification = null;
}
}
private void notifyNothingPlaying() {
NotificationManager notificationManager =
(NotificationManager)mService.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null)
notificationManager.notify(NOTIFICATION_ID, mNothingPlayingNotification);
(NotificationManager) service.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null) {
notificationManager.notify(NOTIFICATION_ID, nothingPlayingNotification);
currentNotification = nothingPlayingNotification;
}
}
}