由于本人能力有限,文中若有錯(cuò)誤之處,歡迎指正。
轉(zhuǎn)載請(qǐng)注明出處:http://m.itdecent.cn/p/d6b3e16cc1d9
從 Android 6.0(API 23)開始,用戶開始在應(yīng)用運(yùn)行時(shí)向其授予權(quán)限,而不是在應(yīng)用安裝時(shí)授予。這種權(quán)限機(jī)制可以讓用戶更好的管理應(yīng)用的權(quán)限,保障用戶隱私。
系統(tǒng)權(quán)限分為兩類:
- 正常權(quán)限不會(huì)直接給用戶隱私權(quán)帶來風(fēng)險(xiǎn)。如果您的應(yīng)用在其清單中列出了正常權(quán)限,系統(tǒng)將自動(dòng)授予該權(quán)限。
- 危險(xiǎn)權(quán)限會(huì)授予應(yīng)用訪問用戶機(jī)密數(shù)據(jù)的權(quán)限。如果您列出了危險(xiǎn)權(quán)限,則用戶必須明確批準(zhǔn)您的應(yīng)用使用這些權(quán)限。
需要注意的是:
- 在 Android 5.1(API 22)或更低版本,并且應(yīng)用的 targetSdkVersion 是 22 或更低版本,則系統(tǒng)會(huì)在安裝時(shí)要求用戶授予權(quán)限。(沿用之前的權(quán)限系統(tǒng))
- 即使在安裝時(shí)已經(jīng)授予應(yīng)用所有權(quán)限,在Android 6.0之后依然可以通過 "Setting" 來關(guān)閉已經(jīng)授予的權(quán)限。
- 在請(qǐng)求權(quán)限時(shí),系統(tǒng)只告訴用戶應(yīng)用需要的權(quán)限組,而不告知具體權(quán)限。
- 如果在未檢查授權(quán)的情況下,直接使用危險(xiǎn)權(quán)限,會(huì)導(dǎo)致程序Crash。
- 使用 v4 包中的 ContextCompat 處理權(quán)限(v13 包中的FragmentCompat),不需要考慮版本問題。
相關(guān)API
- int checkSelfPermission()
檢查應(yīng)用是否有指定權(quán)限。返回值為 PackageManager.PERMISSION_GRANTED 表示有權(quán)限, PackageManager.PERMISSION_DENIED 表示無權(quán)限。
- void requestPermissions()
請(qǐng)求指定權(quán)限,可以是多個(gè),以數(shù)組的方式。
- boolean shouldShowRequestPermissionRationale()
如果應(yīng)用之前請(qǐng)求過此權(quán)限但用戶拒絕了請(qǐng)求,此方法將返回 true。
- void onRequestPermissionsResult()
請(qǐng)求權(quán)限的結(jié)果回調(diào)。
使用原生API
因?yàn)橐陨狭信e的相關(guān)API都是在 API 23 才有的,為了適配低版本,官方提供了 v4 v13 兼容包。我們可以直接使用兼容包中的方法進(jìn)行權(quán)限處理。
步驟(以撥打電話為例)
- 還是和以前一樣,先在清單文件中申請(qǐng)所需要的權(quán)限。
<uses-permission android:name="android.permission.CALL_PHONE"/>
- 在使用到撥打電話的地方,進(jìn)行權(quán)限檢查
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED) {
// 應(yīng)用沒有授予撥打電話權(quán)限,請(qǐng)求權(quán)限
requestCameraPermission();
} else {
// 應(yīng)用被授予撥打電話權(quán)限 PackageManager.PERMISSION_GRANTED
makeCall();
}
- 如果有權(quán)限,直接撥打電話,至此結(jié)束。
- 如果沒有權(quán)限,則請(qǐng)求權(quán)限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CALL_PHONE}, REQUEST_CALLPHONE);
- 在請(qǐng)求權(quán)限過程中可以使用shouldShowRequestPermissionRationale()檢查是否被拒絕過,如果被拒絕過,可以給用戶一個(gè)詳細(xì)解釋。
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
// 向用戶詳細(xì)解釋申請(qǐng)?jiān)摍?quán)限的原因
new AlertDialog.Builder(this)
.setCancelable(false)
.setMessage("撥打電話需要使用電話權(quán)限,如果不授予權(quán)限會(huì)導(dǎo)致該功能無法正常使用")
.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();
}
- 處理授權(quán)結(jié)果回調(diào)
@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) {
// 授予權(quán)限,撥打電話
makeCall();
} else {
Toast.makeText(this, "請(qǐng)求權(quán)限被拒絕", Toast.LENGTH_SHORT).show();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
使用輪子
在處理運(yùn)行時(shí)權(quán)限的時(shí)候,雖然官方提供了兼容包不再需要做版本檢查,但處理起來依然使代碼很雜亂?,F(xiàn)在已經(jīng)出現(xiàn)了很多處理運(yùn)行時(shí)權(quán)限的開源庫,這里給大家推薦 PermissionsDispatcher。該庫在GitHub同比獲得 star 最多。而且使用 apt 技術(shù),在編譯時(shí)期動(dòng)態(tài)生成xxxxPermissionsDispatcher模板代碼,效率很高!
API 簡(jiǎn)介
該庫使用 apt 技術(shù),自然使用的就是注解。
| 注解 | 是否必須 | 作用 |
|---|---|---|
| @RuntimePermissions | √ | 標(biāo)記Activity/Fragment,則注解解釋器會(huì)生成對(duì)應(yīng)類的代碼 |
| @NeedsPermission | √ | 標(biāo)記需要授權(quán)才能執(zhí)行的方法 |
| @OnShowRationale | 對(duì)應(yīng)shouldShowRequestPermissionRationale(),當(dāng)應(yīng)用之前請(qǐng)求過此權(quán)限但用戶拒絕了請(qǐng)求,再次請(qǐng)求時(shí)調(diào)用 | |
| @OnPermissionDenied | 當(dāng)請(qǐng)求權(quán)限遭拒絕時(shí)調(diào)用 | |
| @OnNeverAskAgain | 當(dāng)用戶勾選不再提示,并拒絕權(quán)限時(shí),再次請(qǐng)求時(shí)調(diào)用 |
步驟(以使用相機(jī)為例)
- 還是在清單文件中聲明使用的權(quán)限
<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);// 啟動(dòng)系統(tǒng)相機(jī)
startActivityForResult(intent, 100);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) { // 如果返回?cái)?shù)據(jù)
if (requestCode == 100) { // 判斷請(qǐng)求碼是否為REQUEST_CAMERA,如果是代表是這個(gè)頁面?zhèn)鬟^去的,需要進(jìn)行獲取
Bundle bundle = data.getExtras(); // 從data中取出傳遞回來縮略圖的信息,圖片質(zhì)量差,適合傳遞小圖片
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("拍照需要相機(jī)權(quán)限,應(yīng)用將要申請(qǐng)使用相機(jī)權(quán)限")
.show();
}
@OnPermissionDenied(Manifest.permission.CAMERA)
void showCameraDenied() {
Toast.makeText(getApplicationContext(), "權(quán)限被拒絕", 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)應(yīng)用設(shè)置
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)禁止了相機(jī)權(quán)限,是否現(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 中可以多次使用以上注解,但是同一組權(quán)限處理中注解的value的值應(yīng)該相同。
- AS 中可以配合 PermissionsDispatcher plugin 插件一起使用。
總結(jié)與建議
- 請(qǐng)求權(quán)限顯示的是標(biāo)準(zhǔn)Android對(duì)話框,我們不能自定義。
- targetSdkVersion 設(shè)置為 22 或更低版本只是權(quán)宜之計(jì)。作為App開發(fā)者,需要盡快適配新權(quán)限機(jī)制。
- 在某個(gè)功能模塊嚴(yán)重依賴某些權(quán)限的情況下,為了減少程序中出現(xiàn)過多權(quán)限檢查,可以在該模塊入口處統(tǒng)一檢查,如果沒有授予相應(yīng)權(quán)限,則不提供該模塊使用。
文中的所有代碼以上傳至github
RuntimePermissionDemo