Foreground service getting killed - java

I have a service, that updates my backend from time to time, with the newest location of the user, the problem is, some users are reporting that the Foreground notification sometimes disappears, but it doesn't happen to all the users, there might be users using the app 8 hours a day, and it doesn't disappear, but no crash appears on Crashlytics.
Here is my ServiceClass
package com.xxxxxxx.xxxxxxxx.service
import android.annotation.SuppressLint
import android.app.Notification
import android.app.Notification.FLAG_NO_CLEAR
import android.app.Notification.FLAG_ONGOING_EVENT
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.app.Service
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
import android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP
import android.graphics.Color
import android.location.Location
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.O
import android.os.IBinder
import android.util.Base64
import android.util.Base64.DEFAULT
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.model.LatLng
import com.xxxx.commons.XXXXXXCommons
import com.xxxxxxx.xxxxxxxx.R
import com.xxxxxxx.xxxxxxxx.di.DataSourceModule
import com.xxxxxxx.xxxxxxxx.domain.GetAppStateUseCase.Companion.LOCATION_DELIMITER
import com.xxxxxxx.xxxxxxxx.model.local.CachedLocation
import com.xxxxxxx.xxxxxxxx.model.local.DriverLocation
import com.xxxxxxx.xxxxxxxx.utils.LOCATION_PERMISSIONS
import com.xxxxxxx.xxxxxxxx.utils.OnGoingTrip
import com.xxxxxxx.xxxxxxxx.utils.checkPermissions
import com.xxxxxxx.xxxxxxxx.view.activity.SplashActivity
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.text.Charsets.UTF_8
class LocationTrackingService : Service() {
private companion object {
const val NOTIFICATION_ID = 856
const val FOREGROUND_SERVICE_ID = 529
const val LOCATION_UPDATE_INTERVAL = 10000L
const val LOCATION_UPDATE_FASTEST_INTERVAL = 5000L
}
private val updateJob: Job = Job()
private val errorHandler = CoroutineExceptionHandler { _, throwable ->
XXXXXXCommons.crashHandler?.log(throwable)
}
private val locationTrackingCoroutineScope =
CoroutineScope(Dispatchers.IO + updateJob + errorHandler)
private val rideApiService by lazy { DataSourceModule.provideRideApiService() }
private val locationDao by lazy { DataSourceModule.provideLocationDao(XXXXXXCommons.provideApplicationContext()) }
private val locationRequest = LocationRequest().apply {
interval = LOCATION_UPDATE_INTERVAL
fastestInterval = LOCATION_UPDATE_FASTEST_INTERVAL
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
private val callback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult?) = onLocationReceived(result)
}
#SuppressLint("MissingPermission")
private fun requestLocationUpdates() {
if (checkPermissions(*LOCATION_PERMISSIONS)) {
LocationServices.getFusedLocationProviderClient(this)
.requestLocationUpdates(locationRequest, callback, null)
}
}
private fun onLocationReceived(result: LocationResult?) = result?.lastLocation?.run {
postDriverLocation(this)
} ?: Unit
private fun postDriverLocation(location: Location) {
val driverLocation = DriverLocation(
location.latitude.toFloat(),
location.longitude.toFloat(),
location.bearing,
OnGoingTrip.rideData.rideId,
OnGoingTrip.orderData.orderId
)
OnGoingTrip.currentLocation = LatLng(location.latitude, location.longitude)
OnGoingTrip.reportLocationUpdate()
locationTrackingCoroutineScope.launch(Dispatchers.IO) {
cacheDriverLocation(driverLocation)
rideApiService.postCurrentDriverLocation(driverLocation)
}
}
private suspend fun cacheDriverLocation(driverLocation: DriverLocation) {
val bytes = "${driverLocation.latitude}$LOCATION_DELIMITER${driverLocation.longitude}"
.toByteArray(UTF_8)
val safeLocation = Base64.encode(bytes, DEFAULT).toString(UTF_8)
locationDao.createOrUpdate(CachedLocation(location = safeLocation))
}
override fun onBind(intent: Intent): IBinder? = null
override fun onCreate() {
super.onCreate()
buildNotification(
if (SDK_INT >= O) {
createNotificationChannel(getString(R.string.app_name))
} else {
NOTIFICATION_ID.toString()
}
)
requestLocationUpdates()
}
override fun onDestroy() {
super.onDestroy()
if (locationTrackingCoroutineScope.isActive) {
locationTrackingCoroutineScope.cancel()
}
LocationServices.getFusedLocationProviderClient(this).removeLocationUpdates(callback)
(getSystemService(NOTIFICATION_SERVICE) as? NotificationManager)?.cancel(NOTIFICATION_ID)
}
#RequiresApi(O)
private fun createNotificationChannel(channelName: String): String {
val chan = NotificationChannel(
NOTIFICATION_ID.toString(),
channelName,
NotificationManager.IMPORTANCE_NONE
)
chan.lightColor = Color.CYAN
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val service = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
return NOTIFICATION_ID.toString()
}
private fun buildNotification(channelId: String) {
val ctx = this
(getSystemService(NOTIFICATION_SERVICE) as? NotificationManager)?.run {
val intent = Intent(ctx, SplashActivity::class.java).apply {
flags = FLAG_ACTIVITY_CLEAR_TOP or FLAG_ACTIVITY_SINGLE_TOP
}
val resultPendingIntent =
PendingIntent.getActivity(ctx, 0, intent, FLAG_UPDATE_CURRENT)
val notification = NotificationCompat.Builder(ctx, channelId)
.setSmallIcon(R.drawable.ic_xxxxxx_icon_background)
.setColor(ContextCompat.getColor(ctx, R.color.xxxxxxTurquoise))
.setContentTitle(ctx.getString(R.string.app_name))
.setOngoing(true)
.setContentText(ctx.getString(R.string.usage_message))
.setContentIntent(resultPendingIntent)
.build().apply { flags = FLAG_ONGOING_EVENT or FLAG_NO_CLEAR }
startForeground(FOREGROUND_SERVICE_ID, notification)
}
}
}
Here is how I start the service:
startService(Intent(this, LocationTrackingService::class.java));
Here are the permissions I have and how I declared it on the AndroidManifest.xml:
<service
android:name=".service.LocationTrackingService"
android:enabled="true"
android:exported="true" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
Edit
Most of the devices run Android 9 and 10, a few from 5 to 8, and almost no 11
More information I would be happy to share.

