Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> Android 6.0 運行時權限處理完全解析,android6.0

Android 6.0 運行時權限處理完全解析,android6.0

編輯:關於android開發

Android 6.0 運行時權限處理完全解析,android6.0


一、概述

隨著Android 6.0發布以及普及,我們開發者所要應對的主要就是新版本SDK帶來的一些變化,首先關注的就是權限機制的變化。對於6.0的幾個主要的變化,查看查看官網的這篇文章http://developer.android.com/intl/zh-cn/about/versions/marshmallow/android-6.0-changes.html,其中當然包含Runtime Permissions。

ok,本篇文章目的之一就是對運行時權限處理的一個介紹,以及對目前權限相關的庫的一些了解。

當然非常推薦閱讀官網權限相關文章:

  • Working with System Permissions
  • Permissions Best Practices

本文也是在上述文章基礎上理解、實驗以及封裝。

二、運行時權限的變化及特點

對於6.0以下的權限及在安裝的時候,根據權限聲明產生一個權限列表,用戶只有在同意之後才能完成app的安裝,造成了我們想要使用某個app,就要默默忍受其一些不必要的權限(比如是個app都要訪問通訊錄、短信等)。而在6.0以後,我們可以直接安裝,當app需要我們授予不恰當的權限的時候,我們可以予以拒絕(比如:單機的象棋對戰,請求訪問任何權限,我都是不同意的)。當然你也可以在設置界面對每個app的權限進行查看,以及對單個權限進行授權或者解除授權。

新的權限機制更好的保護了用戶的隱私,Google將權限分為兩類,一類是Normal Permissions,這類權限一般不涉及用戶隱私,是不需要用戶進行授權的,比如手機震動、訪問網絡等;另一類是Dangerous Permission,一般是涉及到用戶隱私的,需要用戶進行授權,比如讀取sdcard、訪問通訊錄等。

  • Normal Permissions如下

 

          Java   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ACCESS_LOCATION_EXTRA_COMMANDS ACCESS_NETWORK_STATE ACCESS_NOTIFICATION_POLICY ACCESS_WIFI_STATE BLUETOOTH BLUETOOTH_ADMIN BROADCAST_STICKY CHANGE_NETWORK_STATE CHANGE_WIFI_MULTICAST_STATE CHANGE_WIFI_STATE DISABLE_KEYGUARD EXPAND_STATUS_BAR GET_PACKAGE_SIZE INSTALL_SHORTCUT INTERNET KILL_BACKGROUND_PROCESSES MODIFY_AUDIO_SETTINGS NFC READ_SYNC_SETTINGS READ_SYNC_STATS RECEIVE_BOOT_COMPLETED REORDER_TASKS REQUEST_INSTALL_PACKAGES SET_ALARM SET_TIME_ZONE SET_WALLPAPER SET_WALLPAPER_HINTS TRANSMIT_IR UNINSTALL_SHORTCUT USE_FINGERPRINT VIBRATE WAKE_LOCK WRITE_SYNC_SETTINGS

 

  • Dangerous Permissions:

 

          Java   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 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

可以通過adb shell pm list permissions -d -g進行查看。

看到上面的dangerous permissions,會發現一個問題,好像危險權限都是一組一組的,恩,沒錯,的確是這樣的,

那麼有個問題:分組對我們的權限機制有什麼影響嗎?

的確是有影響的,如果app運行在Android 6.x的機器上,對於授權機制是這樣的。如果你申請某個危險的權限,假設你的app早已被用戶授權了同一組的某個危險權限,那麼系統會立即授權,而不需要用戶去點擊授權。比如你的app對READ_CONTACTS已經授權了,當你的app申請WRITE_CONTACTS時,系統會直接授權通過。此外,對於申請時彈出的dialog上面的文本說明也是對整個權限組的說明,而不是單個權限(ps:這個dialog是不能進行定制的)。

不過需要注意的是,不要對權限組過多的依賴,盡可能對每個危險權限都進行正常流程的申請,因為在後期的版本中這個權限組可能會產生變化。

三、相關API

好在運行時相關的API也比較簡單,所以適配起來並不會非常痛苦。

API的講解就跟著申請權限步驟一起了:

當然,到此我們的權限申請的不走,基本介紹就如上述。不過還有個API值得提一下:

          Java   1 2 3 4 5 6 7 8 // 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主要用於給用戶一個申請權限的解釋,該方法只有在用戶在上一次已經拒絕過你的這個權限申請。也就是說,用戶已經拒絕一次了,你又彈個授權框,你需要給用戶一個解釋,為什麼要授權,則使用該方法。

那麼將上述幾個步驟結合到一起就是:

            Java   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 // 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.     } }

 

四、簡單的例子

這裡寫一個簡單的例子,針對於運行時權限。相信大家在最開始接觸Android的時候,都利用Intent實驗了打電話、發短信等功能。

我們看看直接撥打電話在Android 6.x的設備上權限需要如何處理。

