I am new to android and am working on an Android app that makes measurements every couple of seconds and links these measurements to the current location of the user. The data should still be collected even when the user is using their phone (app is minimized) or when the screen is locked. I should note that this app is meant to be used internally (not on the Google Play Store) and with full permission of the users!
When looking in the Android documentation I found that a foreground service might be the solution for me. So I implemented this service the way it is documented. When programming the app everything seemed to be working fine but when I started to make a release build of the application and was testing the app I noticed some unexpected behavior. This is the code i used for my service and to call the service from my activity:
public class LocationService extends Service {
private final LocationServiceBinder binder = new LocationServiceBinder();
private final String TAG = "LocationService";
private LocationListener mLocationListener;
private LocationManager mLocationManager;
private PowerManager.WakeLock wakeLock;
private Timer timer;
private final int LOCATION_INTERVAL = 500;
private final int LOCATION_DISTANCE = 1;
@Override
public void onCreate() {
startForeground(12345678, getNotification());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Service:WakeLock");
wakeLock.acquire();
startTracking();
super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public void onTaskRemoved(Intent rootIntent) {
task.cancel();
wakeLock.release();
if (timer != null) {
timer.cancel();
timer.purge();
timer.cancel();
timer = null;
}
if (mLocationManager != null) {
try {
mLocationManager.removeUpdates(mLocationListener);
} catch (Exception ex) {
Log.i(TAG, "fail to remove location listeners, ignore", ex);
}
}
stopForeground(true);
stopSelf();
super.onTaskRemoved(rootIntent);
}
private Notification getNotification() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("channel_01",
"My Channel", NotificationManager.IMPORTANCE_DEFAULT);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, intent, 0);
return new NotificationCompat.Builder(this, "channel_01")
.setContentTitle("APP")
.setContentText("App is running!")
.setContentIntent(pendingIntent).build();
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public class LocationServiceBinder extends Binder {
public LocationService getService() {
return LocationService.this;
}
}
private TimerTask task = new TimerTask() {
@Override
public void run() {
requestLocationInformation();
}
};
public void startTracking() {
initializeLocationManager();
mLocationListener = new LocationListener(LocationManager.GPS_PROVIDER);
if (timer == null) {
timer = new Timer("Measurements");
timer.scheduleAtFixedRate(task, 1000, 5000);
}
try {
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
LOCATION_INTERVAL, LOCATION_DISTANCE, mLocationListener);
} catch (java.lang.SecurityException ex) {
Log.i(TAG, "fail to request location update, ignore", ex);
} catch (IllegalArgumentException ex) {
Log.d(TAG, "gps provider does not exist " + ex.getMessage());
}
}
}
private class LocationListener implements android.location.LocationListener {
private Location lastLocation = null;
private final String TAG = "LocationListener";
private Location mLastLocation;
public LocationListener(String provider) {
mLastLocation = new Location(provider);
}
}
public void requestLocationUpdates() {
try {
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
LOCATION_INTERVAL, LOCATION_DISTANCE, mLocationListener);
} catch (java.lang.SecurityException ex) {
Log.i(TAG, "fail to request location update, ignore", ex);
} catch (IllegalArgumentException ex) {
Log.d(TAG, "gps provider does not exist " + ex.getMessage());
}
}
private void initializeLocationManager() {
if (mLocationManager == null) {
mLocationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
}
}
private void requestLocationInformation() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
Location location = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (location != null && swbRunning) {
String locInfo = "latitude:" + location.getLatitude() + " longitude:" + location.getLongitude() + " accuracy: " + location.getAccuracy() + "\n";
Log.d(TAG, "Location --> " + locInfo);
}
}
}
}
public class MainActivity extends ReactActivity {
//permissions to be requested at runtime - needed for android 6.0+
static String[] runtimePermissions = { Manifest.permission.INTERNET, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.READ_PHONE_STATE, Manifest.permission.WAKE_LOCK };
static int permissionRequestCode = 1;
private LocationService locaService;
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!havePermissions()) {
requestPermissions(runtimePermissions, permissionRequestCode);
} else {
Intent intent = new Intent(this, LocationService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
startForegroundService(intent);
else
startService(intent);
}
}
private ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
locaService = ((LocationService.LocationServiceBinder) service).getService();
}
public void onServiceDisconnected(ComponentName className) {
locaService = null;
}
};
To test the app I used a Samsung Galaxy A42 (android 11) device, this is the device that is also used by our users. In the release version, everything works fine until the app is minimized or the screen is locked. I looked into this issue and based on information I could find online about this problem, I tried some different solutions. I added a partial wake lock to keep the device running smoothly and I also added stopWithTask=false to the android manifest as suggest by the following posts. I also ran the application on an older device (android 8) that did not have this problem.
Keep background service running after killing an application My Android 11 app stops running when the user puts it into the background unless the phone is connected to power Android keep screen on on Samsung devices
As mentioned in that last post Samsung is know for performing battery optimizations which may affect apps running foreground services. So I used the advice on https://dontkillmyapp.com/ but this doesn’t solve the issue. Since I tried every solution I could find, I am starting to think that this might be the main source of my issue. I was wondering if anyone has had a similar issue and if there might be a solution for this problem.