位置更新前台服务 - 在必须请求权限时不启动

时间:2018-03-31 10:14:02

标签: android geolocation location-services foreground-service

我正在尝试根据googlesamples示例实现locationUpdateForegroundService。

我没有使用示例中提供的按钮,只是想在应用程序和MainActivity启动时启动位置跟踪。一切正常,如果位置已经启用,但是当需要请求权限时(以及在授予后),位置更新不再开始.. 根据我的日志,接收器已注册,serviceConnection在那里,但它不会继续。没有错误消息,因为我试图在所有可能的位置重复检查所有内容。

有关解决此问题的任何提示?谢谢!

我的LocationUpdatesForegroundServices类:

public class LocationUpdatesService extends Service {

private static final String PACKAGE_NAME =
        "com.example.myApp";

private static final String TAG = LocationUpdatesService.class.getSimpleName();

/**
 * The name of the channel for notifications.
 */
private static final String CHANNEL_ID = "channel_01";

static final String ACTION_BROADCAST = PACKAGE_NAME + ".broadcast";

static final String EXTRA_LOCATION = PACKAGE_NAME + ".location";
private static final String EXTRA_STARTED_FROM_NOTIFICATION = PACKAGE_NAME +
        ".started_from_notification";

private final IBinder mBinder = new LocalBinder();

/**
 * The desired interval for location updates. Inexact. Updates may be more or less frequent.
 */
private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 50000;

/**
 * The fastest rate for active location updates. Updates will never be more frequent
 * than this value.
 */
private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS =
        UPDATE_INTERVAL_IN_MILLISECONDS / 2;

/**
 * The identifier for the notification displayed for the foreground service.
 */
private static final int NOTIFICATION_ID = 12345678;
/**
 * specifically targetting OREO notifications
 */
private static final String NOTIFICATION_CHANNEL_ID = "0REO12345678";
private static final String NOTIFICATION_CHANNEL_NAME = "OREO";
private static final String NOTIFICATION_CHANNEL_DESC = "Oreo notifications channel";

/**
 * Used to check whether the bound activity has really gone away and not unbound as part of an
 * orientation change. We create a foreground service notification only if the former takes
 * place.
 */
private boolean mChangingConfiguration = false;

private NotificationManager mNotificationManager;

/**
 * Contains parameters used by {@link com.google.android.gms.location.FusedLocationProviderApi}.
 */
private LocationRequest mLocationRequest;

/**
 * Provides access to the Fused Location Provider API.
 */
private FusedLocationProviderClient mFusedLocationClient;

/**
 * Callback for changes in location.
 */
private LocationCallback mLocationCallback;

private Handler mServiceHandler;

/**
 * The current location.
 */
private Location mLocation;

public LocationUpdatesService() {
}

@Override
public void onCreate() {
    mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);

    mLocationCallback = new LocationCallback() {
        @Override
        public void onLocationResult(LocationResult locationResult) {
            super.onLocationResult(locationResult);
            onNewLocation(locationResult.getLastLocation());
        }
    };

    createLocationRequest();
    getLastLocation();

    HandlerThread handlerThread = new HandlerThread(TAG);
    handlerThread.start();
    mServiceHandler = new Handler(handlerThread.getLooper());
    mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

    // Android O requires a Notification Channel.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        CharSequence name = getString(R.string.app_name);
        // Create the channel for the notification
        NotificationChannel mChannel =
                new NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_DEFAULT);

        // Set the Notification Channel for the Notification Manager.
        mNotificationManager.createNotificationChannel(mChannel);
    }
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.d(TAG, "Service started");
    boolean startedFromNotification = intent.getBooleanExtra(EXTRA_STARTED_FROM_NOTIFICATION,
            false);

    // We got here because the user decided to remove location updates from the notification.
    if (startedFromNotification) {
        removeLocationUpdates();
        stopSelf();
    }
    // Tells the system to not try to recreate the service after it has been killed.
    return START_NOT_STICKY;
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    mChangingConfiguration = true;
}

@Override
public IBinder onBind(Intent intent) {
    // Called when a client (MainActivity in case of this sample) comes to the foreground
    // and binds with this service. The service should cease to be a foreground service
    // when that happens.
    Log.d(TAG, "in onBind()");
    stopForeground(true);
    mChangingConfiguration = false;
    return mBinder;
}

@Override
public void onRebind(Intent intent) {
    // Called when a client (MainActivity in case of this sample) returns to the foreground
    // and binds once again with this service. The service should cease to be a foreground
    // service when that happens.
    Log.d(TAG, "in onRebind()");
    stopForeground(true);
    mChangingConfiguration = false;
    super.onRebind(intent);
}

