聊聊Android M 6.0 的運行時權限

一.序

1.1 背景介紹.

為什么要聊這個話題呢?
從官網最新數據(2017.12.11)來看:

  • 現在大概有99.6%的用戶Android版本是在4.0.3(API-15)以上
  • 并且6.0以上(API-23)的用戶占比在53.5%,
官網版本分布截圖

傳送門最新Android版本分布.
Android 產品經理Edward Cunningham 發表文章: Improving app security and performance on Google Play for years to come 表示:

文章截圖

所以我們先將權限部分適配target 26

1.2簡單介紹下build.gradle

android {

    compileSdkVersion 27
    buildToolsVersion "27.0.2"
    
    defaultConfig {
        applicationId "***.***.***"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
}
  • targetSdkVersion:就是APP能夠適配的系統的版本,意味著已經兼容到對應版本.Android Developer也有對應的介紹.告知開發者targetSdkVersion升級要注意什么.官網上兼容8.0的注意事項,大家有空可以自行研究.
  • compileSdkVersion:顧名思義,是編譯時,使用的SDK版本,當然compileSdkVersion>= targetSdkVersion
  • minSdkVersion: 當然就是我們App支持的最低版本號.海外版大多數App都會選擇支持14-15.國內,有些App可能會支持到9.
  • maxSdkVersion:很少見的屬性.卻在權限設置里會有一些應用場景.比如設置這個權限支持的最大版本號。

1.3 擴展:簡單闡述一下maxSdkVersion的神奇之處

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"android:maxSdkVersion="18"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"android:maxSdkVersion="18"/>

當我們使用Environment.getExternalStorageDirectory()方法時,實際的存儲位置是sdcard/Android/data/包名/,這個地址在Sd卡上。
我們在使用這個方法,實際在API-19以上是無需申請權限的??梢灾苯邮褂谩K约偃缥覀儾恍枰~外創建文件夾??梢詿o需申請STORAGE權限。標識了maxSdkVersion=18之后,表示,當手機版本高于18,這個權限將不會出現在權限列表了。

好處是什么?

當然,

  1. 我們申請的權限越少越好。
  2. 用戶手機設備高于6.0之后??梢跃芙^掉這個權限。那么Environment.getExternalStorageDirectory()將失效。
  3. 所以不出現就不能拒絕。我還是可以用。^^

二.權限簡介

2.1 不同版本會產生的狀況

  1. if(targetSdkVersion(App) < 23 && Build.VERSION.SDK_INT(手機版本) < 23)
    安裝APP時,會聲明App所需要的權限,不會詢問用戶.也不可以關閉權限(部分國產定制機型除外Xiaomi就在6.0之前就已經提出了一套自己的權限系統.)
  2. if(targetSdkVersion < 23 && Build.VERSION.SDK_INT >= 23)表示App并未兼容6.0,安裝App時,也不會讓用戶動態申請權限,但是用戶可以自行去設置頁面關閉權限,
  3. if(targetSdkVersion >= 23 && Build.VERSION.SDK_INT < 23) 同1.所述,也是僅僅在安裝App時提示,聲明App所需要的權限,不會詢問用戶動態申請,也不可以關閉.
  4. if(targetSdkVersion >= 23 && Build.VERSION.SDK_INT >= 23) 動態申請權限.也是大勢所趨.
  • 第3點講到的情況

第三點講到的情況.6.0以上的設備,安裝targetSdkVersion<23的App時,也可以跳轉到Setting頁面自行關閉權限.但是會彈出提示,中文翻譯是:此應用專為舊版Android打造.拒絕權限可能會導致其無法正常運行。

2.2權限分類

Android中有很多權限,但并非所有的權限都是敏感權限,于是6.0系統就對權限進行了分類,一般為下述幾類

  • 正常(Normal)權限
  • 危險(Dangerous)權限
  • 特殊(signature)權限

2.2.1正常(Normal)權限

普通權限有很多,不一一列舉了,總結一下他們的特點:

  • 對用戶隱私沒有較大影響或者不會打來安全問題。
  • 安裝后就賦予這些權限,不需要顯示提醒用戶,用戶也不能取消這些權限。

2.2.2危險(Dangerous)權限

Android Dangerous(危險)權限和權限組的劃分。

public class Permissions {

  protected static String[] ABS_CALENDAR;
  protected static String[] ABS_CAMERA;
  protected static String[] ABS_CONTACTS;
  protected static String[] ABS_LOCATION;
  protected static String[] ABS_MICROPHONE;
  protected static String[] ABS_PHONE;
  protected static String[] ABS_SENSORS;
  protected static String[] ABS_SMS;
  protected static String[] ABS_STORAGE;

  static {

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
      ABS_CALENDAR = new String[] {};
      ABS_CAMERA = new String[] {};
      ABS_CONTACTS = new String[] {};
      ABS_LOCATION = new String[] {};
      ABS_MICROPHONE = new String[] {};
      ABS_PHONE = new String[] {};
      ABS_SENSORS = new String[] {};
      ABS_SMS = new String[] {};
      ABS_STORAGE = new String[] {};
    } else {
      ABS_CALENDAR = new String[] {
          Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR
      };

      ABS_CAMERA = new String[] {
          Manifest.permission.CAMERA
      };

      ABS_CONTACTS = new String[] {
          Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS,
          Manifest.permission.GET_ACCOUNTS
      };

      ABS_LOCATION = new String[] {
          Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION
      };

      ABS_MICROPHONE = new String[] {
          Manifest.permission.RECORD_AUDIO
      };

      ABS_PHONE = new String[] {
          Manifest.permission.READ_PHONE_STATE, Manifest.permission.CALL_PHONE,
          Manifest.permission.READ_CALL_LOG, Manifest.permission.WRITE_CALL_LOG,
          Manifest.permission.USE_SIP, Manifest.permission.PROCESS_OUTGOING_CALLS
      };

      ABS_SENSORS = new String[] {
          Manifest.permission.BODY_SENSORS
      };

      ABS_SMS = new String[] {
          Manifest.permission.SEND_SMS, Manifest.permission.RECEIVE_SMS,
          Manifest.permission.READ_SMS, Manifest.permission.RECEIVE_WAP_PUSH,
          Manifest.permission.RECEIVE_MMS
      };

      ABS_STORAGE = new String[] {
          Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE
      };
    }
  }
}