There have been several latest restrictions with Android 11. It's difficult to provide an exact solution. Here is couple of things you should recheck that might resolve your problem:
Disable battery optimization for your app in the device.
Check if the device is not operating in doze mode.
Specify android:foregroundServiceType in manifest .

There are a couple of things here. I cannot see <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> in your manifest. Also make sure about foregroundServiceType="location" in your service attributes inside manifest. In addition, if you have a foreground service for location, ACCESS_BACKGROUND_LOCATION is not required.
On the other hand, Xiaomi devices will kill foreground services when user closes the application from app switcher (Xiaomi is the worst) and you should ask Xiaomi users to manually exclude your application from being killed in battery saver or extra permissions in app details (and even if they do that, still there won't be a guarantee, also different MiUI versions have different rules)
Moreover, star your service using ContextCompat.startForegroundService and in your service's onStartCommand callstartForeground

Related

How to continuously mock a GPS location on Android?

How do I continuously mock a location on Android in 2023? The following code does not work. Only thing that changed is that the blue dot in Google Maps becomes grey. I added all the needed permissions and selected my app as mock location in Developer Settings. Other answers on SO are now outdated due to Google introducing new updates.
import android.location.Location
import android.os.Build
import android.os.Bundle
import android.os.SystemClock
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.location.LocationServices
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val locationProvider = LocationServices.getFusedLocationProviderClient(applicationContext)
locationProvider.setMockMode(true)
val loc = Location("gps")
val mockLocation = Location("gps") // a string
mockLocation.latitude = 52.520901 // double
mockLocation.longitude = 13.370790
mockLocation.altitude = loc.altitude
mockLocation.time = System.currentTimeMillis()
mockLocation.accuracy = 1f
mockLocation.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mockLocation.bearingAccuracyDegrees = 0.1f
mockLocation.verticalAccuracyMeters = 0.1f
mockLocation.speedAccuracyMetersPerSecond = 0.01f
}
locationProvider.setMockLocation(mockLocation)
}
}
In Manifest.xml:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
Did I miss anything else?
The code is from this answer: https://stackoverflow.com/a/59549290/19729496

