Android申请权限
Android 6.0系统加入了危险权限管理,在使用一些涉及到用户隐私方面的操作时,需要获取用户的授权才能使用,如通讯录、打电话、短信、相机、定位、录音、存储等隐私权限。获取用户授权权限,我们提倡动态申请权限,用到的时候才去向用户申请,例如点击扫一扫,这时我们申请相机权限,用户也明白,自然就授权了。即使用户没有授权,也仅仅影响此模块不能使用正常功能,并不影响其他模块。千万不能,在应用打开时的启屏页,一股脑把应用所有地方用到的权限都申请,不给还不能用,简直是流氓行为!
本文大纲:
- Android权限
- 申请权限流程
- 检查是否已被授权危险权限
- 提示或解释接下来的操作需要危险权限
- 申请权限
- 申请权限引起的生命周期变化
- 申请权限封装
- 总结
一、Android权限
Android中的权限分为系统权限和应用自定义权限,系统权限又分为正常权限和危险权限。使用系统权限需要在manifest文件中注册权限,若是危险权限,还需要在使用时动态申请。
terminal中使用这个命令可以列出调试手机的所有权限,包含应用自定义的权限:
adb shell pm list permissions
正常权限(Normal Permissions):
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_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
危险权限(Dangerous Permissions):
group:android.permission-group.CONTACTS
permission:android.permission.WRITE_CONTACTS
permission:android.permission.GET_ACCOUNTS
permission:android.permission.READ_CONTACTS
group:android.permission-group.PHONE
permission:android.permission.READ_CALL_LOG
permission:android.permission.READ_PHONE_STATE
permission:android.permission.CALL_PHONE
permission:android.permission.WRITE_CALL_LOG
permission:android.permission.USE_SIP
permission:android.permission.PROCESS_OUTGOING_CALLS
permission:com.android.voicemail.permission.ADD_VOICEMAIL
group:android.permission-group.CALENDAR
permission:android.permission.READ_CALENDAR
permission:android.permission.WRITE_CALENDAR
group:android.permission-group.CAMERA
permission:android.permission.CAMERA
group:android.permission-group.SENSORS
permission:android.permission.BODY_SENSORS
group:android.permission-group.LOCATION
permission:android.permission.ACCESS_FINE_LOCATION
permission:android.permission.ACCESS_COARSE_LOCATION
group:android.permission-group.STORAGE
permission:android.permission.READ_EXTERNAL_STORAGE
permission:android.permission.WRITE_EXTERNAL_STORAGE
group:android.permission-group.MICROPHONE
permission:android.permission.RECORD_AUDIO
group:android.permission-group.SMS
permission:android.permission.READ_SMS
permission:android.permission.RECEIVE_WAP_PUSH
permission:android.permission.RECEIVE_MMS
permission:android.permission.RECEIVE_SMS
permission:android.permission.SEND_SMS
permission:android.permission.READ_CELL_BROADCASTS
二、申请权限流程
动态申请权限的流程:
1、检查是否已被授权危险权限
/**
* 检查是否已被授权危险权限
* @param permissions
* @return
*/
public boolean checkDangerousPermissions(Activity ac, String[] permissions) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED ||
ActivityCompat.shouldShowRequestPermissionRationale(ac, permission)) {
return false;
}
}
return true;
}
Android 6.0以下,在AndroidManifest.xml文件中注册权限即可。
Android 6.0及其以后,既要在AndroidManifest.xml文件中注册,又要动态申请危险权限。
2、提示或解释接下来的操作需要危险权限
在申请危险权限时,有些用户觉得涉及到了他的隐私内容,可能不会授权。在用户拒绝后,再提示授权危险权限的必要性,就为时已晚,用户需要去设置里打开(拒绝时设置不在提醒)。如若我们在申请权限前,就有一个温馨提示,告诉用户危险权限的必要性,用户理解后更容易授权。
饿了么的权限申请过程:
3、申请权限
在activity中申请权限,权限授权结果会回调在acitvity中:
//acitivty中申请权限
ActivityCompat.requestPermissions(activity, permissions, requestCode);
//activity权限授权结果回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
下面我们看ActivityCompat.requestPermissions
这个方法:
public static void requestPermissions(final @NonNull Activity activity,
final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {
if (Build.VERSION.SDK_INT >= 23) {
if (activity instanceof RequestPermissionsRequestCodeValidator) {
((RequestPermissionsRequestCodeValidator) activity)
.validateRequestPermissionsRequestCode(requestCode);
}
activity.requestPermissions(permissions, requestCode);
} else if (activity instanceof OnRequestPermissionsResultCallback) {
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
final int[] grantResults = new int[permissions.length];
PackageManager packageManager = activity.getPackageManager();
String packageName = activity.getPackageName();
final int permissionCount = permissions.length;
for (int i = 0; i < permissionCount; i++) {
grantResults[i] = packageManager.checkPermission(
permissions[i], packageName);
}
((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult(
requestCode, permissions, grantResults);
}
});
}
}
- 系统版本大于等于Android 6.0,请求权限时,先校验requestCode是否有效,再去调用activity的requestPermissions申请权限。
- 系统小于Android 6.0,请求权限时,在主线程检查AndroidManifest.xml文件中是否注册权限,并将结果回调。
我们把activity.requestPermissions()
放在这,等会再看他。查源码我们知道FragmentActivity
实现了这两个接口:
ActivityCompat.OnRequestPermissionsResultCallback
ActivityCompat.RequestPermissionsRequestCodeValidator
public class FragmentActivity extends BaseFragmentActivityApi16 implements
ActivityCompat.OnRequestPermissionsResultCallback,
ActivityCompat.RequestPermissionsRequestCodeValidator {
.......省略
@Override
public final void validateRequestPermissionsRequestCode(int requestCode) {
// We use 16 bits of the request code to encode the fragment id when
// requesting permissions from a fragment. Hence, requestPermissions()
// should validate the code against that but we cannot override it as
// we can not then call super and also the ActivityCompat would call
// back to this override. To handle this we use dependency inversion
// where we are the validator of request codes when requesting
// permissions in ActivityCompat.
if (!mRequestedPermissionsFromFragment
&& requestCode != -1) {
checkForValidRequestCode(requestCode);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
int index = (requestCode >> 16) & 0xffff;
if (index != 0) {
index--;
String who = mPendingFragmentActivityResults.get(index);
mPendingFragmentActivityResults.remove(index);
if (who == null) {
Log.w(TAG, "Activity result delivered for unknown Fragment.");
return;
}
Fragment frag = mFragments.findFragmentByWho(who);
if (frag == null) {
Log.w(TAG, "Activity result no fragment exists for who: " + who);
} else {
frag.onRequestPermissionsResult(requestCode & 0xffff, permissions, grantResults);
}
}
}
.......省略
}
在BaseFragmentActivityApi14.class
中:
/**
* Checks whether the given request code is a valid code by masking it with 0xffff0000. Throws
* an {@link IllegalArgumentException} if the code is not valid.
*/
static void checkForValidRequestCode(int requestCode) {
if ((requestCode & 0xffff0000) != 0) {
throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
}
}
requestCode
是int数据类型,4个字节,按2进制是32位数据,从上面可以知道它的取值是有范围的,只用到了低16位,0x00000000 ~ 0x0000ffff
即 0 ~ 65535
,那系统保留高16位有什么用呢?我们看到上面FragmentActivity
的onRequestPermissionsResult
中,先查找是否是fragment
请求的权限,然后直接&0xffff
,也就是取requestCode
得低16位,并回调权限结果在fragment
中。
接下来我们看在fragment中请求权限:
//fragment中申请权限
framgent.requestPermissions(permissions, requestCode);
//fragment权限授权结果回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
framgent
的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);
}
我们mHost即是fragment的宿主activity,在 FragmentActivity
中onRequestPermissionsFromFragment
:
@Override
public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
@NonNull String[] permissions, int requestCode) {
FragmentActivity.this.requestPermissionsFromFragment(fragment, permissions,
requestCode);
}
/**
* Called by Fragment.requestPermissions() to implement its behavior.
*/
void requestPermissionsFromFragment(Fragment fragment, String[] permissions,
int requestCode) {
if (requestCode == -1) {
ActivityCompat.requestPermissions(this, permissions, requestCode);
return;
}
checkForValidRequestCode(requestCode);
try {
mRequestedPermissionsFromFragment = true;
int requestIndex = allocateRequestIndex(fragment);
ActivityCompat.requestPermissions(this, permissions,
((requestIndex + 1) << 16) + (requestCode & 0xffff));
} finally {
mRequestedPermissionsFromFragment = false;
}
}
-
requestCode = -1
时,直接调用ActivityCompat.requestPermissions
-
requestCode != -1
时,先校验requestCode
是否有效,即0 ~ 65535
。然后获取一个分配请求索引(范围0 ~ 0xffff - 1
即0 ~ 65534
),索引加1之后左移16位,在和requestCode
的低16组成新的requestCode
。
如0xaaaabbbb
:高16位aaaa
为索引加1之后左移16位,低16位bbbb
为fragment
的requestCode
, 这就和之前在fragment中回调权限结果时requestCode
一致了。
最后都会调用ActivityCompat.requestPermissions
,就回到了前面,既而activity.requestPermissions()
,再来看Acitvity
的requestPermissions
:
public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
if (requestCode < 0) {
throw new IllegalArgumentException("requestCode should be >= 0");
}
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;
}
- 通过
startActivityForResult
打开系统授权权限界面,注意字符串常量REQUEST_PERMISSIONS_WHO_PREFIX
- 不可短时间内重复执行同一请求权限代码,否则第一次之后的直接给定默认值结果,若结果
grantResults
会错误影响授权。
然而在 Activity
中onRequestPermissionsFromFragment
中,这与FragmentActivity
中的不一样:
@Override
public void onRequestPermissionsFromFragment(Fragment fragment, String[] permissions,
int requestCode) {
String who = REQUEST_PERMISSIONS_WHO_PREFIX + fragment.mWho;
Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
startActivityForResult(who, intent, requestCode, null);
}
最终会通过startActivityForResult
打开系统权限授权界面。注意这个who,系统权限授权界面回调结果时,就是根据这个who把结果分配给指定的fragment,即结果回调到fragment的onRequestPermissionsResult
。 fragment请求权限时,requestCode取值范围没有限制.
既然是通过startActivityForResult
打开系统授权权限界面,那我们能在activity的onActivityResult(int requestCode, int resultCode, Intent data)
中拿到授权结果吗?答案是不能,让我们一起来看为什么。当结果回传来时,都会调用Activity
的dispatchActivityResult
分配结果。
void dispatchActivityResult(String who, int requestCode,
int resultCode, Intent data) {
if (false) Log.v(
TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode
+ ", resCode=" + resultCode + ", data=" + data);
mFragments.noteStateNotSaved();
if (who == null) {
onActivityResult(requestCode, resultCode, data);
} else if (who.startsWith(REQUEST_PERMISSIONS_WHO_PREFIX)) {
who = who.substring(REQUEST_PERMISSIONS_WHO_PREFIX.length());
if (TextUtils.isEmpty(who)) {
dispatchRequestPermissionsResult(requestCode, data);
} else {
Fragment frag = mFragments.findFragmentByWho(who);
if (frag != null) {
dispatchRequestPermissionsResultToFragment(requestCode, data, frag);
}
}
} else if (who.startsWith("@android:view:")) {
ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews(
getActivityToken());
for (ViewRootImpl viewRoot : views) {
if (viewRoot.getView() != null
&& viewRoot.getView().dispatchActivityResult(
who, requestCode, resultCode, data)) {
return;
}
}
} else if (who.startsWith(AUTO_FILL_AUTH_WHO_PREFIX)) {
Intent resultData = (resultCode == Activity.RESULT_OK) ? data : null;
getAutofillManager().onAuthenticationResult(requestCode, resultData);
} else {
Fragment frag = mFragments.findFragmentByWho(who);
if (frag != null) {
frag.onActivityResult(requestCode, resultCode, data);
}
}
}
这时字符串常量REQUEST_PERMISSIONS_WHO_PREFIX
就派上用场了。如果who不为空,说明activity是Activity,不是FragmentActivity,直接结果回调在fragment中。如果为空,actvity不一定为FragmentActivity,结果直接进入Activity的 dispatchRequestPermissionsResult(requestCode, data);
private void dispatchRequestPermissionsResult(int requestCode, Intent data) {
mHasCurrentPermissionsRequest = false;
// If the package installer crashed we may have not data - best effort.
String[] permissions = (data != null) ? data.getStringArrayExtra(
PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES) : new String[0];
final int[] grantResults = (data != null) ? data.getIntArrayExtra(
PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS) : new int[0];
onRequestPermissionsResult(requestCode, permissions, grantResults);
}
结果进而进入回调activity的onRequestPermissionsResult
,若activity未处理,且activit是FragmentActivity,则通过requestCode右移16位,进而传递结果到fragment的onRequestPermissionsResult()
,完成整个权限请求流程。
三、申请权限引起的生命周期变化
由上面知道最终通过startActivityForResult
来打开系统权限授权界面,我们在BaseActivity中相关方法中打印日志:
@Override
public void onResume() {
super.onResume();
Log.i(TAG, "onResume: ");
}
@Override
public void onPause() {
super.onPause();
Log.i(TAG, "onPause: ");
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.i(TAG, "onSaveInstanceState: ");
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Log.i(TAG, "onRequestPermissionsResult: ");
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
然后请求权限,我们观察到:
权限结果回调,发生在onPause、onSaveInstanceState之后,onResume之前,即页面未处于前台显示。
注意此时fragment事务提交时会状态丢失,如果执行ft.commit(),会报错。
如此时不能DialogFragment.show(),因为DialogFragment.show()是ft.commit(),这个不允许状态丢失。
public void show(FragmentManager manager, String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commit();
}
四、申请权限封装
a、BaseActivity:
public class BaseActivity extends AppCompatActivity {
/**
* 请求权限
*/
public void requestDangerousPermissions(String[] permissions, int requestCode) {
if (checkDangerousPermissions(permissions)){
handlePermissionResult(requestCode, true);
return;
}
ActivityCompat.requestPermissions(this, permissions, requestCode);
}
/**
* 检查是否已被授权危险权限
* @param permissions
* @return
*/
public boolean checkDangerousPermissions(String[] permissions) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED ||
ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
return false;
}
}
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
boolean granted = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
granted = false;
}
}
boolean finish = handlePermissionResult(requestCode, granted);
if (!finish){
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
/**
* 处理请求危险权限的结果
* @return
*/
public boolean handlePermissionResult(int requestCode, boolean granted) {
return false;
}
}
b、BaseFragment:
public class BaseFragment extends Fragment {
/**
* 请求权限
*
* @param permissions
* @param requestCode
*/
public void requestDangerousPermissions(String[] permissions, int requestCode) {
if (checkDangerousPermissions(permissions)){
handlePermissionResult(requestCode, true);
return;
}
requestPermissions(permissions, requestCode);
}
/**
* 检查是否已被授权危险权限
* @param permissions
* @return
*/
public boolean checkDangerousPermissions(String[] permissions) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
if (getActivity() == null){
return false;
}
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(getActivity(), permission) != PackageManager.PERMISSION_GRANTED ||
ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), permission)) {
return false;
}
}
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
boolean isGranted = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
isGranted = false;
}
}
if (!handlePermissionResult(requestCode, isGranted)){
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
/**
* 处理请求危险权限的结果
*
* @param requestCode
* @param isGranted 是否允许
* @return
*/
public boolean handlePermissionResult(int requestCode, boolean isGranted) {
return false;
}
}
c、使用,如打电话:
private void attemptToCall(String phone) {
if (!isIntentExisting(getContext(), Intent.ACTION_DIAL)) {
Toast.makeText(getContext(), "该设备不能打电话", Toast.LENGTH_SHORT).show();
return;
}
mPhone = phone;
requestDangerousPermissions(callPermission, 88);
}
@Override
public boolean handlePermissionResult(int requestCode, boolean isGranted) {
if (requestCode == 88){
final String phone = mPhone;
getView().post(new Runnable() {
@Override
public void run() {
call(phone);
}
});
mPhone = null;
return true;
}
return super.handlePermissionResult(requestCode, isGranted);
}
private void call(String phone) {
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phone));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
//判断电话应用是否存在
public boolean isIntentExisting(Context context, String action) {
final PackageManager packageManager = context.getPackageManager();
final Intent intent = new Intent(action);
List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (resolveInfo.size() > 0) {
return true;
}
return false;
}
五、总结:
a、当系统小于Android 6.0,只需要在AndroidManifest.xml注册权限即可使用(不包括某些手机厂商自定义的权限管理)。当系统大于等于Android 6.0时,既要在AndroidManifest.xml注册权限,又要去动态申请权限。
b、申请权限时,最终通过startActivityForResult
来打开系统权限授权界面,权限结果回调在onRequestPermissionsResult
中,不会在onActivityResult
中出现。
c、requestCode取值范围:
当activity为FragmentActivity时:
- 在activity中请求权限时,requestCode有取值范围,为
0 ~ 65535
,权限回调结果在activity中。 - 在fragment中请求权限时,requestCode有取值范围,为
0 ~ 65535
,权限回调结果先传递到activity中,若activity未拦截,再传递到fragment中。在activity中拦截时,可requestCode & 0xffff
得到正确的requestCode。
当activity为Activity时:
- 在activity中请求权限时,requestCode取值范围无要求,权限结果回调在activity中。
- 在fragment中请求权限时,requestCode取值范围无要求,权限结果直接回调在fragment中。在activity中不可拦截。
d、不可短时间内重复执行同一请求权限代码,否则第一次之后的直接给定默认值结果,若结果grantResults会错误影响授权
e、权限结果回调,发生在onPause、onSaveInstanceState之后,onResume之前,即页面未处于前台显示。
备注:
本文基于Android API 26
本文测试手机华为P20 Pro
Git示例代码:https://github.com/jinxiyang/RequestPermissions.git