Android 6.0權限全面詳細分析和解決方案

原文:html

http://www.2cto.com/kf/201512/455888.htmljava

http://blog.csdn.net/yangqingqo/article/details/48371123android

http://inthecheesefactory.com/blog/things-you-need-to-know-about-Android-m-permission-developer-edition/enapi

 

1、Marshmallow版本權限簡介安全

android的權限系統一直是首要的安全概念,由於這些權限只在安裝的時候被詢問一次。一旦安裝了,app能夠在用戶絕不知曉的狀況下訪問權限內的全部東西,並且通常用戶安裝的時候不多會去仔細看權限列表,更不會去深刻了解這些權限可能帶來的相關危害。因此在android 6.0 Marshmallow版本以後,系統不會在軟件安裝的時候就賦予該app全部其申請的權限,對於一些危險級別的權限,app須要在運行時一個一個詢問用戶授予權限。app



2、舊版本app兼容問題

  那麼問題來了,是否是全部之前發佈的app都會出現問題呢?答案是不會,只有那些targetSdkVersion 設置爲23和23以上的應用纔會出現異常,在使用危險權限的時候系統必需要得到用戶的贊成才能使用,要否則應用就會崩潰,出現相似
java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvideride

的崩潰日誌。因此targetSdkVersion若是沒有設置爲23版本或者以上,系統仍是會使用舊規則:在安裝的時候賦予該app所申請的全部權限。因此app固然能夠和之前同樣正常使用了,可是還有一點須要注意的是6.0的系統裏面,用戶能夠手動將該app的權限關閉,以下圖函數

  
  那麼問題又來了,若是之前的老應用申請的權限被用戶手動關閉了怎麼辦,應用會崩潰麼?咱們來試一試
  這裏寫圖片描述
  好吧,能夠慶幸了一下了,不會拋出異常,不會崩潰,只不過調用那些被用戶禁止權限的api接口返回值都爲null或者0,因此咱們只須要作一下判空操做就能夠了,不判空固然仍是會崩潰的嘍。


3、普通權限和危險權限列表

測試

  如今對於新版本的權限變動應該有了基本的認識,那麼,是否是全部權限都須要去進行特殊處理呢?固然不是,只有那些危險級別的權限才須要。gradle

 

PROTECTION_NORMAL類權限

 

當用戶安裝或更新應用時,系統將授予應用所請求的屬於 PROTECTION_NORMAL 的全部權限(安裝時受權的一類基本權限)。這類權限包括:

android.permission.ACCESS LOCATIONEXTRA_COMMANDS 
android.permission.ACCESS NETWORKSTATE 
android.permission.ACCESS NOTIFICATIONPOLICY 
android.permission.ACCESS WIFISTATE 
android.permission.ACCESS WIMAXSTATE 
android.permission.BLUETOOTH 
android.permission.BLUETOOTH_ADMIN 
android.permission.BROADCAST_STICKY 
android.permission.CHANGE NETWORKSTATE 
android.permission.CHANGE WIFIMULTICAST_STATE 
android.permission.CHANGE WIFISTATE 
android.permission.CHANGE WIMAXSTATE 
android.permission.DISABLE_KEYGUARD 
android.permission.EXPAND STATUSBAR 
android.permission.FLASHLIGHT 
android.permission.GET_ACCOUNTS 
android.permission.GET PACKAGESIZE 
android.permission.INTERNET 
android.permission.KILL BACKGROUNDPROCESSES 
android.permission.MODIFY AUDIOSETTINGS 
android.permission.NFC 
android.permission.READ SYNCSETTINGS 
android.permission.READ SYNCSTATS 
android.permission.RECEIVE BOOTCOMPLETED 
android.permission.REORDER_TASKS 
android.permission.REQUEST INSTALLPACKAGES 
android.permission.SET TIMEZONE 
android.permission.SET_WALLPAPER 
android.permission.SET WALLPAPERHINTS 
android.permission.SUBSCRIBED FEEDSREAD 
android.permission.TRANSMIT_IR 
android.permission.USE_FINGERPRINT 
android.permission.VIBRATE 
android.permission.WAKE_LOCK 
android.permission.WRITE SYNCSETTINGS 
com.android.alarm.permission.SET_ALARM 
com.android.launcher.permission.INSTALL_SHORTCUT 
com.android.launcher.permission.UNINSTALL_SHORTCUT

這類權限只須要在AndroidManifest.xml中簡單聲明這些權限就好,安裝時就受權。不須要每次使用時都檢查權限,並且用戶不能取消以上受權。

危險權限

 