當然代碼非常簡單:

            Java   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package com.zhy.android160217;   import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Toast;   public class MainActivity extends AppCompatActivity {       private static final int MY_PERMISSIONS_REQUEST_CALL_PHONE = 1;       @Override     protected void onCreate(Bundle savedInstanceState)     {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);     }       public void testCall(View view)     {         if (ContextCompat.checkSelfPermission(this,                 Manifest.permission.CALL_PHONE)                 != PackageManager.PERMISSION_GRANTED)         {               ActivityCompat.requestPermissions(this,                     new String[]{Manifest.permission.CALL_PHONE},                     MY_PERMISSIONS_REQUEST_CALL_PHONE);         } else         {             callPhone();         }     }       public void callPhone()     {         Intent intent = new Intent(Intent.ACTION_CALL);         Uri data = Uri.parse("tel:" + "10086");         intent.setData(data);         startActivity(intent);     }       @Override     public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)     {           if (requestCode == MY_PERMISSIONS_REQUEST_CALL_PHONE)         {             if (grantResults[0] == PackageManager.PERMISSION_GRANTED)             {                 callPhone();             } else             {                 // Permission Denied                 Toast.makeText(MainActivity.this, "Permission Denied", Toast.LENGTH_SHORT).show();             }             return;         }         super.onRequestPermissionsResult(requestCode, permissions, grantResults);     } }

在Android 6.x上運行是,點擊testCall,即會彈出授權窗口,如何你Allow則直接撥打電話,如果Denied則Toast彈出”Permission Denied”。

例子很簡單,不過需要注意的是,對於Intent這種方式,很多情況下是不需要授權的甚至權限都不需要的,比如:你是到撥號界面而不是直接撥打電話,就不需要去申請權限;打開系統圖庫去選擇照片;調用系統相機app去牌照等。更多請參考Consider Using an Intent
.

當然,上例也說明了並非所有的通過Intent的方式都不需要申請權限。一般情況下,你是通過Intent打開另一個app,讓用戶通過該app去做一些事情,你只關注結果(onActivityResult),那麼權限是不需要你處理的,而是由打開的app去處理。

五、封裝

(1)申請權限

雖然權限處理並不復雜,但是需要編寫很多重復的代碼,所以目前也有很多庫對用法進行了封裝,大家可以在github首頁搜索:android permission,對比了幾個庫的使用方式,發現https://github.com/lovedise/PermissionGen這個庫據我所見相比較而言使用算是比較簡單的,那麼就以這個庫的源碼為基礎來講解,大家有興趣可以多看幾個庫的源碼。

封裝的代碼很簡單,正如大家的對上面的權限代碼所見,沒有特別復雜的地方。

對於申請權限的代碼,原本的編寫為:

          Java   1 2 3 4 5 6 7 8 9 10 11 12 if (ContextCompat.checkSelfPermission(this,                 Manifest.permission.CALL_PHONE)                 != PackageManager.PERMISSION_GRANTED) {       ActivityCompat.requestPermissions(this,             new String[]{Manifest.permission.CALL_PHONE},             MY_PERMISSIONS_REQUEST_CALL_PHONE); } else {     callPhone(); }

大家可以看到,對於其他的權限,其實申請的邏輯是類似的;唯一不同的肯定就是參數,那麼看上面的代碼,我們需要3個參數:Activity|Fragment權限字符串數組int型申請碼

也就是說,我們只需要寫個方法,接受這幾個參數即可,然後邏輯是統一的。

            Java   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public static void needPermission(Fragment fragment, int requestCode, String[] permissions) {     requestPermissions(fragment, requestCode, permissions); }   @TargetApi(value = Build.VERSION_CODES.M) private static void requestPermissions(Object object, int requestCode, String[] permissions) {     if (!Utils.isOverMarshmallow())     {         doExecuteSuccess(object, requestCode);         return;     }     List deniedPermissions = Utils.findDeniedPermissions(getActivity(object), permissions);       if (deniedPermissions.size() > 0)     {         if (object instanceof Activity)         {             ((Activity) object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);         } else if (object instanceof Fragment)         {             ((Fragment) object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);         } else         {             throw new IllegalArgumentException(object.getClass().getName() + " is not supported");         }       } else     {         doExecuteSuccess(object, requestCode);     } }

Utils.findDeniedPermissions其實就是check沒有授權的權限。

            Java   1 2 3 4 5 6 7 8 9 10 11 12 13 14 #Utils @TargetApi(value = Build.VERSION_CODES.M) public static ListfindDeniedPermissions(Activity activity, String... permission) {     List denyPermissions = new ArrayList();     for (String value : permission)     {         if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED)         {             denyPermissions.add(value);         }     }     return denyPermissions; }

那麼上述的邏輯就很清晰了,需要的3種參數傳入,先去除已經申請的權限,然後開始申請權限。

ok,我相信上面代碼,大家掃一眼就可以了解了。

(2)處理權限回調

對於回調,因為要根據是否授權去執行不同的事情,所以很多框架也需要些一連串的代碼,或者和前面的申請代碼耦合。不過這個框架還是比較方便的,也是我選擇它來講解的原因。

回調主要做的事情:

對於第一條邏輯都一樣;對於第二條,因為涉及到兩個分支,每個分支執行不同的方法。

對於第二條,很多框架選擇將兩個分支的方法在申請權限的時候進行注冊,然後在回調中根據requestCode進行匹配執行,不過這樣需要在針對每次申請進行對象管理。

不過這個框架采取了一種很有意思的做法,它利用注解去確定需要執行的方法,存在兩個注解:

          Java   1 2 @PermissionSuccess(requestCode = 100) @PermissionFail(requestCode = 100)

利用反射根據授權情況+requestCode即可找到注解標注的方法,然後直接執行即可。

大致的代碼為:

            Java   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Override public void onRequestPermissionsResult(int requestCode, String[] permissions,       int[] grantResults) {     PermissionGen.onRequestPermissionsResult(this, requestCode, permissions, grantResults); }   private static void requestResult(Object obj, int requestCode, String[] permissions,                                   int[] grantResults) {     List deniedPermissions = new ArrayList();     for (int i = 0; i if (grantResults[i] != PackageManager.PERMISSION_GRANTED)         {             deniedPermissions.add(permissions[i]);         }     }       if (deniedPermissions.size() > 0)     {         doExecuteFail(obj, requestCode);     } else     {         doExecuteSuccess(obj, requestCode);     } }

首先根據grantResults進行判斷成功還是失敗,對於成功則:

            Java   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private static void doExecuteSuccess(Object activity, int requestCode) {     Method executeMethod = Utils.findMethodWithRequestCode(activity.getClass(),             PermissionSuccess.class, requestCode);       executeMethod(activity, executeMethod); }   #Utils public static  Method findMethodWithRequestCode(Class clazz,  Class annotation, int requestCode) {     for (Method method : clazz.getDeclaredMethods())     {         if (method.isAnnotationPresent(annotation))         {             if (isEqualRequestCodeFromAnntation(method, annotation, requestCode))             {                 return method;             }         }     }     return null; }

根據注解和requestCode找到方法,然後反射執行即可。失敗的邏輯類似,不貼代碼了。

ok,到此我們的運行時權限相對於早起版本的變化、特點、以及如何處理和封裝都介紹完了。

不過對於上面講解的庫,肯定有人會說:運行時反射會影響效率,沒錯,不過我已經在上述代碼的基礎上將運行時注解改成Annotation Processor的方式了,即編譯時注解,這樣的話,就不存在反射損失效率的問題了。本來准備fork修改,然後PR,結果寫完,改動太大,估計PR是不可能通過了,所以另起項目了,也方便後面的做一些擴展和維護。

詳見庫:https://github.com/hongyangAndroid/MPermissions.

六、MPermissions用法

對外的接口和PermissionGen基本一致,因為申請只需要三個參數,拋棄了使用原本類庫的單例的方式,直接一個幾個靜態方法,簡單整潔暴力。

貼一個用法:

            Java   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class MainActivity extends AppCompatActivity {       private Button mBtnSdcard;     private static final int REQUECT_CODE_SDCARD = 2;       @Override     protected void onCreate(Bundle savedInstanceState)     {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);           mBtnSdcard = (Button) findViewById(R.id.id_btn_sdcard);         mBtnSdcard.setOnClickListener(new View.OnClickListener()         {             @Override             public void onClick(View v)             {                 MPermissions.requestPermissions(MainActivity.this, REQUECT_CODE_SDCARD, Manifest.permission.WRITE_EXTERNAL_STORAGE);             }         });     }       @Override     public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)     {         MPermissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);         super.onRequestPermissionsResult(requestCode, permissions, grantResults);     }       @PermissionGrant(REQUECT_CODE_SDCARD)     public void requestSdcardSuccess()     {         Toast.makeText(this, "GRANT ACCESS SDCARD!", Toast.LENGTH_SHORT).show();     }       @PermissionDenied(REQUECT_CODE_SDCARD)     public void requestSdcardFailed()     {         Toast.makeText(this, "DENY ACCESS SDCARD!", Toast.LENGTH_SHORT).show();     } }

是不是簡單明了~~對於onRequestPermissionsResult所有的Activity都是一致的,所以可以放到BaseActivity中去。此外,在Fragment中使用的方式一致,詳見demo。

詳見庫:https://github.com/hongyangAndroid/MPermissions.

至於為什麼不直接介紹MPermission的源碼,因為主要涉及到Annotation Processor,所以以這種方式引出,後面考慮單篇博文介紹下我目前所會的編譯時注解的相關做法以及API的使用。

原文出處: 張鴻洋(@鴻洋_)

問啊-定制化IT教育平台,牛人一對一服務,有問必答,開發編程社交頭條 官方網站:www.wenaaa.com

QQ群290551701 聚集很多互聯網精英,技術總監,架構師,項目經理!開源技術研究,歡迎業內人士,大牛及新手有志於從事IT行業人員進入!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved