关于Android6.0以上动态获取运行时权限、及无法正常运行百度地图API的问题

Google早在Android6.0的时候就全面修改了应用程序的权限申请机制,除了需要在AndroidManifest.xml配置文件中事先做好权限配置之外,部分被认为是危险的系统权限还需要在代码中专门去单独申请。Google将这一改变称为“运行时权限(RunTimePermissions)”


由于之前一直有在用百度地图API实现一些地图定位的小功能,而自己手边并迟迟没有得到Android6.0以上版本的手机或是移动设备,所以这个运行时权限的问题始终没有被我重视。直到最近周围有人的手机升级到了Android6.x后,才发现了这一问题:百度地图API无法正常进行定位了。


在网上搜索了一下,解决方案其实非常简单,自己整理如下:


第一步:确定哪些系统权限是需要在运行时进行动态申请的

首先,以下这位博主整理出了所有的系统权限及分组:

http://www.tuicool.com/articles/rIjEniV

在他的文章中,将所有的系统权限及分组罗列了出来,并且说明了在代码中是可以按组直接申请运行时权限的,且每组只要有一个权限申请成功了,就默认整组权限都可以使用了

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

除此以外,这位博主还罗列出了一些不需要在运行时申请的普通权限,这些权限还像Android6.0之前那样在AndroidManifest.xml文件中进行配置就可以了:

android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_NOTIFICATION_POLICY
android.permission.ACCESS_WIFI_STATE
android.permission.ACCESS_WIMAX_STATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.BROADCAST_STICKY
android.permission.CHANGE_NETWORK_STATE
android.permission.CHANGE_WIFI_MULTICAST_STATE
android.permission.CHANGE_WIFI_STATE
android.permission.CHANGE_WIMAX_STATE
android.permission.DISABLE_KEYGUARD
android.permission.EXPAND_STATUS_BAR
android.permission.FLASHLIGHT
android.permission.GET_ACCOUNTS
android.permission.GET_PACKAGE_SIZE
android.permission.INTERNET
android.permission.KILL_BACKGROUND_PROCESSES
android.permission.MODIFY_AUDIO_SETTINGS
android.permission.NFC
android.permission.READ_SYNC_SETTINGS
android.permission.READ_SYNC_STATS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.REORDER_TASKS
android.permission.REQUEST_INSTALL_PACKAGES
android.permission.SET_TIME_ZONE
android.permission.SET_WALLPAPER
android.permission.SET_WALLPAPER_HINTS
android.permission.SUBSCRIBED_FEEDS_READ
android.permission.TRANSMIT_IR
android.permission.USE_FINGERPRINT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE_SYNC_SETTINGS
com.android.alarm.permission.SET_ALARM
com.android.launcher.permission.INSTALL_SHORTCUT
com.android.launcher.permission.UNINSTALL_SHORTCUT

在我上网翻查资料的时候,找到了另外一位博主,在他的文章中也提到了哪些是普通权限,仅需AndroidManifest.xml文件配置即可,而哪些是危险权限,需要运行时动态申请:

http://www.cnblogs.com/cr330326/p/5181283.html

他在文章中列出的权限说明如下:

普通权限:

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
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_INSTALL_PACKAGES
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
SET_ALARM
INSTALL_SHORTCUT
UNINSTALL_SHORTCUT

危险权限:

Permission Group Permissions
CALENDAR
CAMERA
CONTACTS
LOCATION
MICROPHONE
PHONE
SENSORS
SMS
STORAGE

所有这些权限及其分组的声明,我相信在Google官方网站上一定会及时更新。只是我比较懒,不想为这事儿去爬梯子。而以上两位博主在自己文章中的说明我认为已经足够细致了。


第二步:在代码中确认应用是否获得到了权限

在我的案例里,我需要为百度地图API动态申请运行时权限。这里我参考了以下这位博主的文章:

http://blog.csdn.net/qq_25817651/article/details/50913055

这位博主在自己的文章中列出了百度地图API所涉及到的系统权限中,以下这些是需要在运行时动态获取的:

Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_STATE

接下来就是首先判断是否获得到了这些权限,如果没有获得到的话,则进行申请。

2.1、判断Android的版本号是否在6.0之上

if(Build.VERSION.SDK_INT >= 23) {
    //Android6.0以上,需要动态申请运行时权限
}
else{
    //Android6.0以下,直接进行地图定位操作
}

2.2、检查是否获得到了想要的权限