Azure Notification Hubs for Android: How do I handle Data-Messages with a Background-Service?

I'm trying to make an app that is able to handle (data-)messages sent by the Azure Notification Hubs. At the current state it sends a Notification when recieving a payload by Azure. While the app is running in the foreground (or still open in the Quick Panel) it has no problems at all and onPushNotificationReceived() handles the incoming message just fine, but when removing the app from the Quick Panel I get an error for trying to invoke a null object refrence:
Logcat
2021-07-22 15:27:33.675 23017-23053/com.example.fcmtutorial1app E/AndroidRuntime: FATAL EXCEPTION: Firebase-Messaging-Intent-Handle
Process: com.example.fcmtutorial1app, PID: 23017
java.lang.NullPointerException: Attempt to invoke interface method 'void com.microsoft.windowsazure.messaging.notificationhubs.NotificationListener.onPushNotificationReceived(android.content.Context, com.google.firebase.messaging.RemoteMessage)' on a null object reference
at com.microsoft.windowsazure.messaging.notificationhubs.FirebaseReceiver.onMessageReceived(FirebaseReceiver.java:52)
at com.google.firebase.messaging.FirebaseMessagingService.dispatchMessage(com.google.firebase:firebase-messaging##22.0.0:13)
at com.google.firebase.messaging.FirebaseMessagingService.passMessageIntentToSdk(com.google.firebase:firebase-messaging##22.0.0:8)
at com.google.firebase.messaging.FirebaseMessagingService.handleMessageIntent(com.google.firebase:firebase-messaging##22.0.0:3)
at com.google.firebase.messaging.FirebaseMessagingService.handleIntent(com.google.firebase:firebase-messaging##22.0.0:3)
at com.google.firebase.messaging.EnhancedIntentService.lambda$processIntent$0$EnhancedIntentService(com.google.firebase:firebase-messaging##22.0.0:1)
at com.google.firebase.messaging.EnhancedIntentService$$Lambda$0.run(Unknown Source:6)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at com.google.android.gms.common.util.concurrent.zza.run(Unknown Source:6)
at java.lang.Thread.run(Thread.java:923)
This only happens when sending data messages, since the Firebase Service handles Messages with notification payload without invoking onPushNotificationReceived().
I've tried the following to fix this:
Extending CustomNotificationListener.class with android.app.Service
Replacing onPushNotificationReceived() with Thunderbirds onMessageReceived()
The first solution resulted in the same error and the second one resulted in no messages at all.
If someone has a way to fix this or knows what could be the fault, I'd be really be happy if you could write an answer :)
Here is the code for both classes (android.app.Service is still included although it didn't work for me). Thanks in advance!
MainActivity.class
package com.example.fcmtutorial1app;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import com.microsoft.windowsazure.messaging.notificationhubs.NotificationHub;
public class MainActivity extends AppCompatActivity
{
public static final String CHANNEL_1_ID = "Channel1";
public static final String CHANNEL_2_ID = "Channel2";
public static String editTextTitle;
public static String editTextMessage;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
createNotificationsChannels();
NotificationHub.setListener(new CustomNotificationListener());
NotificationHub.start(this.getApplication(), "spfcmtutorial1nhub", "Endpoint=sb://azurecloudmessaging.servicebus.windows.net/;SharedAccessKeyName=DefaultListenSharedAccessSignature;SharedAccessKey=abc[...]xyz");
}
public static void sendCloudMessage(Context context)
{
editTextTitle = CustomNotificationListener.title;
editTextMessage = CustomNotificationListener.body;
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, CHANNEL_1_ID)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle(editTextTitle)
.setContentText(editTextMessage)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_MESSAGE);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(1, notificationBuilder.build());
Log.v("MSG", "SENDCLOUDMESSAGE WAS ACTIVATED");
}
public void createNotificationsChannels() //Channel 2 is for tests only
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
NotificationChannel channel1 = new NotificationChannel(
CHANNEL_1_ID,
"Channel 1",
NotificationManager.IMPORTANCE_HIGH
);
channel1.setDescription("This is Channel 1");
NotificationChannel channel2 = new NotificationChannel(
CHANNEL_2_ID,
"Channel 2",
NotificationManager.IMPORTANCE_LOW
);
channel2.setDescription("This is Channel 2");
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel1);
manager.createNotificationChannel(channel2);
}
}
}
CustomNotificationListener.class
package com.example.fcmtutorial1app;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
import com.google.firebase.messaging.RemoteMessage;
import com.microsoft.windowsazure.messaging.notificationhubs.NotificationListener;
import java.util.Map;
public class CustomNotificationListener extends Service implements NotificationListener
{
private static final String TAG = "Message";
public static String title;
public static String body;
public static String dataTitle;
public static String dataBody;
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
Log.d(TAG, "Service started");
return Service.START_NOT_STICKY;
}
#Override
public void onPushNotificationReceived(Context context, RemoteMessage message) //FATAL EXEPTION: Firebase-Messaging-Intent-Handle HERE
{
RemoteMessage.Notification notification = message.getNotification();
try { title = notification.getTitle(); } catch(Exception e) {}
try { body = notification.getBody(); } catch (Exception e) {}
Map<String, String> data = message.getData();
//region LOGGING
if (message != null)
{
Log.d(TAG, "Message Notification Title: " + title);
Log.d(TAG, "Message Notification Body: " + body);
}
else { Log.e(TAG, "ERROR, no message found"); }
if (data != null)
{
for (Map.Entry<String, String> entry : data.entrySet())
{
Log.d(TAG, "key, " + entry.getKey() + "value " + entry.getValue());
}
}
else { Log.e(TAG, "ERROR, no data found"); }
//endregion
Log.v("VERBOSE", data.get("property1"));
MainActivity.sendCloudMessage(context);
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.fcmtutorial1app">
<application
android:allowBackup="true"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.FCMTutorial1App">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".CustomNotificationListener"></service>
</application>
</manifest>
To answer this question, we'll need to discuss some Android lifecycle concepts as documented at https://developer.android.com:
Process and Application Lifecycle
Activity Lifecycle
In the code you provide above, you call NotificationHub.setListener in your application's primary entrypoint, MainActivity. As you see, this works when your end user has started the application manually because MainActivity.onCreate gets invoked.
However, there's a second entrypoint in your scenario: FirebaseMessagingService starting the application when a data-only notification is received in the background. This is subtle and easy to miss - because if the payload contains a notification component, Android will still route to MainActivity when the notification is clicked in the system tray.
In this case MainActivity isn't involved, so MainActivity.onCreate and NotificationHub.setListener are never called as the application initialized, and the following line from the stack trace encounters a null reference:
mHub.getInstanceListener().onPushNotificationReceived(this.getApplicationContext(), remoteMessage);
To fix this, you'll need to call NotificationHub.setListener somewhere that gets called anytime the application gets initialized, regardless of the entrypoint.
The most natural choice is to setup the NotificationHub at the Application level by extending android.app.Application, overriding the onCreate() method, and updating your manifest.

How to show toast message using JobScheduler

I have been learning jobscheduler in andorid. I have trying to show a toast message in the onstartjob() after every 1minute. But its not showing up. Any help will be grateful.
jobschedul_service .class:
package com.prajwal.jobscheduler;
import android.app.Service;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast;
public class jobschedul_service extends JobService {
#Override
public boolean onStartJob(JobParameters jobParameters) {
Toast.makeText(getApplicationContext(), "After 15 minutes", Toast.LENGTH_SHORT).show();
return false;
}
#Override
public boolean onStopJob(JobParameters jobParameters) {
return true;
}
}
MainActivity.class:
package com.prajwal.jobscheduler;
import androidx.appcompat.app.AppCompatActivity;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
private static final int LOAD_ARTWORK_JOB_ID = 1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
JobScheduler jobScheduler =
(JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(new JobInfo.Builder
(LOAD_ARTWORK_JOB_ID, new ComponentName(this, jobschedul_service.class))
.setPeriodic(900000)
.build());
}
}
Manifest.xml:
<service
android:name=".jobschedul_service"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE"></service>
Got the answer!
The reason was the setperiodic(long interval).
As per the ss below:
This function was added in API 21 i.e Lolipop version. Whereas I was running the app on version > Lolipop.
Whereas this new function in the image below:
was added in API 24 i.e. Nougat
Hence, if you are targetting your app below Nougat i.e < Nougat but >= Lolipop then use setPeriodic(long interval) method whereas if u r targetting >= Nougat then use setPeriodic(long interval, long flexinterval).
Solution
if(Build.VERSION.SDK_INT > Build.VERSION_CODES.N)
{
builder.setPeriodic(900000,6000);
}
else
{
builder.setPeriodic(900000);
}

