1. 程式人生 > >Andrid6.0許可權機制介紹及處理

Andrid6.0許可權機制介紹及處理

在Android M新增的許可權機制中,Google將許可權分為兩類:

  • Normal Permissions(普通許可權):不涉及使用者隱私,不需要使用者進行授權,比如訪問網路等
android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_NOTIFICATION_POLICY
android.permission.ACCESS_WIFI_STATE
android.permission.ACCESS_WIMAX_STATE
android.permission
.BLUETOOTH android.permission.BLUETOOTH_ADMIN android.permission.BROADCAST_STICKY android.permission.CHANGE_NETWORK_STATE android.permission.CHANGE_WIFI_MULTICAST_STATE android.permission.CHANGE_WIFI_STATE android.permission.CHANGE_WIMAX_STATE android.permission.DISABLE_KEYGUARD android.permission.EXPAND
_STATUS_BAR android.permission.FLASHLIGHT android.permission.GET_ACCOUNTS android.permission.GET_PACKAGE_SIZE android.permission.INTERNET android.permission.KILL_BACKGROUND_PROCESSES android.permission.MODIFY_AUDIO_SETTINGS android.permission.NFC android.permission.READ_SYNC_SETTINGS android.permission
.READ_SYNC_STATS android.permission.RECEIVE_BOOT_COMPLETED android.permission.REORDER_TASKS android.permission.REQUEST_INSTALL_PACKAGES android.permission.SET_TIME_ZONE android.permission.SET_WALLPAPER android.permission.SET_WALLPAPER_HINTS android.permission.SUBSCRIBED_FEEDS_READ android.permission.TRANSMIT_IR android.permission.USE_FINGERPRINT android.permission.VIBRATE android.permission.WAKE_LOCK android.permission.WRITE_SYNC_SETTINGS com.android.alarm.permission.SET_ALARM com.android.launcher.permission.INSTALL_SHORTCUT com.android.launcher.permission.UNINSTALL_SHORTCUT
  • Dangerous Permission(危險許可權):涉及到使用者隱私,需要使用者進行授權,比如相機訪問、讀取SD卡等
group:android.permission-group.CONTACTS
  permission:android.permission.WRITE_CONTACTS
  permission:android.permission.GET_ACCOUNTS    
  permission:android.permission.READ_CONTACTS

group:android.permission-group.PHONE
  permission:android.permission.READ_CALL_LOG
  permission:android.permission.READ_PHONE_STATE 
  permission:android.permission.CALL_PHONE
  permission:android.permission.WRITE_CALL_LOG
  permission:android.permission.USE_SIP
  permission:android.permission.PROCESS_OUTGOING_CALLS
  permission:com.android.voicemail.permission.ADD_VOICEMAIL

group:android.permission-group.CALENDAR
  permission:android.permission.READ_CALENDAR
  permission:android.permission.WRITE_CALENDAR

group:android.permission-group.CAMERA
  permission:android.permission.CAMERA

group:android.permission-group.SENSORS
  permission:android.permission.BODY_SENSORS

group:android.permission-group.LOCATION
  permission:android.permission.ACCESS_FINE_LOCATION
  permission:android.permission.ACCESS_COARSE_LOCATION

group:android.permission-group.STORAGE
  permission:android.permission.READ_EXTERNAL_STORAGE
  permission:android.permission.WRITE_EXTERNAL_STORAGE

group:android.permission-group.MICROPHONE
  permission:android.permission.RECORD_AUDIO

group:android.permission-group.SMS
  permission:android.permission.READ_SMS
  permission:android.permission.RECEIVE_WAP_PUSH
  permission:android.permission.RECEIVE_MMS
  permission:android.permission.RECEIVE_SMS
  permission:android.permission.SEND_SMS
  permission:android.permission.READ_CELL_BROADCASTS

執行時許可權只適合APP執行在Android 6.x以上的機器,對於危險許可權Google官方也對其進行了分組,只要一組中的某一許可權被授權,同組的許可權也同樣會被授權。

操作步驟

新的許可權機制操作步驟如下

  • 在Androidmanifest.xml檔案宣告相關許可權

  • 通過ContextCompat.checkSelfPermission方法檢查某項許可權被授予情況

  • 通過ActivityCompat.shouldShowRequestPermissionRationale方法檢查許可權是否被永久拒絕需要向用戶說明

  • 通過Activity.requestPermissions申請授權

  • 處理Activity.onRequestPermissionsResult許可權回撥

Google給出的Demo實現方式

/**
 * Check that all given permissions have been granted by verifying that each entry in the
 * given array is of the value {@link PackageManager#PERMISSION_GRANTED}.
 *
 * @see Activity#onRequestPermissionsResult(int, String[], int[])
 */
