4

我正在尝试使用带有 JobScheduler 和 JobService 的新 FusedLocationProviderClient 创建后台位置跟踪服务。我在这里读过

我们应该requestLocationUpdates (LocationRequest request, PendingIntent callbackIntent)用于此目的。我在我的 MainActivity 中安排工作,如下所示:

final ComponentName serviceComponent = new ComponentName(this, LocationJobService.class);
final JobScheduler jobScheduler;
final JobInfo.Builder builder = new JobInfo.Builder(LocationJobService.LOCATION_JOB_SERVICE_ID,
                        serviceComponent);
builder.setPeriodic(JobInfo.getMinPeriodMillis(), JobInfo.getMinFlexMillis());
builder.setPersisted(true);
final JobInfo jobInfo = builder.build();
jobScheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
if (jobScheduler != null)
{
    if(jobScheduler.schedule(jobInfo) == JobScheduler.RESULT_SUCCESS)
       Log.d(TAG, String.format("Job %s successfully scheduled", LocationJobService.LOCATION_JOB_SERVICE_ID));
    else
       Log.e(TAG, "Job schedule failed!");
    }

LocationJobService 类:

private static final String TAG = "Class " + LocationJobService.class.getSimpleName();
public static final int LOCATION_JOB_SERVICE_ID = 1001;
private FusedLocationProviderClient client;

@Override
public boolean onStartJob(final JobParameters params)
{
    //some logic before initializing location update
    initializeLocationUpdate(getApplicationContext());
}
private void initializeLocationUpdate(Context context)
{
    client = LocationServices.getFusedLocationProviderClient(context);
    LocationRequest locationRequest = LocationRequest.create();
    locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    locationRequest.setInterval(3000);
    locationRequest.setFastestInterval(1000);

    client.requestLocationUpdates(locationRequest, pendingIntent)
            .addOnCompleteListener(new OnCompleteListener<Void>()
            {
                @Override
                public void onComplete(@NonNull Task<Void> task)
                {
                    Log.d(TAG, "onComplete " + task.getResult());
                }
            }).addOnFailureListener(new OnFailureListener()
            {
                @Override
                public void onFailure(@NonNull Exception e)
                {
                    Log.d(TAG, "onFailure");
                }
            });
    }

任何想法如何提供 PendingIntentrequestLocationUpdates以及如何从中Location获得onComplete(@NonNull Task<Void> task)

4

2 回答 2

2

在这里使用 Kotlin 而不是 java 推出了这个google android/java 示例项目的 kotlin 分支(未经测试,可能有错误)

下面不包括字符串和维度值文件。自己硬编码或查看fork

MainActivity.kt

    /**
 * Copyright 2017 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.gms.location.sample.locationupdatespendingintent

import android.Manifest
import android.app.PendingIntent
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.preference.PreferenceManager
import android.provider.Settings
import android.support.design.widget.Snackbar
import android.support.v4.app.ActivityCompat
import android.support.v4.app.FragmentActivity
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.TextView

import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationServices


/**
 * The only activity in this sample. Displays UI widgets for requesting and removing location
 * updates, and for the batched location updates that are reported.
 *
 * Location updates requested through this activity continue even when the activity is not in the
 * foreground. Note: apps running on "O" devices (regardless of targetSdkVersion) may receive
 * updates less frequently than the interval specified in the [LocationRequest] when the app
 * is no longer in the foreground.
 */
class MainActivity : FragmentActivity(), SharedPreferences.OnSharedPreferenceChangeListener {

    /**
     * Stores parameters for requests to the FusedLocationProviderApi.
     */
    val mLocationRequest = LocationRequest()

    /**
     * Provides access to the Fused Location Provider API.
     */
    private var mFusedLocationClient: FusedLocationProviderClient? = null

    // UI Widgets.
    private var mRequestUpdatesButton: Button? = null
    private var mRemoveUpdatesButton: Button? = null
    private var mLocationUpdatesResultView: TextView? = null


    // Note: for apps targeting API level 25 ("Nougat") or lower, either
    // PendingIntent.getService() or PendingIntent.getBroadcast() may be used when requesting
    // location updates. For apps targeting API level O, only
    // PendingIntent.getBroadcast() should be used. This is due to the limits placed on services
    // started in the background in "O".
    // TODO(developer): uncomment to use PendingIntent.getService().
    //        Intent intent = new Intent(this, LocationUpdatesIntentService.class);
    //        intent.setAction(LocationUpdatesIntentService.ACTION_PROCESS_UPDATES);
    //        return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    private val pendingIntent: PendingIntent
        get() {
            val intent = Intent(this, LocationUpdatesBroadcastReceiver::class.java)
            intent.action = LocationUpdatesBroadcastReceiver.ACTION_PROCESS_UPDATES
            return PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this)

