Android 9适配经验总结
目录
- 四大组件适配
- Activity启动方式适配
- Service启动方式适配
- 前台服务需要添加权限
- 限制静态广播的接收
- 限制ContentResolver数据更新操作
- 权限与安全相关主要适配点
- 运行时动态权限申请
- 默认不支持 http 请求
- SharedPreferences 适配
四大组件适配
Android 应用的开发离不开 Android 四大组件的使用,Android 四大组件分别是:Activity,用于展示前台页面;Service,用于执行后台任务;BroadCast,用于组件之间的通信;ContentProvider,用于应用间数据的分享。
Activity启动方式适配
Activity 组件用于和用户进行交互,在 Android 9 版本中,系统对于应用进程的任务栈做了调整,Activity 组件是运行在任务栈(task stack)中,而其它三大组件 Broadcast、Service、ContentProvider不需要运行在任务栈中,由这些组件打开 Activity 时需要为这个 Activity 的运行指定一个新的任务栈,否则会导致应用崩溃,并抛出异常信息:
android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
适配方法,在 Broadcast、Service、ContentProvider 这些组件中拉起 Activity,需要添加FLAG_ACTIVITY_NEW_TASK标志,这样便会在拉起 Activity 的同时,创建出一个新的任务栈:
Intent intent = new Intent(context, "xxxxx");//xxxxx 表示 Activity 的类名
intent.addFlag(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActiviy(intent);
Service启动方式适配
Service 是 Activity 外最常用的组件之一,它承接着用户处理后台任务的需求,是一种长生命周期的,没有可视化界面,运行于后台的一种服务程序。在 Android 5.0 之后 google 出于安全的角度禁止了隐式声明 Intent 来启动 Service,隐式声明即是不通过指定 Service 包名和类名,而是通过Intent 过滤机制,设置 intent 的 action、dataType 等方式根据 intent 匹配规则来查找并调用 Service:
Intent intent = new Intent();
intent.setAction("xxxx");//xxxx 为Service 在 IntentFilter 中配置的 action
context.startService(intent);
隐式启动 Service 会产生一个问题,就是传入的 action、dataType 可能会匹配到别的应用的后台服务上去,Service 是在后台运行的,不被用户所感知,因此通过隐式方式启动 Service 可能拉起的不是实际想要启动的服务,从而会产生错误。
为了防止隐式启动 Service 带来的问题,从 Android5.0 开始系统就禁止隐式启动 Service 的方式,上述代码在 Android9 系统上运行时会导致应用崩溃,并抛出异常:
Service Intent must be explicit…
适配方法
解决方式一: 将隐式启动转换为显式启动,通过指定包名、类名的方式拉起服务:
ComponentName cn = new ComponentName("包名","类名");
Intent intent = new Intent(cn);//显示启动 Service
context.startService(intent);
解决方式二:调用 intent 的 setPackage 方法指定包名:
Intent intent = new Intent();
intent.setAction("xxxx");//xxxx 为Service 在 IntentFilter 中配置的 action
intent.setPackage("应用包名");
context.startService(intent);
前台服务需要添加权限
在 Android 9.0 中,为了防止前台服务被滥用,比如各种通知信息,系统规定应用在使用前台服务之前必须先申请 FOREGROUND_SERVICE 权限,否则就会抛出 SecurityException 异常。
java.lang.SecurityException: Permission Denial: startForeground from pid=xxxx, uid=xxxx requires android.permission.FOREGROUND_SERVICE
适配方法,在 AndroidManifest 中添加权限声明:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
通过这样方式便可以申请前台服务的运行权限,前台服务的创建才会正常执行。
限制静态广播的接收
应用监听广播可以通过动态注册和静态注册两种方式,动态注册就是在程序运行起来后,调用注册方法进行注册,静态注册则是把广播注册放在 Manifest 配置文件中。
在一些场景中,比如监听开机启动广播来拉起应用,是需要通过静态注册方式来实现。但对于需要应用正常启动后才能对广播进行正确处理的场景,则应用使用动态注册的方式,这时如果采用静态注册的方式,在应用没有启动时,收到广播可能不会得到正确的处理,同时都采用静态注册的方式也会影响广播传递的效率,因为很多未启动的应用也会被广播唤醒。
也是出于这样的考虑,Android 9.0 之后,隐式广播将会被全面限制,用户的自定义广播和大部分系统广播通过隐式注册的方式,即在 AndroidManifest 中注册的 Receiver 的方式将不能够生效。
适配方法,使用动态注册的方式注册广播监听,如下代码:
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("xxxx");//xxxx 标识广播 action
context.registerReceiver(broadcastReceiver, intentFilter);
采用代码中动态注册广播的方式不仅是 Android9 系统的要求,也是一种比较好的开发习惯,这有利于对广播的使用进行控制,在应用功能已经初始化完成的时候添加广播的监听,确保广播到来时的功能执行能正常进行。
限制ContentResolver数据更新操作
Android 提供 ContentProvider/ContentResolver 组件来让用户开放自己应用中的数据或者访问别的应用的数据,为了防止用户数据监听被滥用,从 Android8.0 系统起,通过 ContentResolver的registerContentObserver 方法监听应用数据变化的操作被加以限制,直接操作会报出如下错误,并导致应用崩溃:
Failed to find provider xxx for user xxx; expected to find a valid ContentProvider for this authority
适配方法,在 AndroidManifest.xml 文件中定义一个 Provider,使用监听数据更新的目标 ContentProvider的 authorities 作为这个 provider 的 authorities:
<providerandroid:name="com.xx.content.ContentProvider"android:authorities="com.xxx.androidclient" android:enabled="true" android:exported="false">
</provider>
权限与安全相关主要适配点
运行时动态权限申请
Google 在 Android 6.0 开始引入了权限申请机制,将所有权限分成了正常权限和危险权限。应用的相关功能每次在使用危险权限时需要动态的申请并得到用户的授权才能使用。
系统权限分为两类:正常权限和危险权限。
正常权限不会直接给用户隐私权带来风险。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。
危险权限会授予应用访问用户机密数据的权限。如果应用在其清单中列出了危险权限,则用户必须明确批准应用使用这些权限。
危险权限主要是和获取用户数据有关,应用会用到的危险权限包括:
android.permission.READ_EXTERNAL_STORAGE 读取外部存储数据
android.permission.WRITE_EXTERNAL_STORAGE 向外部存储写入数据
常用的正常权限包括:
BLUETOOTH 使用蓝牙权限
BROADCAST_STICKY 粘性广播
CHANGE_NETWORK_STATE 改变网络状态
CHANGE_WIFI_STATE 控制 WiFi 开关,改变 WiFi 状态
GET_PACKAGE_SIZE 获取应用安装包大小
INTERNET 网络权限
RECEIVE_BOOT_COMPLETED 监听启动广播
REQUEST_INSTALL_PACKAGES 安装应用程序
WRITE_SYNC_SETTINGS 修改系统设置
适配方法,应用运行过程中,动态申请需要的危险权限,如下代码:
requestPermissions(final @NonNull Activity activity,final @NonNullString[] permissions, final int requestCode)//permissions 即为需要申请权限列表
默认不支持 http 请求
出于网络安全的考虑,Android9 系统上,应用将被禁止使用 http 协议进行网络数据传输,而必须使用 https 安全网络协议,如果应用中使用了 http 协议传输数据,将会抛出下面的错误:
java.net.UnknownServiceException: CLEARTEXT communication to xxxx not permitted by network security policy
最好的适配方式是修改所有的网络接口,改为 https 协议;除了服务端接口改动,Google 也提供了两个方案来让客户端支持 http 协议。
方案 1:
在 AndroidManifest.xml 中的 application 节点添加以下配置, 允许所有明文请求。
<application
... android:usersCleartextTraffic=“true”
>
方案 2:
添加自定义的网络安全配置在 res 目录下新建 xml 文件夹,添加 network_security_config.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
AndroidManifest.xml 中的 application 添加:
<manifest ... ><applicationandroid:networkSecurityConfig="@xml/network_security_config">... </application>
</manifest>
SharedPreferences 适配
SharedPreferences 是 Android 中的数据存储组件,原先针对数据访问没有严格的限制,
Android9.0 以后给 SharedPreferences 引入数据文件访问权限控制,去除了被外部应用读取数据的权限,设置 MODE_WORLD_READABLE 或 MODE_WORLD_WRITEABLE会触发安全异常。
适配方法
方案 1:
将MODE_WORLD_READABLE模 式 换 成MODE_PRIVATE, 通 过 这 种 方 式 将 应 用 的SharedPreference 数据访问权限设置为私有,以防止别的应用对 SharedPreference 数据进行任意的读写操作,保证了应用自身的安全性,对于可以向别的应用暴露的数据,也可以通过ContentProvider组件实现数据共享。
方案 2:
对于需要全局读写的功能,可使用其他方式替换 SharedPreferences,例如DataStore、MMKV等。
相关文章:
Android 9适配经验总结
目录四大组件适配Activity启动方式适配Service启动方式适配前台服务需要添加权限限制静态广播的接收限制ContentResolver数据更新操作权限与安全相关主要适配点运行时动态权限申请默认不支持 http 请求SharedPreferences 适配四大组件适配 Android 应用的开发离不开 Android 四…...
定时任务调度方案——Xxl-Job
定时任务调度方案 随着系统规模的发展,项目的组织结构以及架构越来越复杂,业务覆盖的范围越来越广,定时任务数量日益增多,任务也变得越来越复杂,尤其是为了满足在用户体量日历增大时,系统能够稳定运行&…...
操作系统引导
操作系统是一种程序,程序以数据的形式存放在硬盘中,而硬盘通常分为多个区,一个计算机中又有多个或多种外部设备。 操作系统引导指的是计算机利用CPU运行特定程序,通过程序识别硬盘,识别硬盘分区,识别硬盘分…...
[C#] 多线程单例子,分为阻塞型和分阻塞型, 在unity里的应用
在单例中使用多线程时,需要注意以下几点: 线程安全:在多线程环境下,单例对象可能被多个线程同时访问,因此需要确保单例的线程安全,避免出现数据竞争等问题。 对象创建:如果在单例对象的构造函数…...
使用MAT进行内存分析,并找到OOM问题
前言 在处理一次现场问题时,发现服务还在运行,但是出现假死情况,后通过分析GC日志以及使用MAT分析确定问题是内存溢出OutOfMemery(OOM);这里只记录MAT分析学习过程,最近工作忙,补记录。 GC日志分析 首先,如…...
初识Python
目录初识Python1.Python简介Python的优缺点Python的应用领域2.安装Python解释器Windows环境Linux环境macOS环境3.运行Python程序确认Python的版本编写Python源代码运行程序代码中的注释4.Python开发工具IDLE - 自带的集成开发工具IPython - 更好的交互式编程工具Sublime Text -…...
tmux终端复用软件
一、安装[rootpool-100-1-1-159 test]# yum install tmux [rootpool-100-1-1-159 test]# yum search tmux Repository extras is listed more than once in the configuration Last metadata expiration check: 0:33:52 ago on Fri 03 Mar 2023 09:10:34 AM CST.Name Exactly M…...
IO详解(文件,流对象,一些练习)
目录 文件 文件概念 文件的路径 路径有俩种表示风格 文件类型 如何区分文本文件还是二进制文件? java对文件的操作 File类中的一些方法 流对象 流对象的简单概念 java标准库的流对象 1.字节流,(操作二进制数据的) 2.字符流 (操作文本数据的) 流对象最核心的四个…...
SpringCloud全家桶— — 【1】eureka、ribbon、nacos、feign、gateway
SpringCloud全家桶— — 组件搭建 1 Eureka 1.1 Eureka-server 创建eureka-server的SpringBoot项目 ①导入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId…...
【线程安全篇】
线程安全之原子性问题 x ,在字节码文件中对应多个指令,多个线程在运行多个指令时,就存在原子性、可见性问题 赋值 多线程场景下,一个指令如果包含多个字节码指令,那么就不再是原子操作。因为赋值的同时,…...
错误:EfficientDet网络出现“No boxes to NMS“并且mAP:0.0的解决方案
近日,在使用谷歌新推出来的一个网络EfficientDet进行目标检测训练自己的数据集的时候,出现了如下错误: 其中项目开源地址是:https://github.com/toandaominh1997/EfficientDet.Pytorch 上面截图中的1和2代表我的类别名称。读者可…...
python的opencv操作记录13——区域生长及分水岭算法
文章目录图像区域基本算法——形态学运算腐蚀与膨胀开运算与闭运算opencv中的形态学运算距离计算——distanceTransform函数连通域连通的定义计算连通域——connectedComponents连通域实验基于区域的分割区域生长算法自定义一个最简单区域生长算法实现区域分割一般区域分割open…...
一文看懂网上下单的手机流量卡为什么归属都是随机的!
最近很多网上下单的小伙伴们心中似乎都有一个疑问。那就是网上很多手机卡、流量卡都不能自选号码和归属地,就算能自选号码,归属地也是随机的而且很多都不会跟你说具体的城市,这是为什么呢?莫非其中有什么不可告人的秘密吗?小伙伴…...
python Pytest生成alluer测试报告的完整教程
1.下载allure包到本地,解压 网上很多资料,这边不提供了 2.配置环境变量 将上面解压后bin文件的路径复制,添加到环境变量Path下 3.验证环境变量配置是否功 在cmd中输入allure,回车 。查看allure是否成功: 4.pyc…...
4-spring篇
ApplicationContext refresh的流程 12个步骤 prepareRefresh 这一步创建和准备了Environment对象,并赋值给了ApplicationContext的成员变量 要理解Environment对象的作用 obtainFreshBeanFactory ApplicationContext 里面有一个成员变量,Beanfactory b…...
提升 Web 应用程序的性能:如何使用 JavaScript 编写缓存服务
缓存是一种重要的优化技术,用于加速数据访问和降低服务器负载。缓存存储经常访问的数据,以便在需要时可以快速检索。在本文中,我们将探索如何使用简单的数据结构在 JavaScript 中编写缓存服务。 编码缓存服务的第一步是定义将用于访问缓存的…...
供应商绩效管理指南:挑战、考核指标与管理工具
管理和优化供应商绩效既关键又具有挑战性。要知道价格并不是一切,如果你的供应商在商定的价格范围内向你开具发票,但服务达不到标准或货物不合格,你也无法达到节约成本的目标。 供应商绩效管理可以深入了解供应商可能带来的风险,…...
干货文稿|详解深度半监督学习
分享嘉宾 | 范越文稿整理 | William嘉宾介绍Introduction to Semi-Supervised Learning传统机器学习中的主流学习方法分为监督学习,无监督学习和半监督学习。这里存在一个是问题是为什么需要做半监督学习?首先是希望减少标注成本,因为目前可以…...
信箱|邮箱系统
技术:Java、JSP等摘要:在经济全球化和信息技术飞速发展的今天,通过邮件收发进行信息传递已经成为主流。目前,基于B/S(Browser/Server)模式的MIS(Management information system)日益…...
JS数组拓展
1、Array.from Array.from 方法用于将两类对象转为真正的数组: 类似数组的对象,所谓类似数组的对象,本质特征只有一点,即必须有length属性。 因此,任何有length属性的对象,都可以通过Array.from方法转为数组 和 可遍历…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...
MyBatis中关于缓存的理解
MyBatis缓存 MyBatis系统当中默认定义两级缓存:一级缓存、二级缓存 默认情况下,只有一级缓存开启(sqlSession级别的缓存)二级缓存需要手动开启配置,需要局域namespace级别的缓存 一级缓存(本地缓存&#…...
书籍“之“字形打印矩阵(8)0609
题目 给定一个矩阵matrix,按照"之"字形的方式打印这个矩阵,例如: 1 2 3 4 5 6 7 8 9 10 11 12 ”之“字形打印的结果为:1,…...
