Android6.0,及api23以上,Android系统有些权限需要运行时动态申请。
权限类型:
Android权限分为系统权限和特殊权限,而系统权限又分为正常(normal)权限和涉及用户隐私的危险权限(dangerous)。
两者的区别:
android系统认为normal权限不会威胁用户隐私,可以直接在清单文件中注册,系统就会默认授权这些权限,而dangerous权限则是系统认为此类权限访问会威胁到用户隐私,使用时不仅需要在清单文件中注册,还需要在具体调用代码的地方向系统发起请求授权。
dangerours权限(即需要动态申请的权限列表):
READ_PHONE_STATE
CALL_PHONE
READ_CALL_LOG
WRITE_CALL_LOG
ADD_VOICEMAIL
USE_SIP
PROCESS_OUTGOING_CALLS
申请权限的注意点:
1.如果系统版本是api23以下的,则由于不需要动态申请权限,仅仅判断一下需要的权限(=PermissionChecker.checkSelfPermission(this, Manifest.permission....) )。但实际上,只要清单文件里注册了需要判断的权限,这行代码返回一直是true,遇到的坑下面介绍。
2.如果系统版本是api23及以上的,则需要动态申请以上危险权限。
android sdk提供了相关api:
以录音为例:
//判断是否有权限,
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
//如果有权限
Log.e("tag", "有录音权限!");
} else {
//如果没有权限,则需要申请
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.RECORD_AUDIO},
REQUEST_CODE_RECORD);
}
对于反馈结果处理,需要重写下Activity里的 onRequestPermissionsResult方法:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_RECORD:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.e("tag", "有录音权限!");
//todo
} else {
Log.e("tag", "没有录音权限!");
}
break;
}
}
当然,github上也有很多封装好的第三方库,使用起来也非常方便,比如由严振杰写的权限管理库:
https://github.com/yanzhenjie/AndPermission
具体用法可参考上面的地址里的介绍。
3.对于1种的情况,api23以下的机型,就拿实际开发中录音举例来说,可能会导致无法正常录音。
解决方案:
public void start() throws IOException {
if (mIsRecording) {
return;
}
mIsRecording = true; // 提早,防止init或startRecording被多次调用
initAudioRecorder();
mAudioRecord.startRecording();
if (!checkPermission()) {
mListener.noRecordRight();
return;
} else {
mListener.hasRecordRight();
}
new Thread() {
@Override
public void run() {
//设置线程权限
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
while (mIsRecording) {
int readSize = mAudioRecord.read(mPCMBuffer, 0, mBufferSize);
if (readSize > 0) {
mEncodeThread.addTask(mPCMBuffer, readSize);
calculateRealVolume(mPCMBuffer, readSize);
}
}
// release and finalize audioRecord
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
// stop the encoding thread and try to wait
// until the thread finishes its job
mEncodeThread.sendStopMessage();
}
/**
* 此计算方法来自samsung开发范例
*
* @param buffer buffer
* @param readSize readSize
*/
private void calculateRealVolume(short[] buffer, int readSize) {
double sum = 0;
for (int i = 0; i < readSize; i++) {
// 这里没有做运算的优化,为了更加清晰的展示代码
sum += buffer[i] * buffer[i];
}
if (readSize > 0) {
double amplitude = sum / readSize;
mVolume = (int) Math.sqrt(amplitude);
}
}
}.start();
}
public boolean checkPermission() {
if (null != mAudioRecord) {
return mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING;
}
return false;
}
即通过:mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING判断此状态来判断下是否有录音权限。
4.实际开发中还碰到个坑:个别机型,如果手动去系统权限设置页将某个权限设置由其他状态设置成禁止,再次回到当前界面,会发现当前app进程会被kill掉,再此重启并恢复到当前界面(重新走onCreated方法),但是当前的界面之前的数据可能会导致丢失,进而会导致app各种空指针引发的崩溃。
解决方案:我们可以参考微信的设计思路,当权限在设置页被手动禁止掉后可以通过一下代码判断下,让app重启:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!isFullScreen) {
applyKitKatTranslucency();
initStatusBar();
}
//手动关闭权限-部分手机需要重新启动应用
if (null != savedInstanceState) {
Log.e("tag", "savedInstanceState");
Intent intent = new Intent(this, StartActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
}
//todo 正常逻辑...
}
参考地址:
https://github.com/yanzhenjie/AndPermission