Android API23以上的时候,出现了动态请求权限的APP行为,敏感权限要求用户自己选择允许或拒绝,以及如何提示用户开启相关服务。此文章以定位权限和服务为例。

在AS开发中,可能会遇到一个坑,就是即使写明了动态请求逻辑,还是唤不起系统的权限请求弹窗。在经过漫长的查找过程后,终于发现了原因:必须在build-config.gradle文件中将targetSDKVersion声明为23或者以上:

ext {
    config = [
            //app、sdk版本信息
            compileSdkVersion          : 25,
            minSdkVersion              : 14,
            targetSdkVersion           : 23,//22
            multidex                   : "1.0.3",
            gsonVersion                : "2.8.4",
            supportLibVersion          : "25.3.1",
            constraintLayoutVersion    : "1.1.2",
            orgApacheCommonsLangVersion: "2.6",
            baseLibrary                : ":uhome_base",
    ]
}

升级之后要注意,所有原本清单里声明的敏感权限在应用安装之后都是默认关闭的,都需要进行动态申请。动态申请的权限,在清单文件里也必须声明。

Activity中权限处理流程(在判断有定位权限的情况下,再判断是否开启了定位服务,因定位服务的判断会同时判断定位权限,顺序不能颠倒):

@TargetApi(Build.VERSION_CODES.M)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.community_my);
        initView();
        initDatas();
        checkPermissions();//判断定位权限和服务,然后定位
    }

 checkPermissions()方法定义如下:

