一:介绍
Android版本对应的Api版本
二:Android 6.0 (API 23)
Google I/O 2015大会如约已于2015年5月28日举行。在发布会上代号为“Marshmallow(棉花糖)”的安卓6.0系统正式推出。
Android 6.0 的API级别:23
新特性:
1.运行时权限(最主要)
此版本引入了一种新的权限模式,用户可直接在运行时管理应用权限。
对于以 Android 6.0(API 级别 23)或更高版本为目标平台的应用,请务必在运行时检查和请求权限。要确定您的应用是否已被授予权限,请调用新增的 checkSelfPermission() 方法。要请求权限,请调用新增的 requestPermissions() 方法。即使您的应用并不以 Android 6.0(API 级别 23)为目标平台,您也应该在新权限模式下测试您的应用。
//检查权限是否允许
ContextCompat.checkSelfPermission
//请求某个或某几个权限
ActivityCompat.requestPermissions
//手动请求权限之后的结果回调
onRequestPermissionsResult
//是否显示权限请求弹窗
//第一次请求权限时ActivityCompat.shouldShowRequestPermissionRationale=false;
//第一次请求权限被禁止,但未选择【不再提醒】ActivityCompat.shouldShowRequestPermissionRationale=true;
//允许某权限后ActivityCompat.shouldShowRequestPermissionRationale=false;
//禁止权限,并选中【禁止后不再询问】ActivityCompat.shouldShowRequestPermissionRationale=false;
shouldShowRequestPermissionRationale
2.取消支持Apache HTTP客户端
Android 6.0版本移除了对Apache HTTP客户端的支持。
如果您的应用使用该客户端,并以 Android 2.3(API 级别 9)或更高版本为目标平台,请改用 HttpURLConnection 类。此 API 效率更高,因为它可以通过透明压缩和响应缓存减少网络使用,并可最大限度降低耗电量。要继续使用 Apache HTTP API,您必须先在 build.gradle 文件中声明以下编译时依赖项:
android {
useLibrary 'org.apache.http.legacy'//使用Apache库
}
三:Android 7.0 (API 24-25)
Android 7.0是Google推出的智能手机操作系统,官方代号为“Nougat”(牛轧糖)。于2016年5月18-20日(美国西部时间)在Google I/O开发者大会上正式发布,发布地点是山景城的Shoreline Ampitheatre圆形剧场
Android 7.0 包括旨在延长设备电池寿命和减少 RAM 使用的系统行为变更。这些变更可能会影响您的应用访问系统资源,以及您的应用通过特定隐式 intent 与其他应用交互的方式。
新特性:
1.低电耗模式
Android 6.0引入了低电耗模式,当用户设备未插接电源,处于静止状态且屏幕关闭时,该模式会推迟 CPU 和网络活动,从而延长电池寿命。而 Android 7.0 则通过在设备未插接电源且屏幕关闭状态下、但不一定要处于静止状态(例如用户外出时把手持式设备装在口袋里)时应用部分 CPU 和网络限制,进一步增强了低电耗模式。
2.系统权限的更改
为了提高私有文件的安全性,面向 Android 7.0 或更高版本的应用私有目录被限制访问 (0700)。此设置可防止私有文件的元数据泄漏,如它们的大小或存在性。此权限更改有多重副作用
传递软件包网域外的 file:// URI 可能给接收器留下无法访问的路径。因此,尝试传递 file:// URI 会触发 FileUriExposedException。分享私有文件内容的推荐方法是使用 FileProvider。
Android7.0之前访问系统相册
File file = new File(Environment.getExternalStorageDirectory(), "/temp/" + System.currentTimeMillis() + ".jpg");
if (!file.getParentFile().exists()) file.getParentFile().mkdirs();
Uri imageUri = Uri.fromFile(file);
Intent intent = new Intent();
//设置Action为拍照
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
//将拍取的照片保存到指定URI
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, 1);
在7.0之后的话访问相册会报如下错误:
android.os.FileUriExposedException: file:///storage/emulated/0/temp/1627010812423.jpg exposed beyond app through ClipData.Item.getUri()
在Android7.0系统上,Android 框架强制执行了 StrictMode API 政策禁止向你的应用外公开 file:// URI。 如果一项包含文件 file:// URI类型 的 Intent 离开你的应用,应用失败,并出现 FileUriExposedException 异常,如调用系统相机拍照,或裁切照片。
解决方法:
第一步:在清单文件AndroidManifest.xml中注册provider
<provider
android:name="androidx.core.content.FileProvider"//非androidx下 android:name="android.support.v4.content.FileProvider"
android:authorities="com.ruan.mygitignore.fileprovider"//包名.fileprovider
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path" />
</provider>
参数解释:
android:name:是固定的
android:authorities推荐写您的应用包名+“.fileprovider”,其实这里不一定要写fileprovider,您也可以随便写,只要与后面使用FileProvider.getUriForFile()这个方法中的第二个参数authority对应起来即可;(URI uri = FileProvider.getUriForFile(context, “com.ruan.mygitignore.fileprovider”, file);)
android:grantUriPermissions固定true,表示uri访问授权;
android:exported固定的false,我试着写了true报安全异常。
android:resource表示我们app要共享文件的路径的资源文件。
第二步:res文件夹下,新建一个xml文件夹,名字就是上一步 android:resource=”@xml/file_paths”对应的内容
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="Camera"
path="" />
</paths>
命名为“file_paths”(名字可以随便起,只要和第一步中在manifest注册的provider所引用的resource保持一致即可)的资源文件。
上述代码中 path=”“,是有特殊意义的,它指的是根目录,也就是说您可以向其它的应用访问根目录及其子目录下任何一个文件了,如果您将path设为 path=”pictures”,那么它代表着根目录下的pictures目录(eg:/storage/emulated/0/pictures),这时您访问pictures目录范围之外的文件是不行的。
files-path代表的根目录: Context.getFilesDir()
external-path代表的根目录: Environment.getExternalStorageDirectory()
cache-path代表的根目录: getCacheDir()
第三步:使用FlieProvider
/*
*相册,选择一张图片
*/
private void getPhoto(Context context) {
// 这里用时间命名是发现用固定名命名后第二次裁剪图片任然是第一次的图,没有覆盖上一次图片资源;7.0之前固定名会替换
File file = new File(Environment.getExternalStorageDirectory(), "/temp/" + System.currentTimeMillis() + ".jpg");
if (!file.getParentFile().exists()) file.getParentFile().mkdirs();
// 通过FileProvider创建一个content类型的Uri
Uri imageUri = FileProvider.getUriForFile(context, "com.ruan.mygitignore.fileprovider", file);
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// 设置Action为拍照
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
// 将拍取的照片保存到指定URI
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
// 开启一个带有返回值的Activity,请求码为PHOTO_REQUEST_GALLERY
startActivityForResult(intent, 1);
System.out.println(imageUri);
}
结果
System.out: content://com.ruan.mygitignore.fileprovider/Camera/temp/1627018704311.jpg
最简单的以打开相机为例:
File file = new File(Environment.getExternalStorageDirectory(), "/temp/" + System.currentTimeMillis() + ".jpg");
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (Build.VERSION.SDK_INT >= 24) {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//24以上使用FileProvider
intent.putExtra(MediaStore.EXTRA_OUTPUT,
FileProvider.getUriForFile(FiveTeenActivity.this, "com.ruan.mygitignore.fileprovider", file));
}else{
//24以下
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
}
startActivityForResult(intent, 1);
1、将之前Uri的scheme类型为file的Uri改成了有FileProvider创建一个content类型的Uri。
2、添加了intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);来对目标应用临时授权该Uri所代表的文件。
四:Android 8.0 (API 26-27)
2017年8月22日,谷歌正式发布了Android 8.0的正式版,其正式名称为:Android Oreo(奥利奥) 。2017年12月5日谷歌正式发布了Android 8.1的正式版。
新特性:
1.通知渠道 — Notification Channels
通知渠道是由应用自行定义的通知内容类别,借助渠道,开发者可以让用户对不同种类的通知进行精细控制,用户可以单独拦截或更改每个渠道的行为,而不是统一管理应用的所有通知。
创建通知渠道的步骤:
创建 NotificationChannel 对象,并设置应用内唯一的通知 ID。
配置通知渠道的属性,比如提示声音等。
在 NotificationManager 中注册通知渠道对象。
厂商推送
对接厂商推送时需要先注册渠道并引导用户开启渠道权限,不然第一条推送无法收到
- 通知消息:指定通知标题和内容后,由个推SDK自动处理在系统通知栏中展示通知栏消息,同时响铃或震动提醒用户(响铃和震动受手机系统的设置状态影响)。
- 透传消息:即自定义消息,消息体格式客户可以自己定义,如纯文本、json串等。透传消息个推只传递数据,不做任何处理,客户端接收到透传消息后需要自己去做后续动作处理,如通知栏展示、弹框等。各平台有不同处理(小米、魅族弃用;华为应用不在前台时,推送服务会加密缓存消息内容;荣耀、vivo、oppo不支持)
1.小米,通知总开关默认开启,通知渠道默认开启;
https://dev.mi.com/console/doc/detail?pId=2086
2.华为,通知总开关默认开启,通知渠道默认开启;
https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides-V5/message-classification-management-solution-0000001149358835-V5
- 消息分类
- 服务与通讯,包括社交通讯类消息和服务提醒类消息。
- 社交通讯,指用户间的聊天消息、通话等信息。
- 服务提醒,指应用借助通知中心及时向用户传递重要通知提醒,通常用户对接收此类消息有预期。
- 资讯营销,包括资讯类消息和营销类消息,指的是运营人员向用户发送的活动信息、内容推荐、资讯等。
- 消息分类方式有两种:
- 消息智能分类:智能分类算法将根据您发送的内容等多个维度因素,自动将您的消息按照分类标准进行归类。
- 消息自分类:即日起华为推送服务开始接收开发者自分类权益的申请。申请成功后,允许开发者根据华为推送分类规范,自行对消息进行分类。自分类权益上线日期:2021年07月01日
默认情况下,所有消息一律通过通知消息智能分类功能进行分类。如您希望消息分类能更精准地符合业务需要,您也可以申请自分类权益,我们将信任您所提供的分类信息,按照您提供的分类标准展示对应消息。
- 通知渠道(channel)是Android O版本引入的新功能,意在解决如下问题:
- 应用的通知越来越多,给用户造成明显打扰。
- 华为手机系统EMUI 10.0之前仅有一个“默认通知”渠道,无法做通知消息呈现方式的定制。
为了改善终端用户推送体验、营造良好可持续的通知生态,华为推送从EMUI
10.0对推送消息分类进行管理。根据消息内容,华为推送将通知分类为服务与通讯、资讯营销两大类别。华为手机系统从EMUI 10.0开始新增了两个通知渠道(普通通知、营销通知)来实现不同分类消息(服务与通讯、资讯营销)的通知消息展示。
- 自定义通知渠道,使用自定义渠道推送消息请参见如下步骤:
注意:自定义渠道功能不再适用于数据处理位置为中国区的应用,您的推送消息将按照智能分类系统或消息自分类权益确认的消息级别,归类为服务与通讯类或资讯营销类消息。
- 您应用的客户端创建自定义渠道,详情请参见Android官方文档
- 您的应用具有消息自分类权益,如果没有请参见模板申请自分类权益。
- 下行消息体内容中设置importance字段为NORMAL,channel_id字段设置为您自定义的渠道。消息体示例:
{
"message": {
"notification": {
"title": "message title",
"body": "message body"
},
"android": {
"notification": {
"icon": "/raw/ic_launcher2",
"importance":"NORMAL",
"channel_id": "customizing channels",
"click_action": {
"type": 2,
"url": "https://www.huawei.com"
}
}
},
"token": [
"pushtoken1"
]
}
}
3.荣耀,通知总开关默认开启,通知渠道默认开启;
https://developer.hihonor.com/cn/kitdoc?category=基础服务&kitId=11002&navigation=guides&docId=notification-class.md&token=
- 消息分类
荣耀推送服务将根据应用类型、消息内容和消息发送场景,将推送消息分成服务通讯和资讯营销两大类别。
服务通讯类,包括社交通讯消息和服务提醒消息。
- 社交通讯,指用户间的聊天消息、音视频通话。
- 服务提醒,指应用及时向用户传递重要通知提醒,通常用户对接收此类消息有预期。
资讯营销类,包括内容资讯消息和活动营销消息。
- 内容资讯,指应用向用户推送的推荐内容、资讯等。
- 活动营销,指应用向用户推送的产品促销、功能推荐、运营活动等。
- 消息分类方式
荣耀推送服务针对消息分类有2种处理方式:
- 消息智能分类:智能算法将根据APP类型和消息内容等维度,自动将您的消息按照分类标准进行归类。
- 消息自分类:允许开发者根据消息分类规范,自行对消息进行分类。
目前,所有消息默认通过消息自分类方式进行分类处理,荣耀推送服务将充分信任您提供的分类结果,并且按您提供的分类结果展示对应信息。随着荣耀推送服务能力的不断补充和演进,分类方式也会逐渐更新与升级,请及时留意本文档最新的分类方式说明。
- 消息自分类标准
要求
- 应用已经在荣耀开发者平台开通荣耀推送服务;
- 应用签署《荣耀推送服务使用协议》;
- 应用必须充分遵守消息分类标准,对消息进行归类处理。
应用适配开发
应用的推送消息将根据message.android.notification.importance字段进行归类。
- importance字段值为“LOW”时,表示消息为资讯营销类,默认展示方式为静默通知,仅在下拉通知栏展示;
- importance字段值为“NORMAL”时,表示消息为服务通讯类,默认展示方式为锁屏展示+下拉通知栏展示。
- 每日推送数量上限
4.vivo,通知总开关默认开启,通知渠道类别默认关闭;
发测试推送消息时不可纯数字、英文、特殊字符;不可带测试、test等明显的测试数据标识;运营消息可发送条数有上限
https://dev.vivo.com.cn/documentCenter/doc/359
5.oppo,通知总开关默认关闭,通知渠道类别默认开启;
https://open.oppomobile.com/new/developmentDoc/info?id=11227
2、画中画模式 — PIP
Android O 现已支持 Activity 的画中画模式。PIP 是一种多窗口显示模式,多用于视频播放。这和普通的画中画分屏模式并不相同。这一功能的唤醒只需要点击Home键按钮,如果想结束这一模式,可以将小窗口滑下来以终止。
关于生命周期
PIP 模式不会改变 Activity 的生命周期。在指定时间只有最近与用户交互过的 Activity 为活动状态。 该 Activity 将被视为顶级 Activity。 所有其他 Activity 虽然可见,但均处于暂停状态。当一个 Activity 处于 PIP 模式时,其实它是出在暂停状态,但其内容会继续展示。
API变更
在 Android O 中新增 PictureInPictureArgs 对象来指明你的 Activity 在 PIP 模式中的属性,比如长宽比等。
Android O 还新增了以下方法来支持 PIP。
Activity.enterPictureInPictureMode(PictureInPictureArgs args):将Activity置于 PIP 模式之下。
Activity.setPictureInPictureArgs():用于更新 Activity 在 PIP 模式下的设置。如果 Activity 正处于 PIP 模式之下,那么更改的属性将立即生效。
3、未知应用安装
未知应用安装权限的开关被除掉,取而代之的是未知来源应用的管理列表,需要在里面打开每个应用的未知来源的安装权限
//判断是否是AndroidO以及更高的版本
//Android8.0的变化是,未知应用安装权限的开关被除掉,取而代之的是未知来源应用的管理列表,需要在里面打开每个应用的未知来源的安装权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//是否有安装未知应用权限
val haveInstallPermission = mContext?.packageManager?.canRequestPackageInstalls()
if (haveInstallPermission == true) {
mContext?.startActivity(getInstallIntent(apkFile))
} else {
//未知来源应用的管理列表开启权限,安装应用需要打开未知来源权限,请去设置中开启权限
InstallPermissionDialog(mContext, R.style.Translucent_Dialog)
.apply {
setCancelable(false)
setCanceledOnTouchOutside(false)
setCancelListener {
Log.e("InstallException", "安装应用需要打开未知来源权限,请去设置中开启权限")
Toast.makeText(
mContext,
if (mContext == null) "安装应用需要打开未知来源权限,请去设置中开启权限" else mContext?.getString(
R.string.permission_dialog_content
),
Toast.LENGTH_LONG
).show()
}
setSubmitListener {
//跳转权限设置页面,需要使用onActivityResult中回调结果
InstallPermissionActivity.requestPermission(mContext!!,
object : InstallPermissionCallback {
override fun success(permissionActivity: Activity?) {
mContext?.startActivity(getInstallIntent(apkFile))
}
override fun failure(permissionActivity: Activity?) {
Log.e("InstallException", "安装应用需要打开未知来源权限,请去设置中开启权限")
Toast.makeText(
mContext,
if (mContext == null) "安装应用需要打开未知来源权限,请去设置中开启权限" else mContext?.getString(
R.string.permission_dialog_content
),
Toast.LENGTH_LONG
).show()
}
})
}
}
.show()
}
} else {
mContext?.startActivity(getInstallIntent(apkFile))
}
4、透明主题的 Activity ,不能强制设置横竖屏方向(仅8.0)
在Android 8.0(API level 26)上,部分Activity出现了一个莫名其妙的crash,异常信息如下:
java.lang.RuntimeException: Unable to start activity ComponentInfo{**Activity}: java.lang.IllegalStateException: Only fullscreen activities can request orientation
当你在一个“translucent”的Activity里,试图执行setRequestedOrientation的时候就会触发这个异常。例如:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
五:Android 9.0 (API 28)
Android 9.0是谷歌研发的移动端操作系统,开发代号为“Pie”(派),于2018年8月7日正式发布
1.利用wifi RTT 进行室内定位
Android 9 添加了对 IEEE 802.11mc Wi-Fi 协议(也称为 Wi-Fi Round-Trip-Time (RTT))的平台支持,从而让您的应用可以利用室内定位功能。
在运行 Android 9 且具有硬件支持的设备上,应用可以使用 RTT API 来测量与附近支持 RTT 的 Wi-Fi 接入点 (AP) 的距离。 设备必须已启用位置服务并开启 Wi-Fi 扫描(在 Settings > Location 下),同时您的应用必须具有 ACCESS_FINE_LOCATION 权限。
设备无需连接到接入点即可使用 RTT。 为了保护隐私,只有手机可以确定与接入点的距离;接入点无此信息。
如果您的设备测量与 3 个或更多接入点的距离,您可以使用一个多点定位算法来预估与这些测量值最相符的设备位置。 结果通常精准至 1 至 2 米。
通过这种精确性,您可以打造新的体验,例如楼内导航、基于精细位置的服务,如无歧义语音控制(例如,“打开这盏灯”),以及基于位置的信息(如 “此产品是否有特别优惠?”)。
2.显示屏缺口支持
Android 9 支持最新的全面屏,其中包含为摄像头和扬声器预留空间的屏幕缺口。 通过 DisplayCutout 类可确定非功能区域的位置和形状,这些区域不应显示内容。 要确定这些屏幕缺口区域是否存在及其位置,请使用 getDisplayCutout() 函数。
全新的窗口布局属性 layoutInDisplayCutoutMode 让您的应用可以为设备屏幕缺口周围的内容进行布局。 您可以将此属性设为下列值之一:
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
可以按以下方法在任何运行 Android 9 的设备或模拟器上模拟屏幕缺口:
启用开发者选项。
在 Developer options 屏幕中,向下滚动至 Drawing 部分并选择 Simulate a display with a cutout。
选择屏幕缺口的大小。
注:我们建议您通过使用运行 Android 9 的设备或模拟器测试屏幕缺口周围的内容显示。
3.前台服务
如果应用以 Android 9 或更高版本为目标平台并使用前台服务,则必须请求 FOREGROUND_SERVICE 权限。这是普通权限,因此,系统会自动为请求权限的应用授予此权限。
如果以 Android 9 或更高版本为目标平台的应用尝试创建前台服务且未请求 FOREGROUND_SERVICE,则系统会抛出 SecurityException。
public class FloatPasswordWindowService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
LogUtils.d("FloatWindowService:onBind");
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand()");
// 参数一:唯一的通知标识;参数二:通知消息。
startForeground(110, notification);// 开始前台服务
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy()");
stopForeground(true);// 停止前台服务--参数:表示是否移除之前的通知
super.onDestroy();
}
}
9.0 要求创建一个前台服务需要请求 FOREGROUND_SERVICE 权限,否则系统会引发 SecurityException。
Intent intentService = new Intent(this, MyService.class);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
startForegroundService(intentService);//开启前台服务
} else {
startService(intentService);
}
PS:表忘记AndroidManifest.xml中添加FOREGROUND_SERVICE权限
//9.0这个前台服务权限不要忘了 <uses-permission android:name=“android.permission.FOREGROUND_SERVICE” />
4.启动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);
5.Http请求
5.1:Okhttp网络请求时候报异常: java.net.UnknownServiceException: CLEARTEXT communication to ** not permitted by network security policy
原因是:当targetSdkVersion>=28时,OkHttp通过调用Android API中的isCleartextTrafficPermitted(host),在9.0系统上限制了Http明文请求。
方式一:把targetSdkVersion改成小于28版本
方式二:把请求接口改为https
方式三:通过配置Xml文件绕过Http限制
5.1.1.在res文件下创建一个xml文件,可以命名network_config:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
5.1.2.然后在AndroidMainfest.xml文件下的
<application
android:networkSecurityConfig="@xml/network_config">
</application>
5.2:Volley网络请求时候报异常:Cleartext HTTP traffic to * not permitted
方式一:把targetSdkVersion改成小于28版本
方式二:把请求接口改为https
方式三:5.2.1.在AndroidMainfest.xml文件下application节点下
<application
android:usesCleartextTraffic="true">
</application>
5.2.2.如果添加了上述代码程序还是无法运行,并出现闪退或报错“Didn’t find class “org.apache.http.ProtocolVersion” on path”的情况,在application节点
<application>
<uses-library
android:name="org.apache.http.legacy" android:required="false" />
</application>
6.Apache HTTP 客户端弃用
在 Android 6.0 时,就已经取消了对 Apache HTTP 客户端的支持。从 Android 9.0 开始,默认情况下该库已从 bootclasspath 中移除。但是耐不住有些SDK中还在使用,比如我见到的友盟QQ分享报错问题。
所以要想继续使用Apache HTTP,需要在应用的 AndroidManifest.xml 文件中添加:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyGitignore">
<uses-library android:name="org.apache.http.legacy" android:required="false"/>//使用这个
<application/>
7.多进程使用WebView时不支持同时从多个进程使用具有相同数据目录的WebView
解决方法:
多进程时在Applicationd类的onCreate或者onBaseContextAttached方法中加入
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
initWebViewDataDirectory(this);
}
/**
* 得到进程名称
* @param context
* @return
*/
public static String getProcessName(Context context) {
try {
if (context == null)
return null;
ActivityManager manager = (ActivityManager)
context.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningAppProcessInfo processInfo :
manager.getRunningAppProcesses()) {
if (processInfo.pid == android.os.Process.myPid()) {
return processInfo.processName;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 为webView设置目录后缀
* @param context
*/
@RequiresApi(api = Build.VERSION_CODES.P)
public static void initWebViewDataDirectory(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
String processName = getProcessName(context);
if (!context.getPackageName().equals(processName)) {//判断是否是默认进程名称
WebView.setDataDirectorySuffix(processName);
}
}
六:Android 10 (API 29)
Android 10包含多项功能升级,包括手势导航、通知栏管理、全局黑暗模式等等,通知管理新增了“优先”、“无声”和“自适应通知”三种功能,新增深色主题的背景
1.用户存储权限的变更
Android Q 在外部存储设备中为每个应用提供了一个“隔离存储沙盒”(例如 /sdcard)。任何其他应用都无法直接访问您应用的沙盒文件。由于文件是您应用的私有文件,因此您不再需要任何权限即可在外部存储设备中访问和保存自己的文件。此变更可让您更轻松地保证用户文件的隐私性,并有助于减少应用所需的权限数量。在application中添加android:requestLegacyExternalStorage="true"可以禁用分区存储
谷歌官方推荐应用在沙盒内存储文件的地址为Context.getExternalFilesDir()下的文件夹。
比如要存储一张私有图片,则应放在Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)中;
比如要存储一张可分享其它应用的图片,则应放在Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)中。
getExternalFilesDir和getFilesDir文件路径:/storage/emulated/0/Android/data/包名/files
getExternalCacheDir和getCacheDir文件路径:/storage/emulated/0/Android/data/包名/cache
加External和不加的比较:
相同点:
1. 都可以做app缓存目录。
2. app卸载后,两个目录下的数据都会被清空。
不同点:
1、目录的路径不同。前者的目录存在外部SD卡上的。后者的目录存在app的内部存储上。
2、前者的路径在手机里可以直接看到。后者的路径需要root以后,用Root Explorer 文件管理器才能看到。
2.新增后台位置信息权限(非运行时权限)
如果应用中的某项功能会不断与其他用户分享位置信息或使用 Geofencing API,则该应用需要后台位置信息访问权限。以下是此类情况的几个示例:
- 在家庭位置信息分享应用中,某项功能可让用户与家庭成员持续分享位置信息。
- 在 IoT 应用中,某项功能可让用户配置自己的家居设备,使其在用户离家时关机并在用户回家时重新开机。
除了前台位置信息部分所述的情况之外,如果应用在任何其他情况下访问设备的当前位置信息,系统就会认为应用需要使用后台位置信息。后台位置信息精确度与前台位置信息精确度相同,具体取决于应用声明的位置信息权限。
在 Android 10(API 级别 29)及更高版本中,您必须在应用的清单中声明 ACCESS_BACKGROUND_LOCATION 权限,以便请求在运行时于后台访问位置信息。在较低版本的 Android 系统中,当应用获得前台位置信息访问权限时,也会自动获得后台位置信息访问权限。
<manifest ... >
<!-- Required only when requesting background location access on
Android 10 (API level 29) and higher. -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
</manifest>
注意:Google Play 商店设置了有关设备位置信息的位置信息政策,限制应用仅在实现核心功能所必需的情形下且在满足相关政策要求后才能请求后台位置信息访问权限。
3.设备唯一标识符的变更
从 Android Q 开始,应用必须具有 READ_PRIVILEGED_PHONE_STATE签名权限才能访问设备的不可重置标识符(包含 IMEI 和序列号)。
如果您的应用没有该权限,但您仍尝试查询标识符的相关信息,会返回空值或报错。
设备唯一标识符需要特别注意,原来的READ_PHONE_STATE权限已经不能获得IMEI和序列。
如果想在Q设备上通过使用以下代码获取设备的ID
((TelephonyManager)getActivity().getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId()
则执行以上代码会返回空值(targetSDK<=P)或者报错(targetSDK==Q)。且官方所说的READ_PRIVILEGED_PHONE_STATE权限只提供给系统app,所以这个方法行不通了。
谷歌官方给予了设备唯一ID最佳做法,但是此方法给出的ID可变,可以按照具体需求具体解决。本文给出一个不变和基本不重复的UUID方法:
public static String getUUID() {
String serial = null;
String m_szDevIDShort = "35" +
Build.BOARD.length() % 10 + Build.BRAND.length() % 10 +
Build.CPU_ABI.length() % 10 + Build.DEVICE.length() % 10 +
Build.DISPLAY.length() % 10 + Build.HOST.length() % 10 +
Build.ID.length() % 10 + Build.MANUFACTURER.length() % 10 +
Build.MODEL.length() % 10 + Build.PRODUCT.length() % 10 +
Build.TAGS.length() % 10 + Build.TYPE.length() % 10 +
Build.USER.length() % 10; //13 位
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
serial = android.os.Build.getSerial();
} else {
serial = Build.SERIAL;
}
//API>=9 使用serial号
return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
} catch (Exception exception) {
//serial需要一个初始化
serial = "serial"; // 随便一个初始化
}
//使用硬件信息拼凑出来的15位号码
return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
}
4.新增媒体位置信息权限(非运行时权限)
如果您的应用以 Android 10(API 级别 29)或更高版本为目标平台,为了使您的应用从照片中检索未编辑的 Exif 元数据,您需要在应用的清单中声明 ACCESS_MEDIA_LOCATION 权限,然后在运行时请求此权限。
注意:由于您在运行时请求 ACCESS_MEDIA_LOCATION 权限,因此无法保证应用可以访问照片中未编辑的 Exif 元数据。应用需要用户明确同意才能访问此信息。
七:Android 11 (API 30)
Android 11正式版系统在2020年9月9日正式发布。系统主要增强了聊天气泡,安全性和隐私性的保护,电源菜单,可以更好的支持瀑布屏,折叠屏,双屏和 Vulkan 扩展程序等。
新特性
1.短信更新改进
首先是聊天泡泡。与Facebook多年来在Android上提供的Messenger应用程序类似,Android 11优化了短信功能,提供更加友好的交互。同时,为了确保用户能尽快收到对方的消息,Android 11在通知阴影(Notification Shade)中引入了一个专门的对话部分,它将提供对用户正在进行的任何对话的即时访问。这一更新将有助于短信消息从其他通知中脱颖而出。
2.电话号码相关权限
如果您的应用以 Android 11 或更高版本为目标平台,并且需要访问以下列表中显示的电话号码 API,则必须请求 READ_PHONE_NUMBERS 权限,而不是 READ_PHONE_STATE 权限。 TelephonyManager 类和 TelecomManager 类中的 getLine1Number() 方法。 TelephonyManager 类中不受支持的 getMsisdn() 方法。
如果您的应用声明 READ_PHONE_STATE 以调用前面列表中的方法以外的方法,您可以继续在所有 Android 版本中请求 READ_PHONE_STATE。不过,如果您仅对前面列表中的方法使用 READ_PHONE_STATE 权限,
请按以下方式更新您的清单文件: 更改 READ_PHONE_STATE 的声明,以使您的应用仅在 Android 10(API 级别 29)及更低版本中使用该权限。 添加 READ_PHONE_NUMBERS 权限。 以下清单声明代码段演示了此过程:
<manifest>
<!-- Grants the READ_PHONE_STATE permission only on devices that run
Android 10 (API level 29) and lower. -->
<uses-permission
android:name="READ_PHONE_STATE"
android:maxSdkVersion="29" />
<uses-permission android:name="READ_PHONE_NUMBERS" />
</manifest>
3.现在需要 APK 签名方案 v2
对于以 Android 11(API 级别 30)为目标平台,且目前仅使用 APK 签名方案 v1 签名的应用,现在还必须使用 APK 签名方案 v2 或更高版本进行签名。用户无法在搭载 Android 11 的设备上安装或更新仅通过 APK 签名方案 v1 签名的应用。
如果你的targetSdkVersion修改到30,那么你就必须要加上v2签名才行。否则无法安装和更新。
4.读取应用列表权限
在Android 11上在使用PackageManger的方法来获取安装的应用列表,的时候就需要在AndroidManifest.xml文件中进行申请android.permission.QUERY_ALL_PACKAGES此权限了,但是Android12中部分手机还要添加android.permission.GET_INSTALLED_APPS权限才能正常获取到应用列表,权限代码如下:
<uses-permission android:name="android.permission.GET_INSTALLED_APPS"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
android12虽然不用动态申请这两个权限,但是首次进入应用会弹出如下弹窗:
如果用户拒绝的话还需要提示去系统页面进行授权。
当然,如果大家不是必须获取应用列表而是简单的应用跳转,完全可以用更简单的方法try catch去实现,代码如下:
try {
Intent intent = new Intent(Intent.ACTION_MAIN);
ComponentName cmp = new ComponentName("应用包名", "该应用的class名称");
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setComponent(cmp);
startActivity(intent);
} catch (ActivityNotFoundException e) {
//todo 提示用户没有该应用,可添加跳转应用商店或浏览器代码
}
八:Android 12 (API 31-32)
Android 12 重新发现了代号为 “Columbus”的功能,并且优化了触发问题,新的手势需要更加用力敲击背面。新的双击背面手势可以截取屏幕截图、召唤谷歌 Assistant、打开通知栏、控制媒体播放或打开最近的应用程序列表。
1.AVIF图像支持
为了为您提供更高的图像质量和更有效的压缩,Android 12引入了对AV1图像文件格式(AVIF)的平台支持。AVIF是用于使用AV1编码的图像和图像序列的容器格式。与其他现代图像格式一样,AVIF利用了视频压缩中的帧内编码内容。与JPEG等较旧的图像格式相比,这可以显着提高相同文件大小的图像质量。
AVIF(18.2kB)JPEG(20.7kB)
2.前台服务优化
前台服务是应用程序管理某些类型的面向用户任务的重要方式,但是如果过度使用,它们可能会影响性能,甚至导致应用程序中断。为了确保为用户带来更好的体验,对于以新平台为目标的应用,我们将从后台阻止前台服务启动。为了更轻松地从此模式过渡,我们在JobScheduler中引入了一个新的加急作业,该作业获得了较高的进程优先级,网络访问权限,并且可以在不考虑节电或节电的情况下立即运行。为了实现向后兼容,我们还在最新版本的Jetpack WorkManager库中内置了加急作业。。另外,为了减少用户的注意力,我们现在将某些前台服务通知的显示延迟最多10秒钟。这使短暂的任务有机会在显示通知之前完成
3.android:exported
四大组件如果使用了 intent-filter,但是没显性质配置 exported App 将会无法安装,甚至编译不通过;
启动的 Activity 需要设置 exported 为 true;
com.android.tools.build:gradle:4.0.0 以及其下版本适配方案:
/**
* 修改 Android 12 因为 exported 的构建问题
*/
android.applicationVariants.all { variant ->
variant.outputs.all { output ->
output.processResources.doFirst { pm ->
String manifestPath = output.processResources.manifestFile
def manifestFile = new File(manifestPath)
def xml = new XmlParser(false, true).parse(manifestFile)
def exportedTag = "android:exported"
///指定 space
def androidSpace = new groovy.xml.Namespace('http://schemas.android.com/apk/res/android', 'android')
def nodes = xml.application[0].'*'.findAll {
//挑选要修改的节点,没有指定的 exported 的才需要增加
(it.name() == 'activity' || it.name() == 'receiver' || it.name() == 'service') && it.attribute(androidSpace.exported) == null
}
///添加 exported,默认 false
nodes.each {
def isMain = false
it.each {
if (it.name() == "intent-filter") {
it.each {
if (it.name() == "action") {
if (it.attributes().get(androidSpace.name) == "android.intent.action.MAIN") {
isMain = true
println("......................MAIN FOUND......................")
}
}
}
}
}
it.attributes().put(exportedTag, "${isMain}")
}
PrintWriter pw = new PrintWriter(manifestFile)
pw.write(groovy.xml.XmlUtil.serialize(xml))
pw.close()
}
}
}
com.android.tools.build:gradle:4.0.0 以上版本适配方案:
/**
* 修改 Android 12 因为 exported 的构建问题
*/
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def processManifest = output.getProcessManifestProvider().get()
processManifest.doLast { task ->
def outputDir = task.multiApkManifestOutputDirectory
File outputDirectory
if (outputDir instanceof File) {
outputDirectory = outputDir
} else {
outputDirectory = outputDir.get().asFile
}
File manifestOutFile = file("$outputDirectory/AndroidManifest.xml")
println("----------- ${manifestOutFile} ----------- ")
if (manifestOutFile.exists() && manifestOutFile.canRead() && manifestOutFile.canWrite()) {
def manifestFile = manifestOutFile
///这里第二个参数是 false ,所以 namespace 是展开的,所以下面不能用 androidSpace,而是用 nameTag
def xml = new XmlParser(false, false).parse(manifestFile)
def exportedTag = "android:exported"
def nameTag = "android:name"
///指定 space
//def androidSpace = new groovy.xml.Namespace('http://schemas.android.com/apk/res/android', 'android')
def nodes = xml.application[0].'*'.findAll {
//挑选要修改的节点,没有指定的 exported 的才需要增加
//如果 exportedTag 拿不到可以尝试 it.attribute(androidSpace.exported)
(it.name() == 'activity' || it.name() == 'receiver' || it.name() == 'service') && it.attribute(exportedTag) == null
}
///添加 exported,默认 false
nodes.each {
def isMain = false
it.each {
if (it.name() == "intent-filter") {
it.each {
if (it.name() == "action") {
//如果 nameTag 拿不到可以尝试 it.attribute(androidSpace.name)
if (it.attributes().get(nameTag) == "android.intent.action.MAIN") {
isMain = true
println("......................MAIN FOUND......................")
}
}
}
}
}
it.attributes().put(exportedTag, "${isMain}")
}
PrintWriter pw = new PrintWriter(manifestFile)
pw.write(groovy.xml.XmlUtil.serialize(xml))
pw.close()
}
}
}
}
com.android.tools.build:gradle:4.2.0 以上版本适配方案:
从 gradle:4.2.0 & gradle-6.7.1-all.zip 开始,TargetSDK 31 下脚本会有异常,因为在 processDebugMainManifest (带有 Main) 的阶段,会直接扫描依赖库的 AndroidManifest.xml 然后抛出直接报错,从而进不去 processDebugManifest 任务阶段就编译停止,所以实际上脚本并没有成功运行。
该脚本的作用是在运行时自动帮您打印出现问题的 aar 包依赖路径和组建名称:
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
//println("=============== ${variant.getBuildType().name.toUpperCase()} ===============")
//println("=============== ${variant.getFlavorName()} ===============")
def vn
if (variant.getFlavorName() != null && variant.getFlavorName() != "") {
vn = variant.name.substring(0, 1).toUpperCase() + variant.name.substring(1)
} else {
if (variant.getBuildType().name == "release") {
vn = variant.productFlavors[0].name.substring(0, 1).toUpperCase() + variant.productFlavors[0].name.substring(1) + "Release"
} else {
vn = variant.productFlavors[0].name.substring(0, 1).toUpperCase() + variant.productFlavors[0].name.substring(1) + "Debug"
}
}
def taskName = "process${vn}MainManifest";
try {
println("=============== taskName ${taskName} ===============")
project.getTasks().getByName(taskName)
} catch (Exception e) {
return
}
///您的自定义名字
project.getTasks().getByName(taskName).doFirst {
//def method = it.getClass().getMethods()
it.getManifests().getFiles().each {
if (it.exists() && it.canRead()) {
def manifestFile = it
def exportedTag = "android:exported"
def nameTag = "android:name"
///这里第二个参数是 false ,所以 namespace 是展开的,所以下面不能用 androidSpace,而是用 nameTag
def xml = new XmlParser(false, false).parse(manifestFile)
if (xml.application != null && xml.application.size() > 0) {
def nodes = xml.application[0].'*'.findAll {
//挑选要修改的节点,没有指定的 exported 的才需要增加
//如果 exportedTag 拿不到可以尝试 it.attribute(androidSpace.exported)
(it.name() == 'activity' || it.name() == 'receiver' || it.name() == 'service') && it.attribute(exportedTag) == null
}
if (nodes.application != null && nodes.application.size() > 0) {
nodes.each {
def t = it
it.each {
if (it.name() == "intent-filter") {
println("$manifestFile \n .....................${t.attributes().get(nameTag)}......................")
}
}
}
}
}
}
}
}
}
}
4.位置权限变更
使用针对Android 12的应用程序时,用户可以要求该应用程序只能访问大概的位置信息。为了更好地尊重用户隐私,建议您仅请求ACCESS_COARSE_LOCATION。如果您的应用面向Android 12并请求ACCESS_FINE_LOCATION运行时权限,则您还必须请求ACCESS_COARSE_LOCATION权限。您必须在单个运行时请求中同时包含两个权限。
5.新增附近设备权限
如果您的应用以 Android 12(API 级别 31)或更高版本为目标平台,请在应用的清单文件中声明以下权限:
- 如果您的应用程序寻找蓝牙设备,例如 BLE 外围设备,请声明 BLUETOOTH_SCAN权限。
- 如果您的应用程序使当前设备可被其他蓝牙设备发现,请声明该 BLUETOOTH_ADVERTISE 权限。
- 如果您的应用程序与已配对的蓝牙设备通信,请声明 BLUETOOTH_CONNECT权限。
- 对于遗留的蓝牙相关权限声明,设置android:maxSdkVersion为30. 此应用兼容性步骤有助于系统仅向您的应用授予安装在运行 Android 12 或更高版本的设备上时所需的蓝牙权限。
- 如果您的应用使用蓝牙扫描结果来获取物理位置,请声明 ACCESS_FINE_LOCATION权限。否则,您可以强烈断言您的应用不会获取物理位置。
BLUETOOTH_ADVERTISE,BLUETOOTH_CONNECT 和 BLUETOOTH_SCAN权限是运行时权限。因此,您必须在您的应用程序中明确请求用户批准,然后才能查找蓝牙设备、使设备可被其他设备发现或与已配对的蓝牙设备通信。当您的应用请求至少其中一项权限时,系统会提示用户允许您的应用访问 附近的设备。
以下代码片段演示了如果您的应用以 Android 12 或更高版本为目标,如何在您的应用中声明与蓝牙相关的权限:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 在旧设备上请求传统蓝牙权限。-->
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<!-- 仅当您的应用寻找蓝牙设备时才需要。如果您的应用不使用蓝牙扫描结果来获取物理位置信息,您可以强烈断言您的应用不获取物理位置。-->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<!-- 仅当您的应用使设备可被蓝牙设备发现时才需要。-->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<!-- 仅当您的应用程序与已配对的蓝牙设备通信时才需要。-->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- 仅当您的应用使用蓝牙扫描结果获取物理位置时才需要。-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>
九:Android 13 (API 33)
1.细化的媒体权限
从Android 13开始,以Android13(API 33+)为目标平台的应用,系统新增运行时权限READ_MEDIA_IMAGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO 替代原有的READ_EXTERNAL_STORAGE权限。
如果用户之前向您的应用授予了 READ_EXTERNAL_STORAGE 权限,系统会自动向您的应用授予细化的媒体权限。否则,当应用请求上表中显示的任何权限时,系统会显示面向用户的对话框。在图 1 中,应用请求 READ_MEDIA_AUDIO 权限。
如果您同时请求 READ_MEDIA_IMAGES 权限和 READ_MEDIA_VIDEO 权限,系统只会显示一个系统权限对话框。
注意:如果您的应用只需要访问图片、照片和视频,请考虑使用照片选择器,而不是声明 READ_MEDIA_IMAGES 和 READ_MEDIA_VIDEO 权限。
2. 新增通知运行时权限
Android 13 中引入了新的运行时权限,用于从应用发送非豁免通知:POST_NOTIFICATIONS。此更改有助于用户专注于最重要的通知。
强烈建议您尽快以 Android 13 为目标平台,以获享此功能提供的额外控制和灵活性。如果您继续以 12L(API 级别 32)或更低版本为目标平台,您将失去在应用功能环境中请求权限的机会。
应用的影响总结如下:
2.1 使用新权限
如需向应用请求新的通知权限,请将应用更新为以 Android 13 为目标平台,并完成与请求其他运行时权限类似的流程,如以下几个部分所述。需要在应用的清单文件中声明的权限会显示在以下代码段中:
<manifest ...>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application ...>
...
</application>
</manifest>
2.2 应用功能取决于用户在权限对话框中所做的选择
在此对话框中,用户可执行以下操作:
- 选择允许
- 选择不允许
- 滑开对话框,不按任何一个按钮
注意:如果用户点按不允许,即使仅点按一次,系统也不会再次提示用户,除非他们卸载并重新安装您的应用。
下面几个部分介绍了根据用户操作的不同,应用会有哪些不同的行为表现。
a. 用户选择“允许”如果用户选择允许选项,您的应用可以执行以下操作:
- 发送通知。可以使用所有通知渠道。
- 发送与前台服务相关的通知。这些通知会显示在抽屉式通知栏中。
b. 用户选择“不允许”如果用户选择不允许选项,您的应用将无法发送通知。除了几个特定角色之外,所有通知渠道都会被屏蔽。这类似于用户在系统设置中手动关闭应用的所有通知后发生的行为。
c. 用户滑开对话框
- 如果用户滑开对话框(即他们既没有选择允许,也没有选择不允许),会发生以下行为:
- 如果您的应用符合获得临时通知授权的条件,系统会保留临时授权。
- 如果您的应用没有临时授权,则将无法发送通知。
2.3 对新安装的应用的影响
如果用户在搭载 Android 13 的设备上安装您的应用,应用的通知默认处于关闭状态。在您请求新的权限且用户向您的应用授予该权限之前,您的应用都将无法发送通知。
权限对话框的显示时间取决于应用的目标 SDK 版本:
- 如果您的应用以 Android 13 或更高版本为目标平台,应用将可以完全自行控制权限对话框的显示时间。您可以借此机会向用户说明应用需要此权限的原因,进而鼓励他们授予该权限。
- 如果您的应用以 12L(API 级别 32)或更低版本为目标平台,系统会在您创建第一个通知渠道时显示权限对话框。这通常是在应用启动时。
2.4 对现有应用更新的影响
注意:请考虑下面这种情形,即应用安装在搭载 12L 或更低版本的旧设备上,用户在该设备上允许接收通知,但想淘汰该设备。用户现在使用的是搭载 Android 13 的新设备,并通过备份和恢复功能恢复了应用。
在这种情况下,系统会将您的应用视为“现有应用”,因此该应用会获得发送通知的临时权限。
为最大限度地减少与新通知权限相关的中断,系统会自动向系统升级到 Android 13 之前用户设备上已安装的所有符合条件的应用临时授予新通知权限。此临时授权的持续时间取决于应用的目标 SDK 版本:
- 如果应用以 Android 13 或更高版本为目标平台,则临时授权将持续到应用首次启动 activity 为止。
您的应用可以完全自行控制权限对话框的显示时间。您可以借此机会向用户说明应用需要此权限的原因,进而鼓励他们授予该权限。
- 如果您的应用以 12L 或更低版本为目标平台,则临时授权将一直有效,直到用户在通知权限运行时对话框中明确选择一个选项。也就是说,如果用户在未做出选择的情况下关闭了权限提示,系统会保留应用的临时授权。
2.5 获得临时授权的资格要求
您的应用要获得临时授权必须满足以下条件:应用必须已具有通知渠道,并且用户未在搭载 12L 或更低版本的设备上明确停用应用的通知。
如果用户在搭载 12L 或更低版本的设备上停用了应用的通知,当设备升级到 Android 13 或更高版本后,该停用会继续有效。
2.6 豁免
与媒体会话有关的通知不受此行为变更的影响。
2.7 最佳做法
本部分将介绍几种在应用中最有效地使用新通知权限的方式。
a. 更新应用的目标 SDK 版本
为了让应用更灵活地显示权限对话框,请将应用更新为以 Android 13 为目标平台。
b. 等待一段时间再显示通知权限提示
等到用户熟悉您的应用之后,再请求他们授予任何权限。
新用户可能想要探索您的应用,并切身体会每项通知请求可以带来的好处。您可以通过用户操作触发权限提示。下面列举了几个适合显示通知权限提示的时机:
- 用户点按“提醒铃铛”按钮时。
- 用户选择关注他人的社交媒体帐号时。
- 用户提交外卖订单时。
下图显示了请求通知权限的建议工作流程。或者,您也可以设置一个请求以在应用启动时显示,但仅在应用第 3 次或第 4 次启动后才显示。
c. 在上下文中请求权限
在应用内请求通知权限时,请在正确的上下文中请求,以便用户明确了解通知的用途以及应该选择接收通知的原因。例如,电子邮件应用可能包含为每封新邮件发送通知的选项,或仅为用户是唯一收件人的邮件发送通知的选项。
借此机会明确向用户表明您的意图,有助于鼓励用户向您的应用授予通知权限。
d. 检查您的应用能否发送通知
用户必须为您的应用启用通知,您的应用才能发送通知。要确认用户是否已启用通知,请调用areNotificationsEnabled()。
e. 以负责任的方式使用权限
获得发送通知的许可后,请负责任地使用该权限。用户可以查看您的应用每天发送的通知数量,并且可以随时撤消该权限。
3. 新增附近 Wi-Fi 设备运行时权限
Android 13 引入了NEARBY_WIFI_DEVICES运行时权限,该权限属于NEARBY_DEVICES权限组,适用于会管理设备与附近 Wi-Fi 接入点连接情况的应用。借助此权限,您可以更轻松地说明应用为何访问附近的 Wi-Fi 设备;在以前的 Android 版本中,这类应用需要声明ACCESS_FINE_LOCATION权限。
如果您的应用以 Android 13 为目标平台并调用多个不同的 Wi-Fi API,则必须从用户处获得这项新权限。
如果您的应用尝试在未获得适当权限的情况下调用 Wi-Fi API,则会发生SecurityException。
3.1 受影响的用例
这项新权限会影响几个不同的 Wi-Fi 用例,包括以下用例:
- 查找或连接到附近的设备,如打印机或媒体投射设备。通过该工作流,您的应用可以完成以下类型的任务: 通过带外方式(例如通过 BLE)接收 AP 信息。
- 使用仅限本地使用的热点,通过 Wi-Fi 感知和连接功能发现并连接到设备。
- 通过 Wi-Fi 直连发现和连接到设备。
- 发起与已知 SSID(例如汽车或智能家居设备)的连接。
- 开启仅限本地使用的热点。
- 连接到附近的 Wi-Fi 感知设备。
3.2 该权限属于“附近的设备”权限组
NEARBY_WIFI_DEVICES权限是附近的设备权限组的一部分。此权限组在 Android 12(API 级别 31)中添加,还包含与蓝牙和超宽带相关的权限。如果您的应用请求此权限组中的多项权限,用户会看到一个运行时对话框,其中会请求用户批准您的应用访问附近的设备。在系统设置中,用户必须以组的形式启用和停用附近的设备权限;例如,针对给定应用,用户无法既停用其 Wi-Fi 访问权限,但又保持启用其蓝牙使用权限。
3.3 坚定地声明您的应用不会推导物理位置
在以 Android 13 为目标平台时,请考虑您的应用是否会通过 Wi-Fi API 推导物理位置;如果不会,则应坚定声明此情况。如需做出此声明,请在应用的清单文件usesPermissionFlags属性设为neverForLocation,如以下代码段所示。此过程类似于您声明绝不会将蓝牙设备信息用于获取位置信息时的过程:
<manifest ...>
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation" />
<application ...>
...
</application>
</manifest>
3.4 保持后向兼容性
由于NEARBY_WIFI_DEVICES权限仅适用于 Android 13 或更高版本,因此您应保留对ACCESS_FINE_LOCATION的所有声明,以便在您的应用中提供向后兼容性。不过,只要您坚定声明应用不会使用 Wi-Fi API 推导物理位置信息,就可以将此权限的最高 SDK 版本设为32,如下以下代码段所示:
<manifest ...>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="32" />
<application ...>
...
</application>
</manifest>
3.5 某些 API 仍需要位置信息权限
有几个 Wi-Fi API 仍然需要ACCESS_FINE_LOCATION权限才能获取位置信息,就像它们在 12L 及更低版本上的一样。示例包括WifiManager类中的以下方法:
- getScanResults()
- startScan()
3.6 检查需要新权限的 API
如果您的应用以 Android 13 或更高版本为目标平台,您必须声明NEARBY_WIFI_DEVICES权限才能调用以下任何 Wi-Fi API:
- WifiManager startLocalOnlyHotspot()
- WifiAwareManager attach()
- WifiAwareSession publish()
- subscribe()
- WifiP2pManager addLocalService()
- connect()
- createGroup()
- discoverPeers()
- discoverServices()
- requestDeviceInfo()
- requestGroupInfo()
- requestPeers()
- WifiRttManager startRanging()
4. 新增身体传感器运行时权限
Android 13 中引入了“在使用时”访问身体传感器(例如心率、体温和血氧饱和度)的概念。此访问模式与Android 10(API 级别 29)系统为位置信息引入的模式非常相似。
如果您的应用以 Android 13 为目标平台,并且在后台运行时需要访问身体传感器信息,那么除了现有的BODY_SENSORS权限外,您还必须声明新的BODY_SENSORS_BACKGROUND权限。
注意:这是受到“硬性限制”的权限,除非设备的安装程序针对您的应用将该权限列入了许可名单,否则您的应用将无法获得此权限。如需了解详情,请参阅有关受限权限的指南。
5. intent 过滤器会屏蔽不匹配的 intent
当您的应用向以 Android 13 或更高版本为目标平台的其他应用的导出组件发送 intent 时,仅当该 intent 与接收应用中的<intent-filter>元素匹配时,系统才会传送该 intent。换言之,系统会屏蔽所有不匹配的 intent,但以下情况除外:
- 发送给其他应用的未声明任何 intent 过滤器的组件的 intent。
- 发送给您应用中的其他组件的 intent。
- 由系统发送的 intent。
- 由具有根级特权的用户发送的 intent。
6. 更安全地注册广播接收器
为了帮助提高运行时接收器的安全性,Android 13 允许您指定您应用中的特定广播接收器是否应被导出以及是否对设备上的其他应用可见。如果导出广播接收器,其他应用将可以向您的应用发送不受保护的广播。此导出配置在以 Android 13 或更高版本为目标平台的应用中可用,有助于防止一个主要的应用漏洞来源。
在以前的 Android 版本中,设备上的任何应用都可以向动态注册的接收器发送不受保护的广播,除非该接收器受签名权限的保护。
要实现此安全增强措施,请执行以下操作:
a. 启用DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED兼容性框架更改。
b. 在应用的每个广播接收器中,明确指明其他应用是否可以向其发送广播,如以下代码段所示:
// This broadcast receiver should be able to receive broadcasts from other apps.
// This option causes the same behavior as setting the broadcast receiver's
// "exported" attribute to true in your app's manifest.
context.registerReceiver(sharedBroadcastReceiver, intentFilter,
RECEIVER_EXPORTED);
// For app safety reasons, this private broadcast receiver should **NOT**
// be able to receive broadcasts from other apps.
context.registerReceiver(privateBroadcastReceiver, intentFilter,
RECEIVER_NOT_EXPORTED);
注意:如果启用了DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED兼容性框架更改,则必须为每个广播接收器指定RECEIVER_EXPORTED或RECEIVER_NOT_EXPORTED。否则,当您尝试注册广播接收器时,系统会抛出SecurityException。
为了帮助检测这种情况,Android Studio 将会采用一个 lint 规则,而即将发布的ContextCompat将会检查接收器配置是否正确。
6.剪切板内容隐藏
Android 13(API 33)开始,用户可以选择使用API PersistableBundle#(ClipDescription.EXTRA_IS_SENSITIVE, true)隐藏要复制到剪切板的用户账户、密码登敏感信息。
相关API使用举例如下:
private void addData2Clipboard() {
ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText("111111", "我是密码");
ClipDescription description = clipData.getDescription();
// 隐私内容:剪切板加密
PersistableBundle persistableBundle = new PersistableBundle();
if (Build.VERSION.SDK_INT >= 33) {
persistableBundle.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
} else {
persistableBundle.putBoolean("android.content.extra.IS_SENSITIVE", true);
}
description.setExtras(persistableBundle);
// 剪切板添加加密内容
clipboardManager.setPrimaryClip(clipData);
}
十:Android 14 (API 34)
1.默认拒绝设定精确的闹钟
精确的闹钟适用于用户指定的通知,或是在确切时间需要执行的操作。从 Android 14 开始,系统不再向以 Android 13 及更高版本为目标平台的大多数新安装应用预先授予 SCHEDULE_EXACT_ALARM 权限,该权限默认处于拒绝状态。
2.当应用进入缓存时,上下文注册的广播将加入队列
在 Android 14 中,当应用处于缓存状态时,系统可能会将上下文注册的广播放入队列中。这与 Android 12(API 级别 31)为异步 binder 事务引入的队列行为类似。在清单中声明的广播不会加入队列,并且应用会从缓存状态中移除以进行广播传递。
当应用离开缓存状态(例如返回前台)时,系统会传递所有已加入队列的广播。某些广播的多个实例可以合并为一个广播。
3.应用只能终止自己的后台进程
从 Android 14 开始,当您的应用调用 killBackgroundProcesses() 时,该 API 只能终止您自己应用的后台进程。
如果您传入另一个应用的软件包名称,此方法对该应用的后台进程没有影响,并且 Logcat 中会显示以下消息:
Invalid packageName: com.example.anotherapp
您的应用不应使用 killBackgroundProcesses() API,也不得以其他方式尝试影响其他应用的进程生命周期,即使在旧版操作系统上也是如此。Android 旨在让缓存应用在后台运行,并在系统需要内存时自动终止它们。如果您的应用会不必要地终止其他应用,则由于之后需要完全重启这些应用,因此可能会降低系统性能并增加耗电量,这比恢复现有缓存应用所消耗的资源要多得多。
注意:第三方应用无法改善 Android 设备的内存、电源或散热行为。您应确保您的应用符合 Google Play 关于误导性声明的政策。
4.最低可安装的目标 API 级别
从 Android 14 开始,targetSdkVersion 低于 23 的应用无法安装。要求应用满足这些最低目标 API 级别要求有助于提高用户的安全性和隐私性。
恶意软件通常会以较旧的 API 级别为目标平台,以绕过在较新版本 Android 中引入的安全和隐私保护机制。例如,有些恶意软件应用使用 targetSdkVersion 22,以避免受到 Android 6.0 Marshmallow(API 级别 23)在 2015 年引入的运行时权限模型的约束。这项 Android 14 变更使恶意软件更难以规避安全和隐私权方面的改进限制。尝试安装以较低 API 级别为目标平台的应用将导致安装失败,并且 Logcat 中会显示以下消息:
INSTALL_FAILED_DEPRECATED_SDK_VERSION: App package must target at least SDK version 23, but found 7
在升级到 Android 14 的设备上,targetSdkVersion 低于 23 的所有应用都将继续保持安装状态。
如果您需要测试以旧版 API 级别为目标平台的应用,请使用以下 ADB 命令:
adb install --bypass-low-target-sdk-block FILENAME.apk
5.媒体所有者软件包名称可能会隐去
媒体库支持查询 OWNER_PACKAGE_NAME 列,该列表示存储特定媒体文件的应用。从 Android 14 开始,除非满足以下条件之一,否则系统会隐去此值:
存储媒体文件的应用有一个软件包名称始终对其他应用可见。
查询媒体库的应用会请求 QUERY_ALL_PACKAGES 权限。
注意:使用 QUERY_ALL_PACKAGES 权限需遵守 Google Play 政策。
详细了解 Android 如何出于隐私保护目的而过滤软件包可见性。
6.授予对照片和视频的部分访问权限
注意:如果您的应用已在使用照片选择器,则无需执行任何操作即可支持此变更。
在 Android 14 中,当应用请求 Android 13(API 级别 33)中引入的任何视觉媒体权限时,用户可以授予对其照片和视频的部分访问权限:READ_MEDIA_IMAGES 或 READ_MEDIA_VIDEO。
新对话框会显示以下权限选项:
选择照片和视频:
- Android 14 中的新功能。用户选择希望提供给应用的具体照片和视频。
- 全部允许:用户授予对设备上的所有照片和视频的完整访问权限。
- 不允许:用户拒绝授予所有访问权限。
如需在应用中更妥善地处理此更改,请考虑声明新的 READ_MEDIA_VISUAL_USER_SELECTED 权限。详细了解如何支持用户授予对其媒体库的部分访问权限。
7.安全的全屏 intent 通知
在 Android 11(API 级别 30)中,任何应用都可以在手机处于锁定状态时使用 Notification.Builder.setFullScreenIntent 发送全屏 intent。您可以通过在 AndroidManifest 中声明 USE_FULL_SCREEN_INTENT 权限,在应用安装时自动授予此权限。
全屏 intent 通知适用于需要用户立即注意的极高优先级通知,例如用户来电或用户配置的闹钟设置。从 Android 14 开始,获准使用此权限的应用仅限于提供通话和闹钟的应用。对于不适合此情况的任何应用,Google Play 商店会撤消其默认的 USE_FULL_SCREEN_INTENT 权限。
在用户更新到 Android 14 之前,在手机上安装的应用仍拥有此权限。用户可以开启和关闭此权限。
您可以使用新 API NotificationManager.canUseFullScreenIntent 检查应用是否具有该权限;如果没有,应用可以使用新 intent ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT 启动设置页面,在该页面中,用户可以授予权限。
8.关于不可关闭通知用户体验方式的变更
如果您的应用向用户显示不可关闭的前台通知,请注意:Android 14 已更改此行为,允许用户关闭此类通知。
这项变更适用于通过 Notification.Builder#setOngoing(true) 或 NotificationCompat.Builder#setOngoing(true) 设置 Notification.FLAG_ONGOING_EVENT 来阻止用户关闭前台通知的应用。FLAG_ONGOING_EVENT 的行为已发生变化,使用户实际上能够关闭此类通知。
在以下情况下,此类通知仍不可关闭:
- 当手机处于锁定状态时
- 如果用户选择全部清除通知操作(有助于防止意外关闭)
此外,这一新行为不适用于以下用例中的不可关闭通知:
- 使用 MediaStyle 创建的通知
- 安全和隐私用例的政策限制使用
- 企业设备政策控制器 (DPC) 和支持软件包
9.数据安全信息更显眼
为了加强用户隐私保护,Android 14 增加了系统显示您在 Play 管理中心表单中声明的信息的位置数量。目前,用户可以在 Google Play 中的应用详情的数据安全部分查看此信息。
我们建议您查看应用的位置数据分享政策,并花一点时间对应用的 Google Play“数据安全”部分进行任何适用的更新。
如需了解详情,请参阅有关如何在 Android 14 上以更显眼的方式显示数据安全信息的指南。
10.非线性字体放大至 200%
从 Android 14 开始,系统支持字体放大高达 200%,为弱视用户提供了符合网络内容无障碍指南 (WCAG) 的其他无障碍功能选项。
如果您已使用放大像素 (sp) 单位来定义文本大小,这项更改可能不会对您的应用产生太大影响。不过,您应在启用最大字号 (200%) 的情况下执行界面测试,确保应用能够在不影响易用性的情况下适应较大的字号。
11.前台服务类型是必填项
如果您的应用以 Android 14 为目标平台,则必须为应用中的每个前台服务至少指定一项前台服务类型。您应该选择一个能代表您的应用用例的前台服务类型。系统需要特定类型的前台服务满足特定用例。
注意:Android 14 针对运行状况和远程消息传递用例引入了前台服务类型。系统还为简短服务、特殊用例和系统豁免保留新的类型。
如果应用中的用例与这些类型均不相关,强烈建议您迁移逻辑以使用 WorkManager 或用户发起的数据传输作业。
12.OpenJDK 17 更新
Android 14 将继续更新 Android 的核心库,以与最新 OpenJDK LTS 版本中的功能保持一致,包括适合应用和平台开发者的库更新和 Java 17 语言支持。
以下变更可能会影响应用兼容性:
- 对正则表达式的更改:现在,为了更严格地遵循 OpenJDK 的语义,不允许无效的组引用。您可能会看到 java.util.regex.Matcher 类抛出 IllegalArgumentException 的新情况,因此请务必测试应用中使用正则表达式的情形。如需在测试期间启用或停用此变更,请使用兼容性框架工具切换 DISALLOW_INVALID_GROUP_REFERENCE 标志。
- UUID 处理:现在,验证输入参数时,java.util.UUID.fromString() 方法会执行更严格的检查,因此您可能会在反序列化期间看到 IllegalArgumentException。如需在测试期间启用或停用此变更,请使用兼容性框架工具切换 ENABLE_STRICT_VALIDATION 标志。
- ProGuard 问题:有时,在您尝试使用 ProGuard 缩减、混淆和优化应用时,添加 java.lang.ClassValue 类会导致问题。问题源自 Kotlin 库,该库会根据 Class.forName(“java.lang.ClassValue”) 是否会返回类更改运行时行为。如果您的应用是根据没有 java.lang.ClassValue 类的旧版运行时开发的,则这些优化可能会将 computeValue 方法从派生自 java.lang.ClassValue 的类中移除。
13.对隐式 intent 和待处理 intent 的限制
对于以 Android 14 为目标平台的应用,Android 会通过以下方式限制应用向内部应用组件发送隐式 intent:
- 隐式 intent 只能传送到导出的组件。应用必须使用显式 intent 传送到未导出的组件,或将该组件标记为已导出。
- 如果应用通过未指定组件或软件包的 intent 创建可变待处理 intent,系统现在会抛出异常。
这些变更可防止恶意应用拦截意在供应用内部组件使用的隐式 intent。
例如,下面是可以在应用的清单文件中声明的 intent 过滤器:
<activity
android:exported="false"
android:name=".AppActivity">
<intent-filter>
<action android:name="com.example.action.APP_ACTION" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
如果应用尝试使用隐式 intent 启动此 activity,则系统会抛出异常:
// Throws an exception when targeting Android 14.
context.startActivity(Intent("com.example.action.APP_ACTION"))
如需启动非导出的 activity,应用应改用显式 intent:
// This makes the intent explicit.
val explicitIntent =
Intent("com.example.action.APP_ACTION")
explicitIntent.apply {
package = context.packageName
}
context.startActivity(explicitIntent)
14.在运行时注册的广播接收器必须指定导出行为
以 Android 14 为目标平台并使用上下文注册的接收器的应用和服务必须指定以下标志,以指明接收器是否应导出到设备上的所有其他应用:RECEIVER_EXPORTED 或 RECEIVER_NOT_EXPORTED。此要求有助于利用 Android 13 中引入的这些接收器的功能,来保护应用免受安全漏洞的影响。
仅接收系统广播的接收器的例外情况
如果您的应用仅通过 Context#registerReceiver 方法(例如 Context#registerReceiver())针对系统广播注册接收器,那么它在注册接收器时不应指定标志。
15.更安全的动态代码加载
如果您的应用以 Android 14 为目标平台并使用动态代码加载 (DCL) 功能,则必须将所有动态加载的文件标记为只读。否则,系统会抛出异常。我们建议应用尽可能避免动态加载代码,因为这样做会大大增加应用因代码注入或代码篡改而遭到入侵的风险。
如果必须动态加载代码,请使用以下方法,在动态文件(例如 DEX、JAR 或 APK 文件)打开并写入任何内容之前立即将其设为只读:
val jar = File("DYNAMICALLY_LOADED_FILE.jar")
val os = FileOutputStream(jar)
os.use {
// Set the file to read-only first to prevent race conditions
jar.setReadOnly()
// Then write the actual file content
}
val cl = PathClassLoader(jar, parentClassLoader)
处理已存在的动态加载文件
为防止系统对现有动态加载的文件抛出异常,我们建议您先删除并重新创建文件,然后再尝试在应用中重新动态加载这些文件。重新创建文件时,请按照上述指南在写入时将文件标记为只读。或者,您可以将现有文件重新标记为只读,但在这种情况下,我们强烈建议您先验证文件的完整性(例如,对照可信值检查文件的签名)以保护应用免遭恶意操作的影响。
16.压缩路径遍历
对于以 Android 14 为目标平台的应用,Android 会通过以下方式防止 Zip 路径遍历漏洞:如果 Zip 文件条目名称包含“…”或以“/”开头,ZipFile(String) 和 ZipInputStream.getNextEntry() 会抛出 ZipException。
应用可以通过调用 dalvik.system.ZipPathValidator.clearCallback() 选择停用此验证。
17.针对从后台启动 activity 的其他限制
对于以 Android 14 为目标平台的应用,系统会进一步限制允许应用在后台启动 activity 的时间:文章来源:https://www.toymoban.com/news/detail-723751.html
- 现在,当应用使用 PendingIntent#send() 或类似方法发送 PendingIntent 时,如果它想要授予自己的后台 activity 启动待处理 intent 的启动特权,则必须选择启用。如需选择启用,应用应通过 setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED) 传递 ActivityOptions 软件包。
- 当可见应用使用 bindService() 方法绑定其他在后台应用的服务时,如果可见应用想要授予自己的后台 activity 对绑定服务的启动特权,则必须选择启用。如需选择启用,应用应在调用 bindService() 方法时包含 BIND_ALLOW_ACTIVITY_STARTS 标志。
这些更改扩大了一组现有限制条件的范围,目的是防止恶意应用滥用 API 以在后台启动干扰性活动,从而保护用户。文章来源地址https://www.toymoban.com/news/detail-723751.html
到了这里,关于Android 各版本特性(Android6-14)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!