天天看點

Android 6.0+ 動态權限 一種清爽的封裝過程(以及多個權限的處理)

Android 6.0 之前我們申請權限直接在配置檔案中配置一下即可,但是6.0之後,谷歌官方将權限分為普通權限和危險權限。對于危險權限來說,我們就需要進行動态設定了。本文主要講解為什麼要進行Android 6.0 動态權限的設定、動态權限的使用、以及一個頁面中需要同時申請多個危險權限的情況。非常感謝郭霖大牛的講解,把平時不太注意的點都講得很不錯。最後,我們采用相對優雅的方式對其進行封裝,友善使用。

因為對于危險權限我們需要動态設定,不然應用會崩潰的,首先我們要清楚哪些是危險權限?看下面這張圖

Android 6.0+ 動态權限 一種清爽的封裝過程(以及多個權限的處理)

值得一說的是:同一組的權限不用重複授權,也就是說,同一組的權限隻要有一個授權了,那麼同一組的其它權限也都就授權了。

當你的應用中用到上面這些權限時,你就需要動态設定了。首先,你需要像以前一樣把用到的權限依舊在配置檔案中配置一下,然後,在你需要該權限的地方開撸代碼進行動态設定了。下面以打電話為例來加以說明

首先,我們需要保證我們得有一台6.0的手機,targetSdkVersion的版本在23或者以上。其中需要注意一點,當我們的應用是升上來的時候,也就是說之前是23以下,現在我們讓targetSdkVersion等于23或者以上時,即使不去動态設定這些危險權限,應用依舊不會崩潰掉,對于這種情況,谷歌會預設将所用到的這些危險權限自動授權。也就是說已經授權了,應用肯定不會崩潰啊。

單個權限的處理

按照上面分析的,我們先來一步步實作。首先我們在配置檔案中配置打電話的權限

MainActivity

public class MainActivity extends AppCompatActivity {

    private Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化控件
        iniViews();
    }

    private void iniViews() {
        btn = (Button) findViewById(R.id.btn_call);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                call();
            }
        });
    }

    private void call(){
        try {
            Intent intent = new Intent(Intent.ACTION_CALL);
            Uri uri = Uri.parse("tel:" + "10086");
            intent.setData(uri);
            startActivity(intent);
        }catch (SecurityException e){
            e.printStackTrace();
        }
    }
}
           

點選打電話,尴尬的情況發生了…

Android 6.0+ 動态權限 一種清爽的封裝過程(以及多個權限的處理)

接下來就是去動态設定了,在文章最開始展示的那張危險權限表中可以知道,打電話屬于危險權限。是以,我們在打電話時,先要判斷使用者是否授權了打電話功能,如果授權了,則繼續打電話的邏輯,使用者如果沒有授權,會彈出一個對話框。使用者可以根據對話框上面提示的 同意 / 拒絕 來設定權限,使用者的這一操作我們可以通過回調在onRequestPermissionsResult()方法中獲悉。為了達到向下相容,我們使用v4相容庫下面的ContextCompat以及ActivityCompat。這樣就可以相容了,我們就不用代碼判斷版本了。

我們再來看看修改之後的代碼

public class MainActivity extends AppCompatActivity {

    private Button btn;
    private static final int PERMISSION_REQUESTCODE = ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化控件
        iniViews();
    }

    private void iniViews() {
        btn = (Button) findViewById(R.id.btn_call);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                permission();
            }
        });
    }

    private void permission(){
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){
            //沒有授權
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, PERMISSION_REQUESTCODE);
        }else{
            //已經授權
            call();
        }
    }

    public void call(){
        try {
            Intent intent = new Intent(Intent.ACTION_CALL);
            Uri uri = Uri.parse("tel:" + "10086");
            intent.setData(uri);
            startActivity(intent);
        }catch (SecurityException e){
            e.printStackTrace();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode){
            case PERMISSION_REQUESTCODE:
                if(grantResults.length >  && grantResults[] == PackageManager.PERMISSION_GRANTED){
                    //使用者點選了同意授權
                    call();
                }else{
                    //使用者拒絕了授權
                    Toast.makeText(this,"權限被拒絕",Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
    }
}
           

代碼比較簡單,再來看看效果圖

Android 6.0+ 動态權限 一種清爽的封裝過程(以及多個權限的處理)

細心的你肯定會發現,當我們首次選擇了拒絕後,再次點選打電話會出現不再詢問選項,如果勾選了此選項,再點選拒絕的話,那麼當我們再次執行打電話時,就不會再出現提示的彈窗了。顯然這樣對使用者來說體驗并不是很友好,你可以在使用者點選拒絕後進行彈窗引導使用者去設定中打開此權限等。具體的需求具體對待。這裡我們稍微修改下代碼,實作剛才所說的效果

private void permission(){
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){
            if(ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)){
                //當拒絕了授權後,為提升使用者體驗,可以以彈窗的方式引導使用者到設定中去進行設定
                new AlertDialog.Builder(MainActivity.this)
                        .setMessage("需要開啟權限才能使用此功能")
                        .setPositiveButton("設定", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                //引導使用者到設定中去進行設定
                                Intent intent = new Intent();
                                intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
                                intent.setData(Uri.fromParts("package", getPackageName(), null));
                                startActivity(intent);

                            }
                        })
                        .setNegativeButton("取消", null)
                        .create()
                        .show();
            }else{
                //沒有授權
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, PERMISSION_REQUESTCODE);
            }
        }else{
            //已經授權
            call();
        }
    }
           

效果圖

Android 6.0+ 動态權限 一種清爽的封裝過程(以及多個權限的處理)

接下來我們來處理多個權限的情況

多個權限的處理

比較優雅的寫法是,我們建立一個List集合,用于存放使用者沒有授權的權限。當我們拒絕其中的某一個權限時,再次點選申請時,彈出的對話框中隻會出現被使用者拒絕的權限申請,使用者已經授權過的則不再顯示。看下代碼

public class MainActivity extends AppCompatActivity {

    private Button btn;
    private static final int PERMISSION_REQUESTCODE = ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化控件
        iniViews();
    }

    private void iniViews() {
        btn = (Button) findViewById(R.id.btn_call);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                permission();
            }
        });
    }

    private void permission(){
        List<String> permissionLists = new ArrayList<>();
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){
            permissionLists.add(Manifest.permission.CALL_PHONE);
        }
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED){
            permissionLists.add(Manifest.permission.CAMERA);
        }

        if(!permissionLists.isEmpty()){//說明肯定有拒絕的權限
            ActivityCompat.requestPermissions(this, permissionLists.toArray(new String[permissionLists.size()]), PERMISSION_REQUESTCODE);
        }else{
            Toast.makeText(this, "權限都授權了,可以搞事情了", Toast.LENGTH_SHORT).show();
        }
    }

    public void call(){
        try {
            Intent intent = new Intent(Intent.ACTION_CALL);
            Uri uri = Uri.parse("tel:" + "10086");
            intent.setData(uri);
            startActivity(intent);
        }catch (SecurityException e){
            e.printStackTrace();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode){
            case PERMISSION_REQUESTCODE:
                if(grantResults.length > ){
                    for(int grantResult : grantResults){
                        if(grantResult != PackageManager.PERMISSION_GRANTED){
                            Toast.makeText(this, "某一個權限被拒絕了", Toast.LENGTH_SHORT).show();
                            return;
                        }
                    }
                    //其他邏輯(這裡當權限都同意的話就執行打電話邏輯)
                    call();
                }
                break;
            default:
                break;
        }
    }
}
           

來張效果圖

Android 6.0+ 動态權限 一種清爽的封裝過程(以及多個權限的處理)

接下來,我們開始對其進行封裝處理

對權限進行封裝,讓使用變得更加簡潔

假如,我們有好幾個頁面都需要動态申請權限,那麼我們豈不是要每個頁面都寫這些類似的代碼。顯然,我們可以将這些相似代碼抽出來封裝到我們的BaseActivity中。這樣,使用起來就清爽多了

下面直接給出BaseActivity中的代碼

/**
 * 封裝動态權限
 */
public class BaseActivity extends AppCompatActivity{

    private PermissionListener mListener;
    private static final int PERMISSION_REQUESTCODE = ;

