盒子
盒子
Posts List
  1. 一.android 6.0 为什么需要适配
  2. 二. 权限适配
    1. 1. 权限分类
    2. 2.运行期权限申请
    3. 3.权限适配过程
      1. 整理APP中用到的敏感权限
      2. 第三方库和依赖权限列表
  3. 三. 关于 apache http jar包的适配处理
    1. eclipse
    2. android studio
  4. 四. 关于获取mac地址的处理

app适配android 6.0

一.android 6.0 为什么需要适配

andorid 6.0提出了与之前版本完全不同的权限管理思路,除了需要在andrioiManifest.xml中申明需要用的权限外(这个在安装的时候会提示,用户可以选择同意授权安装或者不同意授权取消安装),在运行期也需要对敏感权限对用户明确申请授权,在申请的过程中,系统会提示每种权限对用户隐私的影响。

对于APP开发者来说,需要触发隐私申请的逻辑,否则应用将获取不到权限而无法正常工作。

除了全新的权限管理系统外,android 6.0还有一些新的特性,如

  • 去除了对apache http的支持,
  • 提供新的休眠省电策略,影响定时任务
  • Mac地址获取限制
  • 声音管理器修改

更多详细修改,参考
链接

二. 权限适配

要适配新的权限管理系统,首先要区分敏感权限和普通权限,从字面上理解,敏感权限基本都涉及到用户的隐私,如读写log,读短信,读联系人,获取地理位置等等。

这里有个已经被发掘的窍门,如果编译时候选择的targetSDK版本号低于23,则当app运行在23或者高于23号版本的系统上时,仍旧使用旧的权限管理机制,也就是在仅在安装期间提示用户授权。

所以设置targetSDK=22(低于22也适用)的APP不需要进行权限适配。

1. 权限分类

普通权限列表如下:
普通权限

敏感权限需要在运行时向用户显示申请,敏感权限按照分组的形式管理,比如

读取联系人(READ_CONTACTS )和修改写入联系人 (WRITE_CONTACTS)都属于联系人管理组(CONTACTS)

在运行期申请敏感权限时,系统提示的信息是按照权限组的形式提示的,如果用户已经授权了权限组中某个权限给APP,则APP在申请该权限组中其他权限时,系统会不通过用户直接授予权限。

敏感权限组列表和说明总结如下:

image

注意: 实际上还有一类权限,它不属于普通和敏感权限,google给它分类为signature ,这类权限仅仅是普通的申明并不一定能正常使用,最常见的如 android.permission.WRITE_SETTINGS
详细说明参考
android6.0适配二

2.运行期权限申请

运行期是指app已经在手机上跑起来了,这时候我们需要显示向用户申请敏感权限。

在申请权限之前,我们需要检测该权限是否已经被授予了,检测代码如下,如果返回true,表示该权限已经被授权,否则需要申请:

int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CALENDAR);

如果某个权限之前申请过授权并且已经被用户禁止了,但是APP需要此权限,我们需要对这个权限的重要性进行说明,以便是的用户再次授权,那我们可以使用方法去检测是否出现过请求授权权限被禁止了:

shouldShowRequestPermissionRationale()

返回true表示之前APP申请过该权限并且用户否定了返回flase有两种情况 1.用户没有请求过授权,那么我们可以发起授权 2.请求授权时,用户勾选了不再提醒,则这个方法一直返回false,这时候需要对特定的情况进行处理,比如是继续请求授权,还是退出等等。

终上所述,一段典型的申请权限的代码逻辑如下:

//是否已经授权该权限
if (ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// 没有授权,之前是否已经否定过
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// 之前否定过,我们需要给用户解释为什么需要这个权限
} else {
// 没有否定过,直接申请权限
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS 是一个标志key,如果申请成功会在回调里面返回
}
}

这段代码会使得系统触发一个对话框显示给用户,这个对话框不能被程序修改和定制,我们可以在收到请求结果,比如请求授权失败的时候弹出自定义的对话框。

对于请求授权的结果,我们可以监听用户授权的情况来获得,监听代码如下:

@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
}
}

以上方法均需要将support-v4.jar升级到23以上的版本才能生效,升级方法参考:
support-v4.jar升级方法

support V4包介绍地址

3.权限适配过程

整理APP中用到的敏感权限

app中用到的所有权限如下列表

