Google Play 開発者サービスと実行時の権限

Android 6.0 Marshmallow 以降、Android ではアプリのインストールと自動更新のプロセスを効率化する権限モデルを採用しています。権限は、アプリのインストール前ではなく、実行時にリクエストされます。特定の権限を拒否することもできます。 このような柔軟性をユーザーに提供するには、ユーザーが特定の権限を有効または無効にしたときに、アプリが想定どおりに動作することを確認する必要があります。

Google Play 開発者サービス自体にも実行時の権限があり、ユーザーはアプリが特別にリクエストした権限とは別に拒否することを選択できます。Google Play 開発者サービスは、API のサポートに必要なすべての権限を自動的に取得します。ただし、アプリは引き続き、必要に応じて実行時の権限を確認し、リクエストする必要があります。また、アプリで使用する API に必要な権限がユーザーが Google Play 開発者サービスを拒否した場合は、エラーを適切に処理する必要があります。

ランタイムに必要な権限を設定する場合のユーザーの想定を管理することをおすすめします。次のベスト プラクティスは、潜在的な問題を回避するのに役立ちます。

前提条件

AndroidManifest.xml ファイルで権限を宣言する必要があります。例:

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

ガイドライン

API を呼び出す前に権限を確認する

AndroidManifest.xml ファイルで使用する API を宣言したら、API を呼び出す前に必要な権限があることを確認してください。これを行うには、ActivityCompat または ContextCompatcheckSelfPermission メソッドを使用します。

呼び出しから false が返された場合は、権限が付与されていないことを意味し、requestPermissions を使用して権限をリクエストする必要があります。これに対するレスポンスは、次のステップで説明するコールバックで返されます。

例:

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
    != PackageManager.PERMISSION_GRANTED) {
  // Check Permissions Now
  ActivityCompat.requestPermissions(this,
      new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
      REQUEST_LOCATION);
} else {
  // permission has been granted, continue as usual
  Task<Location> locationResult = LocationServices
      .getFusedLocationProviderClient(this /** Context */)
      .getLastLocation();
}

権限リクエスト コールバックを実装する

アプリが必要とする権限がユーザーによって付与されていない場合は、requestPermissions メソッドを呼び出してユーザーに権限の付与を依頼する必要があります。ユーザーからのレスポンスは、onRequestPermissionsResult コールバックでキャプチャされます。リクエストが拒否またはキャンセルされる可能性があるため、アプリはこれを実装し、常に戻り値を確認する必要があります。複数の権限を一度にリクエストして確認することもできます。次のサンプルでは、1 つの権限のみをチェックしています。

public void onRequestPermissionsResult(int requestCode,
                                       String[] permissions,
                                       int[] grantResults) {
    if (requestCode == REQUEST_LOCATION) {
        if(grantResults.length == 1
           && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // We can now safely use the API we requested access to
            Task<Location> locationResult = LocationServices
                .getFusedLocationProviderClient(this /** Context */)
                .getLastLocation();
        } else {
            // Permission was denied or request was cancelled
        }
    }
}

権限の根拠を示す

アプリがリクエストする権限がアプリの重要な機能に必要であり、ユーザーが以前に権限リクエストを拒否したことがある場合は、権限を再度リクエストする前に追加の説明を表示する必要があります。ユーザーが権限が必要な理由とユーザーにとって直接的なメリットを理解すれば、権限を付与する可能性が高くなります。

この場合、requestPermissions を呼び出す前に shouldShowRequestPermissionRationale を呼び出す必要があります。true が返された場合は、権限の追加コンテキストを表示する UI を作成する必要があります。

たとえば、コードは次のようになります。

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
    != PackageManager.PERMISSION_GRANTED) {
    // Check Permissions Now
    private static final int REQUEST_LOCATION = 2;

    if (ActivityCompat.shouldShowRequestPermissionRationale(this,
            Manifest.permission.ACCESS_FINE_LOCATION)) {
        // Display UI and wait for user interaction
    } else {
        ActivityCompat.requestPermissions(
            this, new String[]{Manifest.permission.LOCATION_FINE},
            ACCESS_FINE_LOCATION);
    }
} else {
    // permission has been granted, continue as usual
    Task<Location> locationResult = LocationServices
        .getFusedLocationProviderClient(this /** Context */)
        .getLastLocation();
}

接続エラーを処理する

サポートが終了した GoogleApiClient をアプリで使用している場合、connect() を呼び出すと、Google Play 開発者サービスは、必要な権限がすべてアプリに付与されていることを検証します。Google Play 開発者サービス自体が必要とする権限グループがない場合、connect() は失敗します。

connect() の呼び出しが失敗した場合は、アプリが接続の失敗に正しく対処していることを確認してください。Google Play 開発者サービス自体に権限がない場合は、startResolutionForResult() を呼び出してユーザーフローを開始し、権限を修正できます。

例:

@Override
public void onConnectionFailed(ConnectionResult result) {
    if (mResolvingError) {
        // Already attempting to resolve an error.
        return;
    } else if (result.hasResolution()) {
        try {
            mResolvingError = true;
            result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
        } catch (SendIntentException e) {
            // There was an error with the resolution intent. Try again.
            mGoogleApiClient.connect();
        }
    } else {
        // Show dialog using GooglePlayServicesUtil.getErrorDialog()
        showErrorDialog(result.getErrorCode());
        mResolvingError = true;
    }
}

新しい GoogleApi ベースの API 呼び出しでは、ダイアログ(クライアントが Activity でインスタンス化されている場合)またはシステムトレイ通知(クライアントが Context でインスタンス化されている場合)が自動的に表示され、ユーザーはこれをタップして権限解決インテントを開始できます。権限を付与すると、呼び出しはキューに追加され、再試行されます。