從 Android 6.0(API 級(jí)別 23)開(kāi)始砍艾,用戶開(kāi)始在應(yīng)用運(yùn)行時(shí)向其授予權(quán)限悯衬,而不是在應(yīng)用安裝時(shí)授予码耐。 Android 6.0系統(tǒng)6.0以前追迟,所有的權(quán)限,訪問(wèn)網(wǎng)絡(luò)的權(quán)限骚腥,讀取SD卡的權(quán)限敦间,訪問(wèn)通訊錄,撥打電話的權(quán)限都在安裝的時(shí)候系統(tǒng)授予了要安裝的應(yīng)用。在系統(tǒng)運(yùn)行的時(shí)候不需要對(duì)權(quán)限做任何的處理廓块。用戶在安裝的時(shí)候一般都不知道這些權(quán)限用在了什么地方厢绝,這樣做很不安全。一個(gè)應(yīng)用很容易在一個(gè)Service中讀取用戶所有的通訊錄信息發(fā)送到服務(wù)器上带猴。Android 6.0后的動(dòng)態(tài)權(quán)限讓我們的系統(tǒng)更加安全昔汉,犧牲了用戶的方便性,得到的是安全拴清。下圖是Android 6.0系統(tǒng)對(duì)撥打電話和管理電話權(quán)限組的詢問(wèn)靶病。
Android 6.0把權(quán)限分為兩種:Normal Permissions(正常權(quán)限)和Dangerous Permissions(危險(xiǎn)權(quán)限)。其中危險(xiǎn)權(quán)限又進(jìn)行了分類口予。把所有的危險(xiǎn)權(quán)限分成了幾個(gè)組娄周。正常權(quán)限不會(huì)給用戶的隱私帶來(lái)不安全,不需要?jiǎng)討B(tài)申請(qǐng)苹威,在應(yīng)用安裝的時(shí)候就已經(jīng)被授予了昆咽。危險(xiǎn)權(quán)限需要?jiǎng)討B(tài)處理,只有用戶批準(zhǔn)了這些權(quán)限牙甫,應(yīng)用才能被授予這些權(quán)限掷酗。
所有的普通權(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)限:
二、Android 6.0+危險(xiǎn)權(quán)限的動(dòng)態(tài)處理
在開(kāi)發(fā)應(yīng)用的時(shí)候不管是正常權(quán)限還是危險(xiǎn)權(quán)限都必須在應(yīng)用的Manifest.xml文件中聲明窟哺。如果設(shè)備運(yùn)行的是Android 5.1或更低的系統(tǒng)泻轰,或者應(yīng)用的目標(biāo)SDK小于23,那么在Manifest.xml文件中列出的危險(xiǎn)權(quán)限在安裝的時(shí)候用戶必須接受且轨,要不應(yīng)用沒(méi)法安裝浮声。如果設(shè)備運(yùn)行的是Android 6.0或更高的系統(tǒng),或者應(yīng)用的目標(biāo)SDK大于等于23旋奢,那么在Manifest.xml文件中列出的危險(xiǎn)權(quán)限泳挥,會(huì)在應(yīng)用運(yùn)行的時(shí)候被用戶授予或拒絕。開(kāi)發(fā)者需要在代碼中對(duì)危險(xiǎn)權(quán)限進(jìn)行處理至朗。
public class MainActivity extends AppCompatActivity {
final public static int REQUEST_CODE_ASK_CALL_PHONE = 123;
private String mMobile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(new
View.OnClickListener() {
@Override
public void onClick(View v) {
onCallWrapper("12345678912");
}
});
}
public void onCallWrapper(String mobile) {
this.mMobile = mobile;
//檢測(cè)權(quán)限
int checkCallPhonePermisssion = ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE);
if (checkCallPhonePermisssion != PackageManager.PERMISSION_GRANTED || !checkOpsPermission(this,Manifest.permission.CALL_PHONE)) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
Toast.makeText(this, "shouldShowRequestPermissionRationale", Toast.LENGTH_SHORT).show();
new AlertDialog.Builder(this)
.setTitle("提示")
.setMessage("應(yīng)用需要開(kāi)啟拍照的權(quán)限屉符,是否繼續(xù)?")
.setPositiveButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CALL_PHONE}, REQUEST_CODE_ASK_CALL_PHONE);
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
}).show();
} else {
//請(qǐng)求權(quán)限
ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.CALL_PHONE}, REQUEST_CODE_ASK_CALL_PHONE);
}
} else {
callDirectly(mobile);
}
}
//權(quán)限檢測(cè)
private static boolean checkOpsPermission(Context context, String permission) {
//6.0 api 23
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
try {
AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
String opsName = null;
opsName = AppOpsManager.permissionToOp(permission);
if (opsName == null) {
return true;
}
int opsMode = appOpsManager.checkOpNoThrow(opsName, Process.myUid(), context.getPackageName());
return opsMode == AppOpsManager.MODE_ALLOWED;
} catch (Exception ex) {
return true;
}
}
return true;
}
//請(qǐng)求權(quán)限返回的結(jié)果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_ASK_CALL_PHONE:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission Granted 授予權(quán)限
callDirectly(mMobile);
} else {
// Permission Denied 權(quán)限被拒絕
Toast.makeText(MainActivity.this, "Permission Denied",
Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private void callDirectly(String mobile) {
Intent intent = new Intent();
intent.setAction("android.intent.action.CALL");
intent.setData(Uri.parse("tel:" + mobile));
this.startActivity(intent);
}
}
上面這個(gè)簡(jiǎn)單的例子是通過(guò)點(diǎn)擊一個(gè)按鈕撥打電話锹引。在6.0系統(tǒng)之前矗钟,我們只需要直接調(diào)用方法callDirectly()方法就可以了。增加了系統(tǒng)的安全性嫌变,但是多了很多代碼上的處理吨艇。ContextCompat和
ActivityCompat分別是Context和Activity的兼容類,在上的代碼中我們就不用去判斷當(dāng)前系統(tǒng)的API是多好了腾啥,在API23之前也能用上面代碼中的方法东涡。ContextCompat.checkSelfPermission()方法是用來(lái)判斷冯吓,當(dāng)前應(yīng)用是否擁有某個(gè)權(quán)限。如果擁有我們可以直接調(diào)用callDirectly()方法撥打電話软啼。如果當(dāng)前應(yīng)用沒(méi)有撥打電話的權(quán)限桑谍,會(huì)調(diào)ActivityCompat.shouldShowRequestPermissionRationale()方法,注意當(dāng)前系統(tǒng)還沒(méi)有彈出讓用戶去選擇是允許還是拒絕祸挪,這個(gè)時(shí)候shouldShowRequestPermissionRationale()方法返回false锣披。所以接下來(lái)會(huì)執(zhí)ActivityCompat.requestPermissions()方法會(huì)向系統(tǒng)去請(qǐng)求,系統(tǒng)會(huì)彈出一個(gè)對(duì)話框讓用戶去選擇贿条。onRequestPermissionsResult()方法用來(lái)處理用戶的選擇雹仿。這個(gè)方法可以監(jiān)聽(tīng)用戶選擇的是允許還是拒絕。當(dāng)用戶選擇了允許整以,那么直接調(diào)用callDirectly()方法撥打電話胧辽。當(dāng)用戶選擇了拒絕,上面的代碼只是提示一個(gè)Toast公黑。
當(dāng)用戶選擇了拒絕邑商,那么在下次點(diǎn)擊按鈕撥打電話的時(shí)候ActivityCompat.shouldShowRequestPermissionRationale()方法會(huì)返回true,在上面的代碼中我們會(huì)彈出一個(gè)對(duì)話框給用戶,給用戶一個(gè)提示凡蚜。當(dāng)用戶選擇確定人断,會(huì)向系統(tǒng)請(qǐng)求權(quán)限。當(dāng)用戶選擇取消朝蜘,關(guān)閉對(duì)話框什么也不做恶迈。當(dāng)我們選擇了不在提示并且選擇了拒絕的時(shí)候ActivityCompat.shouldShowRequestPermissionRationale()方法返回false。
三谱醇、用Easy Permissions開(kāi)源庫(kù)處理權(quán)限
1.配置
在app層的build.gradle中
dependencies {
// EasyPermissions
compile 'pub.devrel:easypermissions:1.2.0'
}
github : https://github.com/googlesamples/easypermissions
2.舉例
EasyPermissions是谷歌封裝的一個(gè)運(yùn)行時(shí)權(quán)限申請(qǐng)的庫(kù)暇仲,簡(jiǎn)化了操作的過(guò)程
1、builde gradle中依賴
2副渴、清單文件中聲明權(quán)限
3奈附、重寫onRequestPermissionsResult()方法,把執(zhí)行操作給easyPermissions來(lái)
4煮剧、通過(guò)hasPermissions檢查權(quán)限桅狠,或者原生的也行,然后去申請(qǐng)權(quán)限
5轿秧、實(shí)現(xiàn)EasyPermissions.PermissionCallbacks接口,重寫兩個(gè)方法咨堤,成功或失敗
6菇篡、在成功或者失敗方法中編寫要具體做的事。
public class SecondActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {
private static final int RC_CAMERA_PERM = 123;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cameraTask();
}
});
}
//RC_CAMERA_PERM 標(biāo)識(shí)請(qǐng)求碼, (可選)@AfterPermissionGranted()注解 .這里的方法名可以自己取一喘,
//主要是權(quán)限都申請(qǐng)完驱还,就調(diào)用這個(gè)方法嗜暴,執(zhí)行里面的操作。其實(shí)就相當(dāng)于在onPermissionsGranted()調(diào)用這個(gè)方法而已:
@AfterPermissionGranted(RC_CAMERA_PERM)
public void cameraTask() {
//通過(guò)hasPermissions檢查權(quán)限
if (EasyPermissions.hasPermissions(this, Manifest.permission.CAMERA)) {
// 該應(yīng)用已經(jīng)有打電話的權(quán)限
Toast.makeText(this, "TODO: Camera things", Toast.LENGTH_LONG).show();
} else {
//請(qǐng)求權(quán)限
EasyPermissions.requestPermissions(this, "需要獲取系統(tǒng)的拍照的權(quán)限议蟆!", RC_CAMERA_PERM, Manifest.permission.CAMERA);
}
}
//重新以下三個(gè)方法 1,把執(zhí)行結(jié)果的操作給EasyPermissions
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
//請(qǐng)求成功執(zhí)行相應(yīng)的操作
@Override
public void onPermissionsGranted(int requestCode, List<String> perms) {
Toast.makeText(this, "onPermissionsGranted", Toast.LENGTH_SHORT).show();
}
//申請(qǐng)失敗時(shí)調(diào)用
@Override
public void onPermissionsDenied(int requestCode, List<String> perms) {
Toast.makeText(this, "onPermissionsDenied", Toast.LENGTH_SHORT).show();
//不一定執(zhí)行,有些手機(jī)會(huì)執(zhí)行系統(tǒng)的彈框
new AppSettingsDialog.Builder(this)
.setTitle("權(quán)限申請(qǐng)")
.setPositiveButton("確認(rèn)")
.setNegativeButton("取消")
.setRationale("當(dāng)前App需要申請(qǐng)camera權(quán)限,需要打開(kāi)設(shè)置頁(yè)面么?")
.setRequestCode(RC_CAMERA_PERM)
.build()
.show();
}
}
也可以通過(guò)原生來(lái)檢查權(quán)限,比如有多個(gè)權(quán)限,可以抽取一個(gè)工具類
public final class CheckPermissionUtils {
private CheckPermissionUtils() {
}
//需要申請(qǐng)的權(quán)限
private static String[] permissions = new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
};
//檢測(cè)權(quán)限
public static String[] checkPermission(Context context){
List<String> data = new ArrayList<>();//存儲(chǔ)未申請(qǐng)的權(quán)限
for (String permission : permissions) {
int checkSelfPermission = ContextCompat.checkSelfPermission(context, permission);
if(checkSelfPermission == PackageManager.PERMISSION_DENIED){//未申請(qǐng)
data.add(permission);
}
}
return data.toArray(new String[data.size()]);
}
}
以下是在activity中調(diào)用該工具類
//初始化權(quán)限
private void initPermission() {
//檢查權(quán)限
String[] permissions = CheckPermissionUtils.checkPermission(this);
if (permissions.length == 0) {
//權(quán)限都申請(qǐng)了
} else {
//申請(qǐng)權(quán)限 ,參數(shù)2:權(quán)限數(shù)組,參數(shù)3:請(qǐng)求碼code
ActivityCompat.requestPermissions(this, permissions, 100);
//把執(zhí)行結(jié)果的操作給EasyPermissions的onRequestPermissionsResult
}
}
這個(gè)例子是點(diǎn)擊一個(gè)按鈕闷沥,處理拍照的事情,所以我們需要獲取相機(jī)的權(quán)限咐容。首先我們要復(fù)寫Activity或者Fragment的onRequestPermissionsResult()方法舆逃。SecondActivity實(shí)現(xiàn)了EasyPermissions.PermissionCallbacks這個(gè)接口復(fù)寫了onPermissionsGranted()和onPermissionsDenied()這兩個(gè)接口。當(dāng)用戶選擇了允許那么調(diào)用onPermissionsGranted()方法戳粒,當(dāng)用戶選擇了拒絕那么調(diào)用onPermissionsDenied()方法路狮。AppSettingsDialog是一個(gè)詢問(wèn)用戶是否跳轉(zhuǎn)到運(yùn)行應(yīng)用的設(shè)置界面去開(kāi)啟權(quán)限的對(duì)話框。
四蔚约、用Permissions Dispatcher開(kāi)源庫(kù)處理權(quán)限
1.配置
在app層的build.gradle中
dependencies {
// Permissions Dispatcher
compile 'com.github.hotchemi:permissionsdispatcher:3.0.1'
annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:3.0.1'
}
這種配置需要有個(gè)條件:Android Gradle Plugin >= 2.2
2.舉例
這個(gè)庫(kù)用到了5個(gè)注解:
注解 | 是否必須 | 描述 |
---|---|---|
@RuntimePermissions | 是 | 注冊(cè)在Acttivity或者Fragment上 |
@NeedsPermission | 是 | 再需要權(quán)限的方法上注冊(cè) |
@OnShowRationale | 否 | 被注解的方法可以提示為什么需要這個(gè)權(quán)限 |
@OnPermissionDenied | 否 | 如果用戶拒絕了權(quán)限申請(qǐng)那么調(diào)用該方法 |
@OnNeverAskAgain | 否 | 如果用戶選擇了不再詢問(wèn)奄妨,調(diào)用該方法 |
這個(gè)庫(kù)用到了Annotion Processor,需要用到一個(gè)應(yīng)用編譯期間產(chǎn)生的類苹祟,這個(gè)類的命名是:類名+PermissionsDispatcher砸抛。
//注冊(cè)在Acttivity或者Fragment上
@RuntimePermissions
public class ThirdActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_third);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ThirdActivityPermissionsDispatcher.showContactsWithCheck(ThirdActivity.this);
}
});
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
ThirdActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
//處理當(dāng)用戶允許該權(quán)限時(shí)需要處理的方法
@NeedsPermission({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
public void showContacts() {
Toast.makeText(this, "讀取通訊錄", Toast.LENGTH_SHORT).show();
}
//如果用戶拒絕了權(quán)限申請(qǐng)那么調(diào)用該方法
@OnPermissionDenied({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
public void onContactsDenied() {
Toast.makeText(this, "onContactsDenied", Toast.LENGTH_SHORT).show();
}
//如果用戶選擇了不再詢問(wèn),調(diào)用該方法
@OnNeverAskAgain({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
public void onContactsNeverAskAgain() {
Toast.makeText(this, "onContactsNeverAskAgain", Toast.LENGTH_SHORT).show();
}
//// 提示用戶權(quán)限使用的對(duì)話框
@OnShowRationale({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
public void showRationaleContacts(final PermissionRequest request) {
// Toast.makeText(this, "showRationaleContacts", Toast.LENGTH_SHORT).show();
new AlertDialog.Builder(this)
.setPositiveButton("繼續(xù)", new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
request.proceed();
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
request.cancel();
}
})
.setCancelable(false)
.setMessage("需要通訊錄權(quán)限")
.show();
}
}
其實(shí)這個(gè)dispather就是對(duì)動(dòng)態(tài)權(quán)限處理的那些操作進(jìn)行了封裝树枫,通過(guò)查看生成的MainActivityPermissionsDispatcher文件就可以驗(yàn)證這一點(diǎn)直焙。這里的權(quán)限說(shuō)的是危險(xiǎn)權(quán)限,普通權(quán)限只用在Manifest中聲明就可以了团赏。
五箕般、一句代碼搞定權(quán)限請(qǐng)求,XXPermissions
github : https://github.com/getActivity/XXPermissions
集成步驟
dependencies {
implementation 'com.hjq:xxpermissions:3.2'
}
使用
XXPermissions.with(this)
//.constantRequest() //可設(shè)置被拒絕后繼續(xù)申請(qǐng)舔清,直到用戶授權(quán)或者永久拒絕
//.permission(Permission.REQUEST_INSTALL_PACKAGES, Permission.SYSTEM_ALERT_WINDOW) //支持請(qǐng)求安裝權(quán)限和懸浮窗權(quán)限
.permission(Permission.Group.STORAGE) //支持多個(gè)權(quán)限組進(jìn)行請(qǐng)求丝里,不指定則默以清單文件中的危險(xiǎn)權(quán)限進(jìn)行請(qǐng)求
.request(new OnPermission() {
@Override
public void hasPermission(List<String> granted, boolean isAll) {
if (isAll) {
Toast.makeText(MainActivity.this, "獲取權(quán)限成功", Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(MainActivity.this, "獲取權(quán)限成功,部分權(quán)限未正常授予", Toast.LENGTH_SHORT).show();
}
}
@Override
public void noPermission(List<String> denied, boolean quick) {
if(quick) {
Toast.makeText(MainActivity.this, "被永久拒絕授權(quán)体谒,請(qǐng)手動(dòng)授予權(quán)限", Toast.LENGTH_SHORT).show();
//如果是被永久拒絕就跳轉(zhuǎn)到應(yīng)用權(quán)限系統(tǒng)設(shè)置頁(yè)面
XXPermissions.gotoPermissionSettings(MainActivity.this);
}else {
Toast.makeText(MainActivity.this, "獲取權(quán)限失敗", Toast.LENGTH_SHORT).show();
}
}
});
參考文獻(xiàn):
https://github.com/googlesamples/easypermissions
https://github.com/hotchemi/PermissionsDispatcher
http://hotchemi.github.io/PermissionsDispatcher/
https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous
系統(tǒng)所需要的權(quán)限
https://developer.android.com/reference/android/Manifest.permission.html
普通權(quán)限
https://developer.android.com/guide/topics/permissions/normal-permissions.html
危險(xiǎn)權(quán)限
https://developer.android.com/guide/topics/security/permissions.html#perm-groups
在運(yùn)行時(shí)請(qǐng)求權(quán)限
https://developer.android.com/training/permissions/requesting.html
解讀Android官方開(kāi)發(fā)指導(dǎo) - 運(yùn)行時(shí)權(quán)限
http://www.reibang.com/p/0beb6243d650