Android简单适配9.0~12.0

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

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

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

Android 9.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手机模拟有刘海屏:

Android 10.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());
        }
    }
}

在Application里初始化

NightModeUtil.initNightMode();

切换状态后重启App

//两个参数分别是 是否跟随系统,是否选择深色模式
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 特许权限才能正常使用以上这些方法。
 

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上安装时可能会存在两种安装不上的情况。

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属性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值