Android11文件管理权限申请
Android 11文件管理權限申請
Android 11文件管理權限申請,為什么需要這個權限,因為在Android 11后,無法直接在SDcard根目錄寫文件,Android 11之后要使用分區存儲,但是分區存儲使用起來很麻煩,所以可以申請文件管理權限,這樣就可以隨意讀寫SDcard了,寫到根目錄也沒問題。
清單文件聲明如下:
<?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" /><applicationandroid:requestLegacyExternalStorage="true">。。。</application></manifest>這里除了申請MANAGE_EXTERNAL_STORAGE權限之外,同時也是要申請WRITE_EXTERNAL_STORAGE權限的,因為在低于Android11的版本就需要使用WRITE_EXTERNAL_STORAGE權限,且需要在application節點添加android:requestLegacyExternalStorage="true",這個屬性用于Android10版本可以免去分區存儲。在Android11上,申請了MANAGE_EXTERNAL_STORAGE權限之后就不用申請WRITE_EXTERNAL_STORAGE權限了,因為擁有MANAGE_EXTERNAL_STORAGE權限應該就能任意讀寫SDCard了。根據實驗,申請了MANAGE_EXTERNAL_STORAGE權限之后,再申請WRITE_EXTERNAL_STORAGE也是會彈出權限申請對話框的,所以,在Android11上還有沒有必要申請WRITE_EXTERNAL_STORAGE權限,值得思考,但我就懶得去實驗了。
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", "權限申請結果:${it.resultCode == Activity.RESULT_OK}")checkAndroid11FilePermission(this)}findViewById<Button>(R.id.button).setOnClickListener {checkAndroid11FilePermission(this)}}/** 檢查Android 11或更高版本的文件權限 */private fun checkAndroid11FilePermission(activity: FragmentActivity) {// Android 11 (Api 30)或更高版本的寫文件權限需要特殊申請,需要動態申請管理所有文件的權限if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {if (Environment.isExternalStorageManager()) {Log.i("ABCD","此手機是Android 11或更高的版本,且已獲得訪問所有文件權限")// TODO requestOtherPermissions() 申請其他的權限} else {Log.i("ABCD","此手機是Android 11或更高的版本,且沒有訪問所有文件權限")showDialog(activity, """本應用需要獲取"訪問所有文件"權限,請給予此權限,否則無法使用本應用""") {launcher.launch(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION))}}} else {Log.i("ABCD","此手機版本小于Android 11,版本為:API ${Build.VERSION.SDK_INT},不需要申請文件管理權限")// TODO requestOtherPermissions() 申請其他的權限}}private fun showDialog(activity: FragmentActivity, message: String, okClick: () -> Unit) {AlertDialog.Builder(activity).setTitle("提示").setMessage(message).setPositiveButton("確定") { _, _ -> okClick() }.setCancelable(false).show()}}注:在獲取權限的結果時,不能使用it.resultCode == Activity.RESULT_OK來判斷是否獲得權限,因為這個結果永遠為false。
Android 11和低版本的存儲權限結合
為了兼容低于Android 11的版本,我們把之前的存儲權限結合到一起,如下:
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", "此手機是版本低于Android 11,且已獲得存儲權限")// TODO requestOtherPermissions() 申請其他的權限} else {Log.i("ABCD", "此手機是版本低于Android 11,且沒有存儲權限")val desc = if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {// 權限被拒絕"""本應用需要獲取"存儲"權限,請給予此權限,否則無法使用本應用"""} else {// 權限被設置為不再提示"""本App需要使用"存儲"權限,您需要到設置中打開此權限,否則無法使用本app"""}showDialog(this, desc) {storagePermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)}}}}findViewById<Button>(R.id.button).setOnClickListener {requestStoragePermission(this)}}/** 請求存儲權限 */private fun requestStoragePermission(activity: FragmentActivity) {Log.i("ABCD", "當前手機版本:API ${Build.VERSION.SDK_INT}")if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {// Android 11 (Api 30)或更高版本的寫文件權限需要特殊申請,需要動態申請管理所有文件的權限if (Environment.isExternalStorageManager()) {Log.i("ABCD","此手機是Android 11或更高的版本,且已獲得訪問所有文件權限")// TODO requestOtherPermissions() 申請其他的權限} else {Log.i("ABCD","此手機是Android 11或更高的版本,且沒有訪問所有文件權限")showDialog(activity, """本應用需要獲取"訪問所有文件"權限,請給予此權限,否則無法使用本應用""") {android11StoragePermissionLauncher.launch(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION))}}} else {// Android 10或更低的版本,申請存儲權限Log.i("ABCD","此手機是版本低于Android 11,開始申請存儲權限")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()}}工具類封裝
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", "此手機是版本低于Android 11,且已獲得存儲權限")resultCallback()} else {Log.i("ABCD", "此手機是版本低于Android 11,且沒有存儲權限")val desc = if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {// 權限被拒絕"""本應用需要獲取"存儲"權限,請給予此權限,否則無法使用本應用"""} else {// 權限被設置為不再提示"""本App需要使用"存儲"權限,您需要到設置中打開此權限,否則無法使用本app"""}showDialog(activity, desc) {storagePermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)}}}}}/** 請求存儲權限 */fun requestStoragePermission(activity: FragmentActivity, resultCallback: () -> Unit) {this.resultCallback = resultCallbackLog.i("ABCD", "當前手機版本:API ${Build.VERSION.SDK_INT}")if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {// Android 11 (Api 30)或更高版本的寫文件權限需要特殊申請,需要動態申請管理所有文件的權限if (Environment.isExternalStorageManager()) {Log.i("ABCD","此手機是Android 11或更高的版本,且已獲得訪問所有文件權限")resultCallback()} else {Log.i("ABCD","此手機是Android 11或更高的版本,且沒有訪問所有文件權限")showDialog(activity, """本應用需要獲取"訪問所有文件"權限,請給予此權限,否則無法使用本應用""") {android11StoragePermissionLauncher.launch(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION))}}} else {// Android 10或更低的版本,申請存儲權限Log.i("ABCD","此手機是版本低于Android 11,開始申請存儲權限")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", "拿到存儲權限羅,開心^_^")}}}}Android 11申請管理所有文件權限的Bug
在申請權限的時候,會彈出系統授權界面,當我們打開到如下界面后:
此時,我們把權限打開,然后返回到我們Activity是正常的。但是,如果在此界面,我們打開權限后,再關閉,此后不管你是要再開或者不開,當你返回Activity的時候,整個App會瞬間重啟,而且它會自動創建Activity,而且奇怪的時候,它會自動把結果回調到新Activity中注冊的監聽器里,而在新Activity中,我們還沒有點擊按鈕申請權限呢,所以StoragePermissionUtil.requestStoragePermission(this)代碼就沒有被執行到,則StoragePermissionUtil中的resultCallback變量就是未初始化的,所以授權結果就沒辦法返回到Activity中了,而且由于resultCallback沒初始化就使用,就會拋出屬性未初始化的異常。這屬于系統Bug吧感覺,也沒有什么好的解決方案了,在我的項目中,需要程序一運行就自動調用申請權限的函數,這種情況沒有辦法能很好的解決,比如可以把resultCallback在registerForActivityResult中進行傳遞,這樣能保證不會出現屬性未初始化的異常,但是按上面的操作,當出現App重啟時,會收到兩次回調結果,一次回調結果是重啟前調用的申請回調到重啟后的注冊回調中,一次是重啟后再一次申請權限所以也能獲得一次回調。所以,這個Bug可以不管它羅,如果有用戶非要這樣操作,那就只能讓App崩潰了,管不了那么多了。
總結
以上是生活随笔為你收集整理的Android11文件管理权限申请的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 教室机房平面图,一套完整的数据中心机房图
- 下一篇: Android实现背景图下拉回弹效果