java - 如何使用 FusedLocation API 每 5 分钟接收一次位置更新

标签 java android android-fusedlocation android-googleapiclient

我目前正在开发一款必须每五分钟检查一次用户位置并将坐标发送到服务器的应用程序。我决定使用 Google Play 服务中的 FusedLocation API 而不是普通的旧 LocationManager API,主要是因为我注意到 LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY 优先级,它声称可以提供 100 米的精度级别和合理的电池使用情况,这正是我所需要的。

在我的例子中,我有一个 Activity,它的继承结构是:

public class MainActivity extends AppCompatActivity implements
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener, LocationListener

并实现了相关的回调(onConnected、onConnectionFailed、onConnectionSuspended、onLocationChanged)。按照官方文档的建议,我还使用这种方法获得了 GoogleApiClient 的一个实例:

protected synchronized GoogleApiClient buildGoogleApiClient() {
        return new GoogleApiClient.Builder(this).addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API).build();

在 onConnected 中,我通过使用开始位置更新

LocationServices.FusedLocationApi.requestLocationUpdates(mApiClient,
                mLocationRequest, this);

...并在 onLocationChanged() 中捕捉变化。

但是,我很快发现位置更新似乎在一段时间后停止了。也许是因为此方法与 Activity 生命周期相关联,我不确定。无论如何,我试图通过创建一个扩展 IntentService 的内部类并通过 AlarmManager 启动它来解决这个问题。所以在 onConnected 中,我最终这样做了:

AlarmManager alarmMan = (AlarmManager) this
                .getSystemService(Context.ALARM_SERVICE);

        Intent updateIntent = new Intent(this, LocUpService.class);

        PendingIntent pIntent = PendingIntent.getService(this, 0, updateIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        alarmMan.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, 0,
                1000 * 60 * 5, pIntent);

LocUpService 类如下所示:

public static class LocUpService extends IntentService {

        public LocUpService() {
            super("LocUpService");

        }

        @Override
        protected void onHandleIntent(Intent intent) {
            Coords coords = LocationUpdater.getLastKnownLocation(mApiClient);


        }

    }

LocationUpdater是另外一个类,里面包含静态方法getLastKnownLocation,就是这样的:

public static Coords getLastKnownLocation(GoogleApiClient apiClient) {

        Coords coords = new Coords();
        Location location = LocationServices.FusedLocationApi
                .getLastLocation(apiClient);

        if (location != null) {

            coords.setLatitude(location.getLatitude());
            coords.setLongitude(location.getLongitude());

            Log.e("lat ", location.getLatitude() + " degrees");
            Log.e("lon ", location.getLongitude() + " degrees");

        }
        return coords;
    }

但是惊喜!!当我清楚地将引用传递给静态方法时,我得到“IllegalArgumentException:GoogleApiClient 参数是必需的”,我再次猜测这一定与 GoogleApiClient 实例有关,该实例与 Activity 的生命周期有关,并且将实例传递到 Intent 服务。

所以我在想:如何在不发疯的情况下每五分钟定期更新一次位置信息?我是否扩展服务,在该组件上实现所有接口(interface)回调,在其中构建 GoogleApiClient 实例并使其在后台运行?我是否让 AlarmManager 启动一项服务,每五分钟扩展一次 IntentService 来完成这项工作,再次在 IntentService 中构建所有相关的回调和 GoogleApiClient?我是否继续做我现在正在做的事情,但将 GoogleApiClient 构造为单例,并期望它会有所作为?你会怎么做?

感谢并抱歉这么长篇大论。

最佳答案

I am currently working on an app that has to check the user's location every five minutes and send the coordinates to a server. I decided to go with the FusedLocation API in Google Play Services instead of the plain old LocationManager API

我们的应用程序具有完全相同的要求,我在几天前实现了它,这就是我的实现方式。

在启动 Activity 中或任何您想开始的地方,使用 AlarmManager 配置 LocationTracker 每 5 分钟运行一次。

private void startLocationTracker() {
    // Configure the LocationTracker's broadcast receiver to run every 5 minutes.
    Intent intent = new Intent(this, LocationTracker.class);
    AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
    alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, Calendar.getInstance().getTimeInMillis(),
            LocationProvider.FIVE_MINUTES, pendingIntent);
}

LocationTracker.java

public class LocationTracker extends BroadcastReceiver {