<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!-- push sdk start -->
<!-- 富媒体需要声明的权限 -->
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
<!-- 适配Android N系统必需的ContentProvider写权限声明,写权限包含应用包名-->
<uses-permission android:name="baidu.push.permission.WRITE_PUSHINFOPROVIDER.com.duoku.platform.demo.single" />
<permission
android:name="baidu.push.permission.WRITE_PUSHINFOPROVIDER.com.duoku.platform.demo.single"
android:protectionLevel="normal">
</permission>
<!-- Push service 运行需要的权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER"/>
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.GET_TASKS"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!--############################ push sdk end ############################-->
<!-- for gameplus start -->
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
<!-- for gameplus end -->
<uses-permission android:name="android.webkit.permission.PLUGIN" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<!--咪咕基地-->
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
<uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"/>
<uses-permission android:name="com.android.launcher.permission.READ_SETTINGS"/>
<uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS"/>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

其中属于敏感权限的是:

<!--## phone 权限组 #####-->
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!--###### location 权限组 #########-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!--##### storage权限组 #####-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!--##### contact 权限组 ####-->
<uses-permission android:name="android.permission.READ_CONTACTS" />

新建一个util来做权限检测类的工作,名字叫做PermissionUtil

它的工作逻辑是这样的:
检测所有敏感权限是否都被授权了,如果没有启动授权逻辑(授权又会根据是否已经被用户否定而决定是直接申请,还是弹出dialog来说明申请原因),否则告知启动授权的activity授权完成,具体代码如下,比较简单,可以直接拿来用:

package XXX;
import java.util.ArrayList;
import java.util.List;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
/**
* @usage: 将需要检测的权限写入PermissionBeans,可以是单个或者多个
* @author hch
*/
public class PermissionUtil{
private static final String TAG = "PermissionUtil";
private Activity mActivity;
private OnApplyPermissionListener mOnApplyPermissionListener;
public static final int PERMISSION_PHONE_GROUP_TAG = 10001;
public static final int PERMISSION_LOCATION_GROUP_TAG = 20001;
public static final int PERMISSION_STORAGE_GROUP_TAG = 30001;
public static final int PERMISSION_CONTACT_GROUP_TAG = 40001;
public static final String EXPLAIN_PHONE = "我们需要读取手机信息的权限来标识您的身份";
public static final String EXPLAIN_LOCATION = "我们需要读取地理位置信息来定位应用使用情况";
public static final String EXPLAIN_STORAGE = "我们需要您允许我们读写你的存储卡,以方便我们临时保存一些数据";
public static final String EXPLAIN_CONTACTS = "我们需要读取您的联系人信息";
private final static int REQUEST_OPEN_APPLICATION_SETTINGS_CODE = 12345;
/**
* 有米 Android SDK 所需要向用户申请的权限列表
*/
private List<PermissionBean> mPermissionBeans = new ArrayList<PermissionBean>();
private PermissionBean createBean(String permission , String explain , int tag){
if(permission.equals(Manifest.permission.READ_SMS) || permission.equals(Manifest.permission.SEND_SMS) ||permission.equals(Manifest.permission.CALL_PHONE) ||permission.equals(Manifest.permission.READ_PHONE_STATE)){
return new PermissionBean("电话" , permission , explain , tag);
}else if(permission.equals(Manifest.permission.ACCESS_FINE_LOCATION) || permission.equals(Manifest.permission.ACCESS_COARSE_LOCATION) ){
return new PermissionBean("地理位置" , permission , explain , tag);
}else if(permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE) || permission.equals(Manifest.permission.READ_EXTERNAL_STORAGE)){
return new PermissionBean("存储空间" , permission , explain , tag);
}else{
return new PermissionBean("联系人" , permission , explain , tag);
}
}
private PermissionBean createBean(String permission){
if(permission.equals(Manifest.permission.READ_SMS) || permission.equals(Manifest.permission.SEND_SMS) ||permission.equals(Manifest.permission.CALL_PHONE) ||permission.equals(Manifest.permission.READ_PHONE_STATE)){
return new PermissionBean("电话" , permission , EXPLAIN_PHONE , PERMISSION_PHONE_GROUP_TAG);
}else if(permission.equals(Manifest.permission.ACCESS_FINE_LOCATION) || permission.equals(Manifest.permission.ACCESS_COARSE_LOCATION) ){
return new PermissionBean("地理位置" , permission , EXPLAIN_LOCATION , PERMISSION_LOCATION_GROUP_TAG);
}else if(permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE) || permission.equals(Manifest.permission.READ_EXTERNAL_STORAGE)){
return new PermissionBean("存储空间" , permission , EXPLAIN_STORAGE , PERMISSION_STORAGE_GROUP_TAG);
}else{
return new PermissionBean("联系人" , permission , EXPLAIN_CONTACTS , PERMISSION_CONTACT_GROUP_TAG);
}
}
public PermissionUtil(Activity activity) {
mActivity = activity;
}
public void setOnApplyPermissionListener(OnApplyPermissionListener onApplyPermissionListener) {
mOnApplyPermissionListener = onApplyPermissionListener;
}
private void setRequestPermissions(String[] permissions){
if(permissions == null || permissions.length == 0){
return;
}
for(String permission : permissions){
mPermissionBeans.add(createBean(permission));
}
}
private void setRequestPermissionsSimple(){
mPermissionBeans.add(new PermissionBean("电话", Manifest.permission.READ_SMS, EXPLAIN_PHONE, PERMISSION_PHONE_GROUP_TAG));
mPermissionBeans.add(new PermissionBean("电话", Manifest.permission.SEND_SMS, EXPLAIN_PHONE, PERMISSION_PHONE_GROUP_TAG));
mPermissionBeans.add(new PermissionBean("电话", Manifest.permission.CALL_PHONE, EXPLAIN_PHONE, PERMISSION_PHONE_GROUP_TAG));
mPermissionBeans.add(new PermissionBean("电话", Manifest.permission.READ_PHONE_STATE, EXPLAIN_PHONE, PERMISSION_PHONE_GROUP_TAG));
mPermissionBeans.add(new PermissionBean("地理位置", Manifest.permission.ACCESS_FINE_LOCATION, EXPLAIN_LOCATION, PERMISSION_LOCATION_GROUP_TAG));
mPermissionBeans.add(new PermissionBean("地理位置", Manifest.permission.ACCESS_COARSE_LOCATION, EXPLAIN_LOCATION, PERMISSION_LOCATION_GROUP_TAG));
mPermissionBeans.add(new PermissionBean("存储空间", Manifest.permission.WRITE_EXTERNAL_STORAGE, EXPLAIN_STORAGE, PERMISSION_STORAGE_GROUP_TAG));
mPermissionBeans.add(new PermissionBean("存储空间", Manifest.permission.READ_EXTERNAL_STORAGE, EXPLAIN_STORAGE, PERMISSION_STORAGE_GROUP_TAG));
mPermissionBeans.add(new PermissionBean("联系人", Manifest.permission.READ_CONTACTS, EXPLAIN_CONTACTS, PERMISSION_CONTACT_GROUP_TAG));
}
/**
* 一次申请所有权限
*/
private void requestPermissions() {
try {
for (final PermissionBean model : mPermissionBeans) {
if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(mActivity, model.permission)) {
ActivityCompat.requestPermissions(mActivity, new String[] { model.permission }, model.requestCode);
return;
}
}
if (mOnApplyPermissionListener != null) {
mOnApplyPermissionListener.onAfterApplyAllPermission();
}
} catch (Throwable e) {
}
}
/**
* 检测某些权限
* @param permissions
*/
public void requestPermissions(String ...permissions){
setRequestPermissions(permissions);
requestPermissions();
}
/**
* 检测某些权限
* @param permissions
*/
public void requestPermissionsSimple(){
setRequestPermissionsSimple();
requestPermissions();
}
/**
* 对应Activity的 {@code onRequestPermissionsResult(...)} 方法
*
* @param requestCode
* @param permissions
* @param grantResults
*/
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case PERMISSION_PHONE_GROUP_TAG:
case PERMISSION_LOCATION_GROUP_TAG:
case PERMISSION_STORAGE_GROUP_TAG:
case PERMISSION_CONTACT_GROUP_TAG:
// 未授权
if (PackageManager.PERMISSION_GRANTED != grantResults[0]) {
// 首次请求权限被拒绝,且未点击不再提醒,进入解释权限逻辑
if (ActivityCompat.shouldShowRequestPermissionRationale(mActivity, permissions[0])) {
AlertDialog.Builder builder =
new AlertDialog.Builder(mActivity).setTitle("权限申请").setMessage(findPermissionExplain(permissions[0]))
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
requestPermissions();
}
});
builder.setCancelable(false);
builder.show();
}
// 到这里就表示已经是第3+次请求,而且此时用户已经永久拒绝了,这个时候,我们引导用户到应用权限页面,让用户自己手动打开
else {
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity).setTitle("权限申请")
.setMessage("请在打开的窗口的权限中开启" + findPermissionName(permissions[0]) + "权限,以正常使用本应用")
.setPositiveButton("去设置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
openApplicationSettings(REQUEST_OPEN_APPLICATION_SETTINGS_CODE);
}
}).setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mActivity.finish();
}
});
builder.setCancelable(false);
builder.show();
}
return;
}
// 到这里就表示用户允许了本次请求,我们继续检查是否还有待申请的权限没有申请
if (isAllRequestedPermissionGranted()) {
if (mOnApplyPermissionListener != null) {
mOnApplyPermissionListener.onAfterApplyAllPermission();
}
} else {
requestPermissions();
}
break;
}
}
/**
* 对应Activity的 {@code onActivityResult(...)} 方法
*
* @param requestCode
* @param resultCode
* @param data
*/
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_OPEN_APPLICATION_SETTINGS_CODE:
if (isAllRequestedPermissionGranted()) {
if (mOnApplyPermissionListener != null) {
mOnApplyPermissionListener.onAfterApplyAllPermission();
}
} else {
mActivity.finish();
}
break;
}
}
/**
* 判断是否所有的权限都被授权了
* @return
*/
public boolean isAllRequestedPermissionGranted() {
if (Build.VERSION.SDK_INT < 23) {
// 如果系统版本低于23,直接跑应用的逻辑
return true;
} else {
for (PermissionBean model : mPermissionBeans) {
if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(mActivity, model.permission)) {
return false;
}
}
}
return true;
}
/**
* 打开应用设置界面
*
* @param requestCode 请求码
*
* @return
*/
private boolean openApplicationSettings(int requestCode) {
try {
Intent intent =
new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + mActivity.getPackageName()));
intent.addCategory(Intent.CATEGORY_DEFAULT);
// Android L 之后Activity的启动模式发生了一些变化
// 如果用了下面的 Intent.FLAG_ACTIVITY_NEW_TASK ,并且是 startActivityForResult
// 那么会在打开新的activity的时候就会立即回调 onActivityResult
// intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mActivity.startActivityForResult(intent, requestCode);
return true;
} catch (Throwable e) {
}
return false;
}
/**
* 查找申请权限的解释短语
*
* @param permission 权限
*
* @return
*/
private String findPermissionExplain(String permission) {
if (mPermissionBeans != null) {
for (PermissionBean model : mPermissionBeans) {
if (model != null && model.permission != null && model.permission.equals(permission)) {
return model.explain;
}
}
}
return null;
}
/**
* 查找申请权限的名称
*
* @param permission 权限
*
* @return
*/
private String findPermissionName(String permission) {
if (mPermissionBeans != null) {
for (PermissionBean model : mPermissionBeans) {
if (model != null && model.permission != null && model.permission.equals(permission)) {
return model.name;
}
}
}
return null;
}
public static class PermissionBean {
/**
* 权限名称
*/
public String name;
/**
* 请求的权限
*/
public String permission;
/**
* 解析为什么请求这个权限
*/
public String explain;
/**
* 请求代码
*/
public int requestCode;
public PermissionBean(String name, String permission, String explain, int requestCode) {
this.name = name;
this.permission = permission;
this.explain = explain;
this.requestCode = requestCode;
}
}
/**
* 权限申请事件监听
*/
public interface OnApplyPermissionListener {
/**
* 申请所有权限之后的逻辑
*/
void onAfterApplyAllPermission();
}
}

使用方法:

private void requestPermission(){
mPermissionUtil = new PermissionUtil(this);
mPermissionUtil.setOnApplyPermissionListener(new PermissionUtil.OnApplyPermissionListener() {
@Override
public void onAfterApplyAllPermission() {
startApp();
}
});
mPermissionUtil.requestPermissionsSimple();
}
private void startApp(){
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
mPermissionUtil.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mPermissionUtil.onActivityResult(requestCode, resultCode, data);
}

第三方库和依赖权限列表

由于项目中依赖了第三方库,因此需要屡清楚第三方依赖了哪些功能,是否对android6.0做了适配。

如果第三方依赖的权限未获取到,那么在使用特定功能的时候,该如何处理,这里就需要对具体项目的功能进行分析评估了。

image

三. 关于 apache http jar包的适配处理

如果是新项目,没开展多久,建议使用久经考验的okhttp , Retrofit ,volley等

如果重构较为复杂,必须继续使用apache http的jar包,可以使用以下方法:

eclipse

从apache下载最新版本的jar包,放入工程Libs库中,重新构建

下载地址

android studio

在build.gradle android 语句块中加入

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

四. 关于获取mac地址的处理

参考 android 6.0获取mac地址

支持一下
扫一扫,支持牛头码农