        mRequestUpdatesButton = findViewById(R.id.remove_updates_button)
        mRemoveUpdatesButton = findViewById(R.id.request_updates_button)
        mLocationUpdatesResultView = findViewById(R.id.location_updates_result)

        // Check if the user revoked runtime permissions.
        if (!checkPermissions()) {
            requestPermissions()
        }


        createLocationRequest()
    }

    override fun onStart() {
        super.onStart()
        PreferenceManager.getDefaultSharedPreferences(this)
                .registerOnSharedPreferenceChangeListener(this)
    }


    override fun onResume() {
        super.onResume()
        updateButtonsState(Utils.getRequestingLocationUpdates(this))
        mLocationUpdatesResultView?.text = Utils.getLocationUpdatesResult(this)
    }

    override fun onStop() {
        PreferenceManager.getDefaultSharedPreferences(this)
                .unregisterOnSharedPreferenceChangeListener(this)
        super.onStop()
    }

    /**
     * Sets up the location request. Android has two location request settings:
     * `ACCESS_COARSE_LOCATION` and `ACCESS_FINE_LOCATION`. These settings control
     * the accuracy of the current location. This sample uses ACCESS_FINE_LOCATION, as defined in
     * the AndroidManifest.xml.
     *
     *
     * When the ACCESS_FINE_LOCATION setting is specified, combined with a fast update
     * interval (5 seconds), the Fused Location Provider API returns location updates that are
     * accurate to within a few feet.
     *
     *
     * These settings are appropriate for mapping applications that show real-time location
     * updates.
     */
    private fun createLocationRequest() {

        // Sets the desired interval for active location updates. This interval is
        // inexact. You may not receive updates at all if no location sources are available, or
        // you may receive them slower than requested. You may also receive updates faster than
        // requested if other applications are requesting location at a faster interval.
        // Note: apps running on "O" devices (regardless of targetSdkVersion) may receive updates
        // less frequently than this interval when the app is no longer in the foreground.
        mLocationRequest.interval = UPDATE_INTERVAL

        // Sets the fastest rate for active location updates. This interval is exact, and your
        // application will never receive updates faster than this value.
        mLocationRequest.fastestInterval = FASTEST_UPDATE_INTERVAL

        mLocationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY

        // Sets the maximum time when batched location updates are delivered. Updates may be
        // delivered sooner than this interval.
        mLocationRequest.maxWaitTime = MAX_WAIT_TIME
    }

    /**
     * Return the current state of the permissions needed.
     */
    private fun checkPermissions(): Boolean {
        val permissionState = ActivityCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_FINE_LOCATION)
        return permissionState == PackageManager.PERMISSION_GRANTED
    }

    private fun requestPermissions() {
        val 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.activity_main),
                    R.string.permission_rationale,
                    Snackbar.LENGTH_INDEFINITE)
                    .setAction(R.string.ok) {
                        // Request permission
                        ActivityCompat.requestPermissions(this@MainActivity,
                                arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                                REQUEST_PERMISSIONS_REQUEST_CODE)
                    }
                    .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(this@MainActivity,
                    arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                    REQUEST_PERMISSIONS_REQUEST_CODE)
        }
    }

    /**
     * Callback received when a permissions request has been completed.
     */
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
                                            grantResults: IntArray) {
        Log.i(TAG, "onRequestPermissionResult")
        if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) {
            if (grantResults.size <= 0) {
                // If user interaction was interrupted, the permission request is cancelled and you
                // receive empty arrays.
                Log.i(TAG, "User interaction was cancelled.")
            } else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Permission was granted.
                requestLocationUpdates(null)
            } else {
                // Permission denied.

                // Notify the user via a SnackBar that they have rejected a core permission for the
                // app, which makes the Activity useless. In a real app, core permissions would
                // typically be best requested during a welcome-screen flow.

                // Additionally, it is important to remember that a permission might have been
                // rejected without asking the user for permission (device policy or "Never ask
                // again" prompts). Therefore, a user interface affordance is typically implemented
                // when permissions are denied. Otherwise, your app could appear unresponsive to
                // touches or interactions which have required permissions.
                Snackbar.make(
                        findViewById(R.id.activity_main),
                        R.string.permission_denied_explanation,
                        Snackbar.LENGTH_INDEFINITE)
                        .setAction(R.string.settings) {
                            // Build intent that displays the App settings screen.
                            val intent = Intent()
                            intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
                            val uri = Uri.fromParts("package",
                                    BuildConfig.APPLICATION_ID, null)
                            intent.data = uri
                            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                            startActivity(intent)
                        }
                        .show()
            }
        }
    }

    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, s: String) {
        if (s == Utils.KEY_LOCATION_UPDATES_RESULT) {
            mLocationUpdatesResultView?.text = Utils.getLocationUpdatesResult(this)
        } else if (s == Utils.KEY_LOCATION_UPDATES_REQUESTED) {
            updateButtonsState(Utils.getRequestingLocationUpdates(this))
        }
    }

    /**
     * Handles the Request Updates button and requests start of location updates.
     */
    fun requestLocationUpdates(view: View?) {
        try {
            Log.i(TAG, "Starting location updates")
            Utils.setRequestingLocationUpdates(this, true)
            mFusedLocationClient?.requestLocationUpdates(mLocationRequest, pendingIntent)
        } catch (e: SecurityException) {
            Utils.setRequestingLocationUpdates(this, false)
            e.printStackTrace()
        }

    }

    /**
     * Handles the Remove Updates button, and requests removal of location updates.
     */
    fun removeLocationUpdates(view: View) {
        Log.i(TAG, "Removing location updates")
        Utils.setRequestingLocationUpdates(this, false)
        mFusedLocationClient?.removeLocationUpdates(pendingIntent)
    }

    /**
     * Ensures that only one button is enabled at any time. The Start Updates button is enabled
     * if the user is not requesting location updates. The Stop Updates button is enabled if the
     * user is requesting location updates.
     */
    private fun updateButtonsState(requestingLocationUpdates: Boolean) {
        if (requestingLocationUpdates) {
            mRequestUpdatesButton?.isEnabled = false
            mRemoveUpdatesButton?.isEnabled = true
        } else {
            mRequestUpdatesButton?.isEnabled = true
            mRemoveUpdatesButton?.isEnabled = false
        }
    }

    companion object {

        private val TAG = MainActivity::class.java.getSimpleName()
        private val REQUEST_PERMISSIONS_REQUEST_CODE = 34
        /**
         * The desired interval for location updates. Inexact. Updates may be more or less frequent.
         */
        private val UPDATE_INTERVAL: Long = 60000 // Every 60 seconds.

        /**
         * The fastest rate for active location updates. Updates will never be more frequent
         * than this value, but they may be less frequent.
         */
        private val FASTEST_UPDATE_INTERVAL: Long = 30000 // Every 30 seconds

        /**
         * The max time before batched results are delivered by location services. Results may be
         * delivered sooner than this interval.
         */
        private val MAX_WAIT_TIME = UPDATE_INTERVAL * 5 // Every 5 minutes.
    }
}