public static boolean verifyPermissions(int[] grantResults) {
    // At least one result must be checked.
    if(grantResults.length < 1){
        return false;
    }

    // Verify that each required permission has been granted, otherwise return false.
    for (int result : grantResults) {
        if (result != PackageManager.PERMISSION_GRANTED) {
            return false;
        }
    }
    return true;
}

public void showContacts(View v) {
    Log.i(TAG, "Show contacts button pressed. Checking permissions.");

    // 驗證所有聯絡人許可權是否已經獲得
    if (ActivityCompat.checkSelfPermission(this,             Manifest.permission.READ_CONTACTS)
            != PackageManager.PERMISSION_GRANTED
            || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS)
            != PackageManager.PERMISSION_GRANTED) {
        // 通訊錄許可權未獲得
        Log.i(TAG, "Contact permissions has NOT been granted. Requesting permissions.");
        requestContactsPermissions();

    } else {
        // 通訊錄許可權已獲得,顯示通訊錄Fragment
        Log.i(TAG,
                "Contact permissions have already been granted. Displaying contact details.");
        showContactDetails();
    }
}

/**
 * 請求通訊錄許可權
 * 如果許可權已經被拒絕了,那麼會彈出一個彈框提醒使用者去授予許可權,否則會直接請求
 */
private void requestContactsPermissions() {
    // BEGIN_INCLUDE(contacts_permission_request)
    if (ActivityCompat.shouldShowRequestPermissionRationale(this,
            Manifest.permission.READ_CONTACTS)
            || ActivityCompat.shouldShowRequestPermissionRationale(this,
            Manifest.permission.WRITE_CONTACTS)) {

        // 如果沒有授予許可,請向使用者提供額外的理由來讓使用者理解請求此許可權的原因
        // 例如,如果以前已經拒絕了許可權請求
        Log.i(TAG,"Displaying contacts permission rationale to provide additional context.");

        // 顯示一個帶有說明和按鈕的SnackBar來觸發請求。
        Snackbar.make(mLayout, R.string.permission_contacts_rationale,
        Snackbar.LENGTH_INDEFINITE)
                .setAction(R.string.ok, new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        ActivityCompat.requestPermissions(MainActivity.this, PERMISSIONS_CONTACT,
                        REQUEST_CONTACTS);
                    }
                })
                .show();
    } else {
        // 通訊錄許可權尚未授予,直接請求
        ActivityCompat.requestPermissions(this, PERMISSIONS_CONTACT,
        REQUEST_CONTACTS);
    }
    // END_INCLUDE(contacts_permission_request)
}

//許可權處理結果的回撥
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull      String[] permissions,@NonNull int[] grantResults) {

    if (requestCode == REQUEST_CONTACTS) {
        Log.i(TAG, "Received response for contact permissions request.");

        // 對請求的所有聯絡人許可權再核實一遍
        if (PermissionUtil.verifyPermissions(grantResults)) {
            // 所有許可權都已經獲得,顯示聯絡人頁面
            Snackbar.make(mLayout, R.string.permision_available_contacts,
                    Snackbar.LENGTH_SHORT)
                    .show();
        } else {
            Log.i(TAG, "Contacts permissions were NOT granted.");
            Snackbar.make(mLayout, R.string.permissions_not_granted,
                    Snackbar.LENGTH_SHORT)
                    .show();
        }

    } else {
        super.onRequestPermissionsResult(requestCode, permissions,grantResults);
    }
}

關鍵的幾個方法

1.檢查是否擁有指定許可權

android.support.v4.content.ContextCompat.java

/**
* Determine whether <em>you</em> have been granted a particular permission.
*
* @param permission The name of the permission being checked.
*
* @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if you have the
* permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} if not.
*
* @see android.content.pm.PackageManager#checkPermission(String, String)
*/
public static int checkSelfPermission(@NonNull Context context, @NonNull String permission) {
    if (permission == null) {
        throw new IllegalArgumentException("permission is null");
    }

    return context.checkPermission(permission, android.os.Process.myPid(), Process.myUid());
}

2.判斷是否徹底拒絕而了某項許可權而需要對使用者進行說明

android.support.v4.app.ActivityCompat.java

