Android Q Beta 6 存储示例

对 Android Q Beta 6 版本上的存储示例,进行一次测试分析并记录


前言

官方给出: Android Q 会继续使用 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE 权限,这些权限与面向用户的存储运行时权限相对应。不过,默认情况下,以 Android Q 为目标平台的应用(以及选择接受这些变更的应用)在访问外部存储设备中的文件时会进入过滤视图。此类应用只能查看特定于应用的目录和特定类型的媒体,因此应用无需请求任何其他用户权限。

环境

编译环境:Ubuntu 19.04 、Android Studio 3.4.2、compileSdkVersion 29、buildToolsVersion “29.0.1”、 targetSdkVersion 29
运行环境:Pixel 2 、Android Version 10 (QPP6.190730.005) 、API 29

过程

效果

界面上有两个按钮,一个写入内容到文件存储,一个是从存储读取文件内容

测试分类

类别 1 传统目录方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 写文件的方法
*/
private fun writeMethod() {

val file = File(Environment.getExternalStorageDirectory(), "测试Android Q文件.txt")
Log.d(tag, "file.exists():${file.exists()} , file.getAbsolutePath(): ${file.absolutePath}")
if (file.exists()) {
file.delete()
val flag = file.createNewFile()
Log.d(tag, "SD卡目录下创建文件是否成功?:$flag")
}
val fw = FileWriter(file)
fw.write("我是测试Android Q文件写入的内容");
fw.close()
Toast.makeText(this@StorageActivity, "SD卡写入内容完成...", Toast.LENGTH_LONG).show()
Log.d(tag, "SD卡写入内容完成...")
}

/**
* 读文件的方法
*/
private fun readMethod() {
val fr = FileReader(Environment.getExternalStorageDirectory().path+"/测试Android Q文件.txt")
val r = BufferedReader(fr)
val result = r.readLine()
Log.d(tag, "SD卡文件里面的内容:$result")
}

不加读写权限

清单文件不添加如下权限,也没有动态请求代码

1
2
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

失败,程序异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
2019-08-13 00:42:47.945 9976-9976/com.xw.androidqtest D/StorageActivity: file.exists():false , file.getAbsolutePath(): /storage/emulated/0/测试Android Q文件.txt
2019-08-13 00:42:47.947 9976-9976/com.xw.androidqtest D/AndroidRuntime: Shutting down VM
2019-08-13 00:42:47.951 9976-9976/com.xw.androidqtest E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.xw.androidqtest, PID: 9976
java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:502)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) 
Caused by: java.io.FileNotFoundException: /storage/emulated/0/测试Android Q文件.txt: open failed: EACCES (Permission denied)
at libcore.io.IoBridge.open(IoBridge.java:496)
at java.io.FileOutputStream.<init>(FileOutputStream.java:235)
at java.io.FileOutputStream.<init>(FileOutputStream.java:186)
at java.io.FileWriter.<init>(FileWriter.java:90)
at com.xw.androidqtest.StorageActivity.writeMethod(StorageActivity.kt:79)
at com.xw.androidqtest.StorageActivity.access$writeMethod(StorageActivity.kt:21)
at com.xw.androidqtest.StorageActivity$onCreate$1.onClick(StorageActivity.kt:31)
at android.view.View.performClick(View.java:7140)
at android.view.View.performClickInternal(View.java:7117)
at android.view.View.access$3500(View.java:801)
at android.view.View$PerformClick.run(View.java:27351)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) 
Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
at libcore.io.Linux.open(Native Method)
at libcore.io.ForwardingOs.open(ForwardingOs.java:167)
at libcore.io.BlockGuardOs.open(BlockGuardOs.java:252)
at libcore.io.ForwardingOs.open(ForwardingOs.java:167)
at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7255)
at libcore.io.IoBridge.open(IoBridge.java:482)
at java.io.FileOutputStream.<init>(FileOutputStream.java:235) 
at java.io.FileOutputStream.<init>(FileOutputStream.java:186) 
at java.io.FileWriter.<init>(FileWriter.java:90) 
at com.xw.androidqtest.StorageActivity.writeMethod(StorageActivity.kt:79) 
at com.xw.androidqtest.StorageActivity.access$writeMethod(StorageActivity.kt:21) 
at com.xw.androidqtest.StorageActivity$onCreate$1.onClick(StorageActivity.kt:31) 
at android.view.View.performClick(View.java:7140) 
at android.view.View.performClickInternal(View.java:7117) 
at android.view.View.access$3500(View.java:801) 
at android.view.View$PerformClick.run(View.java:27351) 
at android.os.Handler.handleCallback(Handler.java:883) 
at android.os.Handler.dispatchMessage(Handler.java:100) 
at android.os.Looper.loop(Looper.java:214) 
at android.app.ActivityThread.main(ActivityThread.java:7356) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

