一.序
1.1 背景介紹.
為什么要聊這個(gè)話題呢?
從官網(wǎng)最新數(shù)據(jù)(2017.12.11)來看:
- 現(xiàn)在大概有99.6%的用戶Android版本是在4.0.3(API-15)以上
- 并且6.0以上(API-23)的用戶占比在53.5%,
傳送門最新Android版本分布.
Android 產(chǎn)品經(jīng)理Edward Cunningham 發(fā)表文章: Improving app security and performance on Google Play for years to come 表示:
所以我們先將權(quán)限部分適配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能夠適配的系統(tǒng)的版本,意味著已經(jīng)兼容到對應(yīng)版本.Android Developer也有對應(yīng)的介紹.告知開發(fā)者targetSdkVersion升級要注意什么.官網(wǎng)上兼容8.0的注意事項(xiàng),大家有空可以自行研究.
- compileSdkVersion:顧名思義,是編譯時(shí),使用的SDK版本,當(dāng)然compileSdkVersion>= targetSdkVersion
- minSdkVersion: 當(dāng)然就是我們App支持的最低版本號.海外版大多數(shù)App都會選擇支持14-15.國內(nèi),有些App可能會支持到9.
- maxSdkVersion:很少見的屬性.卻在權(quán)限設(shè)置里會有一些應(yīng)用場景.比如設(shè)置這個(gè)權(quán)限支持的最大版本號雪猪。
1.3 擴(kuò)展:簡單闡述一下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"/>
當(dāng)我們使用Environment.getExternalStorageDirectory()
方法時(shí)录煤,實(shí)際的存儲位置是sdcard/Android/data/包名/
却邓,這個(gè)地址在Sd卡上。
我們在使用這個(gè)方法,實(shí)際在API-19以上是無需申請權(quán)限的。可以直接使用居扒。所以假如我們不需要額外創(chuàng)建文件夾〕笊鳎可以無需申請STORAGE權(quán)限喜喂。標(biāo)識了maxSdkVersion=18之后瓤摧,表示,當(dāng)手機(jī)版本高于18夜惭,這個(gè)權(quán)限將不會出現(xiàn)在權(quán)限列表了。
好處是什么铛绰?
當(dāng)然诈茧,
- 我們申請的權(quán)限越少越好。
- 用戶手機(jī)設(shè)備高于6.0之后捂掰「一幔可以拒絕掉這個(gè)權(quán)限。那么
Environment.getExternalStorageDirectory()
將失效这嚣。 - 所以不出現(xiàn)就不能拒絕鸥昏。我還是可以用。^^
二.權(quán)限簡介
2.1 不同版本會產(chǎn)生的狀況
-
if(targetSdkVersion(App) < 23 && Build.VERSION.SDK_INT(手機(jī)版本) < 23)
安裝APP時(shí),會聲明App所需要的權(quán)限,不會詢問用戶.也不可以關(guān)閉權(quán)限(部分國產(chǎn)定制機(jī)型除外Xiaomi就在6.0之前就已經(jīng)提出了一套自己的權(quán)限系統(tǒng).) -
if(targetSdkVersion < 23 && Build.VERSION.SDK_INT >= 23)
表示App并未兼容6.0,安裝App時(shí),也不會讓用戶動態(tài)申請權(quán)限,但是用戶可以自行去設(shè)置頁面關(guān)閉權(quán)限, -
if(targetSdkVersion >= 23 && Build.VERSION.SDK_INT < 23)
同1.所述,也是僅僅在安裝App時(shí)提示,聲明App所需要的權(quán)限,不會詢問用戶動態(tài)申請,也不可以關(guān)閉. -
if(targetSdkVersion >= 23 && Build.VERSION.SDK_INT >= 23)
動態(tài)申請權(quán)限.也是大勢所趨.
- 第3點(diǎn)講到的情況
第三點(diǎn)講到的情況.6.0以上的設(shè)備,安裝targetSdkVersion<23
的App時(shí),也可以跳轉(zhuǎn)到Setting頁面自行關(guān)閉權(quán)限.但是會彈出提示,中文翻譯是:此應(yīng)用專為舊版Android打造.拒絕權(quán)限可能會導(dǎo)致其無法正常運(yùn)行姐帚。
2.2權(quán)限分類
Android中有很多權(quán)限吏垮,但并非所有的權(quán)限都是敏感權(quán)限,于是6.0系統(tǒng)就對權(quán)限進(jìn)行了分類罐旗,一般為下述幾類
- 正常(Normal)權(quán)限
- 危險(xiǎn)(Dangerous)權(quán)限
- 特殊(signature)權(quán)限
2.2.1正常(Normal)權(quán)限
普通權(quán)限有很多膳汪,不一一列舉了,總結(jié)一下他們的特點(diǎn):
- 對用戶隱私?jīng)]有較大影響或者不會打來安全問題。
- 安裝后就賦予這些權(quán)限,不需要顯示提醒用戶荐类,用戶也不能取消這些權(quán)限荠锭。
2.2.2危險(xiǎn)(Dangerous)權(quán)限
Android Dangerous(危險(xiǎn))權(quán)限和權(quán)限組的劃分。
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
};
}
}
}
暫時(shí)分為9個(gè)權(quán)限組醋虏,這些權(quán)限,作為動態(tài)申請中,可能涉及到主要內(nèi)容娇豫。(注:申請同一權(quán)限組內(nèi)的子權(quán)限,系統(tǒng)彈出的權(quán)限申請框中的文案是一樣的畅厢。)
2.2.3特殊(Signature)權(quán)限
這個(gè)分類呢锤躁,可以說對于用戶來說,更危險(xiǎn)的權(quán)限或详。特別敏感的權(quán)限系羞。此類權(quán)限不能在App內(nèi)彈出系統(tǒng)的權(quán)限申請框。只能跳轉(zhuǎn)到設(shè)置頁面修改霸琴。
//修改系統(tǒng)設(shè)置
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
//懸浮窗
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
1.修改系統(tǒng)設(shè)置-WRITE_SETTINGS
申請:
//申請權(quán)限跳轉(zhuǎn)是可以使用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);
}
判斷是否有該權(quán)限:
//我們可以在onActivityResult的時(shí)候判斷權(quán)限是否有獲取
public static boolean hasPermission(Context context) {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.System.canWrite(context);
}
2.系統(tǒng)懸浮窗-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);
}
判斷是否有該權(quán)限:
public static boolean hasPermission(Context context) {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context);
}
三.常用API
注意:因?yàn)锳PI為 23 以上新增的椒振。所以使用的時(shí)候要判斷版本號。
1.ContextCompat.checkSelfPermission
檢測權(quán)限是否已經(jīng)獲取梧乘。
ContextCompat.checkSelfPermission(context, permission)!= PackageManager.PERMISSION_GRANTED
2.shouldShowRequestPermissionRationale(Activity)
//偽代碼
public class Activity{
...
public boolean shouldShowRequestPermissionRationale(@NonNull String permission) {
return getPackageManager().shouldShowRequestPermissionRationale(permission);
}
...
}
這個(gè)方法有點(diǎn)難懂澎迎,大意就是說庐杨,是否要告知用戶申請權(quán)限的重要性。這里有點(diǎn)繞夹供,我們這詳細(xì)說明一下灵份。
- 假設(shè)用戶第一次申請前,這個(gè)方法的返回值false
- 假設(shè)用戶申請被拒絕過一次之后哮洽,這個(gè)方法的返回值true
- 假設(shè)用戶申請被拒絕并點(diǎn)擊不再詢問填渠,這個(gè)方法的返回值false
這個(gè)方法的設(shè)計(jì)思想:是因?yàn)榈谝淮紊暾垼脩艟芙^授予權(quán)限之后鸟辅,以后再申請氛什。彈框中會出現(xiàn)不再詢問的選項(xiàng)。shouldShowRequestPermissionRationale方法返回true可以理解為匪凉,用戶已經(jīng)拒絕過一次以上了枪眉,下一次彈框會出現(xiàn)不再詢問。所以我們發(fā)現(xiàn)返回值為true的時(shí)候再层,可能要整理一份聲淚俱下的文案彈框來感動用戶贸铜,讓他給我們權(quán)限。盡量不要點(diǎn)擊不再詢問聂受。然后用戶被感動到之后萨脑。再彈出系統(tǒng)的權(quán)限申請框。
當(dāng)然這個(gè)方法還可以被我們加以利用:
shouldShowRequestPermissionRationale 關(guān)鍵方法說明:
* 申請權(quán)限前 false --> 申請后 true 第一次申請被拒絕
* 申請權(quán)限前 true --> 申請后 false 已經(jīng)被拒絕過一次以上了饺饭,并且這次拒絕點(diǎn)擊了NeverAskAgain
* 申請權(quán)限前 true --> 申請后 true 第二次及以上拒絕,但是未點(diǎn)擊NeverAskAgain
* 申請權(quán)限前 false --> 申請后 false 本次申請權(quán)限前,就已經(jīng)點(diǎn)擊過了NeverAskAgain渤早,此時(shí)我們可能要提示用戶到Setting中手動開啟權(quán)限了。
3. requestPermissions和onRequestPermissionsResult兩兄弟
用法類似于(startActivityForResult 和 onActivityResult)瘫俊,其實(shí)點(diǎn)開源碼跟進(jìn)去會發(fā)現(xiàn)鹊杖,申請權(quán)限的底層就是用這個(gè)實(shí)現(xiàn)的。(回憶到之前扛芽,領(lǐng)導(dǎo)讓我們每周問自己五個(gè)為什么骂蓖?,第一個(gè)就是問為什么申請權(quán)限一定要在Activity中發(fā)起川尖?最后就研究到了Context中的startActivityForResult,跑題了登下。)
使用方法:
//申請相機(jī)和MIC權(quán)限
requestPermissions(new String[]{ Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO}, 1);
//在回調(diào)中判斷權(quán)限是否已經(jīng)獲取。
//因?yàn)橐淮慰梢陨暾埗鄠€(gè)權(quán)限叮喳。所以返回一組權(quán)限與一組對應(yīng)結(jié)果被芳。
@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]);
}
}
}
結(jié)束
Android M 的動態(tài)運(yùn)行時(shí)的基礎(chǔ)介紹就這些了。下篇會通過我寫的一個(gè)組件來接管運(yùn)行時(shí)權(quán)限的獲取馍悟。 ----待續(xù)畔濒。