Android 危險權限使用申請 工具類 封裝與簡便使用

背景

危險權限的申請使用是現如今app開發必備的一部分內容,這裏就簡單的做一下記錄,方便自己後期使用,可以快速的應用到項目中去。

爲什麼需要危險權限?

  • 回答:當需要使用危險權限時才做申請並等待用戶應答,不需要使用時用戶可以自由的關閉這些危險權限,等到用到相關功能時才繼續向用戶申請危險權限的使用。危險權限顧名思義擁有就很危險,所以使用時纔打開,後面可以自己自由的控制危險權限的開啓,這樣纔是一個正確的權限使用規則。

目前來說,基本上剛進入app後都會將一些用到的危險權限都統一的進行一次性申請使用,等待用戶操作完後再進入到app中,如果用戶拒絕了這些危險權限的使用,那麼也沒關係,我們可以等用戶使用需要相關權限才能用的功能時再向用戶申請危險權限的一個使用。


危險權限整理

這裏就不重複造文字了,很多現有的文章都很詳細

Android 中的危險權限詳細整理


工具類與接口

涉及一個工具類和一個接口

工具類PVerifyUtil.java的代碼如下:

package com.example.pverificationdemo.util;

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Build;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;

import com.example.pverificationdemo.R;
import com.example.pverificationdemo.interfaces.GrantListener;

/**
 * 危險權限 申請使用 工具類
 * 目前是 9組24個
 */
public class PVerifyUtil {

    public static final int CAMERA = 0x1001;//相機

    public static final int READ_CALENDAR = 0x1002;// 日曆
    public static final int WRITE_CALENDAR = 0x1003;

    public static final int READ_CONTACTS = 0x1004;// 聯繫人
    public static final int WRITE_CONTACTS = 0x1005;
    public static final int GET_ACCOUNTS = 0x1006;

    public static final int ACCESS_FINE_LOCATION = 0x1007;// 位置
    public static final int ACCESS_COARSE_LOCATION = 0x1008;

    public static final int RECORD_AUDIO = 0x1009;// 麥克風

    public static final int READ_PHONE_STATE = 0x1010;// 手機
    public static final int CALL_PHONE = 0x1011;
    public static final int READ_CALL_LOG = 0x1012;
    public static final int WRITE_CALL_LOG = 0x1013;
    public static final int ADD_VOICEMAIL = 0x1014;
    public static final int USE_SIP = 0x1015;
    public static final int PROCESS_OUTGOING_CALLS = 0x1016;

    public static final int BODY_SENSORS = 0x1017;// 傳感器

    public static final int SEND_SMS = 0x1018;// 短信
    public static final int RECEIVE_SMS = 0x1019;
    public static final int READ_SMS = 0x1020;
    public static final int RECEIVE_WAP_PUSH = 0x1021;
    public static final int RECEIVE_MMS = 0x1022;

    public static final int READ_EXTERNAL_STORAGE = 0x1023;// 存儲卡
    public static final int WRITE_EXTERNAL_STORAGE = 0x1024;

    public static final int ONCE_TIME_APPLY = 0x2000;

    private String GROUP_NAME = "";// 聲明該變量用來保存危險權限組名

    private GrantListener grantListener = null;

    private Context context;
    private Fragment fragment;

    public PVerifyUtil(Context context, Fragment fragment) {
        this.context = context;
        this.fragment = fragment;
    }