ActivityCompat.checkSelfPermission(YourActivity.class, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED
简单说明一下:
检查是否获取到了权限,使用ActivityCompat.checkSelfPermission()方法
该方法有两个入参:
第一个入参是当前正在试图申请权限的Activity
第二个入参是具体所申请的权限。在我的例子中,申请的是“Manifest.permission.READ_PHONE_STATE”,即查看手机的状态
在调用完ActivityCompat.checkSelfPermission()方法之后,查看其返回值是否等于PackageManager.PERMISSION_GRANTED,即是否已经获取到了权限

所以,在我的案例中,整个检查权限的代码片段是这样的
/**
 * 这个方法在Android6.0以上的版本中,动态申请运行时权限,从而能够保证在百度地图上正常定位
 */
private  void checkRunTimePermissionAndLocateOnTheMap(MapView mapView,String address) {
    if(Build.VERSION.SDK_INT >= 23) {
        /*
         *  判断一下,在6.0以上版本的Android系统中需要申请权限
         *  以下这些权限都是百度地图Api当中所用到的,且必须动态申请的权限
         */
        if (ActivityCompat.checkSelfPermission(this.currentActivity, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
	    || ActivityCompat.checkSelfPermission(this.currentActivity, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
	    || ActivityCompat.checkSelfPermission(this.currentActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
	    || ActivityCompat.checkSelfPermission(this.currentActivity, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
            /*
             *  系统并没有授权,则申请权限
             */
	    requestRunTimePermission();
        } else {
            /*
	     *	已经授予了权限,则在地图上定位地址
	     */
	    Log.i(TAG,"CAMERA permission has already been granted. Displaying camera preview.");
	    this.locateOnMap(mapView,address);
        }
    }
    else{
        /*
         *  在6.0以下版本的Android系统中无需在运行时申请权限,可以直接在地图上定位地址
         */
        this.locateOnMap(mapView,address);
    }
}
对上述代码片段说明一下:
this.locateOnMap(mapView,address)这一句是调用我自己封装的用于在百度地图上定位的方法,在此不做赘述了
requestRunTimePermission()方法就是用来进行动态申请运行时权限的方法了

2.3、动态申请运行时权限

ActivityCompat.shouldShowRequestPermissionRationale(YourActivity.class,Manifest.permission.READ_PHONE_STATE)
简单说明一下:
ActivityCompat.shouldShowRequestPermissionRationale()方法用来判断是否需要向用户提出权限申请
这个方法有两个入参:
第一个入参是当前正在试图申请权限的Activity
第二个入参是具体所申请的权限

经过这个方法判断,发现需要向用户申请权限,即表示应用程序尚未获得权限许可,就用以下方法来申请权限:
ActivityCompat.requestPermissions(YourActivity,
                            new String[]{
                                Manifest.permission.ACCESS_COARSE_LOCATION,
                                Manifest.permission.ACCESS_FINE_LOCATION,
                                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                                Manifest.permission.READ_PHONE_STATE
                            },REQUEST_CODE);
简单说明一下:
ActivityCompat.requestPermissions()方法用来提出权限申请
这个方法有三个入参:
第一个入参是当前正在试图申请权限的Activity
第二个入参是一个数组,你可以在这个数组中一次性罗列所有你需要申请的权限
第三个入参是一个int常量。这个int常量需要程序员自己声明,用来标记程序员希望应用程序向用户索取的权限是用来处理哪些具体业务的。当我们编写代码实现了权限申请的功能后,最终呈现在用户面前的应用程序会询问用户是否同意授予权限,无论用户是否同意,程序都会捕捉到用户的操作,调用一个固定的方法onRequestPermissionResult(),这是一个回调方法,在这个方法中,是有一个int常量的。而这个int常量就是现在在这里由程序员传入到requestPermissions()方法的第三个参数中去的那个常量。有了这个常量的传递,程序员就可以判断自己是在哪些业务需求中申请了相应的权限,从而当用户有了反馈之后,就能够进行下一步操作。

所以,在我的案例中,完整的向用户申请权限的代码如下:
private static final int REQUEST_CODE = 1;
private void requestRunTimePermission() {
    if (ActivityCompat.shouldShowRequestPermissionRationale(this.currentActivity,Manifest.permission.ACCESS_COARSE_LOCATION)
        || ActivityCompat.shouldShowRequestPermissionRationale(this.currentActivity,Manifest.permission.ACCESS_FINE_LOCATION)
        || ActivityCompat.shouldShowRequestPermissionRationale(this.currentActivity,Manifest.permission.WRITE_EXTERNAL_STORAGE)
        || ActivityCompat.shouldShowRequestPermissionRationale(this.currentActivity,Manifest.permission.READ_PHONE_STATE)) {
        /*
         *  如果没有获得过用户的权限许可,则向用户申请
         */

        Snackbar.make(this.currentActivity.findViewById(R.id.LAYOUT_SHOW_ASSET_INFO), R.string.permission_location_rationale,
            Snackbar.LENGTH_INDEFINITE).setAction(R.string.str_confirm, new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    ActivityCompat.requestPermissions(currentActivity,
                        new String[]{
                            Manifest.permission.ACCESS_COARSE_LOCATION,
                            Manifest.permission.ACCESS_FINE_LOCATION,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE,
                            Manifest.permission.READ_PHONE_STATE
                        },REQUEST_CODE);
                }
            }).show();
    } else {
        /*
         *  如果已经获得过用户的权限许可了,则直接申请运行时权限即可
         */
        ActivityCompat.requestPermissions(currentActivity,
            new String[]{
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.READ_PHONE_STATE
            },REQUEST_CODE);
    }
}
简单说明一下:
在上面的代码中,我使用了Snackbar.make().setAction().show()方法,这个方法会弹出一个对话框,询问用户是否同意授权。而Snackbar类是Google官方推出的一个用来代替Toast()的类,它可以在界面上向用户显示一些提示消息。如果你要用Snackbar的话,记得需要在你的Android项目的build.gradle配置文件中增加 以下依赖:
compile 'com.android.support:design:23.1.1'
其中的版本号需要与你的targetSdkVersion相同

回到Snackbar本身,注意make()这个方法有三个入参:
第一个入参是当前申请权限的Activity的根Layout
第二个入参是一个字符串,这个字符串会被显示在弹出的对话框上。在我的案例中,显示了一句“应用程序需要您的授权”,当然了,这句话是被配置在strings.xml中的
第三个入参是一个常量,通过这个常量来设置对话框的滞留时间

而setAction()方法则实现了Snackbar上的操作按钮的文字,以及按钮的点击事件。而在点击事件中,我们使用ActivityCompat.requestPermissions()方法完成了对运行时权限的申请。

2.4、处理用户的反馈

在我的这个案例中,当用户点击了Snackbar上的按钮后,应用程序就会自动调用一个名为onRequestPermissionResult()的和回调方法。这个方法是来自接口ActivityCompat.OnRequestPermissionsResultCallback的,所以你的类必须实现这个接口:

public class YourClass implements ActivityCompat.OnRequestPermissionsResultCallback {
    //...
}


在我的案例中,整个回调方法是这样的:

/**
 * 处理运行时权限的申请结果
 */
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == REQUEST_CODE) {

        if (PermissionUtil.verifyPermissions(grantResults)) {
            
            Snackbar.make(this.currentActivity.findViewById(R.id.LAYOUT_SHOW_ASSET_INFO), R.string.permision_available,Snackbar.LENGTH_SHORT).show();
        } else {
            
            Snackbar.make(this.currentActivity.findViewById(R.id.LAYOUT_SHOW_ASSET_INFO), R.string.permissions_not_granted,Snackbar.LENGTH_SHORT).show();
        }
    } else {
        //
    }
}
简单说明一下:

还记得在步骤2.3中所提到的那个由程序员自己声明的int常量吗,它又在这里出现了。通过它我们能够判断经过用户同意的授权是用在哪个具体的业务上的,从而可以针对性地去编写业务代码。如果你的Activity上十分复杂,需要分开多次申请不同的运行时权限的话,这个int常量会变得十分有用。

方法PermissionUtil.verfyPermissions()来自Google官方示例,这个方法用来处理一组多个权限,查看这些权限是否都被用户所允许了。这个类的完整代码如下:

package com.yumi.mibao.util;

import android.app.Activity;
import android.content.pm.PackageManager;

/**
 * Utility class that wraps access to the runtime permissions API in M and provides basic helper
 * methods.
 */
public abstract class PermissionUtil {

    /**
     * Check that all given permissions have been granted by verifying that each entry in the
     * given array is of the value {@link PackageManager#PERMISSION_GRANTED}.
     *
     * @see Activity#onRequestPermissionsResult(int, String[], int[])
     */
    public static boolean verifyPermissions(int[] grantResults) {
        // At least one result must be checked.
        if(grantResults.length < 1){
            return false;
        }

        // Verify that each required permission has been granted, otherwise return false.
        for (int result : grantResults) {
            if (result != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

}


结束语

至此,关于Android6.0及以上版本的应用运行时权限的申请就全部结束了,同时百度地图API也能够正常定位了。如果对Android6.0及以上版本的运行时权限仍然不清楚的话,可以到以下地址去下载Google官方给出的示例程序。该程序可以直接在AndroidStudio中编译并运行。


  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值