加读写权限但不动态请求

清单文件添加如下权限,但不动态请求

1
2
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

失败,程序异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
2019-08-13 00:47:52.759 11693-11693/com.xw.androidqtest D/StorageActivity: file.exists():false , file.getAbsolutePath(): /storage/emulated/0/测试Android Q文件.txt
2019-08-13 00:47:52.761 11693-11693/com.xw.androidqtest D/AndroidRuntime: Shutting down VM


--------- beginning of crash
2019-08-13 00:47:52.766 11693-11693/com.xw.androidqtest E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.xw.androidqtest, PID: 11693
java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:502)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) 
Caused by: java.io.FileNotFoundException: /storage/emulated/0/测试Android Q文件.txt: open failed: EACCES (Permission denied)
at libcore.io.IoBridge.open(IoBridge.java:496)
at java.io.FileOutputStream.<init>(FileOutputStream.java:235)
at java.io.FileOutputStream.<init>(FileOutputStream.java:186)
at java.io.FileWriter.<init>(FileWriter.java:90)
at com.xw.androidqtest.StorageActivity.writeMethod(StorageActivity.kt:79)
at com.xw.androidqtest.StorageActivity.access$writeMethod(StorageActivity.kt:21)
at com.xw.androidqtest.StorageActivity$onCreate$1.onClick(StorageActivity.kt:31)
at android.view.View.performClick(View.java:7140)
at android.view.View.performClickInternal(View.java:7117)
at android.view.View.access$3500(View.java:801)
at android.view.View$PerformClick.run(View.java:27351)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) 
Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
at libcore.io.Linux.open(Native Method)
at libcore.io.ForwardingOs.open(ForwardingOs.java:167)
at libcore.io.BlockGuardOs.open(BlockGuardOs.java:252)
at libcore.io.ForwardingOs.open(ForwardingOs.java:167)
at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7255)
at libcore.io.IoBridge.open(IoBridge.java:482)
at java.io.FileOutputStream.<init>(FileOutputStream.java:235) 
at java.io.FileOutputStream.<init>(FileOutputStream.java:186) 
at java.io.FileWriter.<init>(FileWriter.java:90) 
at com.xw.androidqtest.StorageActivity.writeMethod(StorageActivity.kt:79) 
at com.xw.androidqtest.StorageActivity.access$writeMethod(StorageActivity.kt:21) 
at com.xw.androidqtest.StorageActivity$onCreate$1.onClick(StorageActivity.kt:31) 
at android.view.View.performClick(View.java:7140) 
at android.view.View.performClickInternal(View.java:7117) 
at android.view.View.access$3500(View.java:801) 
at android.view.View$PerformClick.run(View.java:27351) 
at android.os.Handler.handleCallback(Handler.java:883) 
at android.os.Handler.dispatchMessage(Handler.java:100) 
at android.os.Looper.loop(Looper.java:214) 
at android.app.ActivityThread.main(ActivityThread.java:7356) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

加读写权限但并动态请求

清单文件添加如下权限,并动态请求

1
2
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

private fun initPermission(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// 检查权限状态
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// 用户彻底拒绝授予权限
} else {
// 用户未彻底拒绝授予权限
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)

}
}
}
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 1) {
for (i in permissions.indices) {
if (grantResults[i] == PERMISSION_GRANTED) {
// 申请成功
Log.d(tag,"申请成功")
} else {
// 申请失败
Log.d(tag,"申请失败")
}
}
}
}

失败,程序异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
2019-08-13 00:53:45.690 13471-13471/com.xw.androidqtest D/StorageActivity: file.exists():false , file.getAbsolutePath(): /storage/emulated/0/测试Android Q文件.txt
2019-08-13 00:53:45.692 13471-13471/com.xw.androidqtest D/AndroidRuntime: Shutting down VM


--------- beginning of crash
2019-08-13 00:53:45.698 13471-13471/com.xw.androidqtest E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.xw.androidqtest, PID: 13471
java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:502)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) 
Caused by: java.io.FileNotFoundException: /storage/emulated/0/测试Android Q文件.txt: open failed: EACCES (Permission denied)
at libcore.io.IoBridge.open(IoBridge.java:496)
at java.io.FileOutputStream.<init>(FileOutputStream.java:235)
at java.io.FileOutputStream.<init>(FileOutputStream.java:186)
at java.io.FileWriter.<init>(FileWriter.java:90)
at com.xw.androidqtest.StorageActivity.writeMethod(StorageActivity.kt:79)
at com.xw.androidqtest.StorageActivity.access$writeMethod(StorageActivity.kt:21)
at com.xw.androidqtest.StorageActivity$onCreate$1.onClick(StorageActivity.kt:31)
at android.view.View.performClick(View.java:7140)
at android.view.View.performClickInternal(View.java:7117)
at android.view.View.access$3500(View.java:801)
at android.view.View$PerformClick.run(View.java:27351)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) 
Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
at libcore.io.Linux.open(Native Method)
at libcore.io.ForwardingOs.open(ForwardingOs.java:167)
at libcore.io.BlockGuardOs.open(BlockGuardOs.java:252)
at libcore.io.ForwardingOs.open(ForwardingOs.java:167)
at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7255)
at libcore.io.IoBridge.open(IoBridge.java:482)
at java.io.FileOutputStream.<init>(FileOutputStream.java:235) 
at java.io.FileOutputStream.<init>(FileOutputStream.java:186) 
at java.io.FileWriter.<init>(FileWriter.java:90) 
at com.xw.androidqtest.StorageActivity.writeMethod(StorageActivity.kt:79) 
at com.xw.androidqtest.StorageActivity.access$writeMethod(StorageActivity.kt:21) 
at com.xw.androidqtest.StorageActivity$onCreate$1.onClick(StorageActivity.kt:31) 
at android.view.View.performClick(View.java:7140) 
at android.view.View.performClickInternal(View.java:7117) 
at android.view.View.access$3500(View.java:801) 
at android.view.View$PerformClick.run(View.java:27351) 
at android.os.Handler.handleCallback(Handler.java:883) 
at android.os.Handler.dispatchMessage(Handler.java:100) 
at android.os.Looper.loop(Looper.java:214) 
at android.app.ActivityThread.main(ActivityThread.java:7356) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

类别 2 特定目录方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
   /**
* 写文件的方法
*/
private fun writeMethod() {
//这里我们创建的是一个文本txt,getExternalFilesDir使用Environment.DIRECTORY_DOCUMENTS类型
val file = File(this@StorageActivity.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "测试Android Q文件.txt")
Log.d(tag, "file.exists():${file.exists()} , file.getAbsolutePath(): ${file.absolutePath}")
if (file.exists()) {
file.delete()
val flag = file.createNewFile()
Log.d(tag, "SD卡目录下创建文件是否成功?:$flag")
}
val fw = FileWriter(file)
fw.write("我是测试Android Q文件写入的内容");
fw.close()
Toast.makeText(this@StorageActivity, "SD卡写入内容完成...", Toast.LENGTH_LONG).show()
Log.d(tag, "SD卡写入内容完成...")
}

/**
* 读文件的方法
*/
private fun readMethod() {
//这里我们创建的是一个文本txt,getExternalFilesDir使用Environment.DIRECTORY_DOCUMENTS类型
val fr = FileReader(this@StorageActivity.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)!!.path+"/测试Android Q文件.txt")
val r = BufferedReader(fr)
val result = r.readLine()
Log.d(tag, "SD卡文件里面的内容:$result")
}

