Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 6.0 運行時權限管理最佳實踐

Android 6.0 運行時權限管理最佳實踐

編輯:關於Android編程

這是一篇遲來的博客,Android M已經發布一年多了(6.0的變化),在Android M中權限系統被重新設計,發生了顛覆性的變化,很多人把握不好這個變化,一是對這個權限策略和套路還沒有摸透,二是沒有一個很好的實踐來支撐,在我的技術開發群裡很多人問我關於權限的問題,往往我都沒有直接回答,因為這個問題不是一兩句說的清楚的,這幾點是今天我寫這篇博客的原因。這裡有一切關於Android運行時權限你需要知道的,包括如何在代碼中實現,如果你以前不知道這些東西,現在來看也為時不晚,我將在詳解之後給你一個最佳的實踐方案。

運行時權限開源庫AndPermission:https://github.com/yanzhenjie/AndPermission。正開始開始之前來幾張我的實例圖:

Activity/Fragment中申請單個權限
Activity/Fragment中申請單個權限

Activity/Fragment中同時申請多個權限
Activity/Fragment中同時申請多個權限

Activity/Fragment中被用戶拒絕後,下次申請時提醒用戶
Activity/Fragment中被用戶拒絕後,下次申請時提醒用戶


關於運行時權限

在舊的權限管理系統中,權限僅僅在App安裝時詢問用戶一次,用戶同意了這些權限App才能被安裝(某些深度定制系統另說),App一旦安裝後就可以偷偷的做一些不為人知的事情了。

在Android6.0開始,App可以直接安裝,App在運行時一個一個詢問用戶授予權限,系統會彈出一個對話框讓用戶選擇是否授權某個權限給App(這個Dialog不能由開發者定制),當App需要用戶授予不恰當的權限的時候,用戶可以拒絕,用戶也可以在設置頁面對每個App的權限進行管理。

特別注意:這個對話框不是開發者調用某個權限的功能時由系統自動彈出,而是需要開發者手動調用,如果你直接調用而沒有去申請權限的話,將會導致App奔潰。

也許你已經開始慌了,這對於用戶來說是好事,但是對於開發者來說我們不能直接調用方法了,我們不得不在每一個需要權限的地方檢查並請求用戶授權,所以就引出了以下兩個問題。

哪些權限需要動態申請

新的權限策略講權限分為兩類,第一類是不涉及用戶隱私的,只需要在Manifest中聲明即可,比如網絡、藍牙、NFC等;第二類是涉及到用戶隱私信息的,需要用戶授權後才可使用,比如SD卡讀寫、聯系人、短信讀寫等。

Normal Permissions

此類權限都是正常保護的權限,只需要在AndroidManifest.xml中簡單聲明這些權限即可,安裝即授權,不需要每次使用時都檢查權限,而且用戶不能取消以上授權,除非用戶卸載App。

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

Dangerous Permissions

所有危險的Android系統權限屬於權限組,如果APP運行在Android 6.0 (API level 23)或者更高級別的設備中,而且targetSdkVersion>=23時,系統將會自動采用動態權限管理策略,如果你在涉及到特殊權限操作時沒有做動態權限的申請將會導致App崩潰,因此你需要注意:

此類權限也必須在Manifest中申明,否則申請時不提使用用戶,直接回調開發者權限被拒絕。 同一個權限組的任何一個權限被授權了,這個權限組的其他權限也自動被授權。例如,一旦WRITE_CONTACTS被授權了,App也有READ_CONTACTS和GET_ACCOUNTS了。 申請某一個權限的時候系統彈出的Dialog是對整個權限組的說明,而不是單個權限。例如我申請READ_EXTERNAL_STORAGE,系統會提示"允許xxx訪問設備上的照片、媒體內容和文件嗎?"。

如果App運行在Android 5.1 (API level 22)或者更迭級別的設備中,或者targetSdkVersion<=22時(此時設備可以是Android 6.0 (API level 23)或者更高),在所有系統中仍將采用舊的權限管理策略,系統會要求用戶在安裝的時候授予權限。其次,系統就告訴用戶App需要什麼權限組,而不是個別的某個權限。

CALENDAR(日歷)
READ_CALENDAR WRITE_CALENDAR CAMERA(相機)
CAMERA CONTACTS(聯系人)
READ_CONTACTS WRITE_CONTACTS GET_ACCOUNTS LOCATION(位置)
ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION MICROPHONE(麥克風)
RECORD_AUDIO PHONE(手機)
READ_PHONE_STATE CALL_PHONE READ_CALL_LOG WRITE_CALL_LOG ADD_VOICEMAIL USE_SIP PROCESS_OUTGOING_CALLS SENSORS(傳感器)
BODY_SENSORS SMS(短信)
SEND_SMS RECEIVE_SMS READ_SMS RECEIVE_WAP_PUSH RECEIVE_MMS STORAGE(存儲卡)
READ_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE

使用adb命令可以查看這些需要授權的權限組:

adb shell pm list permissions -d -g

使用adb命令同樣可以授權/撤銷某個權限:

adb shell pm [grant|revoke] ...

關於運行時權限的一些建議

只請求你需要的權限,減少請求的次數,或用Intent來代替,讓其他的應用來處理。

如果你使用Intent,你不需要設計界面,由第三方的應用來完成所有操作。比如打電話、選擇圖片等。 如果你請求權限,你可以完全控制用戶體驗,自己定義UI。但是用戶也可以拒絕權限,就意味著你的應用不能執行這個特殊操作。

防止一次請求太多的權限或請求次數太多,用戶可能對你的應用感到厭煩,在應用啟動的時候,最好先請求應用必須的一些權限,非必須權限在使用的時候才請求,建議整理並按照上述分類管理自己的權限:

普通權限(Normal PNermissions):只需要在Androidmanifest.xml中聲明相應的權限,安裝即許可。 需要運行時申請的權限(Dangerous Permissions):
必要權限:最好在應用啟動的時候,進行請求許可的一些權限(主要是應用中主要功能需要的權限)。 附帶權限:不是應用主要功能需要的權限(如:選擇圖片時,需要讀取SD卡權限)。

解釋你的應用為什麼需要這些權限:在你調用requestPermissions()之前,你為什麼需要這個權限。

例如,一個攝影的App可能需要使用定位服務,因為它需要用位置標記照片。一般的用戶可能會不理解,他們會困惑為什麼他們的App想要知道他的位置。所以在這種情況下,所以你需要在requestpermissions()之前告訴用戶你為什麼需要這個權限。

使用兼容庫support-v4中的方法

ContextCompat.checkSelfPermission()
ActivityCompat.requestPermissions()
ActivityCompat.shouldShowRequestPermissionRationale()

幾個重要的方法與常量解釋

PackageManager中的兩個常量:

PackageManager.PERMISSION_DENIED:該權限是被拒絕的。 PackageManager.PERMISSION_GRANTED:該權限是被授權的。

Activity中或者Fragment都會有以下幾個方法:

int checkSelfPermission(String)
void requestPermissions(int, String...)
boolean shouldShowRequestPermissionRationale(String)
void onRequestPermissionsResult()

上述四個方法中,前三個方法在support-v4的ActivityCompat中都有,建議使用兼容庫中的方法。最後一個方法是用戶授權或者拒絕某個權限組時系統會回調Activity或者Fragment中的方法。

checkSelfPermission() 檢查權限

檢查某一個權限的當前狀態,你應該在請求某個權限時檢查這個權限是否已經被用戶授權,已經授權的權限重復申請可能會讓用戶產生厭煩。 該方法有一個參數是權限名稱,有一個int的返回值,用這個值與上面提到的兩個常量做比較可判斷檢查的權限當前的狀態。
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS)
        != PackageManager.PERMISSION_GRANTED) {
    // 沒有權限,申請權限。
}else{
    // 有權限了,去放肆吧。
}

requestPermissions() 申請權限

請求用戶授權幾個權限,調用後系統會顯示一個請求用戶授權的提示對話框,App不能配置和修改這個對話框,如果需要提示用戶這個權限相關的信息或說明,需要在調用 requestPermissions() 之前處理,該方法有兩個參數:
int requestCode,會在回調onRequestPermissionsResult()時返回,用來判斷是哪個授權申請的回調。 String[] permissions,權限數組,你需要申請的的權限的數組。 由於該方法是異步的,所以無返回值,當用戶處理完授權操作時,會回調Activity或者Fragment的onRequestPermissionsResult()方法。
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.READ_CONTACTS}, MMM);

onRequestPermissionsResult() 處理權限結果回調

該方法在Activity/Fragment中應該被重寫,當用戶處理完授權操作時,系統會自動回調該方法,該方法有三個參數:
int requestCode,在調用requestPermissions()時的第一個參數。 String[] permissions,權限數組,在調用requestPermissions()時的第二個參數。 int[] grantResults,授權結果數組,對應permissions,具體值和上方提到的PackageManager中的兩個常量做比較。
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MMM: {
            if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 權限被用戶同意,可以去放肆了。
            } else {
                // 權限被用戶拒絕了,洗洗睡吧。
            }
            return;
        }
    }
}

shouldShowRequestPermissionRationale()