@Override
public boolean onUnbind(Intent intent) {
    Log.d(TAG, "Last client unbound from service");

    // Called when the last client (MainActivity in case of this sample) unbinds from this
    // service. If this method is called due to a configuration change in MainActivity, we
    // do nothing. Otherwise, we make this service a foreground service.
    if (!mChangingConfiguration && Utils.requestingLocationUpdates(this)) {
        Log.d(TAG, "Starting foreground service");

        // TODO(developer). If targeting O, use the following code.
        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) {

            NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
            channel.setDescription(NOTIFICATION_CHANNEL_DESC);
            mNotificationManager.createNotificationChannel(channel);

        } else {
            startForeground(NOTIFICATION_ID, getNotification());
        }
        startForeground(NOTIFICATION_ID, getNotification());

    }
    return true; // Ensures onRebind() is called when a client re-binds.
}

@Override
public void onDestroy() {
    mServiceHandler.removeCallbacksAndMessages(null);
}

/**
 * Makes a request for location updates. Note that in this sample we merely log the
 * {@link SecurityException}.
 */
public void requestLocationUpdates() {
    Log.d(TAG, "Requesting location updates");
    Utils.setRequestingLocationUpdates(this, true);
    startService(new Intent(getApplicationContext(), LocationUpdatesService.class));
    try {
        mFusedLocationClient.requestLocationUpdates(mLocationRequest,
                mLocationCallback, Looper.myLooper());
    } catch (SecurityException unlikely) {
        Utils.setRequestingLocationUpdates(this, false);
        Log.e(TAG, "Lost location permission. Could not request updates. " + unlikely);
    }
}

/**
 * Removes location updates. Note that in this sample we merely log the
 * {@link SecurityException}.
 */
public void removeLocationUpdates() {
    Log.d(TAG, "Removing location updates");
    try {
        mFusedLocationClient.removeLocationUpdates(mLocationCallback);
        Utils.setRequestingLocationUpdates(this, false);
        stopSelf();
    } catch (SecurityException unlikely) {
        Utils.setRequestingLocationUpdates(this, true);
        Log.e(TAG, "Lost location permission. Could not remove updates. " + unlikely);
    }
}

/**
 * Returns the {@link NotificationCompat} used as part of the foreground service.
 */
private Notification getNotification() {
    Intent intent = new Intent(this, LocationUpdatesService.class);

    CharSequence text = Utils.getLocationText(mLocation);
    Log.d(TAG, "Location: " + text);

    // Extra to help us figure out if we arrived in onStartCommand via the notification or not.
    intent.putExtra(EXTRA_STARTED_FROM_NOTIFICATION, true);

    // The PendingIntent that leads to a call to onStartCommand() in this service.
    PendingIntent servicePendingIntent = PendingIntent.getService(this, 0, intent,
            PendingIntent.FLAG_UPDATE_CURRENT);

    // The PendingIntent to launch activity.
    PendingIntent activityPendingIntent = PendingIntent.getActivity(this, 0,
            new Intent(this, MainActivity.class), 0);

    NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
            .addAction(R.drawable.ic_launch, getString(R.string.launch_activity),
                    activityPendingIntent)
            .addAction(R.drawable.ic_cancel, getString(R.string.remove_location_updates),
                    servicePendingIntent)
            .setContentText(text)
            .setContentTitle(Utils.getLocationTitle(this))
            .setOngoing(true)
            .setPriority(Notification.PRIORITY_HIGH)
            .setSmallIcon(R.drawable.ic_launcher)
            .setTicker(text)
            .setWhen(System.currentTimeMillis());

    // Set the Channel ID for Android O.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        builder.setChannelId(CHANNEL_ID); // Channel ID
    }

    return builder.build();
}

private void getLastLocation() {
    try {
        mFusedLocationClient.getLastLocation()
                .addOnCompleteListener(new OnCompleteListener<Location>() {
                    @Override
                    public void onComplete(@NonNull Task<Location> task) {
                        if (task.isSuccessful() && task.getResult() != null) {
                            mLocation = task.getResult();
                        } else {
                            Log.w(TAG, "Failed to get location.");
                        }
                    }
                });
    } catch (SecurityException unlikely) {
        Log.e(TAG, "Lost location permission." + unlikely);
    }
}

private void onNewLocation(Location location) {
    Log.d(TAG, "New location: " + location);

    mLocation = location;

    // Notify anyone listening for broadcasts about the new location.
    Intent intent = new Intent(ACTION_BROADCAST);
    intent.putExtra(EXTRA_LOCATION, location);
    LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);

    // Update notification content if running as a foreground service.
    if (serviceIsRunningInForeground(this)) {
        mNotificationManager.notify(NOTIFICATION_ID, getNotification());
    }
}