这里的activity_main.xml监听器触发MainActivity.kt中的方法

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.google.android.gms.location.sample.locationupdatespendingintent.MainActivity">

    <TextView
        android:id="@+id/using_batched_location_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/batched_location_updates"
        android:layout_marginBottom="@dimen/default_margin"
        android:textSize="@dimen/text_large" />

    <Button
        android:id="@+id/request_updates_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="requestLocationUpdates"
        android:text="@string/request_updates" />

    <Button
        android:id="@+id/remove_updates_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="removeLocationUpdates"
        android:layout_marginBottom="@dimen/default_margin"
        android:text="@string/remove_updates" />

    <TextView
        android:id="@+id/location_updates_result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

LocationUpdatesIntentService.kt

   /**
 * Copyright 2017 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.android.gms.location.sample.locationupdatespendingintent

import android.app.IntentService
import android.content.Context
import android.content.Intent
import android.location.Location
import android.util.Log

import com.google.android.gms.location.LocationResult


/**
 * Handles incoming location updates and displays a notification with the location data.
 *
 * For apps targeting API level 25 ("Nougat") or lower, location updates may be requested
 * using [android.app.PendingIntent.getService] or
 * [android.app.PendingIntent.getBroadcast]. For apps targeting
 * API level O, only `getBroadcast` should be used.
 *
 * Note: Apps running on "O" devices (regardless of targetSdkVersion) may receive updates
 * less frequently than the interval specified in the
 * [com.google.android.gms.location.LocationRequest] when the app is no longer in the
 * foreground.
 */
class LocationUpdatesIntentService : IntentService(TAG) {