/**
     * 获取定位权限
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    private void checkPermissions() {
        PackageManager pm = getPackageManager();
        List<String> needRequestPermissonList = new ArrayList<>();
        for (String perm : permissions2) {
            if (pm.checkPermission(perm, getPackageName()) != PackageManager.PERMISSION_GRANTED) {//ContextCompat.checkSelfPermission(this, perm) != PackageManager.PERMISSION_GRANTED || ActivityCompat.shouldShowRequestPermissionRationale(this, perm)
                needRequestPermissonList.add(perm);
            }
        }
//        show("size : " + needRequestPermissonList.size());
        //调用系统弹窗
        if (needRequestPermissonList.size() > 0) {
            ActivityCompat.requestPermissions(this,
                    needRequestPermissonList.toArray(new String[needRequestPermissonList.size()]),
                    PERMISSON_REQUESTCODE);
        }else {
            //有定位权限的情况下,再判断是否开启了定位服务,因定位服务的判断会同时判断定位权限,顺序不能颠倒
            checkLocationServices();
        }
    }

其中permissions2取自百度定位文档中提出的需要动态获取权限 :

private String[] permissions2 = {
            "android.permission.CALL_PHONE",
            "android.permission.ACCESS_COARSE_LOCATION",
            "android.permission.ACCESS_FINE_LOCATION",
            "android.permission.WRITE_EXTERNAL_STORAGE",
            "android.permission.READ_EXTERNAL_STORAGE"
    };

checkLocationServices()定义如下:

/**
     * 判断是否有定位服务
     */
    private void checkLocationServices() {
        LocationManager locationManager = (LocationManager) getSystemService(this.LOCATION_SERVICE);
        //获取所有可用的位置提供器
        List<String> providers = locationManager.getProviders(true);
        String locationProvider = null;
        boolean hasGPS = providers.contains(LocationManager.GPS_PROVIDER);
        boolean hasNETWORK = providers.contains(LocationManager.NETWORK_PROVIDER);
        //show("GPS : " + hasGPS + "; NETWORK : " + hasNETWORK);
        if (hasGPS || hasNETWORK) {
            //开启了定位服务和获取了定位权限
            startLocation();
        } else {
            showDialog(R.string.request_locat_service, new OnDailogListener() {
                @Override
                public void onPositiveButton() {
                    //跳转开启定位服务页
                    Intent i = new Intent();
                    i.setAction(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                    startActivityForResult(i, SETTING_REQUESTCODE);
                }
                @Override
                public void onNegativeButton() {
                    stopLocation();
                    //拒绝打开定位服务,缺省是深圳市
                    curCity.setText("深圳市");
                }
            });
        }
    }

回调:

/**
     * 权限获取回调
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] paramArrayOfInt) {
        if (requestCode == PERMISSON_REQUESTCODE) {
            //获得权限
            if (verifyPermissions(paramArrayOfInt)) {
                startLocation();
            }else {
                //没有权限时的操作
            }
        }
    }

    /**
     * 检测是否所有的权限都已经授权
     */
    private boolean verifyPermissions(int[] grantResults) {
        for (int result : grantResults) {
            if (result != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

 

ps:此Activity封装了百度定位的操作: 

/**
 * 定位基类Activity
 */
public abstract class BaseLocationActivity extends BaseActivity {

	protected LocationService locationService;


	protected MyOrientationListener orientationListener;

	protected BDLocation mLocation;

	@Override
	protected void onStart() {
		// TODO Auto-generated method stub
		super.onStart();
		// -----------location config ------------
		locationService = UHomeApp.mLocationClient;
		//获取locationservice实例,建议应用中只初始化1个location实例,然后使用,可以参考其他示例的activity,都是通过此种方式获取locationservice实例的
		locationService.registerListener(mListener);
		//注册监听
		locationService.setLocationOption(locationService.getDefaultLocationClientOption());

	}

	@Override
	protected void onResume() {
		super.onResume();
		locationService.registerListener(mListener);

	}

	@Override
	protected void onPause() {
		super.onPause();
		locationService.unregisterListener(mListener); //注销掉监听
		stopLocation(); //停止定位服务
	}

	@Override
	protected void onStop() {
		super.onStop();
		stopLocation();
	}
	
	@Override
	public void onDestroy() {
		super.onDestroy();
		locationService.unregisterListener(mListener);
	}

	/**
	 * 定位成功回调
	 * @param location
     */
	protected abstract void locationSuccessResult(BDLocation location);
	
	/**
	 * 定位失败回调
	 * @param location
	 */
	protected abstract void locationFailResult(BDLocation location);

	/**
	 * 开启定位
	 */
	protected void startLocation() {
		if (null != locationService) {
			locationService.start();// 定位SDK
		}
	}
	
	/**
	 * 关闭定位
	 */
	protected void stopLocation() {
		if (null != locationService) {
			locationService.stop();// 定位SDK
		}
	}
	
	/**
	 * 设置方向传感器监听
	 */
	protected void setOrientationListener() {
		orientationListener = new MyOrientationListener(this);
		orientationListener.setOnOrientationListener(new MyOrientationListener.OnOrientationListener()
				{
					@Override
					public void onOrientationChanged(float x)
					{
						if(null != mLocation){
//							addMyLocation((int) x);
						}
					}
				});
		// 开启方向传感器
		orientationListener.start();
	}




	

	/*****
	 * 61 : GPS定位结果,GPS定位成功。
	 * 62 : 无法获取有效定位依据,定位失败,请检查运营商网络或者wifi网络是否正常开启,尝试重新请求定位。
	 * 63 : 网络异常,没有成功向服务器发起请求,请确认当前测试手机网络是否通畅,尝试重新请求定位。
	 * 65 : 定位缓存的结果。
	 * 66 : 离线定位结果。通过requestOfflineLocaiton调用时对应的返回结果。
	 * 67 : 离线定位失败。通过requestOfflineLocaiton调用时对应的返回结果。
	 * 68 : 网络连接失败时,查找本地离线定位时对应的返回结果。
	 * 161: 网络定位结果,网络定位定位成功。
	 * 162: 请求串密文解析失败。
	 * 167: 服务端定位失败,请您检查是否禁用获取位置信息权限,尝试重新请求定位。
	 * 502: key参数错误,请按照说明文档重新申请KEY。
	 * 505: key不存在或者非法,请按照说明文档重新申请KEY。
	 * 601: key服务被开发者自己禁用,请按照说明文档重新申请KEY。
	 * 602: key mcode不匹配,您的ak配置过程中安全码设置有问题,请确保:sha1正确,“;”分号是英文状态;且包名是您当前运行应用的包名,请按照说明文档重新申请KEY。
	 * 501~700:key验证失败,请按照说明文档重新申请KEY。
	 * 定位结果回调,重写onReceiveLocation方法,可以直接拷贝如下代码到自己工程中修改
	 *
	 */
	private BDLocationListener mListener = new BDLocationListener() {

		@Override
		public void onReceiveLocation(BDLocation location) {
			mLocation = location;
			if (location.getLocType() == BDLocation.TypeServerError
					|| location.getLocType() == BDLocation.TypeNetWorkException
					|| location.getLocType() == BDLocation.TypeCriteriaException) {
				locationFailResult(location);
			}else{
				locationSuccessResult(location);
			}
		}

	};


}