Dịch vụ Google Play và Quyền khi bắt đầu chạy

Kể từ Android 6.0 Marshmallow, Android sẽ sử dụng một mô hình quản lý quyền giúp đơn giản hoá quy trình cài đặt và tự động cập nhật ứng dụng. Quyền được yêu cầu trong thời gian chạy thay vì trước khi cài đặt ứng dụng. Ngoài ra, người dùng có thể chọn từ chối cấp một số quyền cụ thể. Để mang lại cho người dùng sự linh hoạt này, bạn cần đảm bảo ứng dụng của bạn hoạt động như dự kiến khi người dùng bật hoặc tắt một quyền cụ thể.

Bản thân Dịch vụ Google Play đã có các quyền khi bắt đầu chạy mà người dùng có thể chọn từ chối riêng biệt với các quyền mà ứng dụng của bạn yêu cầu cụ thể. Dịch vụ Google Play tự động nhận tất cả quyền cần thiết để hỗ trợ các API của dịch vụ đó. Tuy nhiên, ứng dụng của bạn vẫn nên kiểm tra và yêu cầu cấp quyền khi bắt đầu chạy khi cần thiết và xử lý lỗi thích hợp trong trường hợp người dùng đã từ chối cấp cho Dịch vụ Google Play quyền cần thiết cho API mà ứng dụng sử dụng.

Bạn nên quản lý kỳ vọng của người dùng khi đặt các quyền mà thời gian chạy có thể yêu cầu. Các phương pháp hay nhất sau đây sẽ giúp bạn tránh các vấn đề tiềm ẩn.

Điều kiện tiên quyết

Bạn cần khai báo quyền trong tệp AndroidManifest.xml. Ví dụ:

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

Nguyên tắc

Xác minh quyền trước khi gọi API

Sau khi khai báo các API mà bạn muốn sử dụng trong tệp AndroidManifest.xml, hãy đảm bảo rằng bạn có quyền cần thiết trước khi gọi API. Bạn có thể thực hiện việc này bằng cách sử dụng phương thức checkSelfPermission của ActivityCompat hoặc ContextCompat.

Nếu lệnh gọi trả về giá trị false, thì điều này có nghĩa là các quyền sẽ không được cấp và bạn nên sử dụng requestPermissions để yêu cầu các quyền đó. Phản hồi cho yêu cầu này được trả về trong một lệnh gọi lại mà bạn sẽ thấy trong bước tiếp theo.

Ví dụ:

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();
}

Triển khai lệnh gọi lại quyền yêu cầu

Nếu người dùng chưa cấp quyền mà ứng dụng của bạn cần, thì phương thức requestPermissions sẽ được gọi để yêu cầu người dùng cấp quyền đó. Phản hồi của người dùng được ghi lại trong lệnh gọi lại onRequestPermissionsResult. Ứng dụng của bạn nên triển khai thao tác này và luôn kiểm tra các giá trị trả về vì yêu cầu có thể bị từ chối hoặc huỷ. Bạn cũng có thể yêu cầu và kiểm tra nhiều quyền cùng một lần – mẫu sau đây chỉ kiểm tra một quyền.

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
        }
    }
}

Trình bày lý do yêu cầu cấp quyền

Nếu các quyền mà ứng dụng yêu cầu là cần thiết đối với các tính năng cốt lõi của ứng dụng và trước đó người dùng đã từ chối yêu cầu cấp quyền đó, thì ứng dụng phải cho thấy nội dung giải thích bổ sung trước khi yêu cầu cấp lại quyền. Nhiều khả năng người dùng sẽ cấp quyền khi họ hiểu lý do cần đến quyền đó và lợi ích tức thì đối với quyền đó.

Trong trường hợp này, trước khi gọi requestPermissions, bạn nên gọi shouldShowRequestPermissionRationale. Nếu giá trị này trả về giá trị true (đúng), bạn nên tạo một số giao diện người dùng để hiển thị ngữ cảnh bổ sung cho quyền.

Ví dụ: mã của bạn có thể giống như sau:

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();
}

Xử lý lỗi kết nối

Nếu ứng dụng của bạn dùng GoogleApiClient không dùng nữa, thì khi bạn gọi connect(), Dịch vụ Google Play sẽ xác thực rằng ứng dụng có tất cả các quyền cần thiết. connect() sẽ không thành công khi thiếu bất kỳ nhóm quyền nào mà chính Dịch vụ Google Play cần.

Nếu lệnh gọi đến connect() không thành công, hãy đảm bảo ứng dụng xử lý lỗi kết nối đúng cách. Nếu chính Dịch vụ Google Play thiếu quyền, bạn có thể gọi startResolutionForResult() để bắt đầu luồng người dùng để khắc phục các quyền đó.

Ví dụ:

@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;
    }
}

Các lệnh gọi API mới hơn dựa trên GoogleApi sẽ tự động hiển thị hộp thoại (nếu ứng dụng được tạo thực thể bằng Activity) hoặc thông báo từ khay hệ thống (nếu ứng dụng được tạo thực thể bằng Context) mà người dùng có thể nhấn để bắt đầu ý định phân giải quyền. Các lệnh gọi sẽ được đưa vào hàng đợi và thử lại sau khi được cấp quyền.