简体   繁体   中英

How Can I Keep My Location Service Running Even When My App Is Killed

I Have this application which sends the location of the user to the server when He/She is Signed In. I want to keep the service alive even when the app is killed

I tried some of the methods that were given in stack overflow the service seems to be working below android marshmallow but doesn't seem to work in android versions above it

This Is My Service


public class MyLocationService extends Service {
    public static final int TWO_MINUTES = 120000; // 120 seconds
    public static Boolean isRunning = false;
    double sendlat,sendlng;
    public LocationManager mLocationManager;
    public LocationUpdaterListener mLocationListener;
    public Location previousBestLocation = null;

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

    @Override
    public void onCreate() {
        mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        mLocationListener = new LocationUpdaterListener();
        super.onCreate();
    }

    Handler mHandler = new Handler();
    Runnable mHandlerTask = new Runnable() {
        @Override
        public void run() {
            if (!isRunning) {
                startListening();
            }
            mHandler.postDelayed(mHandlerTask, TWO_MINUTES);
        }
    };

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

    @Override
    public void onDestroy() {
        stopListening();
        mHandler.removeCallbacks(mHandlerTask);
        super.onDestroy();
    }

    private void startListening() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
                || ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            if (mLocationManager.getAllProviders().contains(LocationManager.NETWORK_PROVIDER))
                mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, mLocationListener);

            if (mLocationManager.getAllProviders().contains(LocationManager.GPS_PROVIDER))
                mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mLocationListener);
        }
        isRunning = true;
    }

    private void stopListening() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
                || ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            mLocationManager.removeUpdates(mLocationListener);
        }
        isRunning = false;
    }

    public class LocationUpdaterListener implements LocationListener {
        @Override
        public void onLocationChanged(Location location) {
            if (isBetterLocation(location, previousBestLocation)) {
                previousBestLocation = location;
                try {
                    if (previousBestLocation != null) {
                        sendlat = previousBestLocation.getLatitude();
                        sendlng = previousBestLocation.getLongitude();

                    } else {
                        CommonObjects.showToast(getApplicationContext(),"Couldn't get the location. Make sure location is enabled on the device");
                    }
                    String sLat = String.valueOf(sendlat);
                    String sLnt = String.valueOf(sendlng);
                    DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
                    Date date = new Date();
                    showNotificationWithImage(sLat,sLnt);
                    Log.i("lat,long",sLat+","+sLnt+","+dateFormat.format(date));
                    if(CommonObjects.isNetworkAvailable(getApplicationContext())) {
                        CommonObjects.sendTechLoc(getApplicationContext(),sLat,sLnt);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onProviderDisabled(String provider) {
            stopListening();
        }

        @Override
        public void onProviderEnabled(String provider) {
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {
        }
    }

    protected boolean isBetterLocation(Location location, Location currentBestLocation) {
        if (currentBestLocation == null) {
            return true;
        }
        long timeDelta = location.getTime() - currentBestLocation.getTime();
        boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
        boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
        boolean isNewer = timeDelta > 0;

        if (isSignificantlyNewer) {
            return true;
        } else if (isSignificantlyOlder) {
            return false;
        }
        int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
        boolean isLessAccurate = accuracyDelta > 0;
        boolean isMoreAccurate = accuracyDelta < 0;
        boolean isSignificantlyLessAccurate = accuracyDelta > 200;

        boolean isFromSameProvider = isSameProvider(location.getProvider(), currentBestLocation.getProvider());

        if (isMoreAccurate) {
            return true;
        } else if (isNewer && !isLessAccurate) {
            return true;
        } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
            return true;
        }
        return false;
    }

    private boolean isSameProvider(String provider1, String provider2) {
        if (provider1 == null) {
            return provider2 == null;
        }
        return provider1.equals(provider2);

    }

    private void showNotificationWithImage(String la,String lo) {
        Intent intent = new Intent(this, HomeActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
                PendingIntent.FLAG_ONE_SHOT);
        //Bitmap largeIcon = BitmapFactory.decodeResource(getResources(), R.mipmap.mapimg);
        NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
        style.setSummaryText("");
        Uri defaultSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getApplicationContext())
                .setSmallIcon(getNotificationIcon())
                .setColor(getResources().getColor(R.color.app_color))
                .setContentTitle("Mr Fix"+la+lo)
                .setStyle(style)
                .setVisibility(VISIBILITY_PUBLIC);
        // Since android Oreo notification channel is needed.
        String channelId = getString(R.string.default_notification_channel_id);
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(channelId,
                    "Mr Fix",
                    NotificationManager.IMPORTANCE_HIGH);
            manager.createNotificationChannel(channel);
        }
        manager.notify(0 , notificationBuilder.build());
    }

    private void sendNotification(String la,String lo) {
        Intent intent = new Intent(this, HomeActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
                PendingIntent.FLAG_ONE_SHOT);

        String channelId = getString(R.string.default_notification_channel_id);
        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder notificationBuilder =
                new NotificationCompat.Builder(this, channelId)
                        .setSmallIcon(R.drawable.ic_stat_name)
                        .setContentTitle(getString(R.string.app_name))
                        .setCategory(NotificationCompat.CATEGORY_SERVICE)
                        .setContentText(la+","+lo)
                        .setSound(defaultSoundUri)
                        .setContentIntent(pendingIntent);

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        // Since android Oreo notification channel is needed.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(channelId,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);
            notificationManager.createNotificationChannel(channel);
        }

        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
    }

    private int getNotificationIcon() {
        boolean useWhiteIcon = (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP);
        return useWhiteIcon ? R.drawable.ic_stat_name: R.mipmap.ic_launcher_round;
    }
}

