当设备处于深度睡眠(打瞌睡)模式时,Geofence 未决意图触发太迟

Geofence pending intent firing too late when device is in deep sleep (doze) mode

我需要在用户靠近给定地点时通知他。我为此使用 Geofencing API。当我使用模拟位置在 Android 模拟器上测试应用程序时,一切正常。与模拟位置的真实设备相同。但是当我走路并且我的 phone 处于深度睡眠模式时,地理围栏会在 5 - 10 分钟后触发。如果我在地理围栏半径内并且我解锁了我的 phone,打开任何使用位置的应用程序我的地理围栏会立即触发。 (Android 5.1,摩托罗拉 moto G 第一代)

下面是我注册地理围栏的方法:

  public void registerLocation(RegisterAlarmRequestModel data) {
    if (isLocationDetectionAllowed() && isConnected) {
        GeofencingRequest geofencingRequest = prepareGeofencingRequest(prepareGeofence(data));
        PendingIntent pendingIntent = prepareIntent(data.getId());
        PendingResult<Status> result = GeofencingApi.addGeofences(
                googleApiClient, geofencingRequest, pendingIntent);
        Status status = result.await();
        if (status.isSuccess())
            Log.d("Location", "Geofence " + data.getId() + " has been registered");
    }
}

//preparing Geofence Pending Intent which will be triggered 
private PendingIntent prepareIntent(int alarmId) {
    Intent intent = new Intent(context, LocationRingingService.class);
    intent.putExtra(LocationRingingService.KEY_ALARM_ID, alarmId);
    return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}

private GeofencingRequest prepareGeofencingRequest(Geofence geofence) {
    GeofencingRequest.Builder builder = new GeofencingRequest.Builder()
            .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
            .addGeofence(geofence);
    return builder.build();
}

private Geofence prepareGeofence(RegisterAlarmRequestModel data) {
    Geofence geofence = new Geofence.Builder()
            .setRequestId(String.valueOf(data.getId()))
            .setCircularRegion(data.getLatitude(), data.getLongitude(), data.getRadius())
            .setLoiteringDelay(100)
            .setExpirationDuration(Geofence.NEVER_EXPIRE)
            .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
            .build();
    return geofence;
}

为了接收意图,我正在使用 IntentService:

@Override
protected void onHandleIntent(Intent intent) {
    Log.d("Location", "accepted intent: " + intent.toString());
    //database request
}

这是我在清单中注册服务的方式:

<service
        android:name=".plugin.delivery.ringing.location.service.LocationRingingService"
        android:enabled="true"
        android:exported="true" />

更新: 我需要尽可能准确地捕捉用户刚刚进入地理围栏的时刻。我有一个想法:注册半径大于需要的地理围栏(例如,如果需要 100 米半径,则注册半径为 200-300 米的地理围栏)。当用户进入半径较大的 Geophence 时 - 启动带有位置更新的服务以提高地理围栏精度。当用户刚进入时 - 禁用定位服务。

为了改进它,让我们进行一些检查

1) 使用广播接收器来轻松触发它而不是服务。并使用 intent-filter 设置优先级。

例如

    <receiver
        android:name=".youpackage.GeoReceiver"
        android:exported="true">
        <intent-filter android:priority="999">
            <action android:name="yourpackage.ACTION_RECEIVE_GEOFENCE" />
        </intent-filter>
    </receiver>

您的待定意向将是:

Intent intent = new Intent("yourpackage.ACTION_RECEIVE_GEOFENCE");
PendingIntent pendingIntent = PendingIntent.getBroadcast(
                youractivity,
                0,
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT);

2) 当您的 GPS 进入休眠模式时,我们需要在创建地理围栏时将其唤醒。一旦你创建了你的地理围栏,你就可以开始 ping 你的 GPS 直到你得到 ENTER 转换。这一定有助于触发它。

public class GPSService extends Service implements GoogleApiClient.ConnectionCallbacks,    GoogleApiClient.OnConnectionFailedListener, LocationListener {

    GoogleApiClient mGoogleApiClient;
    LocationRequest locationRequest;

    public GPSService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
     // TODO: Return the communication channel to the service.
     throw new UnsupportedOperationException("Not yet implemented");
    }

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


     mGoogleApiClient = new GoogleApiClient.Builder(this)
        .addApi(LocationServices.API)
        .addConnectionCallbacks(this)
        .addOnConnectionFailedListener(this)
        .build();

     mGoogleApiClient.connect();
  }

   @Override
   public void onLocationChanged(Location location) {

     Utility.ReadAndWriteData(this, Utility.readFileName(this), "Still Geofence is not triggered!!!");

    }

    @Override
    public void onConnected(Bundle bundle) {

        locationRequest = LocationRequest.create();
      locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        locationRequest.setFastestInterval(1000);
        locationRequest.setInterval(2000);
    LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,locationRequest,this);

    }

   @Override
   public void onConnectionFailed(ConnectionResult connectionResult)   {

  }

     @Override
     public void onConnectionSuspended(int i) {

     }

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

        if(mGoogleApiClient.isConnected()) {

     LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);

       }
 } 

并且不要忘记在您进入 ENTER 转换时立即停止此服务 否则它会耗尽电池电量。此服务仅用于将 GPS 从睡眠模式唤醒。

问题在于,当您的 phone 处于深度睡眠状态时,它无法准确更新位置。更新位置最准确的方法是 GPS,这也是最耗电的。其他更新位置的方法(例如使用蜂窝网络)会消耗更少的电量,但也不太准确。默认情况下,地理围栏希望在发送意图之前真正确定您在地理围栏中。在深度睡眠中很难获得这种准确度,因为 phone 没有获得准确的位置数据。

地理围栏在您解锁 phone 并使用位置感知应用时立即触发的原因是该应用会更新您的地理围栏看到的 LastLocation,然后发送意图.当您的 phone 处于深度睡眠状态时,位置不会更新。

对于地理围栏,您还可以调整一些设置来提高响应速度。我看到您已经在使用 setLoiteringDelay,尝试使用不同的值,也许尝试非常小的值,看看会发生什么。您还可以为 setNotificationResponsiveness 设置一个值,其工作方式类似。这样做应该会让你的围栏反应更快,但它可能会消耗更多的电池寿命。如果您还没有阅读 API setLoiteringDelay and setNotificationResponsiveness. Also read the geofence troubleshooting 部分的参考资料。

您也可以增加地理围栏的大小,尝试将其加倍然后进行测试。由于您在深度睡眠时的定位精度较低,这将使您的 phone 更容易确定它在地理围栏内,一旦确定它在地理围栏内,它就会发送意图。

希望对您有所帮助!