Android Service not staying alive after app closes

I want to have a background service, which will stay alive after the app is closed and which I can bind to again when the app is started.
For testing I made it that a counter will increase every time I bind to the service.
So theoretically the app should start, I will create the service, then bind to it -> the counter should move up.
Then I close the app and press the Bind button again and It should log a "1" and move the counter up again.
But it doesn't ...
It will display a 0 every time I restart the app and bind to it ...
This is my current Test - Service - class:
package com.programm.testapp;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
public class TestService extends Service {
/*
* Service Binder
*/
private final IBinder iBinder = new TestService.LocalConnectionService();
public class LocalConnectionService extends Binder {
public TestService getService(){
return TestService.this;
}
}
/*
* Test var
* It should increase every time the app is started.
*/
private int test;
#Override
public IBinder onBind(Intent intent) {
Log.d("mDEBUG", "Test: " + test);
test++;
return iBinder;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("mDEBUG", "Service: Start Command");
return START_STICKY;
}
}
This is my current Test - Activity:
package com.programm.testapp;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private TestService service;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button createButton = findViewById(R.id.button_create_service);
createButton.setOnClickListener(this::createService);
Button destroyButton = findViewById(R.id.button_destroy_service);
destroyButton.setOnClickListener(this::destroyService);
Button bindButton = findViewById(R.id.button_bind_service);
bindButton.setOnClickListener(this::bindService);
Button unbindButton = findViewById(R.id.button_unbind_service);
unbindButton.setOnClickListener(this::unbindService);
}
private void createService(View v){
Intent intent = new Intent(this.getBaseContext(), TestService.class);
startService(intent);
}
private void destroyService(View v){
Intent intent = new Intent(this.getBaseContext(), TestService.class);
stopService(intent);
}
private void bindService(View v){
Intent intent = new Intent(this.getBaseContext(), TestService.class);
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
private void unbindService(View v){
unbindService(serviceConnection);
}
private ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d("mDEBUG", "Connection: on service connected");
MainActivity.this.service = ((TestService.LocalConnectionService) service).getService();
}
#Override
public void onServiceDisconnected(ComponentName name) {
Log.d("mDEBUG", "Connection: on service disconnected");
}
};
}
This is my AndroidManifest.xml - file:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.programm.testapp">
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".TestService"
android:enabled="true"
android:exported="false"></service>
</application>
</manifest>
This is my output after I ...
Pressed Create Service - Button
Pressed Bind Service - Button
Pressed Unbind Service - Button
Close App and Restart it
Pressed Bind Service - Button
:
.../com.programm.testapp D/mDEBUG: Service: Start Command
.../com.programm.testapp D/mDEBUG: Test: 0
.../com.programm.testapp D/mDEBUG: Connection: on service connected
.../com.programm.testapp D/mDEBUG: Service: Start Command
.../com.programm.testapp D/mDEBUG: Test: 0
.../com.programm.testapp D/mDEBUG: Connection: on service connected
By the way the second "Service: Start Command" is called as I CLOSE the app ... after a few new Logs I noticed, that also the Constructer and the "onCreate" method of the Service - class will be called with it.
Is this normal?
Edit:
When I only minimize the App and not close it via Activity - Menu the behavior is exactly the one I want!!!
Edit 2:
A Foreground service does the job for now ...
I couldn't find any other solution for this
If you actively close the app (by closing it from the Android activity list), Android will most likely kill your service. You can see that in your apps Logcat. The only real way around that is a foreground service.
Furthermore, onBind will not be called every time you bind to the service. From the Android documentation:
You can connect multiple clients to a service simultaneously. However, the system caches the IBinder service communication channel. In other words, the system calls the service's onBind() method to generate the IBinder only when the first client binds. The system then delivers that same IBinder to all additional clients that bind to that same service, without calling onBind() again.
Secondly, just that onStartCommand is called does not mean the service is recreated. It can be called multiple times during the service life cycle. For instance, each time startService is called, onStartCommand is executed, but the service is not necessarily recreated.
Also, it looks like you do not un-bind the service when closing the activity. That makes your activity leak the ServiceConnection and your app crash. It would explain why you see the service re-created every time you close and re-start the app.
Try adding an unbind in your activity's onPause method:
#Override
void onPause() {
super.onPause()
unbindService(this.serviceConnectino)
}
A working configuration could look like below. It implements incrementing the counter using a dedicated service function, rather than onBind:
MyBoundService.kt
package com.test
import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.util.Log
class MyBoundService : Service() {
abstract class MyBinder: Binder() {
abstract fun getService(): MyBoundService
}
val iBinder: MyBinder = object: MyBinder() {
override fun getService(): MyBoundService {
return this#MyBoundService
}
}
private var counter = 0
fun increment() {
counter ++
Log.i("MyBoundService", "Counter: ${counter}")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i("MyBoundService", "startCommand");
return super.onStartCommand(intent, flags, startId)
}
override fun onBind(p0: Intent?): IBinder? {
counter++
Log.i("MyBoundService", "Bound: ${counter}")
return iBinder
}
override fun onUnbind(intent: Intent?): Boolean {
Log.i("MyBoundService", "Unbound")
return super.onUnbind(intent)
}
}
MainActivity.kt
package com.test
import android.content.Intent
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import android.content.ComponentName
import android.content.Context
import android.content.ServiceConnection
import android.os.IBinder
import android.util.Log
import com.test.MyBoundService
class MainActivity : AppCompatActivity() {
private val serviceConnection: ServiceConnection = object: ServiceConnection {
override fun onServiceDisconnected(p0: ComponentName?) {
Log.i("MainActivity", "Service disconnected")
}
override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
Log.i("MainActivity", "Service connected")
p1?.let {
(p1 as MyBoundService.MyBinder).getService().increment()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn_create.setOnClickListener {
val i = Intent(this#MainActivity, MyBoundService::class.java)
startService(i)
}
btn_bind.setOnClickListener {
val i = Intent(this#MainActivity, MyBoundService::class.java)
bindService(i, serviceConnection, Context.BIND_AUTO_CREATE)
}
}
override fun onPause() {
super.onPause()
unbindService(serviceConnection)
}
}

Getting Exception: in android widget

Hi I am trying to make an android widget. In this widget i am trying to update the value of my text view by calling a web service and getting the data from it and displaying it in the text view after a particular tie interval and i am getting an exception.
Exception:java.lang.RuntimeException: Unable to start service com.example.newwidget.UpdateService#40ce9518withIntent{cmp=com.example.newwidget
/.UpdateService (has extras) }: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
Here is the code which i have tried,
Widget.java
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
import android.widget.RemoteViews.RemoteView;
import android.widget.Toast;
public class Widget extends AppWidgetProvider {
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
Toast.makeText(context, "OnUpdate", Toast.LENGTH_LONG).show();
ComponentName thisWidget = new ComponentName(context,
Widget.class);
int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
Toast.makeText(context, "allWidgetIds", Toast.LENGTH_LONG).show();
// Build the intent to call the service
Intent intent = new Intent(context.getApplicationContext(),
UpdateService.class);
Toast.makeText(context, "call UpdateService", Toast.LENGTH_LONG).show();
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetIds);
Toast.makeText(context, "", Toast.LENGTH_LONG).show();
// Update the widgets via the service
context.startService(intent);
}
}
UpdateService.java
import java.io.IOException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import android.app.ProgressDialog;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.Toast;
public class UpdateWidget extends Service {
private RemoteViews views;
private String url;
private String strAPIRender;
private final HttpClient Client = new DefaultHttpClient();
private String Content;
private String Error = null;
private ProgressDialog Dialog = new ProgressDialog(null);
private HttpResponse response;
private HttpEntity httpEntity;
#Override
public void onStart(Intent intent, int startId) {
Log.d("AppWidget.UpdateService", "onStart()");
Toast.makeText(UpdateWidget.this, "Onstart", Toast.LENGTH_LONG).show();
// Build the widget update for today
Toast.makeText(UpdateWidget.this, "Updateviews", Toast.LENGTH_LONG).show();
RemoteViews updateViews = buildUpdate(this);
Log.d("WordWidget.UpdateService", "update built");
Toast.makeText(UpdateWidget.this, "buildupdate finish", Toast.LENGTH_LONG).show();
// Push update for this widget to the home screen
ComponentName thisWidget = new ComponentName(this, WidgetAppActivity.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
Toast.makeText(UpdateWidget.this, "final update", Toast.LENGTH_LONG).show();
manager.updateAppWidget(thisWidget, updateViews);
Log.d("WordWidget.UpdateService", "widget updated");
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
public RemoteViews buildUpdate(Context context) {
// Pick out month names from resources
Toast.makeText(UpdateWidget.this, "buildupdate", Toast.LENGTH_LONG).show();
url = "http://www.webservicex.net/currencyconvertor.asmx/ConversionRate?FromCurrency=USD&ToCurrency=INR";
grabURL(url);
Toast.makeText(UpdateWidget.this, "grabURL finish", Toast.LENGTH_LONG).show();
String result = strAPIRender;
views = new RemoteViews(context.getPackageName(), R.layout.activity_widget_app);
views.setTextViewText(R.id.update, result);
return views;
}
public void grabURL(String url) {
Toast.makeText(UpdateWidget.this, "in grabURL method", Toast.LENGTH_LONG).show();
Toast.makeText(null, "execute url", Toast.LENGTH_LONG).show();
try {
HttpGet httpget = new HttpGet(url);
ResponseHandler<String> responseHandler = new BasicResponseHandler();
Content = Client.execute(httpget, responseHandler);
} catch (ClientProtocolException e) {
Error = e.getMessage();
//cancel(true);
} catch (IOException e) {
Error = e.getMessage();
//cancel(true);
}
parseXml(Content);
}
public String parseXml(String content) {
try {
System.out.println(content);
try {
strAPIRender = XMLHandler.GetTagValue("double",content);
} catch (Exception e) {
System.out.println(" catch b");
}
} catch (Exception e) {
System.out.println(" exception in parseXML");
}
return strAPIRender;
}
}
Manifest file
<uses-sdk android:minSdkVersion="8" />
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<receiver
android:name=".Widget"
android:icon="#drawable/ic_launcher"
android:label="A Widget" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="#xml/widget_info" />
</receiver>
<service android:name="com.example.newwidget.UpdateService"></service>
</application>
Remove the following line from UpdateService:
private ProgressDialog Dialog = new ProgressDialog(null);
That "Dialog" doesn't appear (at quick glance of that class) to be used anywhere. If you do intend to use it you can't instantiate it like that (it needs a real Context). That should resolve the specific error you are asking about.
(Even if you fix that error, I'm not sure this code will work as expected. You might also want to consider, just for starters: remove all the toasts for debug purposes and use logging and logcat, and/or a debugger; don't use the main UI thread (which services also use by default) for network I/O (see IntentService for an easy way around that); don't do dialogs/alerts/toasts from a Service; and watch out for non-standard naming conventions like starting member variables in upper case, that makes the code difficult to follow (especially when it's not consistent, even if it doesn't follow the convention).)

Categories