暫時分為9個權限組,這些權限,作為動態申請中,可能涉及到主要內容。(注:申請同一權限組內的子權限,系統彈出的權限申請框中的文案是一樣的。)

2.2.3特殊(Signature)權限

這個分類呢,可以說對于用戶來說,更危險的權限。特別敏感的權限。此類權限不能在App內彈出系統的權限申請框。只能跳轉到設置頁面修改。

  //修改系統設置
  <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
  //懸浮窗
  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

1.修改系統設置-WRITE_SETTINGS

申請:

//申請權限跳轉是可以使用startActivityForResult
public static void requestPermissionForResult(Activity activity, int requestCode) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || activity == null) {
      return;
    }
    Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,
        Uri.parse("package:" + activity.getPackageName()));
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    activity.startActivityForResult(intent, requestCode);
}

判斷是否有該權限:

//我們可以在onActivityResult的時候判斷權限是否有獲取
public static boolean hasPermission(Context context) {
    return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.System.canWrite(context);
}

2.系統懸浮窗-SYSTEM_ALERT_WINDOW

申請:

public static void requestPermissionForResult(Activity activity, int requestCode) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || activity == null) {
      return;
    }
    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
        Uri.parse("package:" + activity.getPackageName()));
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    activity.startActivityForResult(intent, requestCode);
}

判斷是否有該權限:

public static boolean hasPermission(Context context) {
    return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
}

三.常用API

注意:因為API為 23 以上新增的。所以使用的時候要判斷版本號。

1.ContextCompat.checkSelfPermission

檢測權限是否已經獲取。

ContextCompat.checkSelfPermission(context, permission)!= PackageManager.PERMISSION_GRANTED

2.shouldShowRequestPermissionRationale(Activity)

//偽代碼
public class Activity{

  ...

  public boolean shouldShowRequestPermissionRationale(@NonNull String permission) {
        return getPackageManager().shouldShowRequestPermissionRationale(permission);
    }

  ...

}

這個方法有點難懂,大意就是說,是否要告知用戶申請權限的重要性。這里有點繞,我們這詳細說明一下。

  1. 假設用戶第一次申請前,這個方法的返回值false
  2. 假設用戶申請被拒絕過一次之后,這個方法的返回值true
  3. 假設用戶申請被拒絕并點擊不再詢問,這個方法的返回值false

這個方法的設計思想:是因為第一次申請,用戶拒絕授予權限之后,以后再申請。彈框中會出現不再詢問的選項。shouldShowRequestPermissionRationale方法返回true可以理解為,用戶已經拒絕過一次以上了,下一次彈框會出現不再詢問。所以我們發現返回值為true的時候,可能要整理一份聲淚俱下的文案彈框來感動用戶,讓他給我們權限。盡量不要點擊不再詢問。然后用戶被感動到之后。再彈出系統的權限申請框。
當然這個方法還可以被我們加以利用:

shouldShowRequestPermissionRationale 關鍵方法說明:

 * 申請權限前 false -->  申請后 true  第一次申請被拒絕
 * 申請權限前 true  -->  申請后 false 已經被拒絕過一次以上了,并且這次拒絕點擊了NeverAskAgain
 * 申請權限前 true  -->   申請后 true 第二次及以上拒絕,但是未點擊NeverAskAgain
 * 申請權限前 false -->  申請后 false 本次申請權限前,就已經點擊過了NeverAskAgain,此時我們可能要提示用戶到Setting中手動開啟權限了。

3. requestPermissions和onRequestPermissionsResult兩兄弟

用法類似于(startActivityForResult 和 onActivityResult),其實點開源碼跟進去會發現,申請權限的底層就是用這個實現的。(回憶到之前,領導讓我們每周問自己五個為什么?,第一個就是問為什么申請權限一定要在Activity中發起?最后就研究到了Context中的startActivityForResult,跑題了。)

使用方法:

//申請相機和MIC權限
requestPermissions(new String[]{ Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO}, 1);
//在回調中判斷權限是否已經獲取。
//因為一次可以申請多個權限。所以返回一組權限與一組對應結果。
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults) {
    List<String> deniedList = new ArrayList<>();
    for (int i = 0; i < permissions.length; i++) {
      if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
        deniedList.add(permissions[i]);
      }
    }
}

結束

Android M 的動態運行時的基礎介紹就這些了。下篇會通過我寫的一個組件來接管運行時權限的獲取。 ----待續。

傳送門:Android O 8.0 運行時權限適配方案

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容