简体   繁体   中英

Android service not restarting in lollipop

In my application, I use location based service in background. So I need to restart my service when it gets destroyed.

But I got this message in logcat

Spurious death for ProcessRecord{320afaf6 20614:com.odoo.crm:my_odoo_gps_service/u0a391}, curProc for 20614: null

My service onTaskRemoved

@Override
public void onTaskRemoved(Intent rootIntent) {
    System.out.println("onTaskRemoved called");
    Intent restartServiceIntent = new Intent(App.getAppContext(), this.getClass());
    restartServiceIntent.setPackage(getPackageName());

    PendingIntent restartServicePendingIntent = 
       PendingIntent.getService(App.getAppContext(), 1, restartServiceIntent, 
       PendingIntent.FLAG_ONE_SHOT);

    AlarmManager alarmService = 
        (AlarmManager) App.getAppContext().getSystemService(Context.ALARM_SERVICE);
    alarmService.set(
            AlarmManager.ELAPSED_REALTIME,
            SystemClock.elapsedRealtime() + 1000,
            restartServicePendingIntent);
 }

My service onDestroy

@Override
public void onDestroy() {
    System.out.println("destroy service");
    super.onDestroy();
    wakeLock.release();
}

My service onStartCommand

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
        return Service.START_STICKY;
}

I don`t know what is the error. I searched both in google & stackoverflow. All of them refer Service.START_STICKY . but I already used it.

Same service restart works in KitKat, but with some delay(~5 mins).

Any help is appreciated.

You can restart it by using a BroadcasteReceiver which handles the broadcast sent from onDestroy() of your service.

How to do this:

StickyService.java

public class StickyService extends Service
{

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }


    @Override
    public void onTaskRemoved(Intent rootIntent) {
         super.onTaskRemoved(rootIntent);
         sendBroadcast(new Intent("IWillStartAuto"));
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        sendBroadcast(new Intent("IWillStartAuto"));
    }

}

RestartServiceReceiver.java

public class RestartServiceReceiver extends BroadcastReceiver
{

    @Override
    public void onReceive(Context context, Intent intent) {
    context.startService(new Intent(context.getApplicationContext(), StickyService.class));

    }

}

Declare the components in manifest file:

    <service android:name=".StickyService" >
    </service>

    <receiver android:name=".RestartServiceReceiver" >
        <intent-filter>
            <action android:name="IWillStartAuto" >
            </action>
        </intent-filter>
    </receiver>

Hope this will help you.

Your code in onTaskRemoved is preventing the system to run the killProcess commands. The delay on Kitkat is caused by using alarmService.set , which is inexact from API 19. Use setExact instead.

If you have a service that you want to keep alive, it is recommended that you attach a notification to it and make it foreground . That way the likeliness of it being killed would be lowered.

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Environment;
import android.os.IBinder;
import android.support.v7.app.NotificationCompat;

import java.io.File;
import java.io.IOException;

import activity.MainActivity;
import activity.R;
import fragment.MainFragment;

public class MyService extends Service {
    public static final int NOTIFICATION_CODE = 1;


    @Override
    public void onCreate() {
        super.onCreate();


    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        startForeground(NOTIFICATION_CODE, getNotification());
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        stopForeground(true);
        super.onDestroy();
    }

    @Override
    public boolean stopService(Intent name) {
        return super.stopService(name);
    }


    /**
     * Create and return a simple notification.
     */
    private Notification getNotification() {    
        Notification notification;
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        builder.setColor(getResources()
                        .getColor(R.color.material_deep_teal_500))
                .setAutoCancel(true);

        notification = builder.build();
        notification.flags = Notification.FLAG_FOREGROUND_SERVICE | Notification.FLAG_AUTO_CANCEL;

        return notification;
    }


}

You can modify this code to accomodate your needs but this is the basic structure to start foreground service. Which restarts if gets killed.

how you check issocketalive that socket is connected or not ? if sockettimeoutexception is generated then try to on set getinputstream and getoutputstream. other issue that may be socket not closed properly. So if possible then put your socket code here

this worked for me

Add this attribute in android:allowBackup="false" in manifest file in application tag.

 <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<application
    android:allowBackup="false"
    tools:replace="android:allowBackup">

</application>
</manifest>

The idea of having a service ALWAYS running in background in Android is just wrong 99% of the times.

The system need to "shut down" CPU, and switch to a low battery usage profile.

You are saying you have a location based service. I assume you are using Google Play Services FusedLocationProvider , if not you should .

The FusedLocationProvider allow you to register for location changes using a PendingIntent . Meaning your services doesn't need to run all the time, it just need to register for location changes and then react when a new location come and do its stuff.

See the FusedLocationProviderApi official documentation .

To start listening for location updates

  1. connect to the GoogleClient using the LocationServices.API API
  2. Build your LocationRequest according to your needs (see the doc )
  3. Call requestLocationUpdates() using the PendingIntent version

To stop listening

  1. connect to the GoogleClient using the LocationServices.API API
  2. Call removeLocationUpdates() using the same PendingIntent

Your PendingIntent can launch another service to handle the new location.

For example doing this from a service:

public void startMonitoringLocation(Context context) {
    GoogleApiClient client = new GoogleApiClient.Builder(context)
                 .addApi(LocationServices.API)
                 .build()
    ConnectionResult connectionResult = mApiClient.blockingConnect();
    if (connectionResult.isSuccess()) {
        LocationServices.FusedLocationApi
                .requestLocationUpdates(client, buildLocationRequest(), buildPendingIntent(context));
    } else {
        handleConnectionFailed(context);
    }
}

Then the service can immediately stop.

The first time this code run it WILL fail. The connection to the google client usually require the user to take some actions. The ConnectionResult.hasResolution() method will return true if this is the case. Otherwise the reason is something else and you can't recover from it. Meaning the only thing you can do is inform the user the feature will not work or have a nice fallback.

The ConnectionResult.getResolution() give you a PendingIntent you need to use an Activity and startIntentSenderForResult() method on the Activity to resolve this intent. So you would create a Notification starting your Activity to resolve that, and in the end call your Service again.

I usually just start an Activity dedicated to do all the work. It's lot easier but you don't want to call connectBlocking() in it. Check out this on how to do it.

You may ask why not requesting location updates directly in the Activity . That's actually perfectly fine, unless you need the location monitor to automatically start with the device, even if the user didn't explicitly opened the App.

<receiver android:name=".BootCompletedBroadcastReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

This way you can just run your service to connect and request location updates when the device is rebooted.

Example on how you can build your location request:

    public LocationRequest buildLocationRequest() {
        LocationRequest locRequest = LocationRequest.create();
        // Use high accuracy
        locRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        // how often do you need to check for the location
        // (this is an indication, it's not exact)
        locRequest.setInterval(REQUIRED_INTERVAL_SEC * 1000);
        // if others services requires the location more often
        // you can still receive those updates, if you do not want
        // too many consider setting this lower limit
        locRequest.setFastestInterval(FASTEST_INTERVAL_SEC * 1000);
        // do you care if the user moved 1 meter? or if he move 50? 1000?
        // this is, again, an indication
        locRequest.setSmallestDisplacement(SMALLEST_DISPLACEMENT_METERS);
        return locRequest;
    }

And your pending intent:

public PendingIntent buildPendingIntent(Context context) {
    Intent intent = new Intent(context, LocationUpdateHandlerService.class);
    intent.setAction(ACTION_LOCATION_UPDATE);
    intent.setPackage(context.getPackageName());
    return PendingIntent.getService(context, REQUEST_CODE, intent, PendingIntent.FLAG_CANCEL_CURRENT);
}

Your LocationUpdateHandlerService can be an IntentService if you need to do work in background:

@Override
protected void onHandleIntent(Intent intent) {
    if (intent != null) {
        Bundle extras = intent.getExtras();
        if (extras != null && extras.containsKey(FusedLocationProviderApi.KEY_LOCATION_CHANGED)) {
            Location location = extras.getParcelable(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
            handleLocationChanged(location);
        } else {
            Log.w(TAG, "Didn't receive any location update in the receiver");
        }

    }
}

But can also be a Broadcast or anything that suits you.

Finally I achieved with help of Evernote JobService

Github link - https://github.com/evernote/android-job

Step 1: Add evernote jobservice dependency

implementation 'com.evernote:android-job:1.3.0-alpha03'

Step 2: Create DemoJobCreator.java class

public class DemoJobCreator implements JobCreator {

@Override
@Nullable
public Job create(@NonNull String tag) {
    switch (tag) {
        case DemoSyncJob.TAG:
            return new DemoSyncJob();
        default:
            return null;
    }
}
}

Step 3: Create DemoSyncJob.java class

public class DemoSyncJob extends Job {

public static final String TAG = ">>>> job_demo_tag";

@Override
@NonNull
protected Result onRunJob(Params params) {
    // run your job here
    Log.d(TAG, "onRunJob: ");
    if(!isMyServiceRunning(this.getContext(), TestService.class)){
        Intent intent=new Intent(context,TestService.class);
        context.startService(intent);
    }
    scheduleJob();
    return Job.Result.SUCCESS;
}

public static void scheduleJob() {
    new JobRequest.Builder(DemoSyncJob.TAG)
            .setExecutionWindow(2_000L, 2_000L)
            //.setPeriodic(900000) -> recommended. but it will work after 15 min (if you used this no need scheduleJob(); inside onRunJob();)
            .build()
            .schedule();
 }

 public static boolean isMyServiceRunning(Context context, Class<?> serviceClass) {
    try {
        ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
            if (serviceClass.getName().equals(service.service.getClassName())) {
                return true;
            }
        }
    }catch (Exception e){
        Log.e(TAG, "isMyServiceRunning: ",e );
    }
    return false;
  }
  }

Step 4: In your Application file (If not available create it) add following line in onCreate()

 JobManager.create(this).addJobCreator(new DemoJobCreator());

Step 5: Finally start JobService in your Activity

DemoSyncJob.scheduleJob();

This JobService will check service running or not (every 2 second) If service not running it will restart the service.

Disclaimer : This may be not right solution. But it will 100% working.

I hope it helps atleast anyone in future.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM