当前位置:   article > 正文

Android简单适配9.0~12.0_notchscreenmanager

notchscreenmanager

适配6.0~8.0的可以看下这个:

适配Android6.0到8.0详细过程——小白教程

注意:10.0到12.0的都是些网上看到的资料,我就适配到10.0的文件存储,如有不对的,可以留言,我会查阅修改,谢谢0.0

Android 9.0适配:

限制明文传输:

当 SDK 版本大于 API 28 时,默认限制了 HTTP 请求,并出现相关日志

  1. java.net.UnknownServiceException: CLEARTEXT communication to xxx not permitted
  2. by network security policy


该问题有两种解决方案:
1、在 AndroidManifest.xml 中 Application 节点添加如下代码

<application android:usesCleartextTraffic="true">


2、在 res 目录新建 xml 目录,已建的跳过,在xml目录新建一个network_security_config.xml文件,然后在AndroidManifest.xml 中 Application 添加如下节点代码。

  1. android:networkSecurityConfig="@xml/network_security_config"

network_security_config.xml:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <network-security-config>
  3. <base-config cleartextTrafficPermitted="true" />
  4. </network-security-config>

启动Activity:

在9.0 中,不能直接非 Activity 环境中(比如Service,Application)启动 Activity,否则会崩溃报错,
这类问题一般会在点击推送消息跳转页面这类场景,解决方法就是 Intent 中添加标志FLAG_ACTIVITY_NEW_TASK,

  1. Intent intent = new Intent(this, TestActivity.class);
  2. intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  3. startActivity(intent);

适配刘海屏:

使用开源库NotchAdapter,github地址:NotchAdapter

当我们需要以全屏及沉浸的模式显示我们的页面时,就需要适配刘海屏。

1、添加依赖:

  1. //适配刘海屏的adapter
  2. implementation 'cn.jerechen:notchAdapter:1.0.3'

 2、在需要适配刘海的Activity中:

  1. //添加 @RequiresApi(api = Build.VERSION_CODES.P) 注解 表示在9.0以上起效
  2. btn_keep_out = findViewById(R.id.btn_keep_out);
  3. INotchScreen notchScreen = NotchManager.INSTANCE.getNotchScreen();
  4. if (notchScreen!=null){ //notchScreen不为空代表是刘海屏
  5. boolean isContainNotch = notchScreen.isContainNotch(this);
  6. Log.e("MainActivity", "portrait activity isContainNotch : "+isContainNotch);
  7. notchScreen.getNotchInfo(this, new INotchScreen.NotchInfoCallback() {
  8. @Override
  9. public void getNotchRect(Rect rect) {
  10. Log.e("MainActivity", "Rect Bottom : "+rect.bottom);
  11. //将被刘海挡住的 btn_keep_out 向下移动一个 刘海高度 距离
  12. RelativeLayout.LayoutParams lp =
  13. (RelativeLayout.LayoutParams) btn_keep_out.getLayoutParams();
  14. //在原有的 topMargin 基础上再加上 刘海屏的高度
  15. lp.topMargin += rect.bottom;
  16. btn_keep_out.setLayoutParams(lp);
  17. }
  18. });
  19. }

效果如下:

正常无刘海屏:

9.0手机模拟有刘海屏:

Android 10.0适配:

1、Scoped Storage(分区存储)
在Android 10上即使你拥有了储存空间的读写权限,也无法保证可以正常的进行文件的读写操作。
适配
最简单粗暴的方法就是在AndroidManifest.xml中添加android:requestLegacyExternalStorage="true"来请求使用旧的存储模式。

  1. <application
  2. android:allowBackup="true"
  3. android:icon="@mipmap/ic_launcher"
  4. android:label="@string/app_name"
  5. android:roundIcon="@mipmap/ic_launcher_round"
  6. android:supportsRtl="true"
  7. android:theme="@style/AppTheme"
  8. android:extractNativeLibs="true"
  9. android:usesCleartextTraffic="true"
  10. 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":    

  1. <service
  2. android:name="MyNavigationService"
  3. android:foregroundServiceType="location" ... >
  4. ...
  5. </service>

