GitHub地址: 點擊打開鏈接
二、運行時權限
在Android 6.0以前,在安裝應用的時候,會根據權限聲明產生一個權限列表,用戶只有在同意所有權限以後才能完成app的安裝,這樣就要忍受一些不必要的權限。而在Android 6.0之後,應用可以直接安裝,使用時app需要權限時會給用戶提示,可以選擇同意或者拒絕(當然也可以在系統設置界面對每個app的權限進行查看,以及對單個權限進行授權或者解除授權)。
新的權限機制更好的保護了用戶的隱私,Google 將權限分爲兩類,一類是Normal Permissions,這類權限一般不涉及用戶隱私,不需要用戶進行授權,比如訪問網絡權限;另一類是Dangerous Permissions, 一般涉及到用戶的隱私,需要用戶進行授權,比如SD卡的讀寫權限。
- Normal Permissions
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
CHANGE_NETWORK_STATE
CHANGE_WIFI_MULTICAST_STATE
CHANGE_WIFI_STATE
GET_PACKAGE_SIZE
INTERNET
- Dangerous Permissions
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
Dangerous Permissions 中權限都是分組的,那麼分組對權限機制有什麼影響呢?如果app運行在Android 6.0以上版本的機器上,如果你申請某個危險的權限,假設你的app早已被用戶授權了同一組的某個危險權限,那麼系統會立即授權,而不需要用戶去點擊授權。比如你的app對READ_CONTACTS已經授權了,當你的app申請WRITE_CONTACTS時,系統會直接授權通過。
三、API的使用
1. 在AndroidManifest文件中添加需要的權限
這個步驟和之前的開發並沒有什麼變化
2. 檢查權限
以申請打電話的權限爲例:
// ContextCompat.checkSelfPermission()
// ContextCompat.checkSelfPermission()
// 方法返回值爲PackageManager.PERMISSION_DENIED或者PackageManager.PERMISSION_GRANTED
// 當返回GRANTED表示有該權限,DENIED表示沒有該權限
if(ContextCompat.checkSelfPermission(this,Manifest.permission.CALL_PHONE)!= PackageManager.PERMISSION_GRANTED){
// 沒有該權限 申請打電話權限
// 三個參數 第一個參數是 Context , 第二個參數是用戶需要申請的權限字符串數組,第三個參數是請求碼 主要用來處理用戶選擇的返回結果
}else {
...
}
這裏涉及到一個API方法,
ContextCompat.checkSelfPermission 主要用於檢測某個權限是否已經被授權,方法返回值爲`PackageManager.PERMISSION_DENIED` 或者 `PackageManager.PERMISSION_GRANNTED`
3. 申請權限
ActivityCompat.requestPermissions(this,new String[]{"Manifest.permission.CALL_PHONE"},CALL_PHONE_REQUEST_CODE);
這個方法是異步的,第一個參數是Context,第二個參數是需要申請的權限的字符串數組,第三個參數是requestCode,主要用於回調的時候檢查。從第二個參數來看,是支持一次性申請多個權限的,系統會彈出對話框逐一詢問用戶是否授權。4. 處理回調
如果用戶同意或是拒絕那麼會回調`onRequestPermissionsResult()`
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if(requestCode == CALL_PHONE_REQUEST_CODE){
if (grantResults !=null&&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.
}
}
}
對於權限的申請結果,首先驗證requestCode定位到你的申請,然後驗證grantResults對應於申請的結果,這裏的數組對應於申請時的第二個權限字符串數組。如果你同時申請兩個權限,那麼grantResults的length就爲2,分別記錄你兩個權限的申請結果。如果申請成功,就可以做你的事情了。不過還有一個API方法值得注意,就是`ActivityCompat.shouldShowRequestPermissionRationale`
// 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.
}
這個API主要用於給用戶一個申請權限的解釋,該方法只有在用戶在上一次已經拒絕過你的這個權限申請。也就是說,用戶已經拒絕一次了,又彈個授權框,需要給用戶一個解釋,爲什麼要授權,則使用該方法。四、簡單的例子
這裏寫一個簡單的例子,針對於運行時權限。看看直接撥打電話在Android 6.x的設備上權限需要如何處理。
public class MainActivity extends AppCompatActivity {
// 打電話權限申請的請求碼
private static final int REQUEST_PERMISSION_CALL_CODE = 0x0011;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void phoneClick(View view){
if(ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED){
Toast.makeText(this, "申請權限", Toast.LENGTH_SHORT).show();
ActivityCompat.requestPermissions(this,
new String[]{"Manifest.permission.CALL_PHONE"}, REQUEST_PERMISSION_CALL_CODE);
}else {
callPhone();
}
}
/**
* 撥打電話
**/
private void callPhone() {
Intent intent = new Intent(Intent.ACTION_CALL);
Uri data = Uri.parse("tel:13111111111");
intent.setData(data);
startActivity(intent);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode == REQUEST_PERMISSION_CALL_CODE){
if (grantResults !=null&&grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission Granted
callPhone();
} else {
// Permission Denied
Toast.makeText(this,"權限被拒絕了",Toast.LENGTH_SHORT).show();
}
}
}
}
五、封裝
雖然權限處理並不複雜,但是需要編寫很多重複的代碼,所以目前也有很多庫對用法進行了封裝(可以在github首頁搜索)。本節將要講解的是使用註解和反射的方式封裝,封裝的代碼很簡單,接下來看看如何實現。
(1) 權限申請
從上面的例子中可以看到,如果申請其他權限,申請的邏輯是類似的,區別就在於參數不同,我們需要三個參數: `Activity\Fragment`,`permissions數組`,`requestCode申請碼`, 也就是說只需要寫個方法,接收這幾個參數即可,然後邏輯就是統一的。
@TargetApi(Build.VERSION_CODES.M)
private void requestPermissions(Object obj, int requestCode, String[] permissions) {
if (!PermissionUtil.isOverMarshmallow()) {
//如果運行在Android 6.0以下的版本上,直接執行方法
executeSuccess(obj, requestCode);
return;
}
//尋找是否有未被允許的權限
List<String> deniedPermissions = PermissionUtil.findDeniedPermissions(getActivity(obj), permissions);
if (deniedPermissions.size() > 0) {
//存在未允許的權限,需要申請權限
if (obj instanceof Activity) {
((Activity) obj).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
} else if (obj instanceof Fragment) {
((Fragment) obj).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
} else {
throw new IllegalArgumentException(object.getClass().getName() + " is not supported");
}
} else {
// 權限都已經被允許,直接執行方法
executeSuccess(obj, requestCode);
}
}
`PermissionUtil.findDeniedPermissions` 就是檢查 沒有授權的權限.
@TargetApi(Build.VERSION_CODES.M)
public static List<String> findDeniedPermissions(Activity activity, String... permissions) {
List<String> deniedPermissions = new ArrayList<>();
for (String permission : permissions) {
if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(permission);
}
}
return deniedPermissions;
}
那麼上述的邏輯就很清晰了,需要的3種參數傳入,先去除已經申請的權限,然後開始申請權限。
(2) 回調處理
回調主要做的事情:
1. 瞭解是否授權
2. 根據授權情況進行回調
對於第二條,會涉及到兩個分支,每個分支執行不同的方法,這裏利用註解去確定需要執行的方法,存在兩個註解:
@PermissionSuccess(requestCode = 0x01)
@PermissionFail(requestCode = 0x01)
利用反射根據授權情況+requestCode即可找到註解標註的方法,然後直接執行即可。大致代碼如下:
public static void onRequestPermissionsResult(Object obj, int requestCode, String[] permissions, int[] grantResults) {
List<String> deniedPermissions = new ArrayList<>();
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(permissions[i]);
}
}
if (deniedPermissions.size() > 0) {
executeFail(obj, requestCode);
} else {
executeSuccess(obj, requestCode);
}
}
首先根據grantResults進行判斷成功還是失敗,對於成功則執行成功的回調:
private static void executeSuccess(Object obj, int requestCode) {
Method succMethod = PermissionUtil.findMethodWithRequestCode(obj.getClass(), PermissionSuccess.class, requestCode);
executeMethod(obj, succMethod);
}
`PermissionUtil.findMethodWithRequestCode` 根據註解和requestCode找到方法,然後反射執行即可,代碼如下:
public static <T extends Annotation> Method findMethodWithRequestCode(Class clazz, Class<T> annotation, int requestCode) {
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(annotation)) {
if (isEqualRequestCodeFromAnnotation(method, annotation, requestCode)) {
return method;
}
}
}
return null;
}
對於失敗則執行失敗的回調,代碼如下:
private static void executeFail(Object obj, int requestCode) {
Method failMethod = PermissionUtil.findMethodWithRequestCode(obj.getClass(), PermissionFail.class, requestCode);
executeMethod(obj, failMethod);
}
到此,對於權限處理的封裝基本介紹完了。六、總結
以上講解的封裝方式,採用了註解和反射,對於效率的影響還是存在的;計劃在上述代碼的基礎上將運行時註解修改成`Annotation Processor`的方式,即編譯時註解,這樣就會避免因反射影響到效率的問題了。
參考資料
http://blog.csdn.net/lmj623565791/article/details/50709663
https://github.com/lovedise/PermissionGen