注意: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 节点添加如下代码
2、在 res 目录新建 xml 目录,已建的跳过,在xml目录新建一个network_security_config.xml文件,然后在AndroidManifest.xml 中 Application 添加如下节点代码。
android:networkSecurityConfig="@xml/network_security_config"
network_security_config.xml:
启动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"来请求使用旧的存储模式。
2、权限变化
1)在后台运行时访问设备位置信息需要权限(不建议)
Android 10 引入了 ACCESS_BACKGROUND_LOCATION 权限(危险权限)。
该权限允许应用程序在后台访问位置。如果请求此权限,则还必须请求ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION权限。只请求此权限无效果。
官方推荐使用前台服务来实现,在前台服务中获取位置信息。
首先在清单中对应的service中添加 android:foregroundServiceType="location":
...
启动前台服务前检查是否具有前台的访问权限:
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权限,这是一个普通权限。比如微信来语音或者视频通话时,弹出的接听页面就是使用这一功能。
4、深色主题
适配方法有两种:
1)手动适配(资源替换)(建议使用,可以基本达到预想效果。)
将Application和Activity的主题修改为集成自Theme.AppCompat.DayNight或者Theme.MaterialComponents.DayNight,就可以对于大部分的控件得到较好的深色模式支持。
例如:
#303030
#232323
#008577
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":
这样在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 中添加
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属性申明就好
总结:
targetSdkVersion为31【以Android 12为目标】的应用务必要加入v2签名,务必要对使用的activity,service,broadcast receiver含有intent-filter,显示声明android:exported属性。