Android 10(Q)

作用域存储

最近手机版本升到了 10,突然发现音频功能无效了。明明解决了动态申请权限,却还是报错 open failed: EACCES (Permission denied) 的问题。这是因为在安卓 Q(10) 开始,就采用存储的分区控制。导致明明已经申请了android.permission.WRITE_EXTERNAL_STORAGE 权限,却还是报错。解决办法:manifest 文件的 applicaiton 标签添加 android:requestLegacyExternalStorage="true" 标识。

分析原因

Android Q 文件存储机制修改成了沙盒模式,有点模仿苹果的意思。APP 只能访问自己目录下的文件和公共媒体文件。对于 Android Q 以下,还是使用老的文件存储方式。Android Q 仍然使用 READ_EXTERNAL_STORAGE 和WRITE_EXTERNAL_STORAGE 作为存储相关运行时权限,但现在即使获取了这些权限,访问外部存储也受到了限制。

android:requestLegacyExternalStorage=”true” 的意思就是使用旧的存储策略,不使用 androidQ 的新策略。但这个不是长久之计,很快将会强制都使用新策略。最好的解决策略就是:

1、需要长期保存并且卸载也不能删除的,使用公有目录。
公有目录:Downloads、Documents、Pictures 、DCIM、Movies、Music、Ringtones等
地址:/storage/emulated/0/Downloads(Pictures) 等(公有目录下的文件不会跟随APP卸载而删除)

2、不需要长期保存的,并且涉及安全问题的,使用私有目录。
APP私有目录

地址:/storage/emulated/0/Android/data/包名/files


深色主题

深色主题除了让眼部在夜间使用时更加舒适之外,还可以减少电量消耗,从而延长手机续航,是一项非常有用的功能。

在 Android 10.0 及以上系统的手机,都可以在 ”设置 -> 显示“ 内对深色主题进行开启和关闭。开启深色主题后,系统的界面风格包括一些内置的应用程序都会变成深色主题的色调。为了让我们的应用程序保持与系统的主题风格一致,接下来对深色主题进行适配。

Force Dark

最简单的一种实现方式是使用 Force Dark,它是一种能让应用程序快速适配深色主题,并且几乎不用编写额外代码的方式。它的工作原理是系统会分析浅色主题应用下的每一层 View,并且在这些 View 绘制到屏幕之前,自动将它们的颜色转换成更加适合深色主题的颜色。(注意,只有原本使用浅色主题的应用才能使用这种方式。)

启用 Force Dark 功能要借助 android:forceDarkAllowed 属性:这个属性是 API 29(Android 10)才开始有的,所以需要进行一些系统差异性编程(values-v29 目录是只有 Android 10.0 及以上的系统才会去读取的,因此这是一种系统差异性编程的实现方式)。

res 目录 -> 新建 values-v29 目录 -> 新建 Values resource file -> 新建文件 styles.xml

styles.xml
1
2
3
4
5
6
7
8
9
10
11
12
// 内容都是从 res/values/styles.xml 中复制过来的。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
// 新增属性,这个属性是 API 29(Android 10)才开始有的。
// 将值设置为 ture,表示允许系统使用 Force Dark 将应用程序强制转换成深色主题。
<item name="android:forceDarkAllowed">true</item>
</style>
</resources>

但实际上,这种简单粗暴的转换方式实现的转换效果通常是不尽如人意的。一般推荐使用更加传统的手动实现方式。

手动实现

更好的方式,应该是针对每一个界面都进行浅色和深色两种主题的界面设计。

关于系统主题,除了常用的深色(Theme.AppCompat.NoActionBar)与浅色(Theme.AppCompat.Light.NoActionBar)主题外,现在有了另一种选择,使用 DayNight(Theme.AppCompat.DayNight.NoActionBar) 主题。

使用 DayNight 主题后,当用户在系统设置中开启深色主题时,应用程序会自动使用深色主题,反之则使用浅色主题。

values/styles.xml
1
2
3
4
5
6
7
8
9
10
11
<resources>

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>

</resources>

还有一些问题,比如当一些控件使用定义在 colors.xml 中的颜色值时,这种指定颜色值引用的方式相当于对控件的颜色进行了硬编码,DayNight 主题是不能对这些颜色进行动态转换的。这时需要进行一些主题差异型编程。

res 目录 -> 新建 values-night 目录 -> 新建 Values resource file -> 新建文件 colors.xml。

1
2
3
4
5
6
7
// 在此文件中指定深色主题下的颜色值
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#303030</color>
<color name="colorPrimaryDark">#232323</color>
<color name="colorAccent">#008577</color>
</resources>

虽说使用主题差异型的编程方式几乎可以解决所有的适配问题,但是在 DayNight 主题下,最好还是尽量减少通过硬编码的方式来指定控件的颜色,而是应该更多地使用能够根据当前主题自动切换颜色的主题属性。比如说黑色文字通常应该衬托在白色的背景下,反之白色文字通常应该衬托在黑色背景下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 使用主题属性会自动根据系统当前的主题模式选择最合适的颜色值呈现给用户
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
// 添加背景
android:background="?android:attr/colorBackground">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Hello world"
android:textSize="40sp"
// 添加字体颜色
android:textColor="?android:attr/textColorPrimary"/>

</FrameLayout>

并且,还可以在浅色主题和深色主题下分别执行不同的代码逻辑。

1
2
3
4
5
6
7
8
9
10
/**
* 判断当前系统是浅色主题还是深色主题,然后可以根据返回值做相应处理。
*/
fun isDarkTheme(context:Context):Boolean{
// 由于 Kotlin 取消了按位运算符的写法,改成了使用英文关键字,因此这里的 and 关键字其实就对应了 Java 中的 & 运算符,
// 而 Kotlin 中的 or 关键字对应了 Java 中的 | 运算符,
// xor 关键字对应了 Java 中的 ^ 运算符。
val flag = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
return flag == Configuration.UI_MODE_NIGHT_YES
}

备注

欢迎关注微信公众号:非也缘也


备注

参考资料

Android 10适配要点,作用域存储

第一行代码(第3版)