Permission Group Permissions
android.permission-group.CALENDAR
  • android.permission.READ_CALENDAR
  • android.permission.WRITE_CALENDAR
android.permission-group.CAMERA
  • android.permission.CAMERA
android.permission-group.CONTACTS
  • android.permission.READ_CONTACTS
  • android.permission.WRITE_CONTACTS
  • android.permission.GET_ACCOUNTS
android.permission-group.LOCATION
  • android.permission.ACCESS_FINE_LOCATION
  • android.permission.ACCESS_COARSE_LOCATION
android.permission-group.MICROPHONE
  • android.permission.RECORD_AUDIO
android.permission-group.PHONE
  • android.permission.READ_PHONE_STATE
  • android.permission.CALL_PHONE
  • android.permission.READ_CALL_LOG
  • android.permission.WRITE_CALL_LOG
  • com.android.voicemail.permission.ADD_VOICEMAIL
  • android.permission.USE_SIP
  • android.permission.PROCESS_OUTGOING_CALLS
android.permission-group.SENSORS
  • android.permission.BODY_SENSORS
android.permission-group.SMS
  • android.permission.SEND_SMS
  • android.permission.RECEIVE_SMS
  • android.permission.READ_SMS
  • android.permission.RECEIVE_WAP_PUSH
  • android.permission.RECEIVE_MMS
  • android.permission.READ_CELL_BROADCASTS
android.permission-group.STORAGE
  • android.permission.READ_EXTERNAL_STORAGE
  • android.permission.WRITE_EXTERNAL_STORAGE

 

 

  android開發者官網也有相關描述:
  http://developer.android.com/training/permissions/requesting.html
  http://developer.android.com/guide/topics/security/permissions.html

  因此仔細去看看本身的app,對照列表,若是有須要申請其中的一個權限,就須要進行特殊操做。還有一個比較人性的地方就是若是同一組的任何一個權限被受權了,其餘權限也自動被受權。例如,一旦WRITE_EXTERNAL_STORAGE被受權了,app也有READ_EXTERNAL_STORAGE權限了。

 

4、支持Marshmallow新版本權限機制

關於權限控制主要使用到

PermissionChecker類的checkSelfPermission();

ActivityCompat類的

   public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity,
            @NonNull String permission) 

Fragment類的

 public boolean shouldShowRequestPermissionRationale(@NonNull String permission) 

ActivityCompat類的

    public static void requestPermissions(final @NonNull Activity activity,
            final @NonNull String[] permissions, final int requestCode) 

Fragment類的

  public final void requestPermissions(@NonNull String[] permissions, int requestCode)

 

終於要開始支持android 6.0版本了,最早一步固然就是修改build.gradle文件中的tragetSdkVersion和compileSdkVersion成23版本,同時使用compile ‘com.android.support:appcompat-v7:23.1.1’最新v7包。

android {
    compileSdkVersion 23
    ...
 
    defaultConfig {
        ...
        targetSdkVersion 23
        ...
    }
}
...
dependencies {
...
compile 'com.android.support:appcompat-v7:23.1.1'
  修改完後,感興趣的朋友能夠直接打包在手機上測試一下,看看是否是會出現相似於上面我說的那些崩潰日誌。
  接着下一步固然就是要修改代碼了,最原始代碼,無任何處理:

private void startGetImageThread(){
....
    Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
    ContentResolver contentResolver = getContentResolver();
    //獲取jpeg和png格式的文件,而且按照時間進行倒序
    Cursor cursor = contentResolver.query(uri, null, MediaStore.Images.Media.MIME_TYPE + "=\"image/jpeg\" or " +
    MediaStore.Images.Media.MIME_TYPE + "=\"image/png\"", null, MediaStore.Images.Media.DATE_MODIFIED+" desc");
    ....
}
  這段代碼須要訪問外部存儲(相冊圖片),屬於危險級別的權限,直接使用會形成應用崩潰,因此在這段代碼執行以前咱們須要進行特殊處理:

int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);

if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {

Activity activty=this;

        ActivityCompat.requestPermissions(activty,new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
                CODE_FOR_WRITE_PERMISSION);
    return;
}
  寫完這段代碼以後,就會出現以下系統dialog:
  
  緊接着就須要去處理DENY和ALLOW的回調了,重寫 Activity activity的ActivityCompat.OnRequestPermissionsResultCallback函數:

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    if (requestCode == CODE_FOR_WRITE_PERMISSION){
        if (permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)
            &&grantResults[0] == PackageManager.PERMISSION_GRANTED){
            //用戶贊成使用write
            startGetImageThread();
        }else{
            //用戶不一樣意,自行處理便可
            finish();
        }
    }
}
  好了,這樣就算是簡單初步適配完成了。


