Android 13執行時許可權變更一覽

語言: CN / TW / HK

本文同步發表於我的微信公眾號,掃一掃文章底部的二維碼或在微信搜尋 郭霖 即可關注,每個工作日都有文章更新。

要不了多久,Android 13正式版就要釋出了。

其實就在幾個月前,我寫了一篇關於Android 13首個開發者體驗版的全面介紹,詳情可以參考 Android 13 Developer Preview一覽

那麼相比於首個開發者體驗版,目前Android 13已經進入了平臺穩定期階段,也就是說API基本已經固定,不會再有什麼大的修改了。

於是我又重新回顧了一遍Android 13的重要新特性和行為變更,發現有一處重大變化在首個開發者體驗版中幾乎沒有提及,那就是Android 13的執行時許可權變更。

因此,今天我就再寫一篇Android 13的執行時許可權變更一覽,帶你全面瞭解Android 13的所有執行時許可權變更。

細化的媒體許可權

Google在Android 13上對本地資料訪問許可權做了更進一步的細化。

只能說Google為了保護使用者隱私已經不遺餘力了,而且今天的這步棋其實已經提前佈局了很久了。

要知道,早在Android 10系統中,Google就禁用了本地檔案通過絕對路徑直接訪問的形式,而是要通過MediaStore API來進行訪問,我們稱這個功能為Scoped Storage。

關於Scoped Storage,我在兩年前就寫過一篇文章進行介紹,詳細請參考 Android 10適配要點,作用域儲存

在這篇文章中,有這樣的一處描述:

Android 10系統針對檔案型別進行了分類,圖片、音訊、影片這三類檔案將可以通過MediaStore API來進行訪問,而其他型別的檔案則需要使用系統的檔案選擇器來進行訪問。

另外,我們的應用程式向媒體庫貢獻的圖片、音訊或影片,將會自動擁有其讀寫許可權,不需要額外申請READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE許可權。而如果你要讀取其他應用程式向媒體庫貢獻的圖片、音訊或影片,則必須要申請READ_EXTERNAL_STORAGE許可權才行。WRITE_EXTERNAL_STORAGE許可權將會在未來的Android版本中廢棄。

這部分描述在Android 13之前看起來基本都是正確的。WRITE_EXTERNAL_STORAGE許可權雖然還沒有被廢棄,但是我們無論在各種場景下幾乎都已經不太可能再用到它了。

然而在Android 13當中,Google為了讓使用者能夠更精細化地管理媒體許可權,反而先對READ_EXTERNAL_STORAGE許可權下手了。

從Android 13開始,如果你的應用targetSdk指定到了33或以上,那麼READ_EXTERNAL_STORAGE許可權就完全失去了作用,申請它將不會產生任何的效果。

與此相對應地,Google新增了READ_MEDIA_IMAGES、READ_MEDIA_VIDEO和READ_MEDIA_AUDIO這3個執行時許可權,分別用於管理手機的照片、影片和音訊檔案。

也就是說,以前只要申請一個READ_EXTERNAL_STORAGE許可權就可以了。現在不行了,得按需申請,使用者從而能夠更加精細地瞭解你的應用到底申請了哪些媒體許可權。

至於申請執行時許可權的程式碼都是同樣的模板,並沒有什麼特別的地方。這裡給大家貼出一個用Activity Result API申請的版本,PermissionX由於我還沒去適配Android 13,暫時還沒法跟大家演示,等適配完成後我會再寫一篇文章。

class MainActivity : AppCompatActivity() {
 

    private val requestPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()) {
  granted ->
        if (granted) {
 
            // User allow the permission.
        } else {
 
            // User deny the permission.
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val requestBtn = findViewById<Button>(R.id.request_btn)
        requestBtn.setOnClickListener {
 
            if (Build.VERSION.SDK_INT >= 33) {
 
                requestPermissionLauncher.launch(Manifest.permission.READ_MEDIA_IMAGES)
            }
        }
    }
}

可以看到,這裡使用ActivityResult API來申請了READ_MEDIA_IMAGES許可權。如果你還沒有了解過Activity Result API的朋友,可以參考這篇文章 Activity Result API詳解,是時候放棄startActivityForResult了

執行效果如下圖所示:

比較奇怪的是,這裡我在程式碼中只申請了讀取照片的許可權,但是截圖上卻顯示我們正在申請讀取照片和影片的許可權。並且我在本地進行了驗證,這兩個許可權確實是會一同授予的。而音訊許可權則不會和它們一同授予,還需要單獨申請才行。

我的猜想是,這兩個許可權都屬於同一個許可權組,所以只要其中一個授予了,另外一個許可權也就自動授予了。但是我在官方文件上沒有找到對此的任何說明,所以在編寫程式碼時請不要基於此行為去做任何的業務邏輯,因為許可權組Google是隨時都可能調整的,我們還是應該按照自己的業務需求,按需申請許可權才對。

另外,為了考慮向下相容性,我們在AndroidManifest.xml中宣告許可權時應該這樣寫:

<manifest ...>
    <!-- Required only if your app targets Android 13. -->
    <!-- Declare one or more the following permissions only if your app needs
    to access data that's protected by them. -->
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

    <!-- Required to maintain app compatibility. -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
                     android:maxSdkVersion="32" />
    <application ...>
        ...
    </application>
</manifest>

可以看到,API 32也就是Android 12及以下系統,我們仍然宣告的是READ_EXTERNAL_STORAGE許可權。從Android 13開始,我們就會使用READ_MEDIA_IMAGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO來替代了。

在程式碼中申請許可權時也應該做出同樣的邏輯處理才行,這裡就不再貼出了。

通知執行時許可權

通知執行時許可權可以說是Android 13的重磅功能之一。這麼多年過去了,Google終於將通知納入了執行時許可權管理。

其實我對通知是比較無感的,主要是因為Google太喜歡在通知上面做文章了。幾乎每年的I/O大會,一定會有一個主題是專門講通知新特性的,時間久了我對通知的變更已經基本免疫,實在學不動了。

但是,今年的變更卻是不得不學,因為再不學的話,你的通知都要發不出去了。

通知欄真是一個讓人又愛又恨的東西,這句話我相信不需要多做解釋,用Android手機的人應該都懂。

在之前的Android系統中,任何一個應用想要發出通知的話都是不需要經過使用者同意的,想發就能發。這就使得我們的手機通知欄經常被一些垃圾通知佔領,真正重要的通知反而可能很難被找到。

為了解決這個問題,Google做出過很多改變與調整。比如說Android 8.0時加入的通知渠道,就是為了幫助使用者更好地過濾有用通知和垃圾通知,具體可以參考這篇文章 Android通知欄微技巧,8.0系統中通知欄的適配

但通知渠道的加入,也只是讓使用者可以更加方便地篩選出那些不感興趣的無用通知和垃圾通知,並予以遮蔽。本質上每個應用程式還是可以在完全不經使用者同意的情況下隨意傳送通知。

而這次Android 13則把通知納入了執行時許可權管理,也就是說,以後想要傳送通知,得要先經過使用者同意授權才行了。

先說一下怎樣在Android 13上申請傳送通知許可權吧,其實和一般的執行時許可權並沒有什麼兩樣。首先在AndroidManifest.xml中對傳送通知許可權進行宣告:

<manifest ...>
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    <application ...>
        ...
    </application>
</manifest>

然後我們再次使用Activity Result API來請求許可權即可:

class MainActivity : AppCompatActivity() {
 

    private val requestPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()) {
  granted ->
        if (granted) {
 
            // User allow the permission.
        } else {
 
            // User deny the permission.
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val requestBtn = findViewById<Button>(R.id.request_btn)
        requestBtn.setOnClickListener {
 
            if (Build.VERSION.SDK_INT >= 33) {
 
                requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
            }
        }
    }
}

執行效果如下圖所示:

從用法上講,申請傳送通知許可權和其他執行時許可權並無差別,但仍有不少細節是值得我們注意的。

其中一個必須要注意的點,POST_NOTIFICATIONS許可權只有在應用程式的targetSdk指定成33或更高時才會有用。

當targetSdk為32及以下時,系統會認為你還沒有為Android 13做好適配工作,此時申請POST_NOTIFICATIONS許可權將不會產生任何效果。相對應地,它會在你首次建立通知渠道時彈出一個如上圖所示的對話方塊。

