Android6.0 權限適配,比你想的還要簡單(實踐篇)

VeldaCavana 8年前發布 | 50K 次閱讀 安卓開發 Android開發 移動開發

前言

自從升級到Android M以來,最大的改變就是增加了運行時權限RuntimePermission,6.0以上的系統如果沒有做適配,運行了targetSDK=23的App時就會報權限錯誤;當然如果你還沒準備好適配權限,把targetSDK設置成小于23就ok了,不過適配是遲早的。

 

運行時權限

谷歌官方將權限分為了兩類,一個是正常權限(Normal Permissions),這類權限不涉及用戶隱私,是不需要用戶進行授權的,比如訪問網絡,手機震動等。還有一類是危險權限(Dangerous Permissions),一般是涉及到用戶隱私的,需要用戶進行授權,比如操作SD卡的寫入,相機,錄音等。

我們所要關注的就是危險權限,由上圖可以看到這些權限被分為不同的權限組(PermissionGroup),這里需要說明一下,當一個權限組里的任一權限被授權,這個組里的其他權限也都會被授權,比如:READ_EXTERNAL_STORAGE這個讀SD卡的權限被授權了,這時候WRITE_EXTERNAL_STORAGE也同時被授權。

預覽

我們要在保證權限適配的同時,保證代碼的整潔和可讀,最終我們實現的效果是如下

requestCameraPermission(new PermissionHandler() {
            @Override
            public void onGranted() {
                Intent intent = new Intent(); //調用照相機
                intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
                startActivity(intent);
            }

        @Override
        public void onDenied() {

        }
    });</code></pre> 

實踐

關于運行時權限的理論知識就不多說了,網上一搜也是一大把,我們這里著重講如何實踐。當你準備做6.0權限適配的時候,你的第一反應會是:"臥槽,項目中要修改的地方太多了,心中無數個草泥馬。"這個時候你要淡定,其實一切沒有那么復雜

1.打開應用程序設置-權限,比如微信,這里看到的權限就是你將要進行適配的權限,也不會太多

2.分析哪些權限是基礎權限

所謂基礎權限就是你的App普遍都需要用的,比如位置、存儲權限,如果要在項目中適配這兩個權限的話,代碼肯定會被改得面目全非,所以我們把這兩個權限的獲取放在啟動頁去判斷。如果基礎權限沒有授權通過,我們就不讓進入App,基礎權限都不給還用個毛,這么一來適配的工作就簡單多了。

3.上一個使用原生API獲取權限的小栗子

這是最原生的使用方法,可以看到權限的獲取操作和獲取結果不是在同一個地方的,這樣的話對原有代碼的改動還是比較大的,而且操作過程繁瑣,標題不是說"比你想的還要簡單"嗎?

4.這個時候PermissionsDispatcher就要登場了 github

PermissionsDispatcher是一個通過注解在編譯期間生成權限檢查代碼的工具,以最少的改動來讓你的App對權限進行適配。

主要有下面5個注解

<p>@RuntimePermissions 標記需要運行時判斷的類</p> <p>@NeedsPermission 標記需要檢查權限的方法</p> <p>@OnShowRationale 授權提示回調</p> <p>@OnPermissionDenied 授權被拒絕回調</p> <p>@OnNeverAskAgain 授權不再拒絕不再顯示回調</p>

配置

跟ButterKnife和Dagger2一樣,配置方法很簡單

在項目的build.gradle文件中加上:

buildscript {  
  dependencies {
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
  }
}

在module中的build.gradle加上:

apply plugin: 'android-apt'

dependencies {
compile 'com.github.hotchemi:permissionsdispatcher:2.1.3' apt 'com.github.hotchemi:permissionsdispatcher-processor:2.1.3' }</code></pre>

使用方法

@RuntimePermissions
public class PermissionsDispatcherActivity extends AppCompatActivity    implements View.OnClickListener {

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

        setTitle("PermissionsDispatcher");

        findViewById(R.id.btn_camera).setOnClickListener(this);
        findViewById(R.id.btn_call).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_call:
                PermissionsDispatcherActivityPermissionsDispatcher.startCallWithCheck(this);
                break;
            case R.id.btn_camera:
                PermissionsDispatcherActivityPermissionsDispatcher.startCameraWithCheck(this);
                break;
        }
    }

    @NeedsPermission(Manifest.permission.CAMERA)
    void startCamera() {
        Intent intent = new Intent(); //調用照相機
        intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
        startActivity(intent);
    }

    @NeedsPermission(Manifest.permission.CALL_PHONE)
    void startCall(){
        Intent intent = new Intent(Intent.ACTION_CALL);
        Uri data = Uri.parse("tel:10086");
        intent.setData(data);
        startActivity(intent);
    }
}</code></pre> 

1.首先@RuntimePermissions注解需要進行權限判斷的類

2.將需要權限的操作定義在一個方法里,并用 @NeedsPermission(Manifest.permission.CAMERA)表明需要的權限(可以是多個)

