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 콜백에 캡처됩니다. 앱은 요청이 거부되거나 취소될 수 있으므로 이를 구현하고 항상 반환 값을 확인해야 합니다. 한 번에 여러 권한을 요청하고 확인할 수도 있습니다. 다음 샘플에서는 단일 권한만 확인합니다.

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로 인스턴스화된 경우)을 자동으로 표시합니다. 권한이 부여되면 호출이 큐에 추가되고 다시 시도됩니다.