android6.0运行时权限的使用与封装

在android6.0之前的系统要想申请权限,只需要在AndroidManifest.xml中申明一下就行,而用户在安装软件的时候系统会显示该软件会用到那些权限,用户觉得不合理可以选择不安装,这个设计的初衷是好的,可是总是会出现店大欺客的现象,比如QQ,微信,支付宝等等,明明不需要修改通话记录的权限和修改联系人的权限,可他们就是恬不知耻的申请了,你还必须得同意,不能拒绝安装,因为你的朋友们都在用。或者生活中经常得用到他。

后来android6.0加入了运行时权限

这样的话,一些会涉及到用户隐私和安全的权限即使软件申明了,用户不同意还是可以安装,安装后在使用过程中,软件需要某种权限的话得向用户请求,用户可以选择允许或者拒绝,用户不需要担心软件一直申请,用户可以选择不再询问。同时,如果这样做了得话,只有用户去设置->应用管理->APP->权限管理里手动开启权限了。

权限申请

那么,现在就来看看这个运行时权限怎么弄吧

如何向用户申请权限

最基本简单的方式就是,首先建立一个项目,RunTimePermissionsTest,判断程序是否拥有权限,未拥有就申请,拥有就进行你想做的事,比如拨打电话。


public class MainActivity extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                //获取拨号权限名,返回一个字符串
                String callPhonePermission = Manifest.permission.CALL_PHONE;
                /**
                 * ContextCompat.checkSelfPermission()检查程序是否拥有某个权限,
                 * 接收两个参数:第一个参数时Context,第二个参数是权限名
                 * 返回两个结果:
                 * PackageManager.PERMISSION_GRANTED表示已拥有
                 * PackageManager.PERMISSION_DENIED表示未拥有
                 */
                if (ContextCompat.checkSelfPermission(MainActivity.this, callPhonePermission)
                        != PackageManager.PERMISSION_GRANTED) {
                    //当程序未拥有某个权限的时候进行权限申请
                    /**
                     * ActivityCompat.requestPermissions() 向用户申请权限
                     * 接收两个参数:
                     * 第一个参数是Activity实例
                     * 第二个参数是String数组,把需要申请的权限名放进去
                     * 第三个参数是唯一的权限请求码,这里就写个1就行了
                     */
                    ActivityCompat.requestPermissions(MainActivity.this, new String[]{callPhonePermission}, 1);
                } else {
                    //当程序拥有某个权限的时候运行需要进行的操作
                    //这里就进行拨号操作
                    Intent intent = new Intent(Intent.ACTION_CALL);
                    intent.setData(Uri.parse("tel:10086"));
                    startActivity(intent);
                }
            }
        });

    }

    /**
     * 申请授权后,系统会回调这个方法
     *
     * @param requestCode  权限申请码
     * @param permissions  申请的权限名数组
     * @param grantResults 申请的结果数组
     */

       @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {

        //建立一个List,存储申请失败的权限
        List<String> deniedList = new ArrayList<>();
        //遍历grantResults数组
        for (int i = 0; i < grantResults.length; i++) {
            String mPermissions = permissions[i];
            int mGrantResult = grantResults[i];
            //如果结果是没有授权成功就把对应的权限名添加进List
            if (mGrantResult != PackageManager.PERMISSION_GRANTED) {
                deniedList.add(mPermissions);
            }
        }
        //判断失败的权限数组是不是空的,是空的就代表权限申请成功了
        if(deniedList.isEmpty()){
            //权限申请成功,做点什么把,这里还是打电话
            Intent intent = new Intent(Intent.ACTION_CALL);
            intent.setData(Uri.parse("tel:10086"));
            startActivity(intent);
        }else {
            //Tost失败的权限
            for (String i : deniedList) {
                Toast.makeText(this, "申请失败的权限有" + i, Toast.LENGTH_SHORT).show();
            }
        }
    }

这是基础简单的方式,但是代码量太大了,程序也不能一次性要所有权限,不然用户会觉得很奇怪,你总不能在申请读取联系人权限的时候顺带申请SD卡权限把?这样做用户很可能会拒绝,那只有在需要权限的时候进行申请,每次申请都这么多代码,那肯定不符合我们面向对象的思想的,那么我们就进阶改进一下,把申请权限的代码封装起来,这样在需要权限的时候直接调用封装好的方法,输入要申请的权限,以及通过或者未通过时运行的代码。这篇博文也是在看了郭神也就是郭霖的直播后写的,郭神讲的就是android运行时权限,然后讲了怎么把这个过程封装起来,那么,现在开始吧。