5、處理再也不提醒

  若是用戶拒絕某受權。下一次彈框,用戶會有一個「再也不提醒」的選項的來防止app之後繼續請求受權。

  

  若是這個選項在拒絕受權前被用戶勾選了。下次爲這個權限請求requestPermissions時,對話框就不彈出來了,系統會直接回調onRequestPermissionsResult函數,回調結果爲最後一次用戶的選擇。因此爲了應對這種狀況,系統提供了一個shouldShowRequestPermissionRationale()函數,這個函數的做用是幫助開發者找到須要向用戶額外解釋權限的狀況,這個函數:
應用安裝後第一次訪問,直接返回false;第一次請求權限時,用戶拒絕了,下一次shouldShowRequestPermissionRationale()返回 true,這時候能夠顯示一些爲何須要這個權限的說明;第二次請求權限時,用戶拒絕了,並選擇了「再也不提醒」的選項時:shouldShowRequestPermissionRationale()返回 false;設備的系統設置中禁止當前應用獲取這個權限的受權,shouldShowRequestPermissionRationale()返回false;  注意:第二次請求權限時,纔會有「再也不提醒」的選項,若是用戶一直拒絕,並無選擇「再也不提醒」的選項,下次請求權限時,會繼續有「再也不提醒」的選項,而且shouldShowRequestPermissionRationale()也會一直返回true。
  因此利用這個函數咱們能夠進行相應的優化,針對shouldShowRequestPermissionRationale函數返回false的處理有兩種方案。第一種方案:若是應用是第一次請求該權限,則直接調用requestPermissions函數去請求權限;若是不是則表明用戶勾選了’再也不提醒’,彈出dialog,告訴用戶爲何你須要該權限,讓用戶本身手動開啓該權限。連接:http://stackoverflow.com/questions/32347532/android-m-permissions-confused-on-the-usage-of-shouldshowrequestpermissionrati 。第二種方案:在onRequestPermissionsResult函數中進行檢測,若是返回PERMISSION_DENIED,則去調用shouldShowRequestPermissionRationale函數,若是返回false表明用戶已經禁止該權限(上面的3和4兩種狀況),彈出dialog告訴用戶你須要該權限的理由,讓用戶手動打開。連接:http://stackoverflow.com/questions/30719047/android-m-check-runtime-permission-how-to-determine-if-the-user-checked-nev  處理方法已經有了,修改一下代碼,我這裏就以第二種方案來處理了:
 
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    if (requestCode == CODE_FOR_WRITE_PERMISSION){
        if (permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)
            &&grantResults[0] == PackageManager.PERMISSION_GRANTED){
            //用戶贊成使用write
            startGetImageThread();
        }else{
            //用戶不一樣意,向用戶展現該權限做用
            if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                AlertDialog dialog = new AlertDialog.Builder(this)
                        .setMessage("該相冊須要賦予訪問存儲的權限,不開啓將沒法正常工做!")
                        .setPositiveButton("肯定", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                finish();
                            }
                        })
                        .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                finish();
                            }
                        }).create();
                dialog.show();
                return;
            }
            finish();
        }
    }
}
  當勾選再也不提醒,而且拒絕以後,彈出dialog,提醒用戶該權限的重要性:
  
  


6、使用兼容庫


  以上的代碼在6.0版本上使用沒有問題,可是在以前就有問題了,最簡單粗暴的解決方法可能就是利用Build.VERSION.SDK_INT >= 23這個判斷語句來判斷了,方便的是SDK 23的v4包加入了專門類進行相關的處理:

ContextCompat.checkSelfPermission()被受權函數返回PERMISSION_GRANTED,不然返回PERMISSION_DENIED ,在全部版本都是如此。ActivityCompat.requestPermissions()這個方法在6.0以前版本調用,OnRequestPermissionsResultCallback 直接被調用,帶着正確的 PERMISSION_GRANTED或者PERMISSION_DENIED。ActivityCompat.shouldShowRequestPermissionRationale()在6.0以前版本調用,永遠返回false。  用v4包的這三方法,完美兼容全部版本!下面是代碼:
//使用兼容庫就無需判斷系統版本
int hasWriteContactsPermission = ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (hasWriteContactsPermission == PackageManager.PERMISSION_GRANTED) {
    startGetImageThread();
}
//須要彈出dialog讓用戶手動賦予權限
else{
    ActivityCompat.requestPermissions(PickOrTakeImageActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, CODE_FOR_WRITE_PERMISSION);

}

  onRequestPermissionsResult函數不變。後兩個方法,咱們也能夠在Fragment中使用,用v13兼容包:FragmentCompat.requestPermissions() and FragmentCompat.shouldShowRequestPermissionRationale()和activity效果同樣。



