Android11文件管理權(quán)限申請(qǐng)?jiān)敿?xì)介紹
Android 11文件管理權(quán)限申請(qǐng)
Android 11文件管理權(quán)限申請(qǐng),為什么需要這個(gè)權(quán)限,因?yàn)樵贏ndroid 11后,無(wú)法直接在SDcard根目錄寫(xiě)文件,Android 11之后要使用分區(qū)存儲(chǔ),但是分區(qū)存儲(chǔ)使用起來(lái)很麻煩,所以可以申請(qǐng)文件管理權(quán)限,這樣就可以隨意讀寫(xiě)SDcard了,寫(xiě)到根目錄也沒(méi)問(wèn)題。
清單文件聲明如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.dazhou.permissionrequestdemo" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" /> <application android:requestLegacyExternalStorage="true"> 。。。 </application> </manifest>
這里除了申請(qǐng)MANAGE_EXTERNAL_STORAGE
權(quán)限之外,同時(shí)也是要申請(qǐng)WRITE_EXTERNAL_STORAGE
權(quán)限的,因?yàn)樵诘陀贏ndroid11的版本就需要使用WRITE_EXTERNAL_STORAGE
權(quán)限,且需要在application
節(jié)點(diǎn)添加android:requestLegacyExternalStorage="true"
,這個(gè)屬性用于Android10版本可以免去分區(qū)存儲(chǔ)。在Android11上,申請(qǐng)了MANAGE_EXTERNAL_STORAGE
權(quán)限之后就不用申請(qǐng)WRITE_EXTERNAL_STORAGE
權(quán)限了,因?yàn)閾碛?code>MANAGE_EXTERNAL_STORAGE權(quán)限應(yīng)該就能任意讀寫(xiě)SDCard了。根據(jù)實(shí)驗(yàn),申請(qǐng)了MANAGE_EXTERNAL_STORAGE
權(quán)限之后,再申請(qǐng)WRITE_EXTERNAL_STORAGE
也是會(huì)彈出權(quán)限申請(qǐng)對(duì)話框的,所以,在Android11上還有沒(méi)有必要申請(qǐng)WRITE_EXTERNAL_STORAGE
權(quán)限,值得思考,但我就懶得去實(shí)驗(yàn)了。
class MainActivity : AppCompatActivity() { private lateinit var launcher: ActivityResultLauncher<Intent> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { Log.i("ABCD", "權(quán)限申請(qǐng)結(jié)果:${it.resultCode == Activity.RESULT_OK}") checkAndroid11FilePermission(this) } findViewById<Button>(R.id.button).setOnClickListener { checkAndroid11FilePermission(this) } } /** 檢查Android 11或更高版本的文件權(quán)限 */ private fun checkAndroid11FilePermission(activity: FragmentActivity) { // Android 11 (Api 30)或更高版本的寫(xiě)文件權(quán)限需要特殊申請(qǐng),需要?jiǎng)討B(tài)申請(qǐng)管理所有文件的權(quán)限 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Environment.isExternalStorageManager()) { Log.i("ABCD","此手機(jī)是Android 11或更高的版本,且已獲得訪問(wèn)所有文件權(quán)限") // TODO requestOtherPermissions() 申請(qǐng)其他的權(quán)限 } else { Log.i("ABCD","此手機(jī)是Android 11或更高的版本,且沒(méi)有訪問(wèn)所有文件權(quán)限") showDialog(activity, """本應(yīng)用需要獲取"訪問(wèn)所有文件"權(quán)限,請(qǐng)給予此權(quán)限,否則無(wú)法使用本應(yīng)用""") { launcher.launch(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)) } } } else { Log.i("ABCD","此手機(jī)版本小于Android 11,版本為:API ${Build.VERSION.SDK_INT},不需要申請(qǐng)文件管理權(quán)限") // TODO requestOtherPermissions() 申請(qǐng)其他的權(quán)限 } } private fun showDialog(activity: FragmentActivity, message: String, okClick: () -> Unit) { AlertDialog.Builder(activity) .setTitle("提示") .setMessage(message) .setPositiveButton("確定") { _, _ -> okClick() } .setCancelable(false) .show() } }
注:在獲取權(quán)限的結(jié)果時(shí),不能使用it.resultCode == Activity.RESULT_OK
來(lái)判斷是否獲得權(quán)限,因?yàn)檫@個(gè)結(jié)果永遠(yuǎn)為false。
Android 11和低版本的存儲(chǔ)權(quán)限結(jié)合
為了兼容低于Android 11的版本,我們把之前的存儲(chǔ)權(quán)限結(jié)合到一起,如下:
class MainActivity : AppCompatActivity() { private lateinit var storagePermissionLauncher: ActivityResultLauncher<String> private lateinit var android11StoragePermissionLauncher: ActivityResultLauncher<Intent> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Android 11或更高的版本 android11StoragePermissionLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { requestStoragePermission(this) } } else { // Android 10或更低的版本 storagePermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> if (isGranted) { Log.i("ABCD", "此手機(jī)是版本低于Android 11,且已獲得存儲(chǔ)權(quán)限") // TODO requestOtherPermissions() 申請(qǐng)其他的權(quán)限 } else { Log.i("ABCD", "此手機(jī)是版本低于Android 11,且沒(méi)有存儲(chǔ)權(quán)限") val desc = if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // 權(quán)限被拒絕 """本應(yīng)用需要獲取"存儲(chǔ)"權(quán)限,請(qǐng)給予此權(quán)限,否則無(wú)法使用本應(yīng)用""" } else { // 權(quán)限被設(shè)置為不再提示 """本App需要使用"存儲(chǔ)"權(quán)限,您需要到設(shè)置中打開(kāi)此權(quán)限,否則無(wú)法使用本app""" } showDialog(this, desc) { storagePermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) } } } } findViewById<Button>(R.id.button).setOnClickListener { requestStoragePermission(this) } } /** 請(qǐng)求存儲(chǔ)權(quán)限 */ private fun requestStoragePermission(activity: FragmentActivity) { Log.i("ABCD", "當(dāng)前手機(jī)版本:API ${Build.VERSION.SDK_INT}") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Android 11 (Api 30)或更高版本的寫(xiě)文件權(quán)限需要特殊申請(qǐng),需要?jiǎng)討B(tài)申請(qǐng)管理所有文件的權(quán)限 if (Environment.isExternalStorageManager()) { Log.i("ABCD","此手機(jī)是Android 11或更高的版本,且已獲得訪問(wèn)所有文件權(quán)限") // TODO requestOtherPermissions() 申請(qǐng)其他的權(quán)限 } else { Log.i("ABCD","此手機(jī)是Android 11或更高的版本,且沒(méi)有訪問(wèn)所有文件權(quán)限") showDialog(activity, """本應(yīng)用需要獲取"訪問(wèn)所有文件"權(quán)限,請(qǐng)給予此權(quán)限,否則無(wú)法使用本應(yīng)用""") { android11StoragePermissionLauncher.launch(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)) } } } else { // Android 10或更低的版本,申請(qǐng)存儲(chǔ)權(quán)限 Log.i("ABCD","此手機(jī)是版本低于Android 11,開(kāi)始申請(qǐng)存儲(chǔ)權(quán)限") storagePermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) } } private fun showDialog(activity: FragmentActivity, message: String, okClick: () -> Unit) { AlertDialog.Builder(activity) .setTitle("提示") .setMessage(message) .setPositiveButton("確定") { _, _ -> okClick() } .setCancelable(false) .show() } }
工具類(lèi)封裝
object StoragePermissionUtil { private lateinit var storagePermissionLauncher: ActivityResultLauncher<String> private lateinit var android11StoragePermissionLauncher: ActivityResultLauncher<Intent> private lateinit var resultCallback: () -> Unit /** * 需求: * androidx.activity,1.2.0 或更高版本。 * androidx.fragment,1.3.0 或更高版本。 * 示例如下: * implementation "androidx.activity:activity-ktx:1.3.0" * implementation "androidx.fragment:fragment-ktx:1.3.6" */ fun registerForActivityResult(activity: FragmentActivity) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Android 11或更高的版本 android11StoragePermissionLauncher = activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { requestStoragePermission(activity, resultCallback) } } else { // Android 10或更低的版本 storagePermissionLauncher = activity.registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> if (isGranted) { Log.i("ABCD", "此手機(jī)是版本低于Android 11,且已獲得存儲(chǔ)權(quán)限") resultCallback() } else { Log.i("ABCD", "此手機(jī)是版本低于Android 11,且沒(méi)有存儲(chǔ)權(quán)限") val desc = if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // 權(quán)限被拒絕 """本應(yīng)用需要獲取"存儲(chǔ)"權(quán)限,請(qǐng)給予此權(quán)限,否則無(wú)法使用本應(yīng)用""" } else { // 權(quán)限被設(shè)置為不再提示 """本App需要使用"存儲(chǔ)"權(quán)限,您需要到設(shè)置中打開(kāi)此權(quán)限,否則無(wú)法使用本app""" } showDialog(activity, desc) { storagePermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) } } } } } /** 請(qǐng)求存儲(chǔ)權(quán)限 */ fun requestStoragePermission(activity: FragmentActivity, resultCallback: () -> Unit) { this.resultCallback = resultCallback Log.i("ABCD", "當(dāng)前手機(jī)版本:API ${Build.VERSION.SDK_INT}") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Android 11 (Api 30)或更高版本的寫(xiě)文件權(quán)限需要特殊申請(qǐng),需要?jiǎng)討B(tài)申請(qǐng)管理所有文件的權(quán)限 if (Environment.isExternalStorageManager()) { Log.i("ABCD","此手機(jī)是Android 11或更高的版本,且已獲得訪問(wèn)所有文件權(quán)限") resultCallback() } else { Log.i("ABCD","此手機(jī)是Android 11或更高的版本,且沒(méi)有訪問(wèn)所有文件權(quán)限") showDialog(activity, """本應(yīng)用需要獲取"訪問(wèn)所有文件"權(quán)限,請(qǐng)給予此權(quán)限,否則無(wú)法使用本應(yīng)用""") { android11StoragePermissionLauncher.launch(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)) } } } else { // Android 10或更低的版本,申請(qǐng)存儲(chǔ)權(quán)限 Log.i("ABCD","此手機(jī)是版本低于Android 11,開(kāi)始申請(qǐng)存儲(chǔ)權(quán)限") storagePermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) } } private fun showDialog(activity: FragmentActivity, message: String, okClick: () -> Unit) { AlertDialog.Builder(activity) .setTitle("提示") .setMessage(message) .setPositiveButton("確定") { _, _ -> okClick() } .setCancelable(false) .show() } }
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) StoragePermissionUtil.registerForActivityResult(this) findViewById<Button>(R.id.button).setOnClickListener { StoragePermissionUtil.requestStoragePermission(this) { Log.i("ABCD", "拿到存儲(chǔ)權(quán)限羅,開(kāi)心^_^") } } } }
Android 11申請(qǐng)管理所有文件權(quán)限的Bug
在申請(qǐng)權(quán)限的時(shí)候,會(huì)彈出系統(tǒng)授權(quán)界面,當(dāng)我們打開(kāi)到如下界面后:
此時(shí),我們把權(quán)限打開(kāi),然后返回到我們Activity是正常的。但是,如果在此界面,我們打開(kāi)權(quán)限后,再關(guān)閉,此后不管你是要再開(kāi)或者不開(kāi),當(dāng)你返回Activity的時(shí)候,整個(gè)App會(huì)瞬間重啟,而且它會(huì)自動(dòng)創(chuàng)建Activity,而且奇怪的時(shí)候,它會(huì)自動(dòng)把結(jié)果回調(diào)到新Activity中注冊(cè)的監(jiān)聽(tīng)器里,而在新Activity中,我們還沒(méi)有點(diǎn)擊按鈕申請(qǐng)權(quán)限呢,所以StoragePermissionUtil.requestStoragePermission(this)
代碼就沒(méi)有被執(zhí)行到,則StoragePermissionUtil
中的resultCallback
變量就是未初始化的,所以授權(quán)結(jié)果就沒(méi)辦法返回到Activity中了,而且由于resultCallback沒(méi)初始化就使用,就會(huì)拋出屬性未初始化的異常。這屬于系統(tǒng)Bug吧感覺(jué),也沒(méi)有什么好的解決方案了,在我的項(xiàng)目中,需要程序一運(yùn)行就自動(dòng)調(diào)用申請(qǐng)權(quán)限的函數(shù),這種情況沒(méi)有辦法能很好的解決,比如可以把resultCallback在registerForActivityResult中進(jìn)行傳遞,這樣能保證不會(huì)出現(xiàn)屬性未初始化的異常,但是按上面的操作,當(dāng)出現(xiàn)App重啟時(shí),會(huì)收到兩次回調(diào)結(jié)果,一次回調(diào)結(jié)果是重啟前調(diào)用的申請(qǐng)回調(diào)到重啟后的注冊(cè)回調(diào)中,一次是重啟后再一次申請(qǐng)權(quán)限所以也能獲得一次回調(diào)。所以,這個(gè)Bug可以不管它羅,如果有用戶非要這樣操作,那就只能讓App崩潰了,管不了那么多了。
到此這篇關(guān)于Android11文件管理權(quán)限申請(qǐng)?jiān)敿?xì)介紹的文章就介紹到這了,更多相關(guān)Android11文件管理權(quán)限內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android App中使用AudioManager類(lèi)來(lái)編寫(xiě)音頻播放器
這篇文章主要介紹了Android App中使用AudioManager類(lèi)來(lái)編寫(xiě)音樂(lè)播放器的方法,文中舉了一個(gè)簡(jiǎn)單的例子實(shí)現(xiàn)了基礎(chǔ)的播放暫停和靜音等功能,需要的朋友可以參考下2016-04-04Android自定義View實(shí)現(xiàn)圓形進(jìn)度條
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)圓形進(jìn)度條,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06Android獲取apk簽名指紋的md5值(防止重新被打包)的實(shí)現(xiàn)方法
這篇文章主要介紹了Android獲取apk簽名指紋的md5值以防止重新被打包的實(shí)現(xiàn)方法,結(jié)合實(shí)例形式詳細(xì)分析了Android獲取apk md5值的常用技巧,需要的朋友可以參考下2016-07-07Android拖動(dòng)條的實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了Android拖動(dòng)條的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09Android中TextView自動(dòng)適配文本大小的幾種解決方案
在布局中使用的話,注意按照你最大的設(shè)備來(lái)設(shè)置字體大小,這樣在小設(shè)備上回自動(dòng)縮放,下面這篇文章主要給大家介紹了關(guān)于Android中TextView自動(dòng)適配文本大小的幾種解決方案,需要的朋友可以參考下2022-06-06Android判斷NavigationBar是否顯示的方法(獲取屏幕真實(shí)的高度)
有些時(shí)候,我們需要知道當(dāng)前手機(jī)上是否顯示了NavigationBar,也就是屏幕底部的虛擬按鍵。這篇文章主要介紹了Android判斷NavigationBar是否顯示的方法(獲取屏幕真實(shí)的高度),需要的朋友可以參考下本文2017-01-01Android開(kāi)源AndroidSideMenu實(shí)現(xiàn)抽屜和側(cè)滑菜單
這篇文章主要為大家詳細(xì)介紹了Android開(kāi)源AndroidSideMenu實(shí)現(xiàn)抽屜和側(cè)滑菜單,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02[Alibaba-ARouter]淺談簡(jiǎn)單好用的Android頁(yè)面路由框架
這篇文章主要介紹了[Alibaba-ARouter]淺談簡(jiǎn)單好用的Android頁(yè)面路由框架,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11