    /**
     * 申請危險權限
     * @param requestCode 請求碼
     */
    public void apply(int requestCode, GrantListener grantListener) {

        this.grantListener = grantListener;// 權限同意的監聽設置
        String permission = "";
        permission = getPermission(requestCode);
        if (TextUtils.isEmpty(permission)){
            // 未獲取到對應危險權限,一般是請求碼不按照給定變量設置或請求的不是危險權限時出現
            return;
        }
        // 危險權限申請使用 適配6.0及以上版本
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M){
            if (fragment != null){
                fragment.requestPermissions(new String[]{permission}, requestCode);
            }else {
                ((Activity)context).requestPermissions(new String[]{permission}, requestCode);
            }

        }

    }

    /**
     * 根據請求碼獲取對應危險權限    同時賦值對應的權限組名
     * @param requestCode
     * @return
     */
    private String getPermission(int requestCode) {
        String permission = "";
        switch (requestCode){
            // 相機
            case CAMERA: permission = Manifest.permission.CAMERA;
                GROUP_NAME = "相機";
                break;
            // 日曆
            case READ_CALENDAR: permission = Manifest.permission.READ_CALENDAR;
                GROUP_NAME = "日曆";
                break;
            case WRITE_CALENDAR: permission = Manifest.permission.WRITE_CALENDAR;
                GROUP_NAME = "日曆";
                break;
            // 聯繫人
            case READ_CONTACTS: permission = Manifest.permission.READ_CONTACTS;
                GROUP_NAME = "聯繫人";
                break;
            case WRITE_CONTACTS: permission = Manifest.permission.WRITE_CONTACTS;
                GROUP_NAME = "聯繫人";
                break;
            case GET_ACCOUNTS: permission = Manifest.permission.GET_ACCOUNTS;
                GROUP_NAME = "聯繫人";
                break;
            // 位置
            case ACCESS_FINE_LOCATION: permission = Manifest.permission.ACCESS_FINE_LOCATION;
                GROUP_NAME = "位置";
                break;
            case ACCESS_COARSE_LOCATION: permission = Manifest.permission.ACCESS_COARSE_LOCATION;
                GROUP_NAME = "位置";
                break;
            // 麥克風
            case RECORD_AUDIO: permission = Manifest.permission.RECORD_AUDIO;
                GROUP_NAME = "麥克風";
                break;
            // 手機
            case READ_PHONE_STATE: permission = Manifest.permission.READ_PHONE_STATE;
                GROUP_NAME = "手機";
                break;
            case CALL_PHONE: permission = Manifest.permission.CALL_PHONE;
                GROUP_NAME = "手機";
                break;
            case READ_CALL_LOG: permission = Manifest.permission.READ_CALL_LOG;
                GROUP_NAME = "手機";
                break;
            case WRITE_CALL_LOG: permission = Manifest.permission.WRITE_CALL_LOG;
                GROUP_NAME = "手機";
                break;
            case ADD_VOICEMAIL: permission = Manifest.permission.ADD_VOICEMAIL;
                GROUP_NAME = "手機";
                break;
            case USE_SIP: permission = Manifest.permission.USE_SIP;
                GROUP_NAME = "手機";
                break;
            case PROCESS_OUTGOING_CALLS: permission = Manifest.permission.PROCESS_OUTGOING_CALLS;
                GROUP_NAME = "手機";
                break;
            // 傳感器
            case BODY_SENSORS: permission = Manifest.permission.BODY_SENSORS;
                GROUP_NAME = "傳感器";
                break;
            // 短信
            case SEND_SMS: permission = Manifest.permission.SEND_SMS;
                GROUP_NAME = "短信";
                break;
            case RECEIVE_SMS: permission = Manifest.permission.RECEIVE_SMS;
                GROUP_NAME = "短信";
                break;
            case READ_SMS: permission = Manifest.permission.READ_SMS;
                GROUP_NAME = "短信";
                break;
            case RECEIVE_WAP_PUSH: permission = Manifest.permission.RECEIVE_WAP_PUSH;
                GROUP_NAME = "短信";
                break;
            case RECEIVE_MMS: permission = Manifest.permission.RECEIVE_MMS;
                GROUP_NAME = "短信";
                break;
            // 存儲卡
            case READ_EXTERNAL_STORAGE: permission = Manifest.permission.READ_EXTERNAL_STORAGE;
                GROUP_NAME = "存儲卡";
                break;
            case WRITE_EXTERNAL_STORAGE: permission = Manifest.permission.WRITE_EXTERNAL_STORAGE;
                GROUP_NAME = "存儲卡";
                break;
        }
        return permission;
    }

    /**
     * 用戶是否同意權限
     * @param grantResults
     * @return 返回 true 表示用戶已同意了,false 則反之
     */
    private boolean isGranted(int[] grantResults) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            return true;
        }
        return false;
    }

    /**
     * 請求使用某個權限 結果返回
     * @param requestCode
     * @param grantResults
     */
    public void singleApplyResult(int requestCode, @NonNull int[] grantResults) {
        if(isGranted(grantResults)){
            // 權限同意
            if (grantListener != null){
                grantListener.onAgree();
            }
        }else {
            // 未同意 分兩種情況
            if (ActivityCompat.shouldShowRequestPermissionRationale(((Activity)context),
                    getPermission(requestCode))){
                // 情況一  用戶只拒絕,未選不再提示項
                grantListener.onDeny();
            }else {
                // 情況二  用戶拒絕了,並選擇了不再提示項 這時候可跳轉到應用詳情頁讓用戶手動選擇打開相關權限操作
                grantListener.onDenyNotAskAgain();
            }

        }
    }

    /**
     * 一次性同時申請所有的權限 結果返回
     * @param permissions
     * @param grantResults
     */
    public void onceTimeApplyResult(String[] permissions, int[] grantResults){
        for (int i = 0;i < permissions.length;i++){
            String permission = permissions[i];// 某個權限

            if (grantResults.length > 0 && grantResults[i] == PackageManager.PERMISSION_GRANTED){

            }else {
                if (ActivityCompat.shouldShowRequestPermissionRationale((Activity) context,
                        permission)){

                }else {
                    new AlertDialog.Builder(context)
                            .setTitle("應用詳情頁跳轉")
                            .setIcon(R.mipmap.app_icon_round)
                            .setMessage("請手動開啓相應的 <" + getPermissionGroupName(permission) + "> 權限,否則無法使用相關功能")
                            .setCancelable(false)
                            .setPositiveButton("我知道了", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    dialog.dismiss();
                                }
                            }).create().show();
                }
            }
        }

    }

    /**
     * 根據某權限獲取權限組名
     * @param permission
     * @return
     */
    private String getPermissionGroupName(String permission) {

        String groupName = "";
        switch (permission){
            case Manifest.permission.READ_CALENDAR:
            case Manifest.permission.WRITE_CALENDAR:
                groupName = "日曆";
                break;
            case Manifest.permission.CAMERA:
                groupName = "相機";
                break;
            case Manifest.permission.READ_CONTACTS:
            case Manifest.permission.WRITE_CONTACTS:
            case Manifest.permission.GET_ACCOUNTS:
                groupName = "聯繫人";
                break;
            case Manifest.permission.ACCESS_FINE_LOCATION:
            case Manifest.permission.ACCESS_COARSE_LOCATION:
                groupName = "位置";
                break;
            case Manifest.permission.RECORD_AUDIO:
                groupName = "麥克風";
                break;
            case Manifest.permission.READ_PHONE_STATE:
            case Manifest.permission.CALL_PHONE:
            case Manifest.permission.READ_CALL_LOG:
            case Manifest.permission.WRITE_CALL_LOG:
            case Manifest.permission.ADD_VOICEMAIL:
            case Manifest.permission.USE_SIP:
            case Manifest.permission.PROCESS_OUTGOING_CALLS:
                groupName = "手機";
                break;
            case Manifest.permission.BODY_SENSORS:
                groupName = "傳感器";
                break;
            case Manifest.permission.SEND_SMS:
            case Manifest.permission.RECEIVE_SMS:
            case Manifest.permission.READ_SMS:
            case Manifest.permission.RECEIVE_WAP_PUSH:
            case Manifest.permission.RECEIVE_MMS:
                groupName = "短信";
                break;
            case Manifest.permission.READ_EXTERNAL_STORAGE:
            case Manifest.permission.WRITE_EXTERNAL_STORAGE:
                groupName = "存儲";
                break;
        }
        return groupName;
    }

}