3.Make編譯一下,就會生成【當前類名+PermissionsDispatcher】的類,在原本調用的地方調用@NeedsPermission標記的方法,這時候你會發現會對應生成【方法名+WithCheck】的方法

4.如果你需要監聽拒絕后的操作,則使用@OnPermissionDenied,使用方法一樣。

原理

PermissionsDispatcher在編譯期間,對需要權限判斷的方法前后進行修飾,增加權限檢查、獲取邏輯,我們打開生成的代碼看看便知道了,這里邊并沒有什么高深的東西。

封裝

從上面的使用方法來看,增加一個權限判斷需要定義一個方法,如果需要監聽拒絕,則還要定義對應的方法,當需要獲取不同權限的時候代碼就多了。這時,我們就可以把權限代碼抽取到Activity父類中,這里叫BasePermissionActivity, 代碼如下.

/**
 * 權限管理
 * Created by Laiyimin on 2016/8/16.
 */
@RuntimePermissions
public abstract class BasePermissionActivity extends AppCompatActivity {

    /**
     * 權限回調接口
     */
    public abstract class PermissionHandler {
        /**
         * 權限通過
         */
        public abstract void onGranted();

        /**
         * 權限拒絕
         */
        public void onDenied() {
        }
    }

    private PermissionHandler mHandler;


    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        BasePermissionActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
    }

    //-----------------------------------------------------------
    /**
     * 請求相機權限
     *
     * @param permissionHandler
     */
    protected void requestCameraPermission(PermissionHandler permissionHandler) {
        this.mHandler = permissionHandler;
        BasePermissionActivityPermissionsDispatcher.handleCameraPermissionWithCheck(this);
    }


    @NeedsPermission(Manifest.permission.CAMERA)
    void handleCameraPermission() {
        if (mHandler != null)
            mHandler.onGranted();
    }

    @OnPermissionDenied(Manifest.permission.CAMERA)
    void deniedCameraPermission() {
        if (mHandler != null)
            mHandler.onDenied();
    }

    @OnNeverAskAgain(Manifest.permission.CAMERA)
    void OnCameraNeverAskAgain() {
        showDialog("[相機]");
    }

    //-----------------------------------------------------------
    /**
     * 請求電話權限
     *
     * @param permissionHandler
     */
    protected void requestCallPermission(PermissionHandler permissionHandler) {
        this.mHandler = permissionHandler;
        BasePermissionActivityPermissionsDispatcher.handleCallPermissionWithCheck(this);
    }


    @NeedsPermission(Manifest.permission.CALL_PHONE)
    void handleCallPermission() {
        if (mHandler != null)
            mHandler.onGranted();
    }

    @OnPermissionDenied(Manifest.permission.CALL_PHONE)
    void deniedCallPermission() {
        if (mHandler != null)
            mHandler.onDenied();
    }

    @OnNeverAskAgain(Manifest.permission.CALL_PHONE)
    void OnCallNeverAskAgain() {
        showDialog("[電話]");
    }


    public void showDialog(String permission) {
        new AlertDialog.Builder(this)
                .setTitle("權限申請")
                .setMessage("在設置-應用-薈醫醫生-權限中開啟" + permission + "權限,以正常使用薈醫功能")
                .setPositiveButton("去開啟", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                        Uri uri = Uri.fromParts("package", getPackageName(), null);
                        intent.setData(uri);
                        startActivity(intent);

                        dialog.dismiss();
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if (mHandler != null) mHandler.onDenied();
                        dialog.dismiss();
                    }
                })
                .setCancelable(false)
                .show();
    }

}

這里定義了一個PermissionHandler回調接口,同時mHandler保存了當前權限的回調操作(因為同一時間只能有一次權限請求),

protected void requestCameraPermission(PermissionHandler permissionHandler) {
    this.mHandler = permissionHandler;
    BasePermissionActivityPermissionsDispatcher.handleCameraPermissionWithCheck(this);
}


@NeedsPermission(Manifest.permission.CAMERA)
void handleCameraPermission() {
    if (mHandler != null)
        mHandler.onGranted();
}

這里定義好各種權限請求的方法供子類調用,例如requestCameraPermission,在它里邊調用了權限判斷方法,邏輯很簡單,大家看看代碼就明白了。最后在子類中只要調用這個方法就行了。

requestCameraPermission(new PermissionHandler() {
        @Override
        public void onGranted() {
            Intent intent = new Intent(); //調用照相機
            intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
            startActivity(intent);
        }

        @Override
        public void onDenied() {

        }
    });

后續

在App運行過程中,用戶可能手動去關閉權限,如果這個時候正在使用著權限,應用就會掛掉,我們的處理就是讓App自動重啟,讓其在引導頁重新獲取權限,保證軟件后續正常運行。附帶一個CrashHandler類實現了異常重啟,在項目代碼中可以找到。

GitHub地址 https://github.com/a5533348/XPermission

 

來自:http://xdeveloper.cn/android6-0quan-xian-gua-pei-bi-ni-xiang-de-huan-yao-jian-dan-2/

 

Save

 本文由用戶 VeldaCavana 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!