Androidデバイスの画面がオフのときにリアルタイムでデバイスの位置データを継続的に記録するアプリケーションを作成しようとしています。私のコードはAndroid 6.0以前で正しく動作しますAndroid 7.0+でアプリが壊れるようです。
Androidフォアグラウンドサービスを実装し、ウェイクロックを使用し、Google FusedLocation APIにサブスクライブします。画面がオフになると、onLocationChanged
コールバックはトリガーされません。
誰かがこの問題の解決策を見たことがありますか? Androidアプリのデバイス設定、Googleサービスフレームワーク、フューズドロケーションの両方で、バッテリーの最適化を無効にしてみました。
public class ForegroundLocationService extends Service implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
LocationListener {
private static final String TAG = ForegroundLocationService.class.getSimpleName();
// the notification id for the foreground notification
public static final int GPS_NOTIFICATION = 1;
// the interval in seconds that gps updates are requested
private static final int UPDATE_INTERVAL_IN_SECONDS = 15;
// is this service currently running in the foreground?
private boolean isForeground = false;
// the google api client
private GoogleApiClient googleApiClient;
// the wakelock used to keep the app alive while the screen is off
private PowerManager.WakeLock wakeLock;
@Override
public void onCreate() {
super.onCreate();
// create google api client
googleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
// get a wakelock from the power manager
final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!isForeground) {
Log.v(TAG, "Starting the " + this.getClass().getSimpleName());
startForeground(ForegroundLocationService.GPS_NOTIFICATION,
notifyUserThatLocationServiceStarted());
isForeground = true;
// connect to google api client
googleApiClient.connect();
// acquire wakelock
wakeLock.acquire();
}
return START_REDELIVER_INTENT;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
Log.v(TAG, "Stopping the " + this.getClass().getSimpleName());
stopForeground(true);
isForeground = false;
// disconnect from google api client
googleApiClient.disconnect();
// release wakelock if it is held
if (null != wakeLock && wakeLock.isHeld()) {
wakeLock.release();
}
super.onDestroy();
}
private LocationRequest getLocationRequest() {
LocationRequest locationRequest = LocationRequest.create();
// we always want the highest accuracy
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
// we want to make sure that we get an updated location at the specified interval
locationRequest.setInterval(TimeUnit.SECONDS.toMillis(0));
// this sets the fastest interval that the app can receive updates from other apps accessing
// the location service. for example, if Google Maps is running in the background
// we can update our location from what it sees every five seconds
locationRequest.setFastestInterval(TimeUnit.SECONDS.toMillis(0));
locationRequest.setMaxWaitTime(TimeUnit.SECONDS.toMillis(UPDATE_INTERVAL_IN_SECONDS));
return locationRequest;
}
private Notification notifyUserThatLocationServiceStarted() {
// pop up a notification that the location service is running
Intent notificationIntent = new Intent(this, NotificationActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
final Notification.Builder builder = new Notification.Builder(this)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle(getString(R.string.foreground_location_service))
.setContentText(getString(R.string.service_is_running))
.setContentIntent(pendingIntent)
.setWhen(System.currentTimeMillis());
final Notification notification;
if (Build.VERSION.SDK_INT < 16) {
notification = builder.getNotification();
} else {
notification = builder.build();
}
return notification;
}
@Override
public void onConnected(@Nullable Bundle bundle) {
try {
// request location updates from the fused location provider
LocationServices.FusedLocationApi.requestLocationUpdates(
googleApiClient, getLocationRequest(), this);
} catch (SecurityException securityException) {
Log.e(TAG, "Exception while requesting location updates", securityException);
}
}
@Override
public void onConnectionSuspended(int i) {
Log.i(TAG, "Google API Client suspended.");
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
Log.e(TAG, "Failed to connect to Google API Client.");
}
@Override
public void onLocationChanged(Location location) {
Log.e(TAG, "onLocationChanged: " + location.toString());
}
}
私がここでテストするために使用しているコードの完全な作業サンプルを含めました: https://github.com/joshuajwitter/ForegroundLocationService
FitBitにも同じ問題があるようです、ここに議論があります
ここに私がすでに見てきたリソースがあります:
Android O のコードサンプル
ディープスリープ(居眠り)時のフォアグラウンドサービスでの一貫性のない場所の更新
フォアグラウンドサービスがDozeモードで位置情報の更新を受信しないAndroid O
編集2018.01.30:独自のプロセスでForegroundLocationServiceを実行してみました:
<service
Android:name="foregroundlocation.stackoverflowexample.com.foregroundlocation.ForegroundLocationService"
Android:enabled="true"
Android:stopWithTask="false"
Android:process=":ForegroundLocationService"
Android:exported="false" >
</service>
FusedLocationProviderClient
を使用するようにコードを更新するだけでなく、まだうまくいきません。
7.0と8.0を実行しているエミュレートされたデバイスでこの問題が発生していましたが、画面がオフ(cmd + P)のときは常にロケーションコールバックがトリガーされませんでした。ようやく実際の7.0デバイス(Galaxy S7)を手に入れることができ、問題を再現することができませんでした。私が誰かの時間を無駄にしたら申し訳ありませんが、どうやらそれはエミュレータの問題です。
私は同じ問題を抱えていて、有効な解決策を見つけました。現在地の更新をリッスンするには、このメソッドを呼び出さないでください。
public Task<Void> requestLocationUpdates (LocationRequest request,
LocationCallback callback, Looper looper)
コールバックの代わりに保留中のインテントを使用する必要があります。つまり、メソッドを呼び出す必要があります。
public Task<Void> requestLocationUpdates (LocationRequest request,
PendingIntent callbackIntent)
1つの解決策はこれかもしれません:
2分以内に位置情報が受信されないかどうかを確認し、全体の位置情報の更新をもう一度停止/開始します。フォアグラウンドサービスで問題を解決する必要がありますが
LocationServices.FusedLocationApiは非推奨です。
アプリを次のようにリファクタリングしました。それが役に立てば幸い:
これは私のサービスのonCreate-Methodで私が呼んでいるものです:
public void start(Context ctx) {
FusedLocationProviderClient client = LocationServices
.getFusedLocationProviderClient(ctx);
//Define quality of service:
LocationRequest request = LocationRequest.create();
request.setInterval(1000); //Every second
request.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
if (locCallback != null) {
client.removeLocationUpdates(locCallback);
}
locCallback = createNewLocationCallback();
client.requestLocationUpdates(request, locCallback, null);
}
そして、これはコールバックを作成する方法です:
private LocationCallback createNewLocationCallback() {
LocationCallback locationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult result) {
Location loc = result.getLastLocation();
// Do something with the location (may be null!)
}
};
return locationCallback;
}
推測、私はどこかで例としてそれを見つけましたが、参照を保持しませんでした。
オレオは一人ではありません。しかし、一部のテスターはそれが機能することを確認しました。
サービスをフォアグラウンドで維持するには、サービスのonCreateメソッドで後で「startForeground」を呼び出すだけです。