    override fun onHandleIntent(intent: Intent?) {
        if (intent != null) {
            val action = intent.action
            if (ACTION_PROCESS_UPDATES == action) {
                val result = LocationResult.extractResult(intent)
                if (result != null) {
                    val locations = result.locations
                    Utils.setLocationUpdatesResult(this, locations)
                    Utils.sendNotification(this, Utils.getLocationResultTitle(this, locations))
                    Log.i(TAG, Utils.getLocationUpdatesResult(this))
                }
            }
        }
    }

    companion object {

        private val ACTION_PROCESS_UPDATES = "com.google.android.gms.location.sample.locationupdatespendingintent.action" + ".PROCESS_UPDATES"
        private val TAG = LocationUpdatesIntentService::class.java.simpleName
    }
}

LocationUpdatesBroadcastReceiver.kt

    /**
 * Copyright 2017 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.gms.location.sample.locationupdatespendingintent

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.location.Location
import android.util.Log

import com.google.android.gms.location.LocationResult

/**
 * Receiver for handling location updates.
 *
 * For apps targeting API level O
 * [android.app.PendingIntent.getBroadcast] should be used when
 * requesting location updates. Due to limits on background services,
 * [android.app.PendingIntent.getService] should not be used.
 *
 * Note: Apps running on "O" devices (regardless of targetSdkVersion) may receive updates
 * less frequently than the interval specified in the
 * [com.google.android.gms.location.LocationRequest] when the app is no longer in the
 * foreground.
 */
class LocationUpdatesBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent?) {
        if (intent != null) {
            val action = intent.action
            if (ACTION_PROCESS_UPDATES == action) {
                val result = LocationResult.extractResult(intent)
                if (result != null) {
                    val locations = result.locations
                    Utils.setLocationUpdatesResult(context, locations)
                    Utils.sendNotification(context, Utils.getLocationResultTitle(context, locations))
                    Log.i(TAG, Utils.getLocationUpdatesResult(context))
                }
            }
        }
    }

    companion object {
        private val TAG = "LUBroadcastReceiver"

        internal val ACTION_PROCESS_UPDATES = "com.google.android.gms.location.sample.locationupdatespendingintent.action" + ".PROCESS_UPDATES"
    }
}

AndroidManifest.xml 在这里定义服务和接收者。

   <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.google.android.gms.location.sample.locationupdatespendingintent">

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        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=".LocationUpdatesIntentService"
            android:exported="false" />

        <receiver android:name=".LocationUpdatesBroadcastReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="com.google.android.gms.location.sample.locationupdatespendingintent.LocationUpdatesBroadcastReceiver.ACTION_PROCESS_UPDATES" />
            </intent-filter>
        </receiver>
    </application>

</manifest>
于 2018-04-03T19:00:04.977 回答
2

我想出的解决方案是我创建了扩展接收器,BroadcastReceiver然后使用它来创建PendingIntent. 然后从我进入onReceive方法的意图中提取位置。在JobService

client = LocationServices.getFusedLocationProviderClient(getApplicationContext());
Intent locationReceiverIntent = new Intent(getApplicationContext(), LocationJobServiceReceiver.class);
locationReceiverIntent.setAction(LocationJobServiceReceiver.PROCESS_UPDATES);
PendingIntent locationReceiverPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), LocationJobServiceReceiver.LOCATION_JOB_SERVICE_RECEIVER_CODE, locationReceiverIntent, PendingIntent.FLAG_UPDATE_CURRENT);
LocationRequest locationRequest = new LocationRequest();
locationRequest.setInterval(60000);
locationRequest.setFastestInterval(30000);
locationRequest.setExpirationDuration(60000);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
if (checkPermission(getApplicationContext()))
{
    client.requestLocationUpdates(locationRequest,locationReceiverPendingIntent);
    jobFinished(jobParameters, false);
}

LocationJobServiceReceiver's中onReceive(final Context context, Intent intent)

client = LocationServices.getFusedLocationProviderClient(context);
final LocationResult locationResult = LocationResult.extractResult(intent);
final String action = intent.getAction();
pendingIntent = PendingIntent.getBroadcast(context, LOCATION_JOB_SERVICE_RECEIVER_CODE, intent, PendingIntent.FLAG_NO_CREATE);
if (PROCESS_UPDATES.equals(action))
{
    if (locationResult != null)
    {
        Location location;
        List<Location> locations = locationResult.getLocations();
        if (locations.size() > 0)
        {
            location = locations.get(0);
        }
        else
        {
            location = locationResult.getLastLocation();
        }
    }
}
client.removeLocationUpdates(pendingIntent);
于 2018-02-06T15:50:17.797 回答