    private PowerManager.WakeLock wakeLock;

    @Override
    public void onReceive(Context context, Intent intent) {
        PowerManager pow = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        wakeLock = pow.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "");
        wakeLock.acquire();

        Location currentLocation = LocationProvider.getInstance().getCurrentLocation();

        // Send new location to backend. // this will be different for you
        UserService.registerLocation(context, new Handlers.OnRegisterLocationRequestCompleteHandler() {
            @Override
            public void onSuccess() {
                Log.d("success", "UserService.RegisterLocation() succeeded");

                wakeLock.release();
            }

            @Override
            public void onFailure(int statusCode, String errorMessage) {
                Log.d("error", "UserService.RegisterLocation() failed");
                Log.d("error", errorMessage);

                wakeLock.release();
            }
        }, currentLocation);
    }
}

LocationProvider.java

public class LocationProvider {

    private static LocationProvider instance = null;
    private static Context context;

    public static final int ONE_MINUTE = 1000 * 60;
    public static final int FIVE_MINUTES = ONE_MINUTE * 5;

    private static Location currentLocation;

    private LocationProvider() {

    }

    public static LocationProvider getInstance() {
        if (instance == null) {
            instance = new LocationProvider();
        }

        return instance;
    }

    public void configureIfNeeded(Context ctx) {
        if (context == null) {
            context = ctx;
            configureLocationUpdates();
        }
    }

    private void configureLocationUpdates() {
        final LocationRequest locationRequest = createLocationRequest();
        final GoogleApiClient googleApiClient = new GoogleApiClient.Builder(context)
                .addApi(LocationServices.API)
                .build();

        googleApiClient.registerConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
            @Override
            public void onConnected(Bundle bundle) {
                startLocationUpdates(googleApiClient, locationRequest);
            }

            @Override
            public void onConnectionSuspended(int i) {

            }
        });
        googleApiClient.registerConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
            @Override
            public void onConnectionFailed(ConnectionResult connectionResult) {

            }
        });

        googleApiClient.connect();
    }

    private static LocationRequest createLocationRequest() {
        LocationRequest locationRequest = new LocationRequest();
        locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        locationRequest.setInterval(FIVE_MINUTES);
        return locationRequest;
    }

    private static void startLocationUpdates(GoogleApiClient client, LocationRequest request) {
        LocationServices.FusedLocationApi.requestLocationUpdates(client, request, new com.google.android.gms.location.LocationListener() {
            @Override
            public void onLocationChanged(Location location) {
                currentLocation = location;
            }
        });
    }

    public Location getCurrentLocation() {
        return currentLocation;
    }
}

我首先在扩展应用程序的类中创建 LocationProvider 的实例,在应用程序启动时创建实例:

MyApp.java

public class MyApp extends Application {

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

        LocationProvider locationProvider = LocationProvider.getInstance();
        locationProvider.configureIfNeeded(this);
    }
}

LocationProvider 被实例化并配置为仅一次位置更新,因为它是一个单例。每 5 分钟它将更新其 currentLocation 值,我们可以从任何我们需要的地方检索该值

Location loc = LocationProvider.getInstance().getCurrentLocation();

不需要运行任何类型的后台服务。 AlarmManager 将每 5 分钟向 LocationTracker.onReceive() 广播一次,部分唤醒锁将确保代码将完成运行,即使设备处于待机状态。这也是节能的。

请注意,您需要以下权限

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

<!-- For keeping the LocationTracker alive while it is doing networking -->
<uses-permission android:name="android.permission.WAKE_LOCK" />

并且不要忘记注册接收器:

<receiver android:name=".LocationTracker" />

关于java - 如何使用 FusedLocation API 每 5 分钟接收一次位置更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32979633/

相关文章:

java - Java 中破坏逆变边界的安全解决方法?

java - 通过@Inject 将业务逻辑暴露给 Restful 服务的最佳方式

java - Spring 4 Hibernate 4 找不到当前线程的 session

android - 更新 Android 应用程序

android - android app bundle 和 proguard rules 做同样的优化吗?

android - 定位速度使用融合提供者出错

Android:使用 FusedLocationProviderClient 获取一次位置?

java - 乘法比分支更快

java - 使用 GMailSender 从 Android 应用程序发送带有附件的电子邮件在 Transport.send 处失败

java - 如何等待 requestLocationUpdates() 回调 Intent 完成?