7、一次請求多個權限


  固然了有時候須要多個權限,能夠用上面方法一次請求多個權限。固然最重要的是不要忘了爲每一個權限檢查「再也不提醒」的設置。
List<string> permissionsNeeded = new ArrayList<string>();
permissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
permissionsNeeded.add(Manifest.permission.READ_CONTACTS);
permissionsNeeded.add(Manifest.permission.WRITE_CONTACTS);
requestPermissions(permissionsNeeded.toArray(new String[permissionsList.size()]), CODE_FOR_MULTIPLE_PERMISSION);</string></string>
  最後在onRequestPermissionsResult函數中一個個處理返回結果便可。


8、第三方庫簡化代碼


  固然早就有第三方庫來幫忙作這些事情了:
  Github上的開源項目 PermissionHelper和hotchemi’s PermissionsDispatcher


9、APP處於運行狀態下,被撤銷權限


  若是APP正在運行中,用戶進入設置-應用程序頁面去手動撤銷該APP權限,會出現什麼狀況呢?哈哈,系統又會接着彈出權限請求對話框,挺好挺好:
  
  這樣就沒有問題了吧O(∩_∩)O~
  上面的測試環境爲genymotion6.0模擬器,有朋友跟我反映在6.0nexus 6p真機上會直接退出應用,因此這個應該還和測試環境有關。


使用兼容庫support-v4中的方法
 
The v4 support library also contains the  PermissionChecker class, which provides several static utility methods for apps that use IPC to provide services for other apps. For example, PermissionChecker.checkCallingPermission() checks whether an IPC made by a particular package has a specified permission.
 
 
 
  requestPermissions() 的一些說明:
 
Note: When your app calls the framework's  requestPermissions() method, the system shows a standard dialog box to the user.
 Your app cannot configure or alter that dialog box. If you need to provide any information or explanation to the user, 
you should do that before you call  requestPermissions(), as described in  Explain why the app needs permissions.
 
當調用  requestPermissions() 時,系統會顯示一個獲取權限的提示對話框,當前應用不能配置和修改這個對話框,
若是須要提示用戶一些這個權限相關的信息或說明,須要在調用  requestPermissions() 以前處理。
 
 
 

To help find the situations where you need to provide extra explanation, the system provides theshouldShowRequestPermissionRationale() method. 

This method returns true if the app has requested this permission previously and the user denied the request. 

That indicates that you should probably explain to the user why you need the permission.

 

 

If the user turned down the permission request in the past and chose the Don't ask again option in the permission request system dialog, this method returns false

The method also returns false if the device policy prohibits the app from having that permission.


 

1. 第一次請求權限時,用戶拒絕了,下一次:shouldShowRequestPermissionRationale()  返回 true,應該顯示一些爲何須要這個權限的說明

2.第二次請求權限時,用戶拒絕了,並選擇了「不在提醒」的選項時:shouldShowRequestPermissionRationale()  返回 false

3. 設備的策略禁止當前應用獲取這個權限的受權:shouldShowRequestPermissionRationale()  返回 false 

 

注意:上面的:第二次請求權限時,纔會有「不在提醒」的選項,若是用戶一直拒絕,並無選擇「不在提醒」的選項,下次請求權限時,會繼續有「不在提醒」的選項

 

10、shouldShowRequestPermissionRationale() 的方法說明:

 

 

 

Gets whether you should show UI with rationale for requesting a permission.

 You should do this only if you do not have the permission and the context in which the permission is requested does not clearly communicate to the user what would be the benefit from granting this permission.

 

For example, if you write a camera app, requesting the camera permission would be expected by the user and no rationale for why it is requested is needed. If however, the app needs location for tagging photos then a non-tech savvy user may wonder how location is related to taking photos. In this case you may choose to show UI with rationale of requesting this permission.

根據方法說明:顯示權限說明:是根據你的應用中使用的權限分類來的:1.用戶容易知道應用須要獲取的權限:如一個拍照應用,須要攝像頭的權限,是很正常,不用提示。2.一些用戶感受困惑的一些權限:如:分享圖片,還須要獲取位置的權限,這個須要提示用戶:爲何須要這個權限。

相關文章
相關標籤/搜索