一.序
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,這個權限將不會出現在權限列表了。
好處是什么?
當然,
- 我們申請的權限越少越好。
- 用戶手機設備高于6.0之后??梢跃芙^掉這個權限。那么
Environment.getExternalStorageDirectory()
將失效。 - 所以不出現就不能拒絕。我還是可以用。^^
二.權限簡介
2.1 不同版本會產生的狀況
-
if(targetSdkVersion(App) < 23 && Build.VERSION.SDK_INT(手機版本) < 23)
安裝APP時,會聲明App所需要的權限,不會詢問用戶.也不可以關閉權限(部分國產定制機型除外Xiaomi就在6.0之前就已經提出了一套自己的權限系統.) -
if(targetSdkVersion < 23 && Build.VERSION.SDK_INT >= 23)
表示App并未兼容6.0,安裝App時,也不會讓用戶動態申請權限,但是用戶可以自行去設置頁面關閉權限, -
if(targetSdkVersion >= 23 && Build.VERSION.SDK_INT < 23)
同1.所述,也是僅僅在安裝App時提示,聲明App所需要的權限,不會詢問用戶動態申請,也不可以關閉. -
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);
}
...
}
這個方法有點難懂,大意就是說,是否要告知用戶申請權限的重要性。這里有點繞,我們這詳細說明一下。
- 假設用戶第一次申請前,這個方法的返回值false
- 假設用戶申請被拒絕過一次之后,這個方法的返回值true
- 假設用戶申請被拒絕并點擊不再詢問,這個方法的返回值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 的動態運行時的基礎介紹就這些了。下篇會通過我寫的一個組件來接管運行時權限的獲取。 ----待續。