Android6.0版本适配

运行时权限

android6.0以前,我们把app需要用到的权限全部罗列在Manifest清单文件中。安装app时android系统会询问用户是否授予这些权限,拒绝后则无法安装app。如果授予,则安装app,之后无法修改授予状态。

android6.0将权限分为普通权限(不涉及用户隐私和安全)和危险权限(设计用户隐私和安全)。普通权限和andorid6.0之前一样,在Manifest清单文件中申请即可。危险权限需要在使用时动态申请,由用户决定是否授予。并且危险权限被分成9组,如果应用在运行时请求权限并且被授予该权限,将属于同一权限组并且在清单中注册的其他权限也会自动授予应用。

权限组名 权限名称
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
ERAD_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
STORAGE(存储卡) READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE

以申请CALL_PHONE权限为例,动态申请权限的代码如下:

//检测是否具备该权限
boolean haveCallPhonePermission = checkCallPhonePermission();
if (!haveCallPhonePermission) { //不具备该权限
    if (shouldShowUIToExplain()) { //需要弹出UI解释授予该权限的必要性
        showExplainAndRequestCallPhonePermission(); //弹出UI解释权限用途并再次申请
    } else {
        requestPhonePermission(); //直接申请权限
    }
}

为了直观,我把关键部分的代码都抽离成方法。

先看第一部分,检测是否具备某个权限,

public boolean checkCallPhonePermission() {
    return ContextCompat.checkSelfPermission(MainActivity.this, android.Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED;
}

ContextCompat.checkSelfPermission()用于检测是否具备某个权限,返回值为PackageManager.PERMISSION_GRANTED时表示具备该权限,为PackageManager.PERMISSION_DENIED时表示不具备该权限。

再看第二部分,检测是否需要展示UI向用户解释权限用途以说服用户授予该权限,

public boolean shouldShowUIToExplain() {
    return ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, android.Manifest.permission.CALL_PHONE);
}

ActivityCompat.shouldShowRequestPermissionRationale方法检测是否需要就该权限向用户说明授予权限的必要性。只有用户拒绝过该权限并且没有选择“拒绝后不再提示”时,该方法返回true,我们弹出UI解释授予权限的必要性,用户确认后再次申请该权限,

private void showExplainAndRequestCallPhonePermission() {
    new AlertDialog.Builder(MainActivity.this)
            .setTitle("提示")
            .setMessage("不授予打电话权限将无法通过本app直接拨打电话,降低您的体验!")
            .setPositiveButton("授予", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    requestPhonePermission();
                }
            }).create().show();
}

如果app没有申请过该权限(第一次申请前),用户不一定拒绝该权限,所以没有必要进行解释,ActivityCompat.shouldShowRequestPermissionRationale返回false;如果用户拒绝了该权限且选择不再提示,那么当我们申请该权限时,系统会直接拒绝,不会询问用户,这种情况下也返回false。

在ActivityCompat.shouldShowRequestPermissionRationale返回false的情况下,我们直接申请该权限,

private void requestPhonePermission() {
    ActivityCompat.requestPermissions(MainActivity.this, new String[]{android.Manifest.permission.CALL_PHONE}, REQUEST_CALL_PHONE_CODE);
}

ActivityCompat.requestPermissions方法用于申请权限,第二个参数为数组,表示可一次申请多个权限,第三个参数为请求码,标志该次请求(int值)。

你可能会疑惑,如果用户拒绝了该权限且选择不再提示时,我们直接申请权限有什么用呢?

这是因为app没有申请过该权限(第一次申请前)时,ActivityCompat.shouldShowRequestPermissionRationale也返回false,所以无法判断是否从未申请过,还是要申请权限。那如何获知用户拒绝了权限且选择了不再提示呢?我们可以在权限申请结果的回调中检测。

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (grantedCallPhonePermission(requestCode, permissions, grantResults)) { //用户授予了该权限

    } else { //用户拒绝了该权限
        if (!shouldShowUIToExplain()) { //用户拒绝了该权限并且选择了不再提示
            showExplainAndToSettingPage();//向用户解释权限的必要性,并引导其到设置界面开启权限
        }
    }
}

在activity的onRequestPermissionsResult方法中会得到权限请求的回调。

先看如何判断权限是否被授予,

private boolean grantedCallPhonePermission(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    return requestCode == REQUEST_CALL_PHONE_CODE && permissions[0] == Manifest.permission.CALL_PHONE && grantResults[0] == PackageManager.PERMISSION_GRANTED;
}

判断请求码以确定该次回调是否为请求Call phone权限的结果回调。grantResults数组为结果列表,对应permissions数组的申请结果。

如果权限被拒绝,那么此时调用ActivityCompat.shouldShowRequestPermissionRationale()方法返回false的话,一定是用户选择了“不再提示”。那么我们可以弹出UI提示该权限的必要性并引导他去设置界面开启该权限。

private void showExplainAndToSettingPage() {
    new AlertDialog.Builder(MainActivity.this)
            .setTitle("提示")
            .setMessage("不授予打电话权限将无法通过本app直接拨打电话,降低您的体验!请到设置页面开启该权限。")
            .setPositiveButton("去设置", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    toSettingPage(); //挑传到设置界面
                }
            }).create().show();

}
private void toSettingPage(){
    Intent intent = new Intent();
    intent.setAction(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
    Uri uri = Uri.fromParts("package", getPackageName(), null);
    intent.setData(uri);
    startActivityForResult(intent, REQUEST_TO_SETTING_CODE);
}

在acitivity的onActivityResult()方法里更新权限授予状态。

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_TO_SETTING_CODE && resultCode == RESULT_OK){
        //这里可以重新检测是否授予了该权限
        boolean haveGrantThePermission = checkCallPhonePermission();
    }
}

至此,如何动态申请危险权限的代码就讲述完了。这里有个小问题要注意,上述申请权限的相关代码中我们并没有区分sdk版本是否6.0以上,因为我们使用的都是支持库中的代码,支持库内部会自动适配。

另外,由于国内厂商对android系统五花八门的定制,很多手机设备上,ActivityCompat.shouldShowRequestPermissionRationale()方法总是返回false,我们无法再通过该方法判断展示ui以解释权限及跳转到设置页面的时机。那么我们本地记录权限的申请次数,每次申请前先展示UI解释权限的作用,在用户确认我们的提示信息后,如果是第一次申请则直接申请该权限,否则引导该用户至设置界面开启权限。

HttpClient的移除

自android6.0起,HttpClient系列代码从sdk中剔除,推荐我们使用HttpURLConnection。现在主流的网路请求框架是square公司出的okhttp/retrofit框架。

对于老项目,将编译版本升级至6.0以上时编译报错,找不到HttpClient相关代码。可是网络请求代码遍布整个项目,不太可能把HttpClient相关代码一下子全部拿掉,因为项目还要继续开发新需求并发布新版本,需要HttpClient和新引入的网络框架共存一段时间,直至将HttpClient相关代码全部重构掉。我们可以在module的gradle文件里如下配置,

android {
    useLibrary 'org.apache.http.legacy' 
    ...
}

如此,就可以继续使用HttpClient了,待HttpClient相关代码全部重构完毕,再去掉该配置。

你可能感兴趣的:(系统版本适配)