第六章 Android 權限問題總結

  • 一、前言
  • 二、權限分類
  • 三、權限申請
  • 四、總結

一、前言

安卓平臺權限一直有被流氓應用隨便利用詬病, android M(SDK 23)的發(fā)布徹底解決了這一問題,取而代之的是,app不得不在運行時一個一個詢問用戶授予權限。

Android 6.0,代號棉花糖,其主要的特征運行時權限就很受關注。因為這一特征不僅改善了用戶對于應用的使用體驗,還使得應用開發(fā)者在實踐開發(fā)中需要做出改變。Android 6.0(api23)系統(tǒng)中,做了一些限制, 開發(fā)者在使用到每條權限時必須自己調用相關代碼請求。如果沒有獲得某項權限,直接使用相關功能,則會導致自己程序crash。

沒有深入了解運行時權限的開發(fā)者通常會有很多疑問,比如什么是運行時權限,哪些是運行時的權限,我的應用是不是會在6.0系統(tǒng)上各種崩潰呢,如何才能支持運行時權限機制呢。本文講嘗試回答這一些問題,希望讀者閱讀完成之后,都能找到較為完美的答案。


二、權限分類

  • Android 6.0以前版本:權限管理總結為一句話是:權限一刀切。
    在6.0以前的系統(tǒng),都是權限一刀切的處理方式,只要用戶安裝,Manifest申請的權限都會被賦予,并且安裝后權限也撤銷不了。

  • Android6.0及以后版本:Android M(SDK23) 進行了權限分類,運行時動態(tài)申請權限。
    盡管Android中有很多權限,但并非所有的權限都是敏感權限,于是6.0系統(tǒng)就對權限進行了分類,一般為下述三類:

正常(Normal)權限
危險(Dangerous)權限
特殊(Particular)權限
  • 正常(Normal)權限

一般權限都是一些系統(tǒng)認為比較權限的權限,流氓應用就是擁有這些權限也干不出多大壞事,Normal 權限會在應用安裝是直接授權。 正常(Normal)權限的列表:

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

上述的權限基本設計的是關于網絡,藍牙,時區(qū),快捷方式等方面,只要在Manifest指定了這些權限,就會被授予,并且不能撤銷。

  • 危險(Dangerous)權限

這些權限都是一些敏感性權限,一些廣告平臺或是流氓應用會用這些權限干一些壞壞的事情,因此系統(tǒng)將這類權限分了幾個類別, 應用每次都要檢測下是否有權限,沒有的化必須彈出對話框申請,只要一個組別中的一個權限得到了授權,整個組的權限都會的到授權。

危險權限實際上才是運行時權限主要處理的對象,這些權限可能引起隱私問題或者影響其他程序運行。Android中的危險權限可以歸為以下幾個分組:

CALENDAR
CAMERA
CONTACTS
LOCATION
MICROPHONE
PHONE
SENSORS
SMS
STORAGE

各個權限分組與其具體的權限,可以參考下圖:


權限分組與其具體的權限

這部分權限也是我們重點在M系統(tǒng)上關注和適配的部分。

6.0的運行時權限,我們最終都是要支持的,通常我們需要使用如下的API:

int checkSelfPermission(String permission) 用來檢測應用是否已經具有權限
void requestPermissions(String[] permissions, int requestCode) 進行請求單個或多個權限
void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 用戶對請求作出響應后的回調

具體如何操作,參考本文:權限申請模塊。


  • 特殊(Particular)權限

特殊權限,顧名思義,就是一些特別敏感的權限,在Android系統(tǒng)中,主要由兩個

SYSTEM_ALERT_WINDOW 設置懸浮窗,進行一些黑科技
WRITE_SETTINGS 修改系統(tǒng)設置

SYSTEM_ALERT_WINDOW and WRITE_SETTINGS, 這兩個權限比較特殊,不能通過代碼申請方式獲取,必須得用戶打開軟件設置頁手動打開,才能授權.

關于上面兩個特殊權限的授權,做法是使用startActivityForResult啟動授權界面來完成。

官方文檔中這樣描述:

There are a couple of permissions that don’t behave like normal and dangerous permissions. SYSTEM_ALERT_WINDOW and WRITE_SETTINGS are particularly sensitive, so most apps should not use them. If an app needs one of these permissions, it must declare the permission in the manifest, and send an intent requesting the user’s authorization. The system responds to the intent by showing a detailed management screen to the user.

例如:請求SYSTEM_ALERT_WINDOW,設置懸浮窗

    private static final int REQUEST_CODE_OVERLAY_PERMISSION = 110;
    private  void requestAlertWindowPermission() {
        if (!Settings.canDrawOverlays(getBaseContext())) {
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
            intent.setData(Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, REQUEST_CODE);
        }
       
    }
  
    protected void onActivityResult1(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE_OVERLAY_PERMISSION) {
            if (Settings.canDrawOverlays(this)) {
                // 已成功授權
            }else{
                // 未授權
            }
        }
    }
    

提示

  • 使用Action Settings.ACTION_MANAGE_OVERLAY_PERMISSION啟動隱式Intent
  • 使用"package:" + getPackageName()攜帶App的包名信息
  • 使用Settings.canDrawOverlays()方法來判斷授權結果
Settings.ACTION_MANAGE_OVERLAY_PERMISSION權限授權頁面

請求修改設置權限 WRITE_SETTINGS

private static final int REQUEST_CODE_WRITE_SETTINGS = 120;
private void requestWriteSettings() {
    Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
    intent.setData(Uri.parse("package:" + getPackageName()));
    startActivityForResult(intent, REQUEST_CODE_WRITE_SETTINGS );
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_WRITE_SETTINGS) {
        if (Settings.System.canWrite(this)) {
            Log.i(LOGTAG, "onActivityResult write settings granted" );
        }
    }
}

提示

上述代碼需要注意的是

  • 使用Action Settings.ACTION_MANAGE_WRITE_SETTINGS 啟動隱式Intent
  • 使用"package:" + getPackageName()攜帶App的包名信息
  • 使用Settings.System.canWrite方法檢測授權結果
    注意:關于這兩個特殊權限,除非需要,否則一般不建議應用申請。

三、權限申請

前面已經提到,系統(tǒng)將這類權限分了幾個類別, 應用每次都要檢測下是否有權限,沒有的化必須彈出對話框申請,只要一個組別中的一個權限得到了授權,整個組的權限都會的到授權。

6.0的運行時權限申請時,通常我們需要使用如下的API:

int checkSelfPermission(String permission) 用來檢測應用是否已經具有權限,參數(shù)就是對應的權限名,如:相機權限是Manifest.permission.CAMERA
void requestPermissions(String[] permissions, int requestCode) 進行請求單個或多個權限
void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 用戶對請求作出響應后的回調

提示:

開發(fā)過程中,我們不是馬上就申請權限,而是先判斷是否已具有權限,方法是int checkSelfPermission(String permission) 。如果沒有權限,才去申請void requestPermissions(String[] permissions, int requestCode) 。然后,拿到回調void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) ,判斷用戶是否授權了。

下面以以一個請求讀取聯(lián)系人的權限為例進行說明:

API的講解就跟著申請權限步驟一起了:

  • 第一步:在AndroidManifest文件中添加需要的權限。

這個步驟和我們之前的開發(fā)并沒有什么變化,試圖去申請一個沒有在AndroidManifest文件中聲明的權限可能會導致程序崩潰。

  • 第二步:檢查權限
if (ContextCompat.checkSelfPermission(thisActivity,
                Manifest.permission.READ_CONTACTS)
        != PackageManager.PERMISSION_GRANTED) {
}else{
    //
}

這里涉及到一個API,ContextCompat.checkSelfPermission,主要用于檢測某個權限是否已經被授予,方法返回值為PackageManager.PERMISSION_DENIED或者PackageManager.PERMISSION_GRANTED。當返回DENIED就需要進行申請授權了。

  • 第三步:申請授權
 ActivityCompat.requestPermissions(thisActivity,
                new String[]{Manifest.permission.READ_CONTACTS},
                MY_PERMISSIONS_REQUEST_READ_CONTACTS);

該方法是異步的,第一個參數(shù)是Context;第二個參數(shù)是需要申請的權限的字符串數(shù)組;第三個參數(shù)為requestCode,主要用于回調的時候檢測??梢詮姆椒鹯equestPermissions以及第二個參數(shù)看出,是支持一次性申請多個權限的,系統(tǒng)會通過對話框逐一詢問用戶是否授權。

  • 第四步:處理權限申請回調
@Override
public void onRequestPermissionsResult(int requestCode,  String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                // permission was granted, yay! Do the
                // contacts-related task you need to do.

            } else {

                // permission denied, boo! Disable the
                // functionality that depends on this permission.
            }
            return;
        }
    }
}

ok,對于權限的申請結果,首先驗證requestCode定位到你的申請,然后驗證grantResults對應于申請的結果,這里的數(shù)組對應于申請時的第二個權限字符串數(shù)組。如果你同時申請兩個權限,那么grantResults的length就為2,分別記錄你兩個權限的申請結果。如果申請成功,就可以做你的事情了~

當然,到此我們的權限申請的步驟,基本介紹就如上述。不過還有個API值得提一下:

// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
        Manifest.permission.READ_CONTACTS)) 
    // Show an expanation to the user *asynchronously* -- don't block
    // this thread waiting for the user's response! After the user
    // sees the explanation, try again to request the permission.

}

這個API主要用于給用戶一個申請權限的解釋,該方法只有在用戶在上一次已經拒絕過你的這個權限申請。也就是說,用戶已經拒絕一次了,你又彈個授權框,你需要給用戶一個解釋,為什么要授權,則使用該方法。

樂視2授權頁面示意圖.jpg
  • 小結:那么將上述幾個步驟結合到一起就是:
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
                Manifest.permission.READ_CONTACTS)
        != PackageManager.PERMISSION_GRANTED) {

    // Should we show an explanation?
    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
            Manifest.permission.READ_CONTACTS)) {

        // Show an expanation to the user *asynchronously* -- don't block
        // this thread waiting for the user's response! After the user
        // sees the explanation, try again to request the permission.

    } else {

        // No explanation needed, we can request the permission.

        ActivityCompat.requestPermissions(thisActivity,
                new String[]{Manifest.permission.READ_CONTACTS},
                MY_PERMISSIONS_REQUEST_READ_CONTACTS);

        // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
        // app-defined int constant. The callback method gets the
        // result of the request.
    }
}

四、總結

好在運行時相關的API也比較簡單,所以適配起來并不會非常痛苦。對于其他的權限,其實申請的邏輯是類似的;唯一不同的肯定就是參數(shù)。

Android6.0版本最大的特性:權限。對于6.0以下的權限及在安裝的時候,根據(jù)權限聲明產生一個權限列表,用戶只有在同意之后才能完成app的安裝,造成了我們想要使用某個app,就要默默忍受其一些不必要的權限(比如不是每個app都要訪問通訊錄、短信等)。而在6.0以后,我們可以直接安裝,當app需要我們授予不恰當?shù)臋嘞薜臅r候,我們可以予以拒絕。當然你也可以在設置界面對每個app的權限進行查看,以及對單個權限進行授權或者解除授權。

特別提示

在測試過程中,用的錘子·堅果2,發(fā)現(xiàn)不彈授權框。換了部樂視2手機,可以彈出。

所以總結:以下原因不會彈框

  • 6.0以下版本(系統(tǒng)自動申請)
  • 暫時發(fā)現(xiàn)錘子手機、vivo、oppo、魅族的6.0以上版本
    因為這些廠商修改了6.0系統(tǒng)申請機制,他們修改成系統(tǒng)自動申請權限了。也就是說這些系統(tǒng)會跟以前 6.0 以下的版本一樣,需要用到權限的時候系統(tǒng)會自動申請,就算我們主動申請也是沒用的。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容