不加读写权限

清单文件不添加如下权限,也没有动态请求代码

1
2
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

成功,程序正常运行

1
2
3
2019-08-13 01:01:40.653 14721-14721/com.xw.androidqtest D/StorageActivity: file.exists():false , file.getAbsolutePath(): /storage/emulated/0/Android/data/com.xw.androidqtest/files/Documents/测试Android Q文件.txt
2019-08-13 01:01:40.671 14721-14721/com.xw.androidqtest D/StorageActivity: SD卡写入内容完成...
2019-08-13 01:01:54.410 14721-14721/com.xw.androidqtest D/StorageActivity: SD卡文件里面的内容:我是测试Android Q文件写入的内容

查看设备上应用详情的相关权限模块,并没有相应的权限请求

加读写权限但不动态请求

清单文件添加如下权限,但不动态请求

1
2
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

成功,程序正常运行

1
2
3
4
2019-08-13 01:12:22.104 18040-18040/com.xw.androidqtest D/StorageActivity: file.exists():true , file.getAbsolutePath(): /storage/emulated/0/Android/data/com.xw.androidqtest/files/Documents/测试Android Q文件.txt
2019-08-13 01:12:22.105 18040-18040/com.xw.androidqtest D/StorageActivity: SD卡目录下创建文件是否成功?:true
2019-08-13 01:12:22.116 18040-18040/com.xw.androidqtest D/StorageActivity: SD卡写入内容完成...
2019-08-13 01:12:26.276 18040-18040/com.xw.androidqtest D/StorageActivity: SD卡文件里面的内容:我是测试Android Q文件写入的内容

查看设备上应用详情的相关权限模块,有相应的存储权限请求,为拒绝状态

加读写权限但并动态请求

清单文件添加如下权限,并动态请求

1
2
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

private fun initPermission(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// 检查权限状态
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// 用户彻底拒绝授予权限
} else {
// 用户未彻底拒绝授予权限
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1)

}
}
}
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 1) {
for (i in permissions.indices) {
if (grantResults[i] == PERMISSION_GRANTED) {
// 申请成功
Log.d(tag,"申请成功")
} else {
// 申请失败
Log.d(tag,"申请失败")
}
}
}
}

成功,程序正常运行

1
2
3
4
5
2019-08-13 01:16:06.530 19535-19535/com.xw.androidqtest D/StorageActivity: 申请成功
2019-08-13 01:16:09.301 19535-19535/com.xw.androidqtest D/StorageActivity: file.exists():true , file.getAbsolutePath(): /storage/emulated/0/Android/data/com.xw.androidqtest/files/Documents/测试Android Q文件.txt
2019-08-13 01:16:09.303 19535-19535/com.xw.androidqtest D/StorageActivity: SD卡目录下创建文件是否成功?:true
2019-08-13 01:16:09.316 19535-19535/com.xw.androidqtest D/StorageActivity: SD卡写入内容完成...
2019-08-13 01:16:12.023 19535-19535/com.xw.androidqtest D/StorageActivity: SD卡文件里面的内容:我是测试Android Q文件写入的内容

结论

1.Android Q 如果调用Environment.getExternalStorageDirectory(),需要添加READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE权限,并动态请求权限,同时还需要添加新的清单属性为requestLegacyExternalStorage,否则会出现类别1的测试结果

1
2
3
4
5
6
<manifest ... >
<!-- This attribute is "false" by default on apps targeting Android Q. -->
<application android:requestLegacyExternalStorage="true" ... >
...
</application>
</manifest>

这样添加完成之后,getExternalStorageDirectory方式就能执行成功了。

2、Android Q 如果调用Environment.getExternalFilesDir()。正如官方所言,默认情况下,以 Android Q 为目标平台的应用(以及选择接受这些变更的应用)在访问外部存储设备中的文件时会进入过滤视图。此类应用只能查看特定于应用的目录和特定类型的媒体,因此应用无需请求任何其他用户权限。(类别2测试示例体现)

以上内容简单的对Android Q 存储方式进行简单的示例说明。如果有不足之处,欢迎指正。

0%