參考資料
http://www.jianshu.com/p/d299f22dfbdb
http://blog.csdn.net/qq_34767498/article/details/51814669
Android Runtime Permission
Android 6.0(API 級別 23) 引入了一種新的權限模式,用戶可以在運行時對管理應用權限。有點類似 iOS 。即使你的 APP 不去適配 Android 6.0,但是官方建議還是使用新的權限 API 去開發你的應用。
大致的使用流程如下:
在 AndroidManifest.xml 申明權限,添加 uses-permission ,這一步和以前的權限申請一致
檢查權限,ContextCompat.checkSelfPermission() 方法。如果應用具有此權限,方法將返回 PackageManager.PERMISSION_GRANTED,並且應用可以繼續操作。如果應用不具有此權限,方法將返回 PERMISSION_DENIED,且應用必須明確向用戶要求權限。比如
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CALENDAR);
請求權限,如果你的 APP 權限裏有危險的權限(拍照,短信,麥克風),那麼必須要要讓用戶授予權限,調用 shouldShowRequestPermissionRationale() 會彈出對話框讓用戶選擇,這個對話框是不是能修改的! 來看下大致的代碼:
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}
4 . 處理權限請求回調,看代碼
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
// other 'case' lines to check for other
// permissions this app might request
}
}
大致的流程就是上面的幾步,怎麼區分你的權限是否需要用戶授權,可以查看https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous
當然也可以使用命令 adb shell pm list permissions -d -g 查看Dangerous Permission
下面是完整的代碼,檢測相機的權限,原生的 permission 實現
public class PermissionActivity extends AppCompatActivity {
private String sdPath;//SD卡的路徑
private String picPath;//圖片存儲路徑
public static final int REQUEST_CUST_CAMERA = 0X12;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_permission);
findViewById(R.id.readSDCard).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
checkPermission(Manifest.permission.CAMERA);
}
});
}
// 原生的permission check
public void checkPermission(String permission) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
// 這裏很奇怪,基本進不到這步
Toast.makeText(this, "show permission detail", Toast.LENGTH_SHORT).show();
} else {
ActivityCompat.requestPermissions(this, new String[]{permission}, REQUEST_CUST_CAMERA);
}
} else {
showReadFile();
}
}
void showReadFile() {
sdPath = Environment.getExternalStorageDirectory().getPath();
picPath = sdPath + "/" + "temp.png";
Intent intent2 = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Uri uri = Uri.fromFile(new File(picPath));
//爲拍攝的圖片指定一個存儲的路徑
intent2.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent2, 0x12);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CUST_CAMERA) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
showReadFile();
} else {
//用戶取消了授權
//用戶在安全中心關閉了權限(測試的小米手機)
Toast.makeText(PermissionActivity.this, "permission denied", Toast.LENGTH_SHORT).show();
//如果實在很重要,可以引導用戶去設置界面,開啓
}
}
}
}
## 下面來介紹下第三方的庫: PermissionsDispatcher 這個庫有以下的特點:
- 100% 自由反射
- 支持特殊的權限
- 支持小米
- 支持 kotlin
首先來看下怎麼使用
1. 下載
dependencies {
// 這裏注意替換掉 {latest.version} 去github 查詢最新的版本號,自行替換
compile("com.github.hotchemi:permissionsdispatcher:${latest.version}") {
// if you don't use android.app.Fragment you can exclude support for them
exclude module: "support-v13"
}
annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:${latest.version}"
}
repositories {
jcenter()
// 項目根目錄的 build.gradle repositories 添加 maven 支持
maven { url 'http://oss.jfrog.org/artifactory/oss-snapshot-local/' }
}
添加好後,build 下就可以下載該庫了
2. AndroidManifest.xml 申明權限,添加 uses-permission ,這一步和以前的權限申請一致
// 比如
<uses-permission android:name="android.permission.CAMERA" />
3. 在相應的頁面添加下面的代碼,看註釋
// 這裏註解是必須的, 表明這個Activity 要申請權限, 可以在 Activity 和 Fragment 上使用這個註解
@RuntimePermissions
public class PermissionActivity extends AppCompatActivity {
private String sdPath;//SD卡的路徑
private String picPath;//圖片存儲路徑
public static final int REQUEST_CUST_CAMERA = 0X12;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_permission);
findViewById(R.id.readSDCard).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// PermissionActivityPermissionsDispatcher 這個類是由庫自動生成的,一個封裝好權限檢測的類,其實就是在當前類的後面加上PermissionsDispatcher,所以初學者千萬不要照抄這裏
// showCameraWithCheck 則是根據NeedsPermission 註解生成的,可能有點不好理解,具體看 showCamera()
PermissionActivityPermissionsDispatcher.showCameraWithCheck(PermissionActivity.this);
}
});
}
// 這個註解也是必須的,指的是請求權限的方法,後面跟上對應的權限
// 這個註解修飾方法會在PermissionsDispatcher 類生成 (方法名+WithCheck)的方法,看上面的showCameraWithCheck
@NeedsPermission(Manifest.permission.CAMERA)
void showCamera() {
sdPath = Environment.getExternalStorageDirectory().getPath();
picPath = sdPath + "/" + "temp.png";
Intent intent2 = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Uri uri = Uri.fromFile(new File(picPath));
//爲拍攝的圖片指定一個存儲的路徑
intent2.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent2, 0x12);
}
// 這裏和原生的一致
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// 調用 PermissionActivityPermissionsDispatcher 的靜態方法,然後會回調 @NeedsPermission,@OnPermissionDenied 和 @OnNeverAskAgain,分別對應爲 用戶通過了權限,權限不通過和 用戶點了不再詢問。
PermissionActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
// 權限不通過 這個註解也是必須的
@OnPermissionDenied(Manifest.permission.CAMERA)
void showDeniedForRead() {
Toast.makeText(this, "showDeniedForRead", Toast.LENGTH_SHORT).show();
}
// 用戶點了不再詢問 這個註解也是必須的
@OnNeverAskAgain(Manifest.permission.CAMERA)
void showNeverAskForRead() {
Toast.makeText(this, "showNeverAskForRead", Toast.LENGTH_SHORT).show();
}
}