启动前台服务前检查是否具有前台的访问权限:

  1. boolean permissionApproved = ActivityCompat.checkSelfPermission(this,
  2. Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
  3. if (permissionApproved) {
  4. // 启动前台服务
  5. } else {
  6. // 请求前台访问位置权限
  7. }


如此一来就可以在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,就可以对于大部分的控件得到较好的深色模式支持。

  1. <application
  2. ...
  3. android:theme="@style/AppTheme"
  4. ...
  5. >
  1. <!-- Base application theme. -->
  2. <!--<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">-->
  3. <style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
  4. <!-- Customize your theme here. -->
  5. <item name="colorPrimary">@color/colorPrimary</item>
  6. <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
  7. <item name="colorAccent">@color/colorAccent</item>
  8. </style>
  • 颜色:新建values-night文件夹,里面是深色模式下的colors.xml文件

例如:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <color name="colorPrimary">#303030</color>
  4. <color name="colorPrimaryDark">#232323</color>
  5. <color name="colorAccent">#008577</color>
  6. </resources>
  • 图片:新建drawable-night-xxhdpi图片文件夹

按照ui设计给的深色模式图放进这个文件夹,系统就会在识别到深色模式后加载这个文件夹的资源了。

关键工具类NightModeUtil:

  1. public class NightModeUtil {
  2. /**
  3. * 当前系统是否是深色模式
  4. */
  5. public static boolean isNightMode(Context context) {
  6. int uiMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
  7. return uiMode == Configuration.UI_MODE_NIGHT_YES;
  8. }
  9. /**
  10. * 获取是否跟随系统,默认true
  11. */
  12. public static boolean getSystemMode() {
  13. return SPUtils.getBoolean(Constants.KEY_MODE_SYSTEM, true);
  14. }
  15. public static void setSystemMode(boolean nightMode) {
  16. SPUtils.putBoolean(Constants.KEY_MODE_SYSTEM, nightMode);
  17. }
  18. /**
  19. * 获取是否设置深色模式,默认false
  20. */
  21. public static boolean getNightMode() {
  22. return SPUtils.getBoolean(Constants.KEY_MODE_NIGHT, false);
  23. }
  24. public static void setNightMode(boolean nightMode) {
  25. SPUtils.putBoolean(Constants.KEY_MODE_NIGHT, nightMode);
  26. }
  27. public static void initNightMode() {
  28. initNightMode(getSystemMode(), getNightMode());
  29. }
  30. /**
  31. * 初始化App深色模式
  32. *
  33. * @param systemMode 是否是跟随系统
  34. * @param nightMode 是否是深色模式
  35. */
  36. public static void initNightMode(boolean systemMode, boolean nightMode) {
  37. if (systemMode) {
  38. AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
  39. } else {
  40. if (nightMode) {
  41. AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
  42. } else {
  43. AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
  44. }
  45. }
  46. }
  47. /**
  48. * 重启App
  49. */
  50. public static void restartApp(Activity activity) {
  51. final Intent intent = App.getInstance().getPackageManager().getLaunchIntentForPackage(App.getInstance().getPackageName());
  52. if (intent != null) {
  53. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  54. intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
  55. activity.startActivity(intent);
  56. android.os.Process.killProcess(android.os.Process.myPid());
  57. }
  58. }
  59. }

在Application里初始化

NightModeUtil.initNightMode();

切换状态后重启App

  1. //两个参数分别是 是否跟随系统,是否选择深色模式
  2. NightModeUtil.initNightMode(dayNightSwitch.isChecked, ctvCheckNight.isChecked);
  3. NightModeUtil.restartApp(activity);

WebView的深色模式设置
引入implementation 'androidx.webkit:webkit:1.2.0'后可轻易的实现WebView的深色模式,不过有兼容问题,这和WebView的版本有关,WebView版本独立于Android版本。(亲测在系统6.0和7.1上无效。)

在有WebView的Activity的onCarete里加上如下代码:

 

  1. WebSettings webSetting = webView.getSettings();
  2. // 检查是否支持暗模式
  3. if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
  4. boolean isAppDarkMode;
  5. if (NightModeUtil.getSystemMode()) {
  6. // 是否是跟随系统
  7. isAppDarkMode = NightModeUtil.isNightMode(this);
  8. } else {
  9. isAppDarkMode = NightModeUtil.getNightMode();
  10. }
  11. if (isAppDarkMode) {
  12. WebSettingsCompat.setForceDark(webSetting, WebSettingsCompat.FORCE_DARK_ON);
  13. } else {
  14. WebSettingsCompat.setForceDark(webSetting, WebSettingsCompat.FORCE_DARK_OFF);
  15. }
  16. }

这就是跟随系统深色模式做的配置了。

效果图如下:

2)自动适配(Force Dark)(不建议使用,效果差。)
应用必须选择启用 Force Dark,方法是在其主题背景中设置 android:forceDarkAllowed="true"。
监听深色主题是否开启
首先在清单文件中给对应的Activity配置 android:configChanges="uiMode":

  1. <activity
  2. android:name=".MyActivity"
  3. android:configChanges="uiMode" />


这样在onConfigurationChanged方法中就可以获取:        

  1. @Override
  2. public void onConfigurationChanged(@NonNull Configuration newConfig) {
  3. super.onConfigurationChanged(newConfig);
  4. int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
  5. switch (currentNightMode) {
  6. case Configuration.UI_MODE_NIGHT_NO:
  7. // 关闭
  8. break;
  9. case Configuration.UI_MODE_NIGHT_YES:
  10. // 开启
  11. break;
  12. default:
  13. break;
  14. }
  15. }


判断深色主题是否开启

其实和上面onConfigurationChanged方法同理:

  1. public static boolean isNightMode(Context context) {
  2. int currentNightMode = context.getResources().getConfiguration().uiMode &
  3. Configuration.UI_MODE_NIGHT_MASK;
  4. return currentNightMode == Configuration.UI_MODE_NIGHT_YES;
  5. }


5、标识符和数据
对不可重置的设备标识符实施了限制
从 Android 10 开始,应用必须具有 READ_PRIVILEGED_PHONE_STATE 特许权限才能正常使用以上这些方法。
 

Android 11 适配:


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,并可在运行时切换。在这些设备上,系统会基于首选帧率来为应用选择最佳刷新率。

Android12适配:

targetSdkVersion 31的应用在Android 12上安装时可能会存在两种安装不上的情况。

  1. adb: failed to install xxx.apk: Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES:
  2. Scanning Failed.: No signature found in package of version 2 or newer for
  3. package com.tomes.sharefile]

解决:
 targetSdkVersion 30的应用必须使用v2及以上签名
 

  1. adb: failed to install xxx.apk: Failure [INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: Failed
  2. parse during installPackageLI: /data/app/vmdl2054463318.tmp/base.apk (at Binary XML file
  3. line #49): com.tomes.ShareOpenTestActivity: Targeting S+ (version 10000 and above) requires
  4. 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属性申明就好

  1. <activity android:name=".ShareOpenTestActivity" android:exported="true">
  2. <intent-filter>
  3. <action android:name="android.intent.action.SEND"/>
  4. <category android:name="android.intent.category.DEFAULT" />
  5. <data android:mimeType="text/*" />
  6. </intent-filter>
  7. </activity>

总结:
targetSdkVersion为31【以Android 12为目标】的应用务必要加入v2签名,务必要对使用的activity,service,broadcast receiver含有intent-filter,显示声明android:exported属性。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/161645
推荐阅读
相关标签
  

闽ICP备14008679号