一篇搞定Android M運行時權限

由于本人能力有限,文中若有錯誤之處,歡迎指正。
轉載請注明出處:http://www.lxweimin.com/p/d6b3e16cc1d9

從 Android 6.0(API 23)開始,用戶開始在應用運行時向其授予權限,而不是在應用安裝時授予。這種權限機制可以讓用戶更好的管理應用的權限,保障用戶隱私。

系統(tǒng)權限分為兩類:
  • 正常權限不會直接給用戶隱私權帶來風險。如果您的應用在其清單中列出了正常權限,系統(tǒng)將自動授予該權限。
  • 危險權限會授予應用訪問用戶機密數(shù)據(jù)的權限。如果您列出了危險權限,則用戶必須明確批準您的應用使用這些權限。
危險權限及權限組

需要注意的是:

  1. 在 Android 5.1(API 22)或更低版本,并且應用的 targetSdkVersion 是 22 或更低版本,則系統(tǒng)會在安裝時要求用戶授予權限。(沿用之前的權限系統(tǒng))
  2. 即使在安裝時已經(jīng)授予應用所有權限,在Android 6.0之后依然可以通過 "Setting" 來關閉已經(jīng)授予的權限。
  3. 在請求權限時,系統(tǒng)只告訴用戶應用需要的權限組,而不告知具體權限。
  4. 如果在未檢查授權的情況下,直接使用危險權限,會導致程序Crash。
  5. 使用 v4 包中的 ContextCompat 處理權限(v13 包中的FragmentCompat),不需要考慮版本問題。

相關API

  • int checkSelfPermission()

檢查應用是否有指定權限。返回值為 PackageManager.PERMISSION_GRANTED 表示有權限, PackageManager.PERMISSION_DENIED 表示無權限。

  • void requestPermissions()

請求指定權限,可以是多個,以數(shù)組的方式。

  • boolean shouldShowRequestPermissionRationale()

如果應用之前請求過此權限但用戶拒絕了請求,此方法將返回 true。

  • void onRequestPermissionsResult()

請求權限的結果回調。

使用原生API

因為以上列舉的相關API都是在 API 23 才有的,為了適配低版本,官方提供了 v4 v13 兼容包。我們可以直接使用兼容包中的方法進行權限處理。

步驟(以撥打電話為例)
  • 還是和以前一樣,先在清單文件中申請所需要的權限。