工具類整體來說代碼比較簡單,註釋詳細,裏面有另外用到一個GrantListener接口,接口代碼如下:

package com.example.pverificationdemo.interfaces;

public interface GrantListener {

    void onAgree();// 某權限同意

    void onDeny();// 某權限拒絕但沒選擇不再詢問項

    void onDenyNotAskAgain();// 某權限拒絕並選擇了不再詢問項
}

這個接口的作用分別對應用戶的三種選擇操作。

當用戶拒絕某一危險權限後app因爲相關功能需要繼續向用戶申請危險權限的使用,這時候會出現下面的彈框:

在這裏插入圖片描述

這裏就很明顯了,接口中的三個方法調用時機就分別跟這三個選項的選擇相關聯。


使用

這裏假定同學們把上面的接口和工具類都添加到項目中去了,然後繼續下面的操作。

Activity中單個危險權限申請

  • 聲明與創建PVerifyUtil對象:
private PVerifyUtil pVerifyUtil;

pVerifyUtil = new PVerifyUtil(this, null);

注意:在activity中使用,對象創建的第二個參數固定傳值null

  • 請求單個權限的使用:
// 請求使用單個權限
                pVerifyUtil.apply(PVerifyUtil.CAMERA, new GrantListener() {
                    @Override
                    public void onAgree() {
                        Toast.makeText(MainActivity.this, "相機權限已同意", Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onDeny() {
                        Toast.makeText(MainActivity.this, "相機權限被拒絕,未點擊不再詢問", Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onDenyNotAskAgain() {
                        Toast.makeText(MainActivity.this, "相機權限被拒絕並點擊了不再詢問", Toast.LENGTH_SHORT).show();
                    }
                });
/**
     * 請求權限結果回調
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode){

            case PVerifyUtil.CAMERA:
                pVerifyUtil.singleApplyResult(requestCode, grantResults);
                break;

        }
    }

注意:這裏單個危險權限申請使用的時候用的是 PVerifyUtil.CAMERA,那麼在回調中也要使用 PVerifyUtil.CAMERA與之對應

另外注意記得在清單配置文件中也要聲明要使用的危險權限

其他的危險權限都跟上面的請求代碼一樣,只是修改apply(int requestCode, GrantListener grantListener)方法的第一個參數值即可即可。

申請相機權限第一次拒絕,第二次同意的效果圖示如下:

在這裏插入圖片描述


Activity中多個危險權限一次性申請

  • 示例代碼如下:
				// 一次性請求使用多個權限
				
                // 聲明所有需要一次性申請的危險權限
                String[] permissions = new String[]{Manifest.permission.CAMERA,
                Manifest.permission.READ_EXTERNAL_STORAGE};
                // 申請時的請求碼固定傳值 PVerifyUtil.ONCE_TIME_APPLY
                ActivityCompat.requestPermissions(MainActivity.this, permissions,
                        PVerifyUtil.ONCE_TIME_APPLY);


	/**
     * 請求權限結果回調
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode){

            case PVerifyUtil.ONCE_TIME_APPLY://一次性請求使用多個權限 結果返回處理
                pVerifyUtil.onceTimeApplyResult(permissions, grantResults);
                break;
         

        }
    }

注意:在結果回調方法onRequestPermissionsResult()中,調用工具類的onceTimeApplyResult(int requestCode, @NonNull int[] grantResults)方法來實現多個請求的邏輯

第一次拒絕,第二次允許權限申請的效果圖示如下:

在這裏插入圖片描述


Fragment 中單個危險權限申請

  • 聲明與創建工具類對象
		// 創建工具類對象
        pVerifyUtil = new PVerifyUtil(getContext(), this);

注意:創建對象的第二個參數傳值當前Fragment

  • 請求代碼如下:
				// 請求使用單個權限
                pVerifyUtil.apply(PVerifyUtil.CAMERA, new GrantListener() {
                    @Override
                    public void onAgree() {
                        Toast.makeText(getContext(), "相機權限已同意", Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onDeny() {
                        Toast.makeText(getContext(), "相機權限被拒絕但未點擊不再詢問", Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onDenyNotAskAgain() {
                        Toast.makeText(getContext(), "相機權限被拒絕並點擊過不再詢問", Toast.LENGTH_SHORT).show();
                    }
                });


	/**
     * 請求權限結果回調
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode){
            case PVerifyUtil.CAMERA:// 相機權限
                pVerifyUtil.singleApplyResult(requestCode, grantResults);
                break;

        }
    }

第一次拒絕,第二次同意權限申請的效果圖示如下:

在這裏插入圖片描述


Fragment中多個危險權限一次性申請

示例代碼如下:

				// 一次性請求使用多個權限
                // 聲明所有需要一次性申請的危險權限
                String[] permissions = new String[]{Manifest.permission.CAMERA,
                        Manifest.permission.READ_EXTERNAL_STORAGE};
                // 申請時的請求碼固定傳值 PVerifyUtil.ONCE_TIME_APPLY
                requestPermissions( permissions, PVerifyUtil.ONCE_TIME_APPLY);


	/**
     * 請求權限結果回調
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode){

            case PVerifyUtil.ONCE_TIME_APPLY://一次性請求使用多個權限 結果返回處理
                pVerifyUtil.onceTimeApplyResult(permissions, grantResults);
                break;

        }
    }

注意:在Fragment中的一次性申請跟在Activity中的一次性申請的代碼有些許差異。

第一次拒絕,第二次允許權限申請的效果圖示如下:

在這裏插入圖片描述


子Fragment裏運行時權限申請時的特殊處理

安卓6.0新特性在Fragment申請運行時權限


技術永不眠,我們下期見!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章