封装android6.0运行时权限申请

首先,我们新建一个接口,两个类:

  1. PermissionsListen
  2. ActivityCollector
  3. BestActivity

PermissionsListen接口的作用是对申请结果进行监听,一共有两个抽象方法:

  1. void onGranted();
  2. void onDenied(List<String> permission);
  • onGranted方法在申请成功后进行回调
  • onDenied方法在申请失败后进行回调,接收一个String泛型的List参数,就是申请失败的权限List

代码如下:

package com.example.runtimepermissiontest;

import java.util.List;

public interface PermissionsListen {
    void onGranted();
    void onDenied(List<String> permission);
}

ActivityCollectot的作用是对收集在Activity栈里的Activity,因为申请权限必须要传入Activity,我们希望在任何情况下都能进行权限申请而不是只能在Activity里进行申请,所以需要收集栈里面的Activity,然后在申请权限的时候传入位于栈顶的Activity,好了,上代码

package com.example.runtimepermissiontest;

import android.app.Activity;
import java.util.ArrayList;
import java.util.List;

public class ActivityCollector {
    //存储Activity的LIst
    public static List<Activity> activities = new ArrayList<>();

    /**
     * 向List添加Activity
     * @param activity 传入要添加的Activity
     */
    public static void addActivity(Activity activity){
        activities.add(activity);
    }

    /**
     * 从List中移除Activity
     * @param activity 传入要移除的Activity
     */
    public static void removeActivity(Activity activity){
        activities.remove(activity);
    }

    /**
     * 干掉所有的活动
     */
    public static void finishAll(){
        for (Activity activity : activities){
            if(!activity.isFinishing()){
                activity.finish();
            }
        }
    }
}

BaseActivity的作用就是进行申请权限操作,这里讲一下这个处理逻辑,因为申请权限需要一个Activity实例作为参数,并且我想在程序的任何地方都能进行权限申请而不是局限于只能在Activity中申请,所以我建立了一个ActivityCollector来收集Activity实例,然后在onCreate方法中把实例添加进去,在onDestory方法中把实例移除,活动继承BaseActivity,这样ActivityCollector中的实例栈顶的Activity就是Task的栈顶活动,参数的问题解决了。

在申请到权限或者没有申请到权限的时候会需要有进行相应的操作,所以就新建了一个接口,声明了两个抽象方法,调用的时候实现一下就行。

然后就是申请权限的逻辑,检查要申请的权限有没有获得授权,没有就添加到一个数组,接着检查数组是否为空,不为空就代表有未授权的权限,把这个数组放到requsetPermissions方法中向用户进行权限申请,为空就回调PermissionsListen中的onGranted方法

权限申请后系统会回调onRequestPermissionsResult方法,重写一下这个方法,在方法里对申请结果进行判断,授权失败了就回调onDenied方法,成功了就回调onGranted方法。好,看代码走一下

package com.example.runtimepermissiontest;

import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by jethro on 2016/12/28.
 */