/**
 * Sets the location request parameters.
 */
private void createLocationRequest() {
    mLocationRequest = new LocationRequest();
    mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
    mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
    mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}

/**
 * Class used for the client Binder.  Since this service runs in the same process as its
 * clients, we don't need to deal with IPC.
 */
public class LocalBinder extends Binder {
    LocationUpdatesService getService() {
        return LocationUpdatesService.this;
    }
}

/**
 * Returns true if this is a foreground service.
 *
 * @param context The {@link Context}.
 */
public boolean serviceIsRunningInForeground(Context context) {
    ActivityManager manager = (ActivityManager) context.getSystemService(
            Context.ACTIVITY_SERVICE);
    for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(
            Integer.MAX_VALUE)) {
        if (getClass().getName().equals(service.service.getClassName())) {
            if (service.foreground) {
                return true;
            }
        }
    }
    return false;
   }
}

这里仅对googlesamples代码的不同之处在于我定义了NotificationChannel,因为它没有其他工作。 (可能是因为针对奥利奥)

我的主要活动:

public class MainActivity extends BaseActivity
    implements NavigationView.OnNavigationItemSelectedListener, SharedPreferences.OnSharedPreferenceChangeListener {
[...]
 //SEND LOCATION UPDATES
// private boolean isOutdoorTraining => if true, update location every few seconds, if false, do nothing
// Used in checking for runtime permissions.
//Constants used in the location settings dialog.
private static final int REQUEST_LOCATION_PERMISSION = 1;

// The BroadcastReceiver used to listen from broadcasts from the service.
private MyReceiver myReceiver;

// A reference to the service used to get location updates.
private LocationUpdatesService mService = null;

// Tracks the bound state of the service.
private boolean mBound = false;
// Monitors the state of the connection to the service.
private final ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        LocationUpdatesService.LocalBinder binder = (LocationUpdatesService.LocalBinder) service;
        mService = binder.getService();
        Log.d(TAG, "mService = " + mService);
        mBound = true;
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mService = null;
        mBound = false;
    }
};


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    myReceiver = new MyReceiver();
    Log.d(TAG, "Receiver object created");

    setContentView(R.layout.activity_main);

    /*
     * Location Updates
     */
    // Check that the user hasn't revoked permissions by going to Settings.
    if (Utils.requestingLocationUpdates(this)) {
        if (!checkPermissions()) {
            Log.d(TAG, "No permission. Requesting permission");
            requestPermissions();
        }
    }     

}


/**
 * starting location updates
 */
@Override
protected void onStart() {
    super.onStart();
    PreferenceManager.getDefaultSharedPreferences(this)
            .registerOnSharedPreferenceChangeListener(this);
    if (Utils.requestingLocationUpdates(this)) {
        if (!checkPermissions()) {
            requestPermissions();
        }
    }
    if (mBound) mService.requestLocationUpdates();

    // Bind to the service. If the service is in foreground mode, this signals to the service
    // that since this activity is in the foreground, the service can exit foreground mode.
    if (!checkPermissions()) {
        requestPermissions();
    } else bindService(new Intent(this, LocationUpdatesService.class), mServiceConnection,
            Context.BIND_AUTO_CREATE);
    Log.d(TAG, "binding service");

}


@Override
protected void onResume() {
    super.onResume();
    LocalBroadcastManager.getInstance(this).registerReceiver(myReceiver,
            new IntentFilter(LocationUpdatesService.ACTION_BROADCAST));
    Log.d(TAG, "Receiver registered");

}

@Override
protected void onPause() {
    LocalBroadcastManager.getInstance(this).unregisterReceiver(myReceiver);
    Log.d(TAG, "Receiver unregistered");
    super.onPause();
}

@Override
protected void onStop() {
    if (mBound) {
        // Unbind from the service. This signals to the service that this activity is no longer
        // in the foreground, and the service can respond by promoting itself to a foreground
        // service.
        unbindService(mServiceConnection);
        mBound = false;
    }
    PreferenceManager.getDefaultSharedPreferences(this)
            .unregisterOnSharedPreferenceChangeListener(this);
    super.onStop();
}

/**
 * Return the current state of the permissions needed.
 */
public boolean checkPermissions() {
    int permissionState = ActivityCompat.checkSelfPermission(this,
            Manifest.permission.ACCESS_FINE_LOCATION);
    return permissionState == PackageManager.PERMISSION_GRANTED;
}

/**
 * show Dialog asking the user to give permission to access his or her location
 */