/**
 * Gets whether you should show UI with rationale for requesting a permission.
 * You should do this only if you do not have the permission and the context in
 * which the permission is requested does not clearly communicate to the user
 * what would be the benefit from granting this permission.
 * <p>
 * For example, if you write a camera app, requesting the camera permission
 * would be expected by the user and no rationale for why it is requested is
 * needed. If however, the app needs location for tagging photos then a non-tech
 * savvy user may wonder how location is related to taking photos. In this case
 * you may choose to show UI with rationale of requesting this permission.
 * </p>
 *
 * @param activity The target activity.
 * @param permission A permission your app wants to request.
 * @return Whether you can show permission rationale UI.
 *
 * @see #checkSelfPermission(android.content.Context, String)
 * @see #requestPermissions(android.app.Activity, String[], int)
 */
public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity,
        @NonNull String permission) {
    if (Build.VERSION.SDK_INT >= 23) {
        return ActivityCompatApi23.shouldShowRequestPermissionRationale(activity, permission);
    }
    return false;
}

3.發出許可權請求

android.app.Activity

/**
 * Requests permissions to be granted to this application. These permissions
 * must be requested in your manifest, they should not be granted to your app,
 * and they should have protection level {@link android.content.pm.PermissionInfo
 * #PROTECTION_DANGEROUS dangerous}, regardless whether they are declared by
 * the platform or a third-party app.
 * <p>
 * Normal permissions {@link android.content.pm.PermissionInfo#PROTECTION_NORMAL}
 * are granted at install time if requested in the manifest. Signature permissions
 * {@link android.content.pm.PermissionInfo#PROTECTION_SIGNATURE} are granted at
 * install time if requested in the manifest and the signature of your app matches
 * the signature of the app declaring the permissions.
 * </p>
 * <p>
 * If your app does not have the requested permissions the user will be presented
 * with UI for accepting them. After the user has accepted or rejected the
 * requested permissions you will receive a callback on {@link
 * #onRequestPermissionsResult(int, String[], int[])} reporting whether the
 * permissions were granted or not.
 * </p>
 * <p>
 * Note that requesting a permission does not guarantee it will be granted and
 * your app should be able to run without having this permission.
 * </p>
 * <p>
 * This method may start an activity allowing the user to choose which permissions
 * to grant and which to reject. Hence, you should be prepared that your activity
 * may be paused and resumed. Further, granting some permissions may require
 * a restart of you application. In such a case, the system will recreate the
 * activity stack before delivering the result to {@link
 * #onRequestPermissionsResult(int, String[], int[])}.
 * </p>
 * <p>
 * When checking whether you have a permission you should use {@link
 * #checkSelfPermission(String)}.
 * </p>
 * <p>
 * Calling this API for permissions already granted to your app would show UI
 * to the user to decide whether the app can still hold these permissions. This
 * can be useful if the way your app uses data guarded by the permissions
 * changes significantly.
 * </p>
 * <p>
 * You cannot request a permission if your activity sets {@link
 * android.R.styleable#AndroidManifestActivity_noHistory noHistory} to
 * <code>true</code> because in this case the activity would not receive
 * result callbacks including {@link #onRequestPermissionsResult(int, String[], int[])}.
 * </p>
 * <p>
 * The <a href="http://developer.android.com/samples/RuntimePermissions/index.html">
 * RuntimePermissions</a> sample app demonstrates how to use this method to
 * request permissions at run time.
 * </p>
 *
 * @param permissions The requested permissions. Must me non-null and not empty.
 * @param requestCode Application specific request code to match with a result
 *    reported to {@link #onRequestPermissionsResult(int, String[], int[])}.
 *    Should be >= 0.
 *
 * @see #onRequestPermissionsResult(int, String[], int[])
 * @see #checkSelfPermission(String)
 * @see #shouldShowRequestPermissionRationale(String)
 */
public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
    if (mHasCurrentPermissionsRequest) {
        Log.w(TAG, "Can reqeust only one set of permissions at a time");
        // Dispatch the callback with empty arrays which means a cancellation.
        onRequestPermissionsResult(requestCode, new String[0], new int[0]);
        return;
    }
    Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
    startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
    mHasCurrentPermissionsRequest = true;
}

4.在Activity中接受使用者處理結果回撥

android.app.Activity

/**
 * Callback for the result from requesting permissions. This method
 * is invoked for every call on {@link #requestPermissions(String[], int)}.
 * <p>
 * <strong>Note:</strong> It is possible that the permissions request interaction
 * with the user is interrupted. In this case you will receive empty permissions
 * and results arrays which should be treated as a cancellation.
 * </p>
 *
 * @param requestCode The request code passed in {@link #requestPermissions(String[], int)}.
 * @param permissions The requested permissions. Never null.
 * @param grantResults The grant results for the corresponding permissions
 *     which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED}
 *     or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null.
 *
 * @see #requestPermissions(String[], int)
 */
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
        @NonNull int[] grantResults) {
    /* callback - no nothing */
}