望文生義,是否應該顯示請求權限的說明。 第一次請求權限時,用戶拒絕了,調用shouldShowRequestPermissionRationale()後返回true,應該顯示一些為什麼需要這個權限的說明。 用戶在第一次拒絕某個權限後,下次再次申請時,授權的dialog中將會出現“不再提醒”選項,一旦選中勾選了,那麼下次申請將不會提示用戶。 第二次請求權限時,用戶拒絕了,並選擇了“不在提醒”的選項,調用shouldShowRequestPermissionRationale()後返回false。 設備的策略禁止當前應用獲取這個權限的授權:shouldShowRequestPermissionRationale()返回false 。 加這個提醒的好處在於,用戶拒絕過一次權限後我們再次申請時可以提醒該權限的重要性,面得再次申請時用戶勾選“不再提醒”並決絕,導致下次申請權限直接失敗。

綜上所述,整合代碼後:

if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {// 沒有權限。
    if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_CONTACTS)) {
            // 用戶拒絕過這個權限了,應該提示用戶,為什麼需要這個權限。
    } else {
        // 申請授權。
        ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.READ_CONTACTS}, MMM);
    }
}

...

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MMM: {
            if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 權限被用戶同意,可以去放肆了。
            } else {
                // 權限被用戶拒絕了,洗洗睡吧。
            }
            return;
        }
    }
}

運行時權限最佳實踐的套路

總體下來我們應該對運行時權限有一個系統的認識,我總結出了一些套路:

需要區分各種Normal Permissioin和Dangerous Permissions。 判斷多個權限授權回調時需要判斷每一個權限是否全都是被授權了,否則操作不能繼續。 需要請求多個權限時需要挨個檢查是否已經被授權過,沒授權的才去請求,還要檢查這些權限是否需要提示用戶,如果多個權限都需要提示,該如何處理。 上述1 2 3如果在需要在多個頁面 實現,代碼重復。 …

其實問題遠遠不止這些,認真看過文章的人應該會發現,實現代碼比較簡單,但是代碼重復加上需要我們考慮和注意的細節太多了,那麼下面我就為大家介紹一個開源內褲來解決這一系列問題。

AndPermission

這個開源庫名叫AndPermission:https://github.com/yanzhenjie/AndPermission,經過我的實踐是完全解決了上述問題,推薦大家使用,有興趣的朋友可以去star下。

AndroidStudio使用方法,gradle一句話遠程依賴

  com.yanzhenjie
  permission
  1.0.0
  pom
Eclipse 下載jar包,或者下載源碼。

使用介紹

1、申請權限就是這麼簡單

AndPermission.with(this)
    .requestCode(101)
    .permission(Manifest.permission.WRITE_CONTACTS,
        Manifest.permission.READ_SMS,
        Manifest.permission.WRITE_EXTERNAL_STORAGE)
    .send();

只需要在Activity中或者Fragment中直接調用即可,AndPermission自動為你打理好後宮。

2、接受權限回調更簡單
只需要重寫Activity/Fragment的一個方法,然後提供一個授權時回調的方法即可:

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    // 只需要調用這一句,剩下的AndPermission自動完成。
    AndPermission.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}

// 成功回調的方法,用注解即可,裡面的數字是請求時的requestCode。
@PermissionYes(100)
private void getLocationYes() {
    // 申請權限成功,可以去做點什麼了。
    Toast.makeText(this, "獲取定位權限成功", Toast.LENGTH_SHORT).show();
}

// 失敗回調的方法,用注解即可,裡面的數字是請求時的requestCode。
@PermissionNo(100)
private void getLocationNo() {
    // 申請權限失敗,可以提醒一下用戶。
    Toast.makeText(this, "獲取定位權限失敗", Toast.LENGTH_SHORT).show();
}

只需要上面這麼幾句話即可,你就可以大刀闊斧的干了,在總結中提到的各種判斷、復雜的情況AndPermission自動完成。

3、如果你需要在用戶多次拒絕權限後提示用戶

AndPermission.with(this)
    .requestCode(101)
    .permission(Manifest.permission.WRITE_CONTACTS,
        Manifest.permission.READ_SMS,
        Manifest.permission.WRITE_EXTERNAL_STORAGE)
    .rationale(mRationaleListener)
    .send();

private RationaleListener mRationaleListener = new RationaleListener() {
    @Override
    public void showRequestPermissionRationale(int requestCode, final Rationale rationale) {
        new AlertDialog.Builder(RationalePermissionActivity.this)
            .setTitle("友好提醒")
            .setMessage("沒有定位權限將不能為您推薦附近妹子,請把定位權限賜給我吧!")
            .setPositiveButton("好,給你", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.cancel();
                    rationale.resume();// 用戶同意繼續申請。
                }
            })
            .setNegativeButton("我拒絕", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.cancel();
                    rationale.cancel(); // 用戶拒絕申請。
                }
        }).show();
    }
};

這麼做的好處請看上面shouldShowRequestPermissionRationale()方法的介紹。

 
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved