源碼傳送門
前言
現(xiàn)在談?wù)揂ndroid權(quán)限適配可能有點(diǎn)沒必要扶踊,因?yàn)榫W(wǎng)上關(guān)于權(quán)限適配的文章很多收津,搜一下Android6.0權(quán)限適配關(guān)鍵詞能搜到一堆文章歹颓,而且很多寫的還很不錯(cuò)铜秆。不過自己想了想還是總結(jié)一下奢米,因?yàn)槟切┪恼露际莿e人的抓韩,不是自己的,之前一直想總結(jié)一下鬓长,但是一直沒做谒拴,今天就簡單記錄一下,方便以后查閱涉波,也對Android6.0的權(quán)限機(jī)制再次進(jìn)行一次全面的認(rèn)識英上。
從Android M開始,用戶開始在應(yīng)用運(yùn)行時(shí)向其授予權(quán)限啤覆,而不是在應(yīng)用安裝時(shí)授予苍日。這樣更友好的讓用戶選擇,當(dāng)真正需要權(quán)限的時(shí)候再去申請權(quán)限窗声,而不是Android M之前在安裝時(shí)一下子去申請相恃。
正常權(quán)限
正常權(quán)限不會直接給用戶隱私權(quán)帶來風(fēng)險(xiǎn)。如果您的應(yīng)用在其清單中列出了正常權(quán)限笨觅,系統(tǒng)將自動授予該權(quán)限拦耐。而不需要我們?nèi)フ埱髾?quán)限见剩。
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_IGNORE_BATTERY_OPTIMIZATIONS
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
危險(xiǎn)權(quán)限
危險(xiǎn)權(quán)限涵蓋應(yīng)用需要涉及用戶隱私信息的數(shù)據(jù)或資源杀糯,或者可能對用戶存儲的數(shù)據(jù)或其他應(yīng)用的操作產(chǎn)生影響的區(qū)域固翰。例如,能夠讀取用戶的聯(lián)系人屬于危險(xiǎn)權(quán)限犯戏。如果應(yīng)用聲明其需要危險(xiǎn)權(quán)限种吸,則用戶必須明確向應(yīng)用授予該權(quán)限坚俗。
在危險(xiǎn)權(quán)限中,我們需要了解一個(gè)權(quán)限組的概念剧董,所有危險(xiǎn)的 Android 系統(tǒng)權(quán)限都屬于權(quán)限組,如果應(yīng)用請求其清單中列出的危險(xiǎn)權(quán)限,而應(yīng)用目前在權(quán)限組中沒有擁有任何權(quán)限,則系統(tǒng)會向用戶顯示一個(gè)對話框宠蚂,描述應(yīng)用要訪問的權(quán)限組。對話框不描述該組內(nèi)的具體權(quán)限呀癣。
例如我們需要讀取獲取手機(jī)卡imsi沼沈,此時(shí)需要請求權(quán)限READ_PHONE_STATE页衙,發(fā)現(xiàn)此時(shí)提示框也展示了請求打電話權(quán)限响巢。(系統(tǒng)只告訴用戶應(yīng)用需要的權(quán)限組伏穆,而不告知具體權(quán)限)其實(shí)READ_PHONE_STATE和打電話權(quán)限CALL_PHONE都屬于一個(gè)權(quán)限組PHONE烟瞧,如果我們此時(shí)允許了權(quán)限诗鸭,那么下次再其他地方使用了打電話權(quán)限時(shí)系統(tǒng)將立即授予該權(quán)限。
注:任何權(quán)限都可屬于一個(gè)權(quán)限組参滴,包括正常權(quán)限和應(yīng)用定義的權(quán)限强岸。但權(quán)限組僅當(dāng)權(quán)限危險(xiǎn)時(shí)才影響用戶體驗(yàn)±猓可以忽略正常權(quán)限的權(quán)限組蝌箍。
權(quán)限組 | 權(quán)限 |
---|---|
CALENDAR | READ_CALENDAR WRITE_CALENDAR |
CAMERA | CAMERA |
CONTACTS | READ_CONTACTS WRITE_CONTACTS GET_ACCOUNTS |
LOCATION | ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION |
MICROPHONE | RECORD_AUDIO |
PHONE | READ_PHONE_STATE CALL_PHONE READ_CALL_LOG WRITE_CALL_LOG ADD_VOICEMAIL USE_SIP PROCESS_OUTGOING_CALLS |
SENSORS | BODY_SENSORS |
SMS | SEND_SMS RECEIVE_SMS READ_SMS RECEIVE_WAP_PUSH RECEIVE_MMS |
權(quán)限請求API
/**
* 確定權(quán)限是否已經(jīng)被授予
* @param permission 被檢測權(quán)限的名字.
* @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} 如果權(quán)限被授予,
* {@link android.content.pm.PackageManager#PERMISSION_DENIED} 如果權(quán)限被拒絕返回值.
*/
public static int checkSelfPermission(@NonNull Context context, @NonNull String permission)
/**
* 是否顯示自定義UI提示用戶
* 華為手機(jī)測試 第一次使用時(shí)返回false
* 如果拒絕返回true
* 如果拒絕并點(diǎn)擊不在提醒返回false
* 已經(jīng)同意過權(quán)限青灼,但在設(shè)置拒絕此時(shí)返回true
* 沒有同意過權(quán)限,在設(shè)置中開啟并拒絕權(quán)限返回false
* @param activity 請求權(quán)限Activity.
* @param permission 需要請求的權(quán)限.
* @return 是否顯示自定義對話框提示用戶.
*/
public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity,
@NonNull String permission)
/**
* 給應(yīng)用申請權(quán)限妓盲,申請的權(quán)限必須在manifest文件注冊杂拨,正常權(quán)限在安裝時(shí)自動被授權(quán),不需要使用此方法請求權(quán)限
* 請求之后會彈出系統(tǒng)提示框悯衬,供我們選擇是拒絕還是允許弹沽,點(diǎn)擊后
* android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult(
* int, String[], int[])} 方法將會被回調(diào),
* @param activity 請求權(quán)限的Activity.
* @param permissions 需要請求的權(quán)限.
* @param requestCode 指定一個(gè)請求碼甚亭,用于區(qū)別返回結(jié)果
*
*/
public static void requestPermissions(final @NonNull Activity activity,
final @NonNull String[] permissions, final int requestCode)
/**
* 調(diào)用requestPermissions方法請求權(quán)限的回調(diào)
*需要注意的是可能請求的權(quán)限與用戶互動中斷;正在這種情況下回調(diào)將接收一個(gè)空的permissions和grantResults數(shù)組
* @param permissions 請求的權(quán)限. 不為null,長度可能為0.
* @param grantResults 請求權(quán)限的結(jié)果PERMISSION_GRANTED表示權(quán)限被允許抠艾,PERMISSION_DENIED表示權(quán)限被拒絕
*/
void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults)
需要注意的是截驮,對于如果在Activity中請求權(quán)限則可使用上面API ActivityCompat類苔严,如果在Frament請求權(quán)限則每瞒,需要使用Fragment類中的對應(yīng)方法,否則回調(diào)會有問題暇唾。
簡單封裝
/**
* 判斷是否具備所有權(quán)限
*
* @param permissions 所有權(quán)限
* @return true 具有所有權(quán)限 false沒有具有所有權(quán)限促脉,此時(shí)包含未授予的權(quán)限
*/
public static boolean isHasPermissions(String... permissions) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return true;
for (String permission : permissions) {
if (!isHasPermission(permission))
return false;
}
return true;
}
/**
* 判斷該權(quán)限是否已經(jīng)被授予
*
* @param permission
* @return true 已經(jīng)授予該權(quán)限 ,false未授予該權(quán)限
*/
private static boolean isHasPermission(String permission) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return true;
return ContextCompat.checkSelfPermission(MyApplication.getAppContext(), permission) == PackageManager.PERMISSION_GRANTED;
}
/**
* 請求權(quán)限,經(jīng)測試發(fā)現(xiàn)TabActivity管理Activity時(shí)策州,在Activity中請求權(quán)限時(shí)需要傳入父Activity對象瘸味,即TabActivity對象
* 并在TabActivity管理Activity中重寫onRequestPermissionsResult并分發(fā)到子Activity,否則回調(diào)不執(zhí)行 够挂。TabActivity回調(diào)中 調(diào)用getLocalActivityManager().getCurrentActivity().onRequestPermissionsResult(requestCode, permissions, grantResults);分發(fā)到子Activity
*
*
* @param object Activity or Fragment
* @param requestCode 請求碼
* @param permissions 請求權(quán)限
*/
public static void requestPermissions(Object object, int requestCode, String... permissions) {
ArrayList<String> arrayList = new ArrayList<>();
for (String permission : permissions) {
if (!isHasPermissions(permission)) {
arrayList.add(permission);
}
}
if (arrayList.size() > 0) {
if (object instanceof Activity) {
Activity activity = (Activity) object;
Activity activity1 = activity.getParent() != null && activity.getParent() instanceof TabActivity ? activity.getParent() : activity;
ActivityCompat.requestPermissions(activity1, arrayList.toArray(new String[]{}), requestCode);
} else if (object instanceof Fragment) {
Fragment fragment = (Fragment) object;
//當(dāng)Fragment嵌套Fragment時(shí)使用getParentFragment(),然后在父Fragment進(jìn)行分發(fā)旁仿,否則回調(diào)不執(zhí)行
Fragment fragment1 = fragment.getParentFragment() != null ? fragment.getParentFragment() : fragment;
fragment1.requestPermissions(arrayList.toArray(new String[]{}), requestCode);
} else {
throw new RuntimeException("the object must be Activity or Fragment");
}
}
}
如果想展示自定義UI友好的提示用戶申請?jiān)摍?quán)限的原因,則需要使用shouldShowRequestPermissionRationale方法孽糖,簡要封裝如下
public static boolean shouldShowRequestPermissionRationale(@NonNull Object object, String... permissions) {
for (String permission : permissions) {
if (object instanceof Activity) {
if (ActivityCompat.shouldShowRequestPermissionRationale((Activity) object, permission)) {
return true;
}
} else if (object instanceof Fragment) {
if(((Fragment) object).shouldShowRequestPermissionRationale(permission)){
return true;
}
} else {
throw new RuntimeException("the object must be Activity or Fragment");
}
}
return false;
}
/**
* 二次申請權(quán)限時(shí)枯冈,彈出自定義提示對話框
*
* @param activity
* @param message
* @param iPermissionRequest
* @see com.example.xh.ui.BaiduLocationFragment 可以查看該類onRequestPermissionsResult方法當(dāng)選擇永不提醒時(shí)的處理辦法。
*/
public static void showDialog(Activity activity, String message, final IPermissionRequest iPermissionRequest) {
new AlertDialog.Builder(activity)
.setPositiveButton("允許", new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
iPermissionRequest.agree();
dialog.dismiss();
}
})
.setNegativeButton("拒絕", new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
iPermissionRequest.refuse();
dialog.dismiss();
}
})
.setCancelable(false)
.setMessage(message)
.show();
}
彈出對話框后办悟,點(diǎn)擊了拒絕或者允許后尘奏,給一個(gè)回調(diào),方便進(jìn)行不同的處理病蛉,當(dāng)然如果統(tǒng)一處理的話炫加,就不需要寫接口,直接在上述點(diǎn)擊允許的時(shí)候請求權(quán)限铺然,點(diǎn)擊不允許的時(shí)候俗孝,顯示一個(gè)Toast再次做下權(quán)限拒絕提示。當(dāng)然也可在onRequestPermissionsResult中進(jìn)行判斷魄健,當(dāng)選中永不提醒后給用戶一個(gè)友好跳轉(zhuǎn)到權(quán)限設(shè)置界面驹针。
接口方法
public interface IPermissionRequest {
void agree();
void refuse();
}
特殊權(quán)限
有許多權(quán)限其行為方式與正常權(quán)限及危險(xiǎn)權(quán)限都不同。SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS 特別敏感诀艰,因此大多數(shù)應(yīng)用不應(yīng)該使用它們柬甥。如果某應(yīng)用需要其中一種權(quán)限,必須在清單中聲明該權(quán)限其垄,并且發(fā)送請求用戶授權(quán)的 intent(注意特殊權(quán)限和危險(xiǎn)權(quán)限請求方式不一樣)苛蒲。系統(tǒng)將向用戶顯示詳細(xì)管理屏幕,以響應(yīng)該 intent绿满。
請求WRITE_SETTINGS權(quán)限
/**
* 測試請求WRITE_SETTINGS權(quán)限
*/
@OnClick(R.id.request_write_setting)
@TargetApi(android.os.Build.VERSION_CODES.M)
public void requestWriteSetting() {
if (!Settings.System.canWrite(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, requestCodeWriteSetting);
} else {
Toast.makeText(PermissionTestActivity.this, "WRITE_SETTINGS 已經(jīng)被授權(quán)", Toast.LENGTH_SHORT).show();
}
}
@TargetApi(Build.VERSION_CODES.M)
private void showToast() {
if (Settings.System.canWrite(this)) {
//檢查返回結(jié)果
Toast.makeText(PermissionTestActivity.this, "WRITE_SETTINGS 被授權(quán)", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(PermissionTestActivity.this, "WRITE_SETTINGS 沒有被授權(quán)", Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == requestCodeWriteSetting) {
showToast();
}else if(requestCode==requestCodeAlertWindow){
showToastAlerterWindow();
}
}
請求SYSTEM_ALERT_WINDOW權(quán)限
/**
* 測試請求SYSTEM_ALERT_WINDOW權(quán)限
*/
@OnClick(R.id.request_alert_window)
@TargetApi(android.os.Build.VERSION_CODES.M)
public void requestAlertWindow() {
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings. ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, requestCodeAlertWindow);
} else {
Toast.makeText(PermissionTestActivity.this, "SYSTEM_ALERT_WINDOW 已經(jīng)被授權(quán)", Toast.LENGTH_SHORT).show();
}
}
@TargetApi(Build.VERSION_CODES.M)
private void showToastAlerterWindow() {
if (Settings.System.canWrite(this)) {
//檢查返回結(jié)果
Toast.makeText(PermissionTestActivity.this, "SYSTEM_ALERT_WINDOW 被授權(quán)", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(PermissionTestActivity.this, "SYSTEM_ALERT_WINDOW 沒有被授權(quán)", Toast.LENGTH_SHORT).show();
}
}
注意:權(quán)限必須在清單文件中聲明臂外,否則進(jìn)入上面界面時(shí)開關(guān)是不可點(diǎn)擊的灰色。
打開權(quán)限設(shè)置界面
在上面危險(xiǎn)權(quán)限申請中喇颁,如果用戶拒絕了權(quán)限漏健,并且選中永不提醒,那么下次請求權(quán)限時(shí)直接執(zhí)行onRequestPermissionsResult回調(diào)橘霎,并且返回狀態(tài)是權(quán)限被拒絕狀態(tài)蔫浆,那么若想授予權(quán)限,必須去手機(jī)的權(quán)限管理中設(shè)置姐叁,如果用戶去手機(jī)里找是不是很麻煩瓦盛,況且一步人不知道設(shè)置權(quán)限的地方在哪,那么為了程序的體驗(yàn)更好外潜,我們可以在我們的應(yīng)用中引導(dǎo)用戶跳轉(zhuǎn)到設(shè)置權(quán)限的界面原环。實(shí)現(xiàn)代碼如下
/**
* 打開應(yīng)用權(quán)限設(shè)置界面
*/
@OnClick(R.id.open_permission_setting)
public void requestOpenPermissionSetting() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
// Uri uri = Uri.fromParts("package", getPackageName(), null);
Uri uri1=Uri.parse("package:" + getPackageName());
intent.setData(uri1);
startActivity(intent);
}
介紹到此就結(jié)束了,水平有限若有問題請指出处窥,Hava a wonderful day.
最后放幾篇感覺不錯(cuò)的文章: