Android 10 适配攻略,深度解析跳槽从开始到结束完整流程

应用在卸载后,会将App-specific目录下的数据删除,如果在AndroidManifest.xml中声明:android:hasFragileUserData="true"用户可以选择是否保留。

对于SAF的使用,可以查看我之前写的SAF使用攻略,这里就不展开说了。

最后这里有一个介绍Scoped Storage的视频,推荐观看:

准备好使用分区存储 | ADS 中文字幕视频

准备好使用分区存储

2.权限变化

=================================================================

从6.0开始,基本每次都会有权限方面变动,这次也不例外。(前几天发布了Android 11的预览版,看来也有权限方面的变化。。。单次权限即将到来)

1.在后台运行时访问设备位置信息需要权限

Android 10 引入了 ACCESS_BACKGROUND_LOCATION 权限(危险权限)。

该权限允许应用程序在后台访问位置。如果请求此权限,则还必须请求ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION权限。只请求此权限无效果。

在Android 10的设备上,如果你的应用的 targetSdkVersion < 29,则在请求ACCESS_FINE_LOCATION 或ACCESS_COARSE_LOCATION权限时,系统会自动同时请求ACCESS_BACKGROUND_LOCATION。在请求弹框中,选择“始终允许”表示同意后台获取位置信息,选择“仅在应用使用过程中允许”或"拒绝"选项表示拒绝授权。

如果你的应用的 targetSdkVersion >= 29,则请求ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION权限表示在前台时拥有访问设备位置信息的权。在请求弹框中,选择“始终允许”表示前后台都可以获取位置信息,选择“仅在应用使用过程中允许”只表示拥有前台的权限。

总结一下就是下图:

Android 10 适配攻略,深度解析跳槽从开始到结束完整流程

其实官方不推荐你使用申请后台访问权的方式,因为这样的结果无非就是多请求一个权限,那么这像变更还有什么意义?申请过多的权限,也会造成用户的反感。所以官方推荐使用前台服务来实现,在前台服务中获取位置信息。

  • 首先在清单中对应的service中添加 android:foregroundServiceType=“location”:

<service

android:name=“MyNavigationService”

android:foregroundServiceType=“location” … >

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

boolean permissionApproved = ActivityCompat.checkSelfPermission(this,

Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;

if (permissionApproved) {

// 启动前台服务

} else {

// 请求前台访问位置权限

}

如此一来就可以在Service中获取位置信息。

2.一些电话、蓝牙和WLAN的API需要精确位置权限

下面列举了Android 10中必须具有 ACCESS_FINE_LOCATION 权限才能使用类和方法:

  • 电话

TelephonyManager

getCellLocation()

getAllCellInfo()

requestNetworkScan()

requestCellInfoUpdate()

getAvailableNetworks()

getServiceState()

TelephonyScanManager

requestNetworkScan()

TelephonyScanManager.NetworkScanCallback

onResults()

PhoneStateListener

onCellLocationChanged()

onCellInfoChanged()

onServiceStateChanged()

  • WLAN

WifiManager

startScan()

getScanResults()

getConnectionInfo()

getConfiguredNetworks()

WifiAwareManager

WifiP2pManager

WifiRttManager

  • 蓝牙

BluetoothAdapter

startDiscovery()

startLeScan()

BluetoothAdapter.LeScanCallback

BluetoothLeScanner

startScan()

我们可以根据上面提供的具体类和方法,在适配项目中检查是否有使用到并及时处理。

3.ACCESS_MEDIA_LOCATION

Android 10新增权限,上面有提到,不赘述了。

4.PROCESS_OUTGOING_CALLS

Android 10上该权限已废弃。

3.后台启动 Activity 的限制

==============================================================================

简单解释就是应用处于后台时,无法启动Activity。比如点开一个应用会进入启动页或者广告页,一般会有几秒的延时再跳转至首页。如果这期间你退到后台,那么你将无法看到跳转过程。而在之前的版本中,会强制弹出页面至前台。

既然是限制,那么肯定有不受限的情况,主要有以下几点:

  • 应用具有可见窗口,例如前台 Activity。

  • 应用在前台任务的返回栈中已有的 Activity。

  • 应用在 Recents 上现有任务的返回栈中已有的 Activity。Recents 就是我们的任务管理列表。

  • 应用收到系统的 PendingIntent 通知。

  • 应用收到它应该在其中启动界面的系统广播。示例包括 ACTION_NEW_OUTGOING_CALL 和 SECRET_CODE_ACTION。应用可在广播发送几秒钟后启动 Activity。

  • 用户已向应用授予 SYSTEM_ALERT_WINDOW 权限,或是在应用权限页开启后台弹出页面的开关。

因为此项行为变更适用于在 Android 10 上运行的所有应用,所以这一限制导致最明显的问题就是点击推送信息时,有些应用无法进行正常的跳转(具体的实现问题导致)。所以针对这类问题,可以采取PendingIntent的方式,发送通知时使用setContentIntent方法。

当然你也可以申请相应权限或者白名单:

Android 10 适配攻略,深度解析跳槽从开始到结束完整流程

不过申请白名单这种方法受各种手机厂商所限,很麻烦。感觉还不如引导用户手动开启权限。。。

对于全屏 intent,注意设置最高优先级和添加USE_FULL_SCREEN_INTENT权限,这是一个普通权限。比如微信来语音或者视频通话时,弹出的接听页面就是使用这一功能。

Intent fullScreenIntent = new Intent(this, CallActivity.class);

PendingIntent fullScreenPendingIntent = PendingIntent.getActivity(this, 0,

fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT);

NotificationCompat.Builder notificationBuilder =

new NotificationCompat.Builder(this, CHANNEL_ID)

.setSmallIcon(R.drawable.notification_icon)

.setContentTitle(“Incoming call”)

.setContentText("(919) 555-1234")

.setPriority(NotificationCompat.PRIORITY_HIGH) // <— 高优先级

.setCategory(NotificationCompat.CATEGORY_CALL)

// Use a full-screen intent only for the highest-priority alerts where you

// have an associated activity that you would like to launch after the user

// interacts with the notification. Also, if your app targets Android 10

// or higher, you need to request the USE_FULL_SCREEN_INTENT permission in

// order for the platform to invoke this notification.

.setFullScreenIntent(fullScreenPendingIntent, true); // <— 全屏 intent

Notification incomingCallNotification = notificationBuilder.build();

注意:在部分手机上,直接设置setPriority无效(或者说以渠道优先级为准)。所以需要创建通知渠道时将重要性设置为IMPORTANCE_HIGH。

NotificationChannel channel = new NotificationChannel(channelId, “xxx”, NotificationManager.IMPORTANCE_HIGH);

后台启动 Activity 的限制的目的是为了减少对用户操作的中断。如果你有要弹出的页面,推荐你先弹出通知,让用户自己选择接下来的操作,而不是一股脑的强制弹出。(如果你的全屏intent都让用户反感,那他也可以关掉你的通知,不至于任你摆布。)

4.深色主题

=================================================================

Android 10 新增了一个系统级的深色主题(在系统设置中开启)。虽然深色主题并不是强制适配项,但是它可以带给用户更好的体验:

  • 可大幅减少耗电量。 OLED 屏幕中每个像素都是自主发光,所以在显示深色元素时像素所消耗的电流更低,尤其在纯黑颜色时像素点可以完全关闭来达到省电的效果。

  • 为弱视以及对强光敏感的用户提高可视性。深色可以降低屏幕的整体视觉亮度,减少对眼睛的视觉压力。

  • 让所有人都可以在光线较暗的环境中更轻松地使用设备。

适配方法有两种:

1.手动适配(资源替换)

官方文档中提到的继承Theme.AppCompat.DayNight 或者 Theme.MaterialComponents.DayNight的方法,但这只是将我们使用的各种View的默认样式进行了适配,并不太适用于实际项目的适配。因为具体的项目中的View都按照设计的风格进行了重定义。

其实适配的方法很简单,类似屏幕适配、国际化的操作,并不需要继承上面的主题。比如你要修改颜色,就在res 下新建 values-night目录,创建对应的colors.xml文件。将具体要修改的色值定义在里面。图标之类的也是一个思路,创建对应的 drawable-night目录。

只要你之前的代码不是硬编码且代码规范,那么适配起来还是很轻松。

2.自动适配(Force Dark)

Android 10 提供 Force Dark 功能。一如其名,此功能可让开发者快速实现深色主题背景,而无需明确设置 DayNight 主题背景。

如果您的应用采用浅色主题背景,则 Force Dark 会分析应用的每个视图,并在相应视图在屏幕上显示之前,自动应用深色主题背景。有些开发者会混合使用 Force Dark 和本机实现,以缩短实现深色主题背景所需的时间。

应用必须选择启用 Force Dark,方法是在其主题背景中设置 android:forceDarkAllowed=“true”。此属性会在所有系统及 AndroidX 提供的浅色主题背景(例如 Theme.Material.Light)上设置。使用 Force Dark 时,您应确保全面测试应用,并根据需要排除视图。

如果您的应用使用Dark Theme主题(例如Theme.Material),则系统不会应用 Force Dark。同样,如果应用的主题背景继承自 DayNight 主题(例如Theme.AppCompat.DayNight),则系统不会应用 Force Dark,因为会自动切换主题背景。

您可以通过 android:forceDarkAllowed 布局属性或 setForceDarkAllowed(boolean) 在特定视图上控制 Force Dark。

上述内容我直接照搬文档的说明。总结一下,使用Force Dark需要注意几点:

  • 如果使用的是 DayNight 或 Dark Theme 主题,则设置forceDarkAllowed 不生效。

  • 如果有需要排除适配的部分,可以在对应的View上设置forceDarkAllowed为false。

这里说说我实际使用此方法的感受:整体还是不错的,设置的色值会自动取反。但也因此颜色不受控制,能否达到预期效果是个需要注意的问题。追求快速适配可以采取此方案。

手动切换主题

使用 AppCompatDelegate.setDefaultNightMode(@NightMode int mode)方法,其中参数mode有以下几种:

  • 浅色 - MODE_NIGHT_NO

  • 深色 - MODE_NIGHT_YES

  • 由省电模式设置 - MODE_NIGHT_AUTO_BATTERY

  • 系统默认 - MODE_NIGHT_FOLLOW_SYSTEM

下面的代码是官方Demo中的使用示例:

public class ThemeHelper {

public static final String LIGHT_MODE = “light”;

public static final String DARK_MODE = “dark”;

public static final String DEFAULT_MODE = “default”;

public static void applyTheme(@NonNull String themePref) {

switch (themePref) {

case LIGHT_MODE: {

AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);

break;

}

case DARK_MODE: {

AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);

break;

}

default: {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {

AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);

} else {

AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);

}

break;

}

}

}

}

通过AppCompatDelegate.getDefaultNightMode()方法,可以获取到当前的模式,这样便于代码中去适配。

监听深色主题是否开启

首先在清单文件中给对应的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;

}

}

详细的内容你可以参看官方文档和官方Demo。

判断深色主题是否开启

其实和上面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.标识符和数据

===================================================================

对不可重置的设备标识符实施了限制

受影响的方法包括:

  • Build

getSerial()

  • TelephonyManager

作者2013年从java开发,转做Android开发,在小厂待过,也去过华为,OPPO等大厂待过,18年四月份进了阿里一直到现在。

参与过不少面试,也当面试官 面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!

我整理了一份阿里P7级别的最系统的Android开发主流技术,特别适合有3-5年以上经验的小伙伴深入学习提升。

主要包括阿里,以及字节跳动,腾讯,华为,小米,等一线互联网公司主流架构技术。如果你想深入系统学习Android开发,成为一名合格的高级工程师,可以收藏一下这些Android进阶技术选型

我搜集整理过这几年阿里,以及腾讯,字节跳动,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。

Android 10 适配攻略,深度解析跳槽从开始到结束完整流程

Java语言与原理;
大厂,小厂。Android面试先看你熟不熟悉Java语言

Android 10 适配攻略,深度解析跳槽从开始到结束完整流程

高级UI与自定义view;
自定义view,Android开发的基本功。

Android 10 适配攻略,深度解析跳槽从开始到结束完整流程

性能调优;
数据结构算法,设计模式。都是这里面的关键基础和重点需要熟练的。

Android 10 适配攻略,深度解析跳槽从开始到结束完整流程

NDK开发;
未来的方向,高薪必会。

Android 10 适配攻略,深度解析跳槽从开始到结束完整流程

前沿技术;
组件化,热升级,热修复,框架设计

Android 10 适配攻略,深度解析跳槽从开始到结束完整流程

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

我在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多,CodeChina上可见;

当然,想要深入学习并掌握这些能力,并不简单。关于如何学习,做程序员这一行什么工作强度大家都懂,但是不管工作多忙,每周也要雷打不动的抽出 2 小时用来学习。

不出半年,你就能看出变化!

Java语言与原理;*
大厂,小厂。Android面试先看你熟不熟悉Java语言

[外链图片转存中…(img-WExg4tx6-1643518291431)]

高级UI与自定义view;
自定义view,Android开发的基本功。

[外链图片转存中…(img-6HlOD06u-1643518291431)]

性能调优;
数据结构算法,设计模式。都是这里面的关键基础和重点需要熟练的。

[外链图片转存中…(img-AJ7c2lRd-1643518291432)]

NDK开发;
未来的方向,高薪必会。

[外链图片转存中…(img-00R3TlGc-1643518291432)]

前沿技术;
组件化,热升级,热修复,框架设计

[外链图片转存中…(img-pNO0jW1a-1643518291432)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

我在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多,CodeChina上可见;

当然,想要深入学习并掌握这些能力,并不简单。关于如何学习,做程序员这一行什么工作强度大家都懂,但是不管工作多忙,每周也要雷打不动的抽出 2 小时用来学习。

不出半年,你就能看出变化!

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

上一篇:4.文件系统


下一篇:Python实现中英互译