private void requestPermissions() {
    boolean shouldProvideRationale =
            ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.ACCESS_FINE_LOCATION);

    // Provide an additional rationale to the user. This would happen if the user denied the
    // request previously, but didn't check the "Don't ask again" checkbox.
    if (shouldProvideRationale) {
        Log.i(TAG, "Displaying permission rationale to provide additional context.");
        Snackbar.make(
                findViewById(R.id.drawer_layout),
                R.string.permission_rationale,
                Snackbar.LENGTH_INDEFINITE)
                .setAction(R.string.ok, new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        // Request permission
                        ActivityCompat.requestPermissions(MainActivity.this,
                                new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                                REQUEST_LOCATION_PERMISSION);
                    }
                })
                .show();
    } else {
        Log.i(TAG, "Requesting permission");
        // Request permission. It's possible this can be auto answered if device policy
        // sets the permission in a given state or the user denied the permission
        // previously and checked "Never ask again".
        ActivityCompat.requestPermissions(MainActivity.this,
                new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                REQUEST_LOCATION_PERMISSION);
    }
}
public void bindService(){
    if (mBound) mService.requestLocationUpdates();
    Log.d(TAG, "permission was granted. requesting updates.");
    bindService(new Intent(this, LocationUpdatesService.class), mServiceConnection,
            Context.BIND_AUTO_CREATE);
    Log.d(TAG, "permission was granted. Binding service.");
}

/**
 * Callback received when a permissions request has been completed.
 */
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    Log.d(TAG, "onRequestPermissionResult");
    if (requestCode == REQUEST_LOCATION_PERMISSION) {
        if (grantResults.length <= 0) {
            // If user interaction was interrupted, the permission request is cancelled and you
            // receive empty arrays.
            Log.d(TAG, "User interaction was cancelled.");
        } else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Permission was granted.
            Log.d(TAG, "Permission granted.");
            bindService();
            //if (mBound) mService.requestLocationUpdates();
        } else {
            // Permission denied.
            Snackbar.make(
                    findViewById(R.id.drawer_layout),
                    R.string.permission_denied_explanation,
                    Snackbar.LENGTH_INDEFINITE)
                    .setAction(R.string.settings, new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            // Build intent that displays the App settings screen.
                            Intent intent = new Intent();
                            intent.setAction(
                                    Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                            Uri uri = Uri.fromParts("package",
                                    BuildConfig.APPLICATION_ID, null);
                            intent.setData(uri);
                            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            startActivity(intent);
                        }
                    })
                    .show();
        }
    }
}

/**
 * Receiver for broadcasts sent by {@link LocationUpdatesService}.
 */
private class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Location location = intent.getParcelableExtra(LocationUpdatesService.EXTRA_LOCATION);
        if (location != null) {
            Toast.makeText(MainActivity.this, Utils.getLocationText(location),
                    Toast.LENGTH_SHORT).show();
        }
    }
}

@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
    // Update the buttons state depending on whether location updates are being requested.
    if (s.equals(Utils.KEY_REQUESTING_LOCATION_UPDATES)) {
        setButtonsState(sharedPreferences.getBoolean(Utils.KEY_REQUESTING_LOCATION_UPDATES,
                false));
    }
}

private void setButtonsState(boolean requestingLocationUpdates) {
    if (requestingLocationUpdates) {
    }
}

My Utils课程

class Utils {

static final String KEY_REQUESTING_LOCATION_UPDATES = "requesting_locaction_updates";

/**
 * Returns true if requesting location updates, otherwise returns false.
 *
 * @param context The {@link Context}.
 */
static boolean requestingLocationUpdates(Context context) {
    return PreferenceManager.getDefaultSharedPreferences(context)
            .getBoolean(KEY_REQUESTING_LOCATION_UPDATES, false);
}

/**
 * Stores the location updates state in SharedPreferences.
 * @param requestingLocationUpdates The location updates state.
 */
static void setRequestingLocationUpdates(Context context, boolean requestingLocationUpdates) {
    PreferenceManager.getDefaultSharedPreferences(context)
            .edit()
            .putBoolean(KEY_REQUESTING_LOCATION_UPDATES, requestingLocationUpdates)
            .apply();
}

/**
 * Returns the {@code location} object as a human readable string.
 * @param location  The {@link Location}.
 */
static String getLocationText(Location location) {
    return location == null ? "Unknown location" :
            "(" + location.getLatitude() + ", " + location.getLongitude() + ")";
}

static String getLocationTitle(Context context) {
    return context.getString(R.string.location_updated,
            DateFormat.getDateTimeInstance().format(new Date()));
}
}

0 个答案:

没有答案