<uses-permission android:name="android.permission.CALL_PHONE"/>
  • 在使用到撥打電話的地方,進行權限檢查
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
        != PackageManager.PERMISSION_GRANTED) {
    // 應用沒有授予撥打電話權限,請求權限
    requestCameraPermission();
} else {
    // 應用被授予撥打電話權限 PackageManager.PERMISSION_GRANTED
    makeCall();
}
  • 如果有權限,直接撥打電話,至此結束
  • 如果沒有權限,則請求權限
ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.CALL_PHONE}, REQUEST_CALLPHONE);
  • 在請求權限過程中可以使用shouldShowRequestPermissionRationale()檢查是否被拒絕過,如果被拒絕過,可以給用戶一個詳細解釋。
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
    // 向用戶詳細解釋申請該權限的原因
    new AlertDialog.Builder(this)
            .setCancelable(false)
            .setMessage("撥打電話需要使用電話權限,如果不授予權限會導致該功能無法正常使用")
            .setPositiveButton("好的", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    ActivityCompat.requestPermissions(
                            OriginalActivity.this,
                            new String[]{Manifest.permission.CALL_PHONE},
                            REQUEST_CALLPHONE
                    );
                }
            })
            .setNegativeButton("不給", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            })
            .show();
} 
  • 處理授權結果回調
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                   @NonNull int[] grantResults) {

    if (requestCode == REQUEST_CALLPHONE) {
        if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 授予權限,撥打電話
            makeCall();
        } else {
            Toast.makeText(this, "請求權限被拒絕", Toast.LENGTH_SHORT).show();
        }
    } else {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

使用輪子

在處理運行時權限的時候,雖然官方提供了兼容包不再需要做版本檢查,但處理起來依然使代碼很雜亂。現(xiàn)在已經(jīng)出現(xiàn)了很多處理運行時權限的開源庫,這里給大家推薦 PermissionsDispatcher。該庫在GitHub同比獲得 star 最多。而且使用 apt 技術,在編譯時期動態(tài)生成xxxxPermissionsDispatcher模板代碼,效率很高!

API 簡介

該庫使用 apt 技術,自然使用的就是注解。

注解 是否必須 作用
@RuntimePermissions 標記Activity/Fragment,則注解解釋器會生成對應類的代碼
@NeedsPermission 標記需要授權才能執(zhí)行的方法
@OnShowRationale 對應shouldShowRequestPermissionRationale(),當應用之前請求過此權限但用戶拒絕了請求,再次請求時調用
@OnPermissionDenied 當請求權限遭拒絕時調用
@OnNeverAskAgain 當用戶勾選不再提示,并拒絕權限時,再次請求時調用
步驟(以使用相機為例)
  • 還是在清單文件中聲明使用的權限
<uses-permission android:name="android.permission.CAMERA" />
  • 配置依賴 PermissionsDispatcher,這里不再贅述
  • 代碼示例
@RuntimePermissions
public class PermissionsDispatcherActivity extends AppCompatActivity {

    private ImageView imageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imageView = (ImageView) findViewById(R.id.imageView);

        findViewById(R.id.btn_camera).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PermissionsDispatcherActivityPermissionsDispatcher.takePhotoWithCheck(PermissionsDispatcherActivity.this);
            }
        });

    }

    @NeedsPermission(Manifest.permission.CAMERA)
    void takePhoto() {
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);// 啟動系統(tǒng)相機
        startActivityForResult(intent, 100);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) { // 如果返回數(shù)據(jù)
            if (requestCode == 100) { // 判斷請求碼是否為REQUEST_CAMERA,如果是代表是這個頁面?zhèn)鬟^去的,需要進行獲取
                Bundle bundle = data.getExtras(); // 從data中取出傳遞回來縮略圖的信息,圖片質量差,適合傳遞小圖片
                Bitmap bitmap = (Bitmap) bundle.get("data"); // 將data中的信息流解析為Bitmap類型
                imageView.setImageBitmap(bitmap);// 顯示圖片
            }
        }
    }

    @OnShowRationale(Manifest.permission.CAMERA)
    void showRationaleForRecord(final PermissionRequest request) {
        new AlertDialog.Builder(this)
                .setPositiveButton("好的", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        request.proceed();
                    }
                })
                .setNegativeButton("不給", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        request.cancel();
                    }
                })
                .setCancelable(false)
                .setMessage("拍照需要相機權限,應用將要申請使用相機權限")
                .show();
    }

    @OnPermissionDenied(Manifest.permission.CAMERA)
    void showCameraDenied() {
        Toast.makeText(getApplicationContext(), "權限被拒絕", Toast.LENGTH_LONG).show();
    }

    @OnNeverAskAgain(Manifest.permission.CAMERA)
    void onRCameraNeverAskAgain() {
        new AlertDialog.Builder(this)
                .setPositiveButton("好的", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // 打開系統(tǒng)應用設置
                        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                        intent.setData(Uri.parse("package:" + getPackageName()));
                        intent.addCategory(Intent.CATEGORY_DEFAULT);
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        startActivity(intent);
                        dialog.cancel();
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                    }
                })
                .setCancelable(false)
                .setMessage("您已經(jīng)禁止了相機權限,是否現(xiàn)在去開啟")
                .show();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        PermissionsDispatcherActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
    }
}
使用注意
  • 注解的方法不能是private
  • 在同一 Activity/Fragment 中可以多次使用以上注解,但是同一組權限處理中注解的value的值應該相同。
  • AS 中可以配合 PermissionsDispatcher plugin 插件一起使用。

總結與建議

  1. 請求權限顯示的是標準Android對話框,我們不能自定義。
  2. targetSdkVersion 設置為 22 或更低版本只是權宜之計。作為App開發(fā)者,需要盡快適配新權限機制。
  3. 在某個功能模塊嚴重依賴某些權限的情況下,為了減少程序中出現(xiàn)過多權限檢查,可以在該模塊入口處統(tǒng)一檢查,如果沒有授予相應權限,則不提供該模塊使用。

文中的所有代碼以上傳至github
RuntimePermissionDemo

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容