public class BestActivity extends AppCompatActivity {
    // 申明一个全局PermissionsListen对象,在程序调用onRequestPermissions方法时为他赋值
    // 这个对象会在回调onRequestPermissionsResult方法时用到
    static PermissionsListen permissionsListen;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //把活动添加到ActivityCollector中去
        ActivityCollector.addActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //活动被销毁的时候,把活动从ActivityCollector中移除
        ActivityCollector.removeActivity(this);
    }

    /**
     * 这是重头戏,在申请权限时调用的就是这个方法
     * 
     * @param permissions 这个参数就是权限数组
     * @param listen      这个参数是一个回调接口,实现两个抽象方法,
     *                    之前在建立PermissionsListen时有介绍这两个抽象方法
     */
    public static void onRequestPermissions(String[] permissions, PermissionsListen listen) {
        permissionsListen = listen;
        //获取在栈顶的Activity
        int index = ActivityCollector.activities.size() - 1;
        Activity activity = ActivityCollector.activities.get(index);
        //建立一个List,存储没有获得授权的权限,以备在后面申请权限的时候使用
        List<String> mpermissions = new ArrayList<>();
        //遍历判断那些权限没有获得授权
        for (int i = 0; i < permissions.length; i++) {
            if (ContextCompat.checkSelfPermission(activity, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
                //将没有授权的权限添加到mpermissions数组
                mpermissions.add(permissions[i]);
            }
        }
        //判断mpermissions是否为空
        if (mpermissions.isEmpty()) {
            //如果是空的,就执行PermissionsListen对象的onGranted方法
            listen.onGranted();
        } else {
            //如果不是空的就把mpermissions数组转换成String[]数组
            String[] str = mpermissions.toArray(new String[mpermissions.size()]);
            //进行权限申请,传入Activity,还有权限数组,以及权限申请码
            ActivityCompat.requestPermissions(activity, str, 1);
        }
    }

    /**
     * 权限申请后系统就会回调这个方法,用户点了不再询问,系统会直接调用这个方法
     *
     * @param requestCode  权限申请码
     * @param permissions  申请的权限名数组
     * @param grantResults 申请的结果数组
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        switch (requestCode) {
            case 1:
                List<String> deniedPermissions = new ArrayList<>();
                for (int i = 0; i < grantResults.length; i++) {
                    int grantResult = grantResults[i];
                    String mpermissions = permissions[i];
                    if (grantResult != PackageManager.PERMISSION_GRANTED) {
                        deniedPermissions.add(mpermissions);
                    }
                }
                if (deniedPermissions.isEmpty()) {
                    //申请成功
                    permissionsListen.onGranted();
                } else {
                    //申请失败
                    permissionsListen.onDenied(deniedPermissions);
                }
                break;
            default:
                break;
        }
    }
}

好啦,运行时权限处理已经被封装好了,那么,现在就来试一下把

package com.example.runtimepermissiontest;

import android.Manifest;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import java.util.List;

public class MainActivity extends BestActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    //Button的onClick
    public void getPermissions(View view) {
        String callPhone = Manifest.permission.CALL_PHONE;
        String carmera = Manifest.permission.CAMERA;
        String storagr = Manifest.permission.WRITE_EXTERNAL_STORAGE;
        String[] permissions = {callPhone, carmera, storagr};
        onRequestPermissions(permissions, new PermissionsListen() {
            /**
             * 实现申请成功后调用的代码
             */
            @Override
            public void onGranted() {
                Intent intent = new Intent(Intent.ACTION_CALL);
                intent.setData(Uri.parse("tel:10086"));
                //如果IDE有错误提示,不用在意,只是让我们应该检查一下是否有权限
                startActivity(intent);
            }

            /**
             * 实现申请失败后调用的代码
             * @param permission 
             */
            @Override
            public void onDenied(List<String> permission) {
                for (String i : permission) {
                    Toast.makeText(MainActivity.this, "权限" + i + "的申请被拒绝", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

}

即使是在不同的类里面也能进行权限申请
就像这样用:

package com.example.runtimepermissiontest;

import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.widget.Toast;

import java.util.List;

/**
 * Created by jethro on 2016/12/29.
 */

public class Test {

    public void onTest() {
        //获取栈顶活动
        final Activity activity = ActivityCollector.activities.get(ActivityCollector.activities.size() - 1);

        String callPhone = Manifest.permission.CALL_PHONE;
        String carmera = Manifest.permission.CAMERA;
        String storagr = Manifest.permission.WRITE_EXTERNAL_STORAGE;
        String[] permissions = {callPhone, carmera, storagr};
        BestActivity.onRequestPermissions(permissions, new PermissionsListen() {
            @Override
            public void onGranted() {
                Intent intent = new Intent(Intent.ACTION_CALL);
                intent.setData(Uri.parse("tel:10086"));
                //如果IDE有错误提示,不用在意,只是让我们应该检查一下是否有权限
                activity.startActivity(intent);
            }

            /**
             * 实现申请失败后调用的代码
             * @param permission
             */
            @Override
            public void onDenied(List<String> permission) {
                for (String i : permission) {
                    Toast.makeText(activity, "权限" + i + "的申请被拒绝", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }
}

感谢郭神,这个封装方式太巧妙了!

添加新评论