    @Override
    public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
        super.onCreate(savedInstanceState, persistentState);

    }

    public void requestRunPermisssion(String[] permissions, PermissionListener listener){
        mListener = listener;
        List<String> permissionLists = new ArrayList<>();
        for(String permission : permissions){
            if(ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED){
                permissionLists.add(permission);
            }
        }

        if(!permissionLists.isEmpty()){
            ActivityCompat.requestPermissions(this, permissionLists.toArray(new String[permissionLists.size()]), PERMISSION_REQUESTCODE);
        }else{
            //表示全都授權了
            mListener.onGranted();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode){
            case PERMISSION_REQUESTCODE:
                if(grantResults.length > ){
                    //存放沒授權的權限
                    List<String> deniedPermissions = new ArrayList<>();
                    for(int i = ; i < grantResults.length; i++){
                        int grantResult = grantResults[i];
                        String permission = permissions[i];
                        if(grantResult != PackageManager.PERMISSION_GRANTED){
                            deniedPermissions.add(permission);
                        }
                    }
                    if(deniedPermissions.isEmpty()){
                        //說明都授權了
                        mListener.onGranted();
                    }else{
                        mListener.onDenied(deniedPermissions);
                    }
                }
                break;
            default:
                break;
        }
    }
}
           

我們定義的處理已授權、以及未授權的回調接口

/**
 * 已授權、未授權的接口回調
 */
public interface PermissionListener {

    void onGranted();//已授權

    void onDenied(List<String> deniedPermission);//未授權

}
           

再看看我們的MainActivity是如何調用的。隻需要繼承我們的BaseActivity即可

public class MainActivity extends BaseActivity {

    private Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化控件
        iniViews();
    }

    private void iniViews() {
        btn = (Button) findViewById(R.id.btn_call);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                permission();
            }
        });
    }

    private void permission(){
        requestRunPermisssion(new String[]{Manifest.permission.CALL_PHONE, Manifest.permission.CAMERA}, new PermissionListener() {
            @Override
            public void onGranted() {
                //表示所有權限都授權了
                Toast.makeText(MainActivity.this, "所有權限都授權了,可以搞事情了", Toast.LENGTH_SHORT).show();
                //我們可以執行打電話的邏輯
                call();
            }

            @Override
            public void onDenied(List<String> deniedPermission) {
                for(String permission : deniedPermission){
                    Toast.makeText(MainActivity.this, "被拒絕的權限:" + permission, Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    public void call(){
        try {
            Intent intent = new Intent(Intent.ACTION_CALL);
            Uri uri = Uri.parse("tel:" + "10086");
            intent.setData(uri);
            startActivity(intent);
        }catch (SecurityException e){
            e.printStackTrace();
        }
    }

}
           

最後再來張效果圖

Android 6.0+ 動态權限 一種清爽的封裝過程(以及多個權限的處理)

上面的封裝是針對Activity頁面的,如果我們想在其他類中也是用此權限封裝的方法,我們可以這麼幹,修改BaseActivity中的代碼

public class BaseActivity extends AppCompatActivity{

    private static PermissionListener mListener;
    private static final int PERMISSION_REQUESTCODE = ;

    @Override
    public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
        super.onCreate(savedInstanceState, persistentState);

        ActivityUtil.addActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityUtil.removeActivity(this);
    }

    public static void requestRunPermisssion(String[] permissions, PermissionListener listener){
        Activity topActivity = ActivityUtil.getTopActivity();
        if(topActivity == null){
            return;
        }
        mListener = listener;
        List<String> permissionLists = new ArrayList<>();
        for(String permission : permissions){
            if(ContextCompat.checkSelfPermission(topActivity, permission) != PackageManager.PERMISSION_GRANTED){
                permissionLists.add(permission);
            }
        }

        if(!permissionLists.isEmpty()){
            ActivityCompat.requestPermissions(topActivity, permissionLists.toArray(new String[permissionLists.size()]), PERMISSION_REQUESTCODE);
        }else{
            //表示全都授權了
            mListener.onGranted();
        }
    }
           

Activity的管理工具類

/**
 * 管理Activity的工具類
 */
public class ActivityUtil {

    private static List<Activity> activityList = new ArrayList<>();

    public static void addActivity(Activity activity){
        activityList.add(activity);
    }

    public static void removeActivity(Activity activity){
        activityList.remove(activity);
    }

    public static Activity getTopActivity(){
        if(activityList.isEmpty()){
            return null;
        }else{
            return activityList.get(activityList.size() - );
        }
    }
}
           

在其他類中調用的話直接通過BaseActivity.requestRunPermisssion()即可。然而這種方式下,如果activity被意外銷毀了,就會導緻記憶體溢出等問題。如果想避免這種問題,我們可以使用上面的介紹,隻在activity頁面中使用即可。如果既想能在其他類中使用,又想盡量沒有别的錯誤,我們可以到github上找一些寫的不錯的第三方架構。用起來也十分友善

最後非常感謝郭霖大神,郭大牛還推薦了一個比較不錯的管理權限的第三方架構RxPermissions,另外還有PermissionsDispatcher