赞
踩
注意:10.0到12.0的都是些网上看到的资料,我就适配到10.0的文件存储,如有不对的,可以留言,我会查阅修改,谢谢0.0
限制明文传输:
当 SDK 版本大于 API 28 时,默认限制了 HTTP 请求,并出现相关日志
- java.net.UnknownServiceException: CLEARTEXT communication to xxx not permitted
- by network security policy
该问题有两种解决方案:
1、在 AndroidManifest.xml 中 Application 节点添加如下代码
<application android:usesCleartextTraffic="true">
2、在 res 目录新建 xml 目录,已建的跳过,在xml目录新建一个network_security_config.xml文件,然后在AndroidManifest.xml 中 Application 添加如下节点代码。
-
- android:networkSecurityConfig="@xml/network_security_config"
network_security_config.xml:
- <?xml version="1.0" encoding="utf-8"?>
- <network-security-config>
- <base-config cleartextTrafficPermitted="true" />
- </network-security-config>
启动Activity:
在9.0 中,不能直接非 Activity 环境中(比如Service,Application)启动 Activity,否则会崩溃报错,
这类问题一般会在点击推送消息跳转页面这类场景,解决方法就是 Intent 中添加标志FLAG_ACTIVITY_NEW_TASK,
- Intent intent = new Intent(this, TestActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(intent);
适配刘海屏:
使用开源库NotchAdapter,github地址:NotchAdapter
当我们需要以全屏及沉浸的模式显示我们的页面时,就需要适配刘海屏。
1、添加依赖:
- //适配刘海屏的adapter
- implementation 'cn.jerechen:notchAdapter:1.0.3'
2、在需要适配刘海的Activity中:
- //添加 @RequiresApi(api = Build.VERSION_CODES.P) 注解 表示在9.0以上起效
-
- btn_keep_out = findViewById(R.id.btn_keep_out);
- INotchScreen notchScreen = NotchManager.INSTANCE.getNotchScreen();
- if (notchScreen!=null){ //notchScreen不为空代表是刘海屏
- boolean isContainNotch = notchScreen.isContainNotch(this);
- Log.e("MainActivity", "portrait activity isContainNotch : "+isContainNotch);
- notchScreen.getNotchInfo(this, new INotchScreen.NotchInfoCallback() {
- @Override
- public void getNotchRect(Rect rect) {
- Log.e("MainActivity", "Rect Bottom : "+rect.bottom);
- //将被刘海挡住的 btn_keep_out 向下移动一个 刘海高度 距离
- RelativeLayout.LayoutParams lp =
- (RelativeLayout.LayoutParams) btn_keep_out.getLayoutParams();
- //在原有的 topMargin 基础上再加上 刘海屏的高度
- lp.topMargin += rect.bottom;
- btn_keep_out.setLayoutParams(lp);
- }
- });
- }
正常无刘海屏:
9.0手机模拟有刘海屏:
1、Scoped Storage(分区存储)
在Android 10上即使你拥有了储存空间的读写权限,也无法保证可以正常的进行文件的读写操作。
适配
最简单粗暴的方法就是在AndroidManifest.xml中添加android:requestLegacyExternalStorage="true"来请求使用旧的存储模式。
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/AppTheme"
- android:extractNativeLibs="true"
- android:usesCleartextTraffic="true"
- android:requestLegacyExternalStorage="true">
2、权限变化
1)在后台运行时访问设备位置信息需要权限(不建议)
Android 10 引入了 ACCESS_BACKGROUND_LOCATION 权限(危险权限)。
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
该权限允许应用程序在后台访问位置。如果请求此权限,则还必须请求ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION权限。只请求此权限无效果。
官方推荐使用前台服务来实现,在前台服务中获取位置信息。
首先在清单中对应的service中添加 android:foregroundServiceType="location":
- <service
- android:name="MyNavigationService"
- android:foregroundServiceType="location" ... >
- ...
- </service>
启动前台服务前检查是否具有前台的访问权限:
- boolean permissionApproved = ActivityCompat.checkSelfPermission(this,
- Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
-
- if (permissionApproved) {
- // 启动前台服务
- } else {
- // 请求前台访问位置权限
- }
如此一来就可以在Service中获取位置信息。
2)一些电话、蓝牙和WLAN的API需要精确位置权限
3、后台启动 Activity 的限制(不太需要管,问题不大)
简单解释就是应用处于后台时,无法启动Activity。 这一限制导致最明显的问题就是点击推送信息时,有些应用无法进行正常的跳转(具体的实现问题导致)。所以针对这类问题,可以采取PendingIntent的方式,发送通知时使用setContentIntent方法。
对于全屏 intent,注意设置最高优先级和添加USE_FULL_SCREEN_INTENT权限,这是一个普通权限。比如微信来语音或者视频通话时,弹出的接听页面就是使用这一功能。
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
4、深色主题
适配方法有两种:
1)手动适配(资源替换)(建议使用,可以基本达到预想效果。)
将Application和Activity的主题修改为集成自Theme.AppCompat.DayNight或者Theme.MaterialComponents.DayNight,就可以对于大部分的控件得到较好的深色模式支持。
- <application
- ...
- android:theme="@style/AppTheme"
- ...
- >
- <!-- Base application theme. -->
- <!--<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">-->
- <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>
values-night
文件夹,里面是深色模式下的colors.xml
文件例如:
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
- <color name="colorPrimary">#303030</color>
- <color name="colorPrimaryDark">#232323</color>
- <color name="colorAccent">#008577</color>
- </resources>
drawable-night-xxhdpi
图片文件夹按照ui设计给的深色模式图放进这个文件夹,系统就会在识别到深色模式后加载这个文件夹的资源了。
关键工具类NightModeUtil:
- public class NightModeUtil {
-
- /**
- * 当前系统是否是深色模式
- */
- public static boolean isNightMode(Context context) {
- int uiMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
- return uiMode == Configuration.UI_MODE_NIGHT_YES;
- }
-
- /**
- * 获取是否跟随系统,默认true
- */
- public static boolean getSystemMode() {
- return SPUtils.getBoolean(Constants.KEY_MODE_SYSTEM, true);
- }
-
- public static void setSystemMode(boolean nightMode) {
- SPUtils.putBoolean(Constants.KEY_MODE_SYSTEM, nightMode);
- }
-
- /**
- * 获取是否设置深色模式,默认false
- */
- public static boolean getNightMode() {
- return SPUtils.getBoolean(Constants.KEY_MODE_NIGHT, false);
- }
-
- public static void setNightMode(boolean nightMode) {
- SPUtils.putBoolean(Constants.KEY_MODE_NIGHT, nightMode);
- }
-
- public static void initNightMode() {
- initNightMode(getSystemMode(), getNightMode());
- }
-
- /**
- * 初始化App深色模式
- *
- * @param systemMode 是否是跟随系统
- * @param nightMode 是否是深色模式
- */
- public static void initNightMode(boolean systemMode, boolean nightMode) {
- if (systemMode) {
- AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
- } else {
- if (nightMode) {
- AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
- } else {
- AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
- }
- }
- }
-
- /**
- * 重启App
- */
- public static void restartApp(Activity activity) {
- final Intent intent = App.getInstance().getPackageManager().getLaunchIntentForPackage(App.getInstance().getPackageName());
- if (intent != null) {
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
- activity.startActivity(intent);
- android.os.Process.killProcess(android.os.Process.myPid());
- }
- }
- }
NightModeUtil.initNightMode();
- //两个参数分别是 是否跟随系统,是否选择深色模式
- NightModeUtil.initNightMode(dayNightSwitch.isChecked, ctvCheckNight.isChecked);
- NightModeUtil.restartApp(activity);
WebView的深色模式设置
引入implementation 'androidx.webkit:webkit:1.2.0'后可轻易的实现WebView的深色模式,不过有兼容问题,这和WebView的版本有关,WebView版本独立于Android版本。(亲测在系统6.0和7.1上无效。)
在有WebView的Activity的onCarete里加上如下代码:
- WebSettings webSetting = webView.getSettings();
- // 检查是否支持暗模式
- if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
- boolean isAppDarkMode;
- if (NightModeUtil.getSystemMode()) {
- // 是否是跟随系统
- isAppDarkMode = NightModeUtil.isNightMode(this);
- } else {
- isAppDarkMode = NightModeUtil.getNightMode();
- }
- if (isAppDarkMode) {
- WebSettingsCompat.setForceDark(webSetting, WebSettingsCompat.FORCE_DARK_ON);
- } else {
- WebSettingsCompat.setForceDark(webSetting, WebSettingsCompat.FORCE_DARK_OFF);
- }
- }
这就是跟随系统深色模式做的配置了。
效果图如下:
2)自动适配(Force Dark)(不建议使用,效果差。)
应用必须选择启用 Force Dark,方法是在其主题背景中设置 android:forceDarkAllowed="true"。
监听深色主题是否开启
首先在清单文件中给对应的Activity配置 android:configChanges="uiMode":
- <activity
- android:name=".MyActivity"
- android:configChanges="uiMode" />
这样在onConfigurationChanged方法中就可以获取:
- @Override
- public void onConfigurationChanged(@NonNull Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
- switch (currentNightMode) {
- case Configuration.UI_MODE_NIGHT_NO:
- // 关闭
- break;
- case Configuration.UI_MODE_NIGHT_YES:
- // 开启
- break;
- default:
- break;
- }
- }
判断深色主题是否开启
其实和上面onConfigurationChanged方法同理:
- public static boolean isNightMode(Context context) {
- int currentNightMode = context.getResources().getConfiguration().uiMode &
- Configuration.UI_MODE_NIGHT_MASK;
- return currentNightMode == Configuration.UI_MODE_NIGHT_YES;
- }
5、标识符和数据
对不可重置的设备标识符实施了限制
从 Android 10 开始,应用必须具有 READ_PRIVILEGED_PHONE_STATE 特许权限才能正常使用以上这些方法。
1、分区存储
Android 10之前,外部存储区的内容主要以两种形式存在,一种是由应用的包名命名,归属于特定应用目录下的内容,另一种是存储在公共存储区域的内容。
在Android 10 中,Google首次引入了分区存储,将公共区域划分成了不同的集合,并且在媒体文件和其他文档之间建立了清楚的分割。经过划分之后应用不可以随意访问外部存储区中的文件,而只能访问媒体文件。如果想访问包含更多细节数据的其他文档,应用专门向用户申请有关文档的访问权限。
分区存储是需要以 Android 10 为目标平台的,系统默认强制执行。
如果在 AndroidManifest 的application中添加了 requestLegacyExternalStorage=true 标志,就可以不受此限制。
但是当 target API 更新为 30 后,系统会忽略该配置。
如果有数据需要迁移,可以在 AndroidManifest 中将 preserveLegacyExternalStorage 属性设为 true ,当用户升级到以 Android 11 为目标平台时,这个配置就会生效。具体而言,这个配置在用户重新安装该应用前都是有效的。
针对以 Android 11 为目标平台的应用 (targetSdkVersion = 30) ,WRITEEXTENRNALSTORAGE 和 WRITEMEDIASTORAGE 不再提供其他任何访问权限 。
某些应用的核心功能可能需要访问大量的文件,例如文件管理操作、备份和恢复操作等等,此时就需要申请 MANAGEEXTERNALSTORAGE 权限。我们可以通过使用 ACTIONMANAGEALLFILESACCESS_PERMISSION intent 操作将用户引导至一个系统设置页面,让用户为应用授予所有文件的管理权限。
2、应用包可见性
在 Android 11 之前,我们可以通过 PackageManager.getInstalledPackages(0) 获取其他所有应用的包名等信息。
Android 11 为了增加安全性,更好地保护用户的隐私,对应用包的可见性做出了一些改动。
当 targetSdkVersion 为 30 时,如果我们用getPackageInfo(“another.app”,0) 获取其他应用包信息时 ,会出现 NameNotFoundException 的异常。
我们可以在 AndroidMainfest 中添加 <queries style="margin: 0px; padding: 0px;">来适配特定的使用场景:(该配置相当于是添加应用白名单)</queries>
3、权限变化
在 Android 11 中,系统为用户的私人数据提供了更多可供选择的授权方式,应用也加大了后台对位置的访问权限限制。
对应摄像头、位置信息和麦克风这几个数据类型,用户可以授予一次性的临时访问权限。
只要是在Android 11 上,该限制都会生效,如下 :
仅使用期间,仅限这一次等。
这个一次性权限的生效周期指的是:
应用 Activity 可见期间
应用转为后台后的短时间内
前台服务存活期间
当用户撤销单次授权后,应用进程退出,再次打开之后需要对应用进行重新授权期间
4、位置权限
在Android10 之前,我们通过ACCESSCOARRSELOCATION 或 ACCESSFINELOCATION(精确位置) 配置即可申请前后台位置权限。
Android 11将位置权限分为前台和后台两种权限。前文说的主要是前台权限,授权方式没有变化。应用想要申请后台权限,除了需要在清单文件中额外添加 ACCESSBACKGROUNDLOCATION 权限外,还需要应用主动引导用户到指定页面授权。
5、新功能
增加应用退出原因功能
在Android 11之前,我们想要了解应用退出的原因以及状态,都比较费劲。现 Android 11 引入了 方法:ActivityManager.getHistoricalProcessExitReasons() ,
可以让我们清楚地了解到应用退出的原因。
6、可变刷新率
应用和游戏现在可以为其窗口设置首选帧率。大多数 Android 设备以 60Hz 的刷新率更新屏幕,但是某些设备支持多种刷新率,例如 90Hz 和 60Hz,并可在运行时切换。在这些设备上,系统会基于首选帧率来为应用选择最佳刷新率。
targetSdkVersion 31的应用在Android 12上安装时可能会存在两种安装不上的情况。
- adb: failed to install xxx.apk: Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES:
- Scanning Failed.: No signature found in package of version 2 or newer for
- package com.tomes.sharefile]
解决:
targetSdkVersion 30的应用必须使用v2及以上签名
- adb: failed to install xxx.apk: Failure [INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: Failed
- parse during installPackageLI: /data/app/vmdl2054463318.tmp/base.apk (at Binary XML file
- line #49): com.tomes.ShareOpenTestActivity: Targeting S+ (version 10000 and above) requires
- that an explicit value for android:exported be defined when intent filters are present]
我们知道,当我们的应用以Android 12为目标,使用的activity,service,broadcast receiver含有intent-filter,则必须显示声明android:exported属性,如果没有声明,则我们的应用不能安装在Android 12上
解决方法:
声明android:exported属性即可解决。
如我上面的错误,只需要对ShareOpenTestActivity增加android:exported属性申明就好
- <activity android:name=".ShareOpenTestActivity" android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.SEND"/>
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="text/*" />
- </intent-filter>
- </activity>
总结:
targetSdkVersion为31【以Android 12为目标】的应用务必要加入v2签名,务必要对使用的activity,service,broadcast receiver含有intent-filter,显示声明android:exported属性。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。