Android之Android 6.0權限機制及開發流程詳解

權限機制變更的背景

在Android6.0之前,app安裝時會提示用戶此app需要使用哪些權限,但用戶不能單獨對某項權限進行授權或拒絕,只要用戶選擇了安裝,即表示用戶接受了app對這些權限的使用,如果用戶不希望app獲取某些涉及隱私的信息,例如讀取通訊錄,讀取短信,獲取地理位置等,只能選擇不安裝。

在這套權限機制下,用戶只能在安裝應用和拒絕權限之間二選一,選擇拒絕權限就意味着不能使用此應用,這樣做的代價太大,和用戶下載此應用的初衷相違背,大多數時候用戶只能選擇妥協,而安裝了應用則意味着將個人隱私信息完全暴露給了應用。當用戶習慣了這種方式之後,在應用安裝時基本都不會再關注提示的權限信息,因此Android的這套權限機制並沒有真正的起到權限管理和保護信息的作用。

新的權限管理機制

從Android6.0開始,Android引入了新的權限管理機制,將應用可使用的權限劃分成了兩類,一類是normal permissions,也就是普通權限,例如訪問網絡,創建快捷方式,開啓閃光燈等 ,這類權限一般不涉及用戶隱私,另一類是dangerous permissions,例如撥打電話,讀取通訊錄,讀取短信,獲取地理位置等。對normal permissions,仍然和以前一樣,開發者只需要在AndroidManifest中配置即可,應用安裝時提示用戶所需的權限,用戶同意安裝即表示授權應用使用這些權限。對dangerous permissions這類涉及用戶隱私的權限,不僅需要在AndroidManifest中配置,還需要在運行時請求用戶授權,用戶這時可以單獨允許或拒絕某項權限。當用戶選擇了拒絕某項權限時,應用將無法執行該權限對應的api。

通過引入這套新的權限管理機制,用戶在權限管理上有了更高的自由度,用戶不再需要爲了限制某項信息不被獲取而捨棄整個應用的使用權。對涉及用戶隱私的這類操作,用戶可以選擇拒絕,而應用的其他功能又不受影響。

運行時權限申請流程

dangerous permissions運行時的權限申請主要用到如下幾個API。

  1. Context.checkSelfPermission(String permission) 檢查是否被授予了某個權限
  2. Activity.requestPermissions(String[] permissions, int requestCode) 申請一組權限
  3. Activity.shouldShowRequestPermissionRationale(String permission) 判斷是否需要顯示申請此權限的原因,在應用第一次申請某個權限,或者用戶對該權限請求授權界面選擇了不再顯示時此方法返回false,否則返回true。
  4. Activity.onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 權限申請結果回調

這四個都是從Android 6.0系統 (API Level 21)纔開始有的new API,因此使用前都需要判斷當前系統的版本是否是Android 6.0以上。

完整的權限申請流程如下,虛線表示這是一個異步的過程。

這裏寫圖片描述

官網資料: 
https://developer.android.com/intl/zh-cn/training/permissions/requesting.html 
https://developer.android.com/reference/android/content/pm/PermissionInfo.html 
https://developer.android.com/intl/zh-cn/guide/topics/security/permissions.html#normal-dangerous

其他資料: 
http://www.codeceo.com/article/android-6-runtime.html 
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0830/3387.html

運行時權限申請注意事項

  1. requestPermissions()的第一個參數是一個數組,可以一次申請多個權限。
  2. 如果一次申請了多個權限,權限請求對話框的彈出順序是按照數組的順序來的,數組前面的權限會先讓用戶確認。一般來說,將必要的權限放在數組前面,輔助的權限放在數組後面。這樣可以增加必要權限申請的成功率。
  3. 如果一次申請了多個權限,只有所有的權限被用戶處理(拒絕或接受)後,onRequestPermissionsResult()纔會被回調,不是處理一個回調一次。
  4. 如果一次申請的權限中,部分權限沒有在AndroidManifest.xml中聲明,則不會彈出該權限的請求對話框,只會彈出那些在AndroidManifest.xml中聲明過的權限,等用戶處理完後onRequestPermissionsResult()被回調,那些未在AndroidManifest.xml中聲明的權限請求結果一定是PERMISSION_DENIED。
  5. 如果一次申請的權限中,所有的權限都沒有在AndroidManifest.xml中聲明,則不會彈出任何請求對話框,回調onRequestPermissionsResult()會被立刻執行(這裏立刻的含義是指整個過程中沒有需要用戶交互的地方,不是指onRequestPermissionsResult()會在執行requestPermissions()時就被調用,onRequestPermissionsResult()回調仍然是一個異步的過程),所有權限請求結果都是PERMISSION_DENIED。
  6. 如果一次申請的權限中,部分權限已經被授予,對已經授予的權限並不是忽略,而是仍然會彈出請求對話框,不同的是沒有下次不再提醒的複選框。如果用戶此次選擇了拒絕,則應用將會失去該權限。所以在申請權限前一定要先判斷哪些權限是已經獲得的,已經授予的權限不要再次申請。特別是一次申請多個權限的時候,一定要每次都判斷哪些權限已經獲得了,只申請哪些未被授予的權限。 
    以下是正確的處理方式

    <code class="language-java hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">String [] permissions = {Manifest.permission.READ_PHONE_STATE,
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.RECORD_AUDIO,
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.GET_ACCOUNTS};
    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// getUngrantedPermissions()會遍歷permissions,返回其中未被授予的權限,如果所有權限都被授予,則返回空的數組。</span>
    String [] unGranted = getUngrantedPermissions(permissions);
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (unGranted.length != <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>) {
        requestPermissions(unGranted);
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul>

    以下是錯誤的處理方式

    <code class="language-java hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">String [] permissions = {Manifest.permission.READ_PHONE_STATE,
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.RECORD_AUDIO,
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.GET_ACCOUNTS};
    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// checkPermissions()會遍歷permissions,如果存在未被授予的權限,返回false,所有權限都被授予,返回true</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> isGranted = checkPermissions(permissions);
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (!isGranted) {
        requestPermissions(permissions);
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul>
  7. 如果一個app的targetSdkVersion設置爲23以下,當這個app在Android 6.0系統上運行時,系統會自動爲它授予dangerous的權限。不過用戶仍然可以通過系統設置來取消某項權限。在取消授權時,和targetSdkVersion設置爲23的app不同的是,會多出一個警告提示,告知用戶取消授權可能會導致應用異常。

  8. 如果一個app的targetSdkVersion設置爲23以下,在Android 6.0系統上執行checkSelfPermission()檢查是否有某項權限時,只要在AndroidManifest.xml中聲明瞭該權限,無論當前是否被授予了該權限,返回結果都是PERMISSION_GRANTED。也就是說如果該權限沒有在AndroidManifest.xml中聲明,則checkSelfPermission()返回PERMISSION_DENIED,如果該權限在AndroidManifest.xml中聲明瞭,即使用戶手動禁止了該權限,checkSelfPermission()也會返回PERMISSION_GRANTED。所以,無法通過後checkSelfPermission()來判斷用戶是否禁止了某項權限。
  9. 如果一個app的targetSdkVersion設置爲23以下,在Android 6.0系統上執行requestPermissions(),結果和targetSdkVersion設置爲23的app差不多,唯一不同的對話框中沒有下次不再提醒的複選框。這點和targetSdkVersion設置爲23時申請已經被授予的權限的效果相同。原因應該也是系統認爲所有申請的權限都已經被授予了。
  10. 如果一個app的targetSdkVersion設置爲23以下,在Android 6.0系統上調用一個需要權限的api時,如果這個權限被用戶手動取消了,不會拋出異常。但是該api將什麼也不做,如果有返回值的話會返回null或者0。
  11. requestPermissions()的第二個參數requestCode是一個int類型的整數,用來標識一次請求過程,在onRequestPermissionsResult()中可以通過requestCode來區分此次返回的是哪一次請求的結果,不過一般很少需要這樣去做,所以requestCode的選取並不是很重要,可以隨機取一個整數,也可以用某個固定的整數。需要注意的是,requestCode必須是一個大於等於0的整數。如果傳入了一個小於0的整數,雖然不會有異常,但是也不會有任何效果。不會彈出請求對話框,onRequestPermissionsResult()也不會被執行。因此,如果用隨機整數的話一定要選擇大於等於0的隨機數,如果用固定整數的話,一定不要用小於0的整數。例如,用0xFF000001作爲requestCode是不會有任何效果的。

WRITE_SETTINGS和SYSTEM_ALERT_WINDOW權限申請

從normal permissions (https://developer.android.com/guide/topics/security/normal-permissions.html) 和dangerous permissions (https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous) 列表中可以看到,這兩個列表中都沒有包含WRITE_SETTINGS和SYSTEM_ALERT_WINDOW這兩個權限,也就是說這兩個權限既不屬於normal permission,也不屬於dangerous permission。這是因爲Android認爲這兩個權限非常敏感,已經超出了dangerous permissions的程度,一般app中都不應該使用這兩個權限,因此將這兩個權限單獨分成一類,稱爲special permissions。

這兩個權限在Android6.0系統上同樣需要在運行時申請,不過針對dangerous permissions的運行時權限申請方法對這兩個權限是不適用的,Android單獨製作了一個activity作爲這兩個權限的用戶授權界面,必須通過指定intent,然後通過startActivity(intent)的方式來申請。

special permissions運行時的權限申請主要用到如下幾個api。

  1. Settings.System.canWrite(Context context) 檢查是否被授予了WRITE_SETTINGS權限
  2. Settings.canDrawOverlays(Context context) 檢查是否被授予了SYSTEM_ALERT_WINDOW權限
  3. startActivityForResult(Intent intent, in requestCode) 打開用戶授權界面
  4. onActivityResult(int requestCode, int resultCode, Intent data) 權限申請結果回調

此外還用到兩個字符串常量

  1. Settings.ACTION_MANAGE_WRITE_SETTINGS申請WRITE_SETTINGS權限對應的intent action
  2. Settings.ACTION_MANAGE_OVERLAY_PERMISSION申請SYSTEM_ALERT_WINDOW權限對應的intent action

前兩個API和兩個字符串常量同樣是從Android 6.0系統(API Level 21)纔開始有的,因此使用前都需要判斷當前系統的版本是否是Android 6.0以上。

申請WRITE_SETTINGS權限示例代碼如下。

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 判斷是否有WRITE_SETTINGS權限</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(!Settings.System.canWrite(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>)) {
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 申請WRITE_SETTINGS權限</span>
        Intent intent = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS, 
                                Uri.parse(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"package:"</span> + getPackageName()));
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// REQUEST_CODE1是本次申請的請求碼</span>
        startActivityForResult(intent, REQUEST_CODE1);
    } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
        dosomething();
    }
} <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
    dosomething();
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li></ul>

判斷WRITE_SETTINGS權限申請結果流程示例代碼如下。

<code class="hljs haskell has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">@<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">Override</span>
<span class="hljs-title" style="box-sizing: border-box;">protected</span> void onActivityResult(int requestCode, int resultCode, <span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">Intent</span> <span class="hljs-typedef" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">data</span>) <span class="hljs-container" style="box-sizing: border-box;">{
    // 請求碼是<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">REQUEST_CODE1</span>,表示本次結果是申請<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">WRITE_SETTINGS</span>權限的結果
    <span class="hljs-title" style="box-sizing: border-box;">if</span> (<span class="hljs-title" style="box-sizing: border-box;">requestCode</span> == <span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">REQUEST_CODE1</span>) {
        <span class="hljs-title" style="box-sizing: border-box;">if</span> (<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">Build</span>.<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">VERSION</span>.<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">SDK_INT</span> >= <span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">Build</span>.<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">VERSION_CODES</span>.<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">M</span>) {
            // 判斷是否有<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">WRITE_SETTINGS</span>權限
            <span class="hljs-title" style="box-sizing: border-box;">if</span> (<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">Settings</span>.<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">System</span>.<span class="hljs-title" style="box-sizing: border-box;">canWrite</span>(<span class="hljs-title" style="box-sizing: border-box;">this</span>)) {
                <span class="hljs-title" style="box-sizing: border-box;">dosomething</span>();
            }</span></span>
        }
    }
    super.onActivityResult(requestCode, resultCode, <span class="hljs-typedef" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">data</span>);</span>
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li></ul>

申請SYSTEM_ALERT_WINDOW權限示例代碼如下。

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 判斷是否有SYSTEM_ALERT_WINDOW權限</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span>(!Settings.canDrawOverlays(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>)) {
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 申請SYSTEM_ALERT_WINDOW權限</span>
        Intent intent = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, 
                                Uri.parse(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"package:"</span> + getPackageName()));
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// REQUEST_CODE2是本次申請的請求碼</span>
        startActivityForResult(intent, REQUEST_CODE2);
    } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
        dosomething();
    }
} <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
    dosomething();
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li></ul>

判斷SYSTEM_ALERT_WINDOW權限申請結果流程示例代碼如下。

<code class="hljs haskell has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">@<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">Override</span>
<span class="hljs-title" style="box-sizing: border-box;">protected</span> void onActivityResult(int requestCode, int resultCode, <span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">Intent</span> <span class="hljs-typedef" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">data</span>) <span class="hljs-container" style="box-sizing: border-box;">{
    // 請求碼是<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">REQUEST_CODE2</span>,表示本次結果是申請<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">SYSTEM_ALERT_WINDOW</span>權限的結果
    <span class="hljs-title" style="box-sizing: border-box;">if</span> (<span class="hljs-title" style="box-sizing: border-box;">requestCode</span> == <span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">REQUEST_CODE2</span>) {
        <span class="hljs-title" style="box-sizing: border-box;">if</span> (<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">Build</span>.<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">VERSION</span>.<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">SDK_INT</span> >= <span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">Build</span>.<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">VERSION_CODES</span>.<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">M</span>) {
            // 判斷是否有<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">SYSTEM_ALERT_WINDOW</span>權限
            <span class="hljs-title" style="box-sizing: border-box;">if</span> (<span class="hljs-type" style="box-sizing: border-box; color: rgb(102, 0, 102);">Settings</span>.<span class="hljs-title" style="box-sizing: border-box;">canDrawOverlays</span>(<span class="hljs-title" style="box-sizing: border-box;">this</span>)) {
                  <span class="hljs-title" style="box-sizing: border-box;">dosomething</span>();
            }</span></span>
        }
    }
    super.onActivityResult(requestCode, resultCode, <span class="hljs-typedef" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">data</span>);</span>
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li></ul>

這裏同樣有幾個需要注意的地方

  1. special permission同樣需要先在AndroidManifest中配置,如果未在AndroidManifest中配置,執行startActivityForResult()後仍然會顯示用戶授權界面,不過文字和按鈕都是灰色的,用戶無法更改。
  2. 申請的權限如果已經被授予,執行startActivityForResult()後仍然會顯示用戶授權界面,用戶可以選擇取消授權,所以在執行startActivityForResult()前一定要先判斷是否已經授予了權限。
  3. 多次執行startActivityForResult(),會按照startActivityForResult()的執行順序依次彈出多個用戶授權界面。即使申請的是同一個權限,也是如此。
  4. 和onRequestPermissionsResult()中的第三個參數grantResults的作用不同,onActivityResult()中的resultCode不能用來判斷權限申請的結果,無論用戶是否授予了權限,resultCode始終爲0。
  5. 對這兩個special permission,無論是否已經授予權限,checkSelfPermission()返回的都是PERMISSION_DENIED,如果試圖使用requestPermissions()來申請權限,不會彈出任何權限確認的界面,不過onRequestPermissionsResult()回調方法仍然會被執行,grantResults的結果始終都是PERMISSION_DENIED。因此,試圖用申請dangerous permission的方法來檢查和申請這兩個special permission是沒有任何效果的。

一些額外的話題

Android設備的唯一標識問題

很多app都需要獲取Android設備的唯一標識(UDID),用來作爲臨時的身份認證,後臺的日誌記錄,數據統計等。但由於Android版本和機型衆多,同時又有數量龐大的各種非官方ROM,通過某個單一特徵難以唯一標識一臺設備。因此,通常會獲取多個特徵,然後整合在一起作爲該設備標識。設備的IMEI碼是其中的一個重要特徵,對有電話功能的設備,IMEI碼都是唯一的。國內大量的app都會使用IMEI碼作爲標記用戶身份的一個關鍵信息。 
然而獲取IMEI碼需要READ_PHONE_STATE權限,此權限被Android定義爲dangerous的權限,所以在Android 6.0之後需要在運行時申請。這帶來了如下兩個問題。 
1. 獲取設備唯一標識通常都在進入應用後立刻執行的,而申請權限則是一個異步的過程,用戶可能幾個小時後纔會處理,這使得所有需要此信息的地方都需要放到onRequestPermissionsResult()回調後才能執行。 
2. 由於用戶可以選擇允許和拒絕此權限,如果用戶選擇了允許此權限,則可以獲取到IMEI碼,如果用戶選擇了拒絕,則無法獲取。這意味着同一個設備,用戶的不同選擇會產生兩個不同的設備識別碼(注意:用戶選擇後可以隨時在系統裏面更改是否授予此權限)。如果設備識別碼只是用來記錄一些用戶日誌,可能不會有太大問題,只是同一個用戶產生了兩份用戶日誌。但是如果將設備識別碼作爲登陸時的身份認證,則可能會產生一些問題。用戶選擇允許或拒絕權限就會變成兩個不同的用戶。

爲了避免此類問題,建議調整設備識別碼的計算方式,對Android 6.0及以上版本的設備不再將IMEI碼作爲設備識別碼(或設備識別碼的一部分)。另一個常用的作爲設備識別碼的信息是ANDROID ID,這也是Google官方推薦的設備標識。不過在早期的Android版本中,ANDROID ID的設置存在一些bug,此外幾年前有些國內手機廠家出廠時會將同一個批次的所有手機用同一個ANDROID ID。這些問題使得ANDROID ID在早期的版本上不是很可靠,不過目前這些問題應該都已得到解決。ANDROID ID的bug Google早已修復,國內幾個大廠應該也不會再犯這種錯誤。因此,對Android 6.0及以上版本使用ANDROID ID已經完全可以唯一標識一個設備。對Android 6.0以下版本,仍然可以使用原先的混合多個信息的方式。

相關文章: 
http://technet.weblineindia.com/mobile/getting-unique-device-id-of-an-android-smartphone/ 
http://developer.android.com/intl/zh-cn/reference/android/provider/Settings.Secure.html#ANDROID_ID 
http://stackoverflow.com/questions/2785485/is-there-a-unique-android-device-id?rq=1

第三方SDK的問題

一個功能完整的app通常需要接入多個第三方sdk,如地圖,推送,統計,社交,廣告,渠道等。目前國內大量的第三方sdk仍然是在低版本上開發,沒有兼容Android 6.0。當sdk中的代碼需要使用某個權限時,沒有經過權限檢查,權限申請的流程。當集成了這些sdk的app在Android 6.0系統上運行時, 如果此時應用沒有被授予對應的權限,就會導致程序異常。

對app開發者來說。可以嘗試以下幾個方法。 
1. 將targetSDKVersion設置爲Android 23以下。由於Android 6.0系統會爲targetSDKVersion爲23以下的app自動授予dangerous權限,這樣就可以避免由於沒有權限導致的異常。不過這種方法並不是萬能的,Google Play現在強制要求所有新提交的apk的targetSDKVersion必須爲23。所以如果要將應用發佈到Google Play, 就不能這樣設置了。此外,雖然Android 6.0系統會爲targetSDKVersion爲23以下的app自動授予dangerous權限,但是用戶仍然可以通過系統設置來禁止某項權限。如果用戶手動禁止了某項權限,仍然會導致程序異常。 
2. app代替sdk申請權限。在sdk api調用之前,在app代碼中增加權限申請流程。待權限申請通過後再去調用sdk的api。這種方法並不能完全解決問題,sdk中很多代碼都是在後臺執行的,不是通過某個api,而是通過某些事件來觸發,沒有辦法在app中知道何時該申請權限,即使是在app啓動時就立刻申請權限,也無法保證sdk中代碼執行時,用戶已經授予了權限。

對sdk開發者來說,應當儘快升級sdk版本,支持Android 6.0的權限機制。

通過intent使用相機的權限問題

通常我們會使用如下代碼來使用系統相機,將拍照保存到指定的文件中。

<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">Uri uri = Uri<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.fromFile</span>(picFile)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
Intent intent = new Intent(MediaStore<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.ACTION</span>_IMAGE_CAPTURE)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
intent<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.putExtra</span>(MediaStore<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.EXTRA</span>_OUTPUT, uri)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
startActivityForResult(intent, REQUEST_CODE)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul>

無論是Android6.0之前的版本,還是Android6.0及之後的版本, 通過這種方式使用相機都不需要在AndroidManifest.xml中聲明CAMERA權限(https://developer.android.com/training/permissions/best-practices.html#perms-vs-intents)。也就是,如果沒有在AndroidManifest.xml中聲明CAMERA權限,那麼這段代碼運行是沒有問題的。但是反過來,如果在AndroidManifest.xml中聲明瞭CAMERA權限(這可能是其他地方代碼或者接入的某個第三方SDK中需要用到CAMERA權限),則這段代碼在Android6.0系統上運行時,會檢查app此時是否已經被授予了使用相機的權限,如果沒有,則會產生SecurityException。 
這個設定看起來非常奇怪,沒有聲明權限可以正常運行,聲明瞭權限卻有可能導致應用崩潰。但是Google就是這樣設計的,只能在代碼中做兼容了。

對app開發者來說,如果app的AndroidManifest.xml中沒有聲明CAMERA權限,則不需要修改。如果app的AndroidManifest.xml中聲明瞭CAMERA權限,則在通過intent啓動相機前需要先判斷是否已經被授予了CAMERA權限,如果沒有,則需要先通過requestPermissions()申請拍照權限,申請到權限後再執行上述代碼。

對sdk開發者來說,由於不知道接入sdk的app是否需要CAMERA權限,所以必須先判斷AndroidManifest.xml中是否聲明瞭CAMERA權限,如果沒有聲明,則直接啓動相機,如果聲明瞭,則同樣是先判斷是否已經被授予了CAMERA權限,如果沒有授予權限,則需要再通過requestPermissions()申請拍照權限。這個流程是通用的,對app開發者來說也是適用的。建議app開發者也採用這個流程,避免出現剛開始項目中沒有用到CAMERA權限,但是後來由於接入第三方sdk等原因加了CAMERA權限,但是卻忘記修改之前的調用相機代碼的情況。

判斷AndroidManifest.xml中是否聲明瞭某項權限的方法如下。

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> <span class="hljs-title" style="box-sizing: border-box;">hasPermissionInManifest</span>(Context context, String permissionName) {
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> String packageName = context.getPackageName();
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">try</span> {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> PackageInfo packageInfo = context.getPackageManager()
                .getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> String[] declaredPermisisons = packageInfo.requestedPermissions;
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (declaredPermisisons != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span> && declaredPermisisons.length > <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>) {
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (String p : declaredPermisisons) {
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (p.equals(permissionName)) {
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>;
                }
            }
        }
    } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">catch</span> (NameNotFoundException e) {

    }
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>;
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li></ul>

參考:http://stackoverflow.com/questions/32789027/android-m-camera-intent-permission-bug

WIFI和藍牙掃描問題

Android 6.0增加了對附近設備掃描的權限限制,如下三個API在調用前都需要先獲取ACCESS_FINE_LOCATION 或者 ACCESS_COARSE_LOCATION權限。 
1. WifiManager.getScanResults() 
2. BluetoothDevice.ACTION_FOUND 
3. BluetoothLeScanner.startScan()

例如,通過BluetoothAdapter.startDiscovery()來搜索附近的藍牙設備,在Android 6.0之前只需要在AndroidManifest中聲明BLUETOOTH和BLUETOOTH_ADMIN權限即可,從Android 6.0之後還需要在AndroidManifest中聲明ACCESS_FINE_LOCATION 或者 ACCESS_COARSE_LOCATION權限。由於這兩個權限屬於dangerous permission,所以還需要在運行時申請該權限,等用戶授權後纔可以通過BluetoothAdapter.startDiscovery()來搜索附近的藍牙設備。如果沒有在AndroidManifest中聲明ACCESS_FINE_LOCATION 或者 ACCESS_COARSE_LOCATION權限,或者沒有得到用戶授權就調用BluetoothAdapter.startDiscovery(),那麼定義的BroadcastReceiver在Android 6.0系統上中是不會收到任何消息的。

參考: 
1. https://developer.android.com/about/versions/marshmallow/android-6.0-changes.html#behavior-hardware-id 
2. http://stackoverflow.com/questions/33052811/since-marshmallow-update-bluetooth-discovery-using-bluetoothadapter-getdefaultad

感謝http://blog.csdn.net/ccpat/article/details/51151863



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