This IS My Call

Intent i=new Intent(HomeActivity.this, MyLocationService.class);
        Log.i("Service","Service Started....");
        startService(i);

Oppositely to what @sven-menschner said, I think an unbound Service is exactly what you need, as bound services are subject to bind/unbind mechanisms that would kill your service. That's what I would do:

In your Manifest file, define your service:

<service
  android:name=".YourService"
  android:enabled="true"
  android:exported="true"
  android:description="@string/my_service_desc"
  android:label="@string/my_infinite_service">
  <intent-filter>
    <action android:name="com.yourproject.name.LONGRUNSERVICE" />
  </intent-filter>
</service>

Note: There's a list of already implemented actions, but you can define your own actions for the intent to launch the service. Simply create a singleton class and define the strings assigning them a String that should be unique. The "enabled" set to true is just to instantiate the service, and exported set to true is just in the case you need other applications sending intents to your Service. If not, you can safely set that last to false.

The following step would be starting your service from your activity. That can be easily done by:

public class MainActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Intent servIntent = new Intent("com.yourproject.name.LONGRUNSERVICE");
    startService(servIntent);

    ...
  }
}

The final step is to define your Service initializations. Keep an eye on the onBind() method. Since you don't want it to be bound, simply return null. It would be something like this:

public class MyService extends Service {
  @Override
  public IBinder onBind(Intent intent) {
    // This won't be a bound service, so simply return null
    return null;
  }

  @Override
  public void onCreate() {
    // This will be called when your Service is created for the first time
    // Just do any operations you need in this method.
  }

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

Now your service will run even if you close your main Activity. There's just one step left: To help your Service not being finished, run it as a foreground service (do that within your Service). This will basically create a notification icon in the status bar. This doesn't mean your main Activity is running too (this is why you don't want a bound service), as Activities and Services have different life-cycles. In order to help that Service run for so long, try keeping your heap as low as possible so it will avoid the Android SO killing it.

One more acclaration: You cannot test whether the Service is still running killing the DVM. If you kill the DVM, you'll killing everything, thus also the Service.

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