運(yùn)行時(shí)權(quán)限從Android 6.0版本開始的,如果你的項(xiàng)目中 targetSdkVersion 大于等于23,那么你就必須要考慮動(dòng)態(tài)權(quán)限了。
權(quán)限又分為普通權(quán)限和危險(xiǎn)權(quán)限。普通權(quán)限如下:
android.permission.ACCESS LOCATIONEXTRA_COMMANDS
android.permission.ACCESS NETWORKSTATE
android.permission.ACCESS NOTIFICATIONPOLICY
android.permission.ACCESS WIFISTATE
android.permission.ACCESS WIMAXSTATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.BROADCAST_STICKY
android.permission.CHANGE NETWORKSTATE
android.permission.CHANGE WIFIMULTICAST_STATE
android.permission.CHANGE WIFISTATE
android.permission.CHANGE WIMAXSTATE
android.permission.DISABLE_KEYGUARD
android.permission.EXPAND STATUSBAR
android.permission.FLASHLIGHT
android.permission.GET_ACCOUNTS
android.permission.GET PACKAGESIZE
android.permission.INTERNET
android.permission.KILL BACKGROUNDPROCESSES
android.permission.MODIFY AUDIOSETTINGS
android.permission.NFC
android.permission.READ SYNCSETTINGS
android.permission.READ SYNCSTATS
android.permission.RECEIVE BOOTCOMPLETED
android.permission.REORDER_TASKS
android.permission.REQUEST INSTALLPACKAGES
android.permission.SET TIMEZONE
android.permission.SET_WALLPAPER
android.permission.SET WALLPAPERHINTS
android.permission.SUBSCRIBED FEEDSREAD
android.permission.TRANSMIT_IR
android.permission.USE_FINGERPRINT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE SYNCSETTINGS
com.android.alarm.permission.SET_ALARM
com.android.launcher.permission.INSTALL_SHORTCUT
com.android.launcher.permission.UNINSTALL_SHORTCUT
普通權(quán)限是當(dāng)需要用到時(shí)举庶,只需要在清單文件中聲明就可。危險(xiǎn)權(quán)限除了需要在清單文件中聲明外揩抡,還需在代碼中動(dòng)態(tài)進(jìn)行判斷申請户侥。危險(xiǎn)權(quán)限如下:
android.permission.READ_CALENDAR
android.permission.WRITE_CALENDAR
android.permission-group.CAMERA
android.permission.CAMERA
android.permission-group.CONTACTS
android.permission.READ_CONTACTS
android.permission.WRITE_CONTACTS
android.permission.GET_ACCOUNTS
android.permission-group.LOCATION
android.permission.ACCESS_FINE_LOCATION
android.permission.ACCESS_COARSE_LOCATION
android.permission-group.MICROPHONE
android.permission.RECORD_AUDIO
android.permission-group.PHONE
android.permission.READ_PHONE_STATE
android.permission.CALL_PHONE
android.permission.READ_CALL_LOG
android.permission.WRITE_CALL_LOG
com.android.voicemail.permission.ADD_VOICEMAIL
android.permission.USE_SIP
android.permission.PROCESS_OUTGOING_CALLS
android.permission-group.SENSORS
android.permission.BODY_SENSORS
android.permission-group.SMS
android.permission.SEND_SMS
android.permission.RECEIVE_SMS
android.permission.READ_SMS
android.permission.RECEIVE_WAP_PUSH
android.permission.RECEIVE_MMS
android.permission.READ_CELL_BROADCASTS
android.permission-group.STORAGE
android.permission.READ_EXTERNAL_STORAGE
android.permission.WRITE_EXTERNAL_STORAGE
危險(xiǎn)權(quán)限是按組劃分的,每一組中當(dāng)某一個(gè)權(quán)限被允許或者拒絕后峦嗤,同一組的其他權(quán)限也相應(yīng)的自動(dòng)允許或者拒絕蕊唐。當(dāng)targetSdkVersion大于等于23時(shí),我們用到危險(xiǎn)權(quán)限時(shí)烁设,應(yīng)按照這樣的邏輯去處理替梨。
先判斷當(dāng)前應(yīng)用是否具有權(quán)限,如果沒有就去申請装黑,當(dāng)用戶允許或者拒絕后副瀑,會(huì)回調(diào)相應(yīng)的方法,我們在回調(diào)中處理自己的邏輯恋谭。
申請單個(gè)權(quán)限
在Activity/Fragment中糠睡,判斷是否具有權(quán)限的方法是 checkSelfPermission(),申請權(quán)限的方法是requestPermissions(),然后用戶允許或者拒絕后會(huì)回調(diào)方法onRequestPermissionsResult()疚颊。
雖然Activity/Fragment提供了這些方法狈孔,如果我們用這些的話,要判斷安卓版本是否大于等于23材义,代碼如下:
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){ //動(dòng)態(tài)請求權(quán)限 }else{ //直接去調(diào)用代碼 }
谷歌工程師當(dāng)然考慮到了這些除抛,于是給開發(fā)者提供了兼容包,在Activity/Fragment中用ContextCompat.checkSelfPermission()判斷是否具有權(quán)限母截;
在Activity中用 ActivityCompat.requestPermissions()請求權(quán)限;
在Fragment中直接用 requestPermissions()請求權(quán)限橄教,不要在前面加上ActivityCompat清寇,否則會(huì)回調(diào)Fragment所在Activity的回調(diào)方法喘漏;
在Activity/Fragment中用戶允許或者拒絕權(quán)限后會(huì)回調(diào)onRequestPermissionsResult()方法。
下面看一個(gè)平常的調(diào)用系統(tǒng)相機(jī)拍照功能的代碼:
private void takePhoto() {
Intent photoIn = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(photoIn, TAKE_PHOTO_REQUEST);
}
如果我們項(xiàng)目targetSdkVersion大于等于23,那么就需要?jiǎng)討B(tài)申請權(quán)限了:
if (ContextCompat.checkSelfPermission(MySetupActivity.this, Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MySetupActivity.this,new String[]{Manifest.permission.CAMERA}, 100);
} else {
takePhoto();
}
先判斷是否具有權(quán)限华烟,如果有直接去執(zhí)行相關(guān)代碼翩迈,沒有則去申請權(quán)限。當(dāng)用戶點(diǎn)擊允許或者拒絕權(quán)限時(shí)盔夜,會(huì)回調(diào)onRequestPermissionsResult方法负饲,我們在此方法中進(jìn)行處理:
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == 100) {//相機(jī)
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
takePhoto();
} else {
// Permission Denied
AlertDialog mDialog = new AlertDialog.Builder(MySetupActivity.this)
.setTitle("友好提醒")
.setMessage("您已拒絕權(quán)限,請開啟權(quán)限!")
.setPositiveButton("開啟", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
ShowAppSetDetails.showInstalledAppDetails(MySetupActivity.this, "user.zhuku.com");
LogPrint.logILsj(TAG, "開啟權(quán)限設(shè)置");
}
})
.setCancelable(true)
.create();
mDialog.show();
}
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
當(dāng)用戶允許權(quán)限后我們直接執(zhí)行相關(guān)代碼喂链,若拒絕則提示用戶返十,彈窗提示是否需要開啟權(quán)限。其中的
ShowAppSetDetails.showInstalledAppDetails(MySetupActivity.this, "user.zhuku.com");
是開啟當(dāng)前app信息設(shè)置界面的代碼椭微。具體代碼如下:
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
/**
* 類描述:通用的調(diào)用“應(yīng)用程序信息”
* 創(chuàng)建人:Li Shengjie
* 創(chuàng)建時(shí)間:2016/11/25 1:40
* 修改人:Li Shengjie
* 修改時(shí)間:2016/11/25 1:40
* 修改備注:
*/
public class ShowAppSetDetails {
private static final String SCHEME = "package";
/**
* 調(diào)用系統(tǒng)InstalledAppDetails界面所需的Extra名稱(用于Android 2.1及之前版本)
*/
private static final String APP_PKG_NAME_21 = "com.android.settings.ApplicationPkgName";
/**
* 調(diào)用系統(tǒng)InstalledAppDetails界面所需的Extra名稱(用于Android 2.2)
*/
private static final String APP_PKG_NAME_22 = "pkg";
/**
* InstalledAppDetails所在包名
*/
private static final String APP_DETAILS_PACKAGE_NAME = "com.android.settings";
/**
* InstalledAppDetails類名
*/
private static final String APP_DETAILS_CLASS_NAME = "com.android.settings.InstalledAppDetails";
/**
* 調(diào)用系統(tǒng)InstalledAppDetails界面顯示已安裝應(yīng)用程序的詳細(xì)信息洞坑。 對于Android 2.3(Api Level
* 9)以上,使用SDK提供的接口蝇率; 2.3以下迟杂,使用非公開的接口(查看InstalledAppDetails源碼)。
*
* @param context
* @param packageName 應(yīng)用程序的包名
*/
public static void showInstalledAppDetails(Context context, String packageName) {
Intent intent = new Intent();
final int apiLevel = Build.VERSION.SDK_INT;
if (apiLevel >= 9) { // 2.3(ApiLevel 9)以上本慕,使用SDK提供的接口
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts(SCHEME, packageName, null);
intent.setData(uri);
} else { // 2.3以下排拷,使用非公開的接口(查看InstalledAppDetails源碼)
// 2.2和2.1中,InstalledAppDetails使用的APP_PKG_NAME不同锅尘。
final String appPkgName = (apiLevel == 8 ? APP_PKG_NAME_22
: APP_PKG_NAME_21);
intent.setAction(Intent.ACTION_VIEW);
intent.setClassName(APP_DETAILS_PACKAGE_NAME,
APP_DETAILS_CLASS_NAME);
intent.putExtra(appPkgName, packageName);
}
context.startActivity(intent);
}
}
當(dāng)app申請權(quán)限時(shí)监氢,如果用戶點(diǎn)擊了“不再提醒”,則會(huì)直接回調(diào)拒絕權(quán)限鉴象,為此谷歌工程師提供了shouldShowRequestPermissionRationale()方法忙菠。
兼容包中方法是ActivityCompat.shouldShowRequestPermissionRationale(),如果是在Fragment中使用請直接用shouldShowRequestPermissionRationale()纺弊。
ActivityCompat.shouldShowRequestPermissionRationale()方法返回值是boolean類型牛欢,當(dāng)?shù)谝淮紊暾垯?quán)限時(shí),此方法會(huì)返回false淆游,如果用戶點(diǎn)擊“不再提醒”傍睹,則此方法會(huì)返回true。詳細(xì)代碼如下:
if (ContextCompat.checkSelfPermission(MySetupActivity.this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(MySetupActivity.this, Manifest.permission.CAMERA)) {
//已經(jīng)禁止提示了
mDialog = new AlertDialog.Builder(MySetupActivity.this)
.setTitle("友好提醒")
.setMessage("您已拒絕相機(jī)權(quán)限,此功能需要開啟,是否開啟?")
.setPositiveButton("是", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
ActivityCompat.requestPermissions(MySetupActivity.this,
new String[]{Manifest.permission.CAMERA},
100);
}
})
.setNegativeButton("否", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
})
.setCancelable(true)
.create();
mDialog.show();
} else {
ActivityCompat.requestPermissions(MySetupActivity.this,
new String[]{Manifest.permission.CAMERA},
100);
}
} else {
takePhoto();
}
如果是在Fragment中犹菱,則代碼如下:
if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
//已經(jīng)禁止提示了
mDialog = new AlertDialog.Builder(getContext())
.setTitle("友好提醒")
.setMessage("您已拒絕相機(jī)權(quán)限,此功能需要開啟,是否開啟?")
.setPositiveButton("是", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
requestPermissions(new String[]{Manifest.permission.CAMERA},
PERMISSIONS_CAMERA);
}
})
.setNegativeButton("否", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
})
.setCancelable(true)
.create();
mDialog.show();
} else {
requestPermissions(new String[]{Manifest.permission.CAMERA},
PERMISSIONS_CAMERA);
}
} else {
selectPicFromCamera();
}
特別要注意的是拾稳,如果在Fragment中請求權(quán)限,若在Activity中也重寫了onRequestPermissionsResult(),則onRequestPermissionsResult()方法中一定要寫 super.onRequestPermissionsResult(requestCode, permissions, grantResults);這句代碼腊脱,若不寫访得,則Activity不會(huì)分發(fā)執(zhí)行Fragment中的權(quán)限回調(diào)方法。
因?yàn)镕ragment中requestPermissions()源碼如下:
public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
if (mHost == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
mHost.onRequestPermissionsFromFragment(this, permissions, requestCode);
}
其實(shí)在Fragment請求權(quán)限也是在它Activity中請求,只是把回調(diào)結(jié)果傳遞給了Fragment悍抑。
一次申請多個(gè)權(quán)限
例如需要申請的權(quán)限如下:
/**
* 需要進(jìn)行檢測的權(quán)限數(shù)組
*/
protected String[] permissionList = {
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
};
我們要先判斷每一個(gè)權(quán)限是否已經(jīng)允許或者拒絕鳄炉,當(dāng)有某一個(gè)權(quán)限未被允許時(shí),則申請未被允許的權(quán)限搜骡。
protected void onStart() {
super.onStart();
if (PermissionUtils.checkSelfPermission(SplashActivity.this, permissionList)) {
PermissionUtils.checkPermissions(this, 0, permissionList);
} else {
//處理業(yè)務(wù)邏輯
}
}
其中PermissionUtils類的代碼如下:
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import java.util.ArrayList;
import java.util.List;
public class PermissionUtils {
/**
* 檢查權(quán)限
*/
public static void checkPermissions(Activity activity, int permissRequestCode, String... permissions) {
List<String> needRequestPermissonList = findDeniedPermissions(activity, permissions);
if (null != needRequestPermissonList
&& needRequestPermissonList.size() > 0) {
ActivityCompat.requestPermissions(activity,
needRequestPermissonList.toArray(
new String[needRequestPermissonList.size()]),
permissRequestCode);
}
}
/**
* 獲取權(quán)限中需要申請權(quán)限的列表
*/
public static List<String> findDeniedPermissions(Activity activity, String[] permissions) {
List<String> needRequestPermissonList = new ArrayList<String>();
for (String perm : permissions) {
if (ContextCompat.checkSelfPermission(activity,
perm) != PackageManager.PERMISSION_GRANTED) {
needRequestPermissonList.add(perm);
} else {
if (ActivityCompat.shouldShowRequestPermissionRationale(
activity, perm)) {
needRequestPermissonList.add(perm);
}
}
}
return needRequestPermissonList;
}
public static boolean checkSelfPermission(Context context, String[] permissions) {
for (String perm : permissions) {
if (ContextCompat.checkSelfPermission(context, perm) != PackageManager.PERMISSION_GRANTED) {
return true;
}
}
return false;
}
public static boolean checkSelfResult(int[] grantResults) {
for (int grantResult : grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
}
onRequestPermissionsResult回調(diào)方法中則這樣處理:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 0) {
if (PermissionUtils.checkSelfResult(grantResults)) {
// Permission Granted
//處理業(yè)務(wù)邏輯
} else {
// Permission Denied
if (null == mDialog)
mDialog = new AlertDialog.Builder(SplashActivity.this)
.setTitle("友好提醒")
.setMessage("沒有權(quán)限將不能更好的使用,請開啟權(quán)限拂盯!")
.setPositiveButton("開啟", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
ShowAppSetDetails.showInstalledAppDetails(SplashActivity.this, "user.zhuku.com");
LogPrint.logILsj(TAG, "開啟權(quán)限設(shè)置");
}
})
.setCancelable(false)
.create();
if (!mDialog.isShowing()) {
mDialog.show();
}
}
}
}
如果項(xiàng)目需要在頁面可見時(shí)進(jìn)行權(quán)限申請,請放在onStart()方法中记靡,不要寫在onResume()中谈竿。可以想象一下摸吠,如果寫在onResume()中空凸,當(dāng)用戶同意了權(quán)限,則無礙蜕便,若是點(diǎn)擊拒絕劫恒,則會(huì)回調(diào)權(quán)限拒絕的方法,這時(shí)系統(tǒng)申請權(quán)限的對話框消失不見轿腺,會(huì)再次調(diào)用onResume()請求權(quán)限顯示對話框两嘴,若是還拒絕,則會(huì)再次調(diào)用onResume()方法族壳,一直處于死循環(huán)憔辫。因?yàn)橄到y(tǒng)請求權(quán)限的對話框其實(shí)一個(gè)開啟了一個(gè)Activity。部分源碼如下:
public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
if (mHasCurrentPermissionsRequest) {
Log.w(TAG, "Can reqeust only one set of permissions at a time");
// Dispatch the callback with empty arrays which means a cancellation.
onRequestPermissionsResult(requestCode, new String[0], new int[0]);
return;
}
Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
mHasCurrentPermissionsRequest = true;
}