而如果使用者在此時選擇了Don’t allow,就將沒有機會再次看到這個對話方塊了,也就是使用者永久拒絕了我們傳送通知的許可權。直到以下兩個情況發生:

  • 使用者解除安裝並重新安裝了我們的應用。
  • 我們將targetSdk升級到了33或更高。

另外,當用戶的手機從Android 12升級到了Android 13,已安裝應用的傳送通知能力並不會發生變化。

也就是說,如果使用者在Android 12上將我們應用的通知給遮蔽了,那麼該裝置升級到Android 13時,我們的應用也不會擁有傳送通知許可權。

但只要使用者在Android 12上沒有明確遮蔽我們應用的通知,那麼該裝置升級到Android 13後,我們的應用將會自動被授予傳送通知許可權。

最後,如果要判斷一個執行時許可權有沒有被授權,通常情況下都可以這樣寫:

if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
    == PackageManager.PERMISSION_GRANTED) {
 
    // Permission granted
} else {
 
    // Permission not granted
}

但是這種寫法在判斷髮送通知許可權上面有一個明顯的弊端,因為它只能在Android 13上使用,Android 13以下的系統是沒有POST_NOTIFICATIONS許可權的。

所以如果只是為了判斷我們的應用現在有沒有能力發出通知讓使用者看到,可以使用如下的寫法,將保證在各個系統版本上都是能正常工作的:

val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
if (notificationManager.areNotificationsEnabled()) {
 
    // Permission granted
} else {
 
    // Permission not granted
}

那麼如果我們檢測到傳送通知沒有被授權,同時使用者還將這個許可權永久拒絕了,該怎麼辦呢?

這個話題我準備留到PermissionX升級支援Android 13的時候,專門再寫一篇文章進行介紹。

其他新增許可權

Android 13上最需要我們關注的新增許可權就是以上這些,但它們並不是全部。

還有一些比較小眾的新增許可權可能大家用到的機會很少,這裡就簡單概括一下吧。

去年,Google在Android 12當中新增了幾個藍芽相關的執行時許可權。原因是因為當開發者去訪問一些藍芽相關的介面時,卻需要申請地理位置許可權才行,這就讓一些對隱私敏感的使用者非常反感。

這是一個歷史遺留問題,為了更好地保護使用者隱私,Google在Android 12當中增加了BLUETOOTH_SCAN,BLUETOOTH_ADVERTISE,BLUETOOTH_CONNECT,這3個執行時許可權。這樣當開發者需要訪問藍芽相關的介面時,只需要請求這些藍芽許可權即可。

而在今年的Android 13當中,Google將保護使用者隱私延伸到了WIFI領域。

和藍芽類似,當開發者去訪問一些WIFI相關的介面時,如熱點、WIFI直連、WIFI RTT等,也需要申請地理位置許可權才行。

這其實也是一個歷史遺留問題,使用者肯定無法理解為什麼使用一些WIFI功能時卻需要授權地理位置許可權。

為此,Android 13當中新增了一個NEARBY_WIFI_DEVICES許可權,當再使用以上場景相關的WIFI API時,我們只需申請NEARBY_WIFI_DEVICES許可權即可,從而更好地保護了使用者的隱私。

另外還有一個變化是運動感測器許可權。

之前我們如果想要讀取手機運動感測器的資料,需要申請BODY_SENSORS許可權。而在Android 13當中,Google給BODY_SENSORS許可權又添加了一個只能在前臺使用的限定。

可以看到,在Android 13上申請BODY_SENSORS許可權時,使用者只能授權在前臺使用。

那麼如果我們的應用程式就是要在後臺獲取運動感測器資料怎麼辦呢?別擔心,Android 13又新增了一個BODY_SENSORS_BACKGROUND許可權,申請這個許可權即可。

需要注意的是,申請BODY_SENSORS_BACKGROUND許可權之前必須得要先獲得BODY_SENSORS授權才行,不然申請就是無效的。這個設定有點像當初Android 10增加後臺獲取地理位置許可權的設定。

好了,以上就是Android 13執行時許可權變更一覽,希望對大家有所幫助。

如果想要學習Kotlin和最新的Android知識,可以參考我的新書 《第一行程式碼 第3版》點選此處檢視詳情

關注我的技術公眾號,每天都有優質技術文章推送。

微信掃一掃下方二維碼即可關注: