Android 12 应用适配指南 新功能和API
新功能和API
1 用于接收内容的统一API
当前,用户更喜欢图片、视频等富有表现力的内容,但在应用中插入和移动并非易事。为了使应用能够轻松地接收富媒体内容,Android 12 引入了全新的统一 API,您可以从任何可用来源(剪贴板粘贴、键盘或拖放操作)接收富媒体内容。
1.1 接收内容的API
以前每个界面机制(例如长按菜单或拖放)都有对应的 API。这意味着您必须单独与每个 API 集成,并为每种插入内容的机制添加类似的代码:
![图片关键词 Android 12 应用适配指南2 新功能和API](https://kb.myapp.ltd/yimen/2022/11/20221124154403871.png)
现在新的统一 API 会通过创建一个要实现的单一 API 来整合这些不同的代码路径,这样您就可以专注于应用特定的逻辑,而让系统处理其余的工作:
![图片关键词 Android 12 应用适配指南2 新功能和API](https://kb.myapp.ltd/yimen/2022/11/20221124154404332.png)
新 API 是一个监听器接口,包含一种方法,即 OnReceiveContentListener。此回调会成为您的代码处理接收所有内容(从纯文本和样式文本到标记、图片、视频、音频文件等)的唯一位置。为了兼容较低版本的 Android 平台,我们建议您使用 AndroidX,可在 Core 1.5.0-beta1 和 Appcompat 1.3.0-beta-01 中获得该API。
1.2 实现代码
如需使用该 API,请先指定您的应用可以处理哪些类型的内容,以开始实现该监听器:
public class MyReceiver implements OnReceiveContentListener { public static final String[] MIME_TYPES = new String[] {"image/*", "video/*"}; // ...
指定您的应用支持的所有内容 MIME 类型后,实现该监听器的其余部分:
public class MyReceiver implements OnReceiveContentListener { public static final String[] MIME_TYPES = new String[] {"image/*", "video/*"}; @Override public ContentInfoCompat onReceiveContent(View view, ContentInfoCompat contentInfo) { Pair<ContentInfoCompat, ContentInfoCompat> split = contentInfo.partition( item -> item.getUri() != null); ContentInfo uriContent = split.first; ContentInfo remaining = split.second; if (uriContent != null) { ClipData clip = uriContent.getClip(); for (int i = 0; i < clip.getItemCount(); i++) { Uri uri = clip.getItemAt(i).getUri(); // App-specific logic to handle the URI ... } } // Return anything that your app didn't handle. This preserves the default platform // behavior for text and anything else that you aren't implementing custom handling for. return remaining; } }
1.3 实现效果
![图片关键词 Android 12 应用适配指南2 新功能和API](https://kb.myapp.ltd/yimen/2022/11/20221124154404240.gif)
2 兼容的媒体转码
Android 12 引入了一项新功能,让录制视频的应用可以对设备上录制的视频进行更现代、存储效率更高的编码,同时又不影响与其他应用的兼容性。如果视频被不支持 HEVC 格式的应用打开,Android 会自动将以 HEVC (H.265) 等格式录制的视频转码为 AVC (H.264) 格式。对于在设备上创建的以下格式的内容,系统可以自动进行转码:
媒体格式 | XML属性 | MediaFormat MMIE类型 |
HEVC (H.265) | HEVC | MediaFormat.MIMETYPE_VIDEO_HEVC |
HDR10 | HDR10 | MediaFeature.HdrType.HDR10 |
HDR10+ | HDR10Plus | MediaFeature.HdrType.HDR10_PLUS |
Android系统默认应用可以支持播放所有媒体格式,因此“兼容的媒体转码”默认情况下是关闭的。应用有两种方法来请求将媒体文件转码成更兼容的格式:1.在资源文件中声明;2.在代码中声明。
2.1 在资源文件中声明功能
首先在xml目录下创建media_capabilities.xml资源文件,内容如下:
<?xml version="1.0" encoding="utf-8"?> <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android"> <format android:name="HEVC" supported="true"/> <format android:name="HDR10" supported="false"/> <format android:name="HDR10Plus" supported="false"/> </media-capabilities>
这个例子中,录制的HEVC视频无法转码,而HDR视频会被转码为AVC SDR(标准动态范围)视频。同时,需要在应用AndroidManifest.xml中application 标签中使用property来标记对媒体功能文件的引用:
<property android:name="android.media.PROPERTY_MEDIA_CAPABILITIES" android:resource="@xml/media_capabilities" />
2.2 在代码中声明功能
您可以使用构建器构造 ApplicationMediaCapabilities对象的实例,在代码中声明媒体功能:
ApplicationMediaCapabilities mediaCapabilities = new ApplicationMediaCapabilities.Builder() .addSupportedVideoMimeType(MediaFormat.MIMETYPE_VIDEO_HEVC) .addUnsupportedHdrType(MediaFeature.HdrType.HDR10) .addUnsupportedHdrType(MediaFeature.HdrType.HDR10_PLUS) .build();
通过 ContentResolver#openTypedAssetFileDescriptor()等方法访问媒体内容时,请使用此对象:
Bundle providerOptions = new Bundle(); providerOptions.putParcelable(MediaStore.EXTRA_MEDIA_CAPABILITIES, mediaCapabilities); try (AssetFileDescriptor fileDescriptor =contentResolver.openTypedAssetFileDescriptor(mediaUri, mediaMimeType, providerOptions)) { //视频内容会转换成ApplicationMediaCapabilities指定的格式 }
注意:代码的方式会优先于资源文件,这样可以对特定代码路径做更精细控制。
3 在企业中使用Android
3.1 Android 12中的企业功能新变化
密码复杂度功能以预定义复杂存储分区(高、中、低和无)的形式设定设备级密码要求。如有必要,可对工作资料安全验证应用严格的密码要求。
工作资料安全验证新手入门流程已简化。现在,设置会考虑设备密码是否符合管理员要求,让用户可以轻松选择是增强设备密码的强度还是使用工作资料安全验证。
注册特定 ID 提供了一个唯一 ID,用于标识特定组织的工作资料注册,并且可在恢复出厂设置后保持稳定。在 Android 12 中,对于具有工作资料的个人设备,系统会移除对该设备的其他硬件标识符(IMEI、MEID、序列号)的访问权限。
公司自有设备(无论是否具有工作资料)可以选择是否保留上述列表项中列出的功能,但不需要在 Android 12 中采用。
更详细的资料,请参阅:Android 12企业功能新变化
4.新的应用启动动画
Android 12 添加了SplashScreenAPI,它可为所有应用启用新的应用启动动画。这包括启动时的进入应用运动、显示应用图标的启动画面,以及向应用本身的过渡。
这种新体验可让应用每次启动时都呈现标准设计元素,但它也可自定义,以便您的应用能够保持其独特的品牌。
4.1 启动画面的工作原理
当用户启动应用而应用的进程未在运行(冷启动)或 Activity 尚未创建(温启动)时,会发生以下事件。(在热启动期间从不显示启动画面。)
系统使用主题以及您已定义的任何动画显示启动画面。
当应用准备就绪时,会关闭启动画面并显示应用。
4.2 动画的元素和机制
动画的元素由 Android 清单中的 XML 资源文件定义。每个元素都有浅色模式和深色模式版本。它们由窗口背景、动画形式的应用图标和图标背景组成。
关于这些元素,请注意以下几点:
应用图标应该是矢量可绘制对象,它可以是静态或动画形式。虽然动画的时长可以不受限制,但我们建议让其不超过 1000 毫秒。默认情况下,使用启动器图标。
图标背景是可选的,在图标与窗口背景之间需要更高的对比度时很有用。如果您使用一个自适应图标,当该图标与窗口背景之间的对比度足够高时,就会显示其背景。
与自适应图标一样,前景的 ⅓ 被遮盖。
窗口背景由不透明的单色组成。如果窗口背景已设置且为纯色,则未设置相应的属性时默认使用该背景。
启动画面动画机制由进入动画和退出动画组成。
进入动画由系统视图到启动画面组成。这由系统控制且不可自定义。
退出动画由隐藏启动画面的动画运行组成。如果您要对其进行自定义,您将可以访问 SplashScreenView 及其图标,并且可以在它们之上运行任何动画(需要设置转换、不透明度和颜色)。在这种情况下,当动画完成时,需要手动移除启动画面。
4.3 自定义应用中的启动画面
默认情况下,SplashScreen 使用主题的 windowBackground(如果它是单色)和启动器图标。启动画面的自定义通过向应用主题添加属性来完成。
您可以通过以下任一方式自定义应用的启动画面:
设置主题属性以更改其外观
让其在屏幕上显示更长时间
自定义用于关闭启动画面的动画
设置启动画面的主题以更改其外观
您可以在 Activity 主题中指定以下属性来自定义应用的启动画面。如果您已有使用 android:windowBackground 等属性的旧版启动画面实现,不妨考虑为 Android 12 提供替代资源文件。
1.使用windowSplashScreenBackground 以特定的单色填充背景:
<item name="android:windowSplashScreenBackground">@color/...</item>
2.使用windowSplashScreenAnimatedIcon替换起始窗口中心的图标。如果对象通过AnimationDrawable和AnimatedVectorDrawable可呈现动画效果且可绘制,则也会在显示起始窗口的同时播放动画。
<item name="android:windowSplashScreenAnimatedIcon">@drawable/...</item>
3.使用windowSplashScreenAnimationDuration设置启动画面在关闭之前显示的时长。最长时间为 1000 毫秒。
4.使用 windowSplashScreenIconBackground 设置启动画面图标后面的背景。当窗口背景与图标之间的对比度不够高时,这很有用。
<item name="android:windowSplashScreenIconBackground">@color/...</item>
5.(可选)您可以使用windowSplashScreenBrandingImage设置要显示在启动画面底部的图片。设计准则建议不要使用品牌图片。
<item name="android:windowSplashScreenBrandingImage">@drawable/...</item>
注意:建议的设计准则是不要使用品牌图片。
让启动画面在屏幕上显示更长时间
当应用绘制第一帧后,启动画面会立即关闭。如果您需要从本地磁盘异步加载少量数据(如应用内主题设置),您可以使用ViewTreeObserver.OnPreDrawListener让应用暂停绘制第一帧。
// Create a new event for the activity. @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Set the layout for the content view. setContentView(R.layout.main_activity); // Set up an OnPreDrawListener to the root view. final View content = findViewById(android.R.id.content); content.getViewTreeObserver().addOnPreDrawListener( new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { // Check if the initial data is ready. if (mViewModel.isReady()) { // The content is ready; start drawing. content.getViewTreeObserver().removeOnPreDrawListener(this); return true; } else { // The content is not ready; suspend. return false; } } }); }
自定义用于关闭启动画面的动画
您可以通过Activity.getSplashScreen进一步自定义启动画面的动画。
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... // Add a callback that's called when the splash screen is animating to // the app content. getSplashScreen().setOnExitAnimationListener(splashScreenView -> { final ObjectAnimator slideUp = ObjectAnimator.ofFloat( splashScreenView, View.TRANSLATION_Y, 0f, -splashScreenView.getHeight() ); slideUp.setInterpolator(new AnticipateInterpolator()); slideUp.setDuration(200L); // Call SplashScreenView.remove at the end of your custom animation. slideUp.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { splashScreenView.remove(); } }); // Run your animation. slideUp.start(); }); }
在此回调开始时,启动画面上动画形式的矢量可绘制对象已经开始。根据应用启动的时长,可绘制对象可能在其动画的中间。使用SplashScreenView.getIconAnimationStartMillis 可了解动画何时开始。您可以按如下方式计算图标动画的剩余时长:
// Get the duration of the animated vector drawable. long animationDuration = splashScreenView.getIconAnimationDurationMillis(); // Get the start time of the animation. long animationStart = splashScreenView.getIconAnimationStartMillis(); // Calculate the remaining duration of the animation. long remainingDuration = Math.max( animationDuration - (SystemClock.uptimeMillis() - animationStart), 0L );
5 画中画 (PiP) 改进
Android 12 针对画中画 (PiP) 模式引入了行为改进和新功能
5.1 点按一次和点按两次的行为改进
Android 12 改进了点按一次和点按两次的画中画行为,具体说明如下:
现在,点按一次画中画窗口会为用户显示控件。以前,点按一次会展开画中画窗口并显示控件。
现在,点按两次画中画窗口会在当前画中画大小与最大画中画大小之间切换。以前,点按两次会离开画中画模式而改为全屏模式。
5.2 新功能
Android 12 针对画中画模式引入了以下新功能:
5.2.1 用于在手势导航中更流畅地过渡到画中画模式的新 API 标志
Android 12 可让您使用新的setAutoEnterEnabled标志,在手势导航模式下向上滑动转到主屏幕时更流畅地过渡到画中画模式。以前,Android 会等到“向上滑动转到主屏幕”动画结束,然后再淡入画中画窗口。
如需实现此功能,请执行以下操作:
1.使用setAutoEnterEnabled 构造PictureInPictureParams.Builder,如下所示:
setPictureInPictureParams(new PictureInPictureParams.Builder() .setAspectRatio(aspectRatio) .setSourceRectHint(sourceRectHint) .setAutoEnterEnabled(true) .build());
注意:启用 setAutoEnterEnabled 后,无需在onUserLeaveHint中显式调用enterPictureInPictureMode。
2.尽早使用最新的 PictureInPictureParams 调用 setPictureInPictureParams。应用不应等待 onUserLeaveHint 回调(就像在 Android 11 中所做的那样)。
例如,应用可能要在第一次播放以及后续任何一次播放时(如果宽高比发生了变化)调用 setPictureInPictureParams。
3.根据需要调用 setAutoEnterEnabled(false)。例如,如果当前播放处于暂停状态,则视频应用进入画中画模式可能不是最佳选择。
5.2.2 用于为非视频内容停用无缝大小调整的新 API 标志
Android 12 添加了SeamlessResizeEnabled标志,在画中画窗口中调整非视频内容的大小时,该标志可提供更流畅的交替淡变动画。以前,在画中画窗口中调整非视频内容的大小时会产生烦人的视觉伪影。
为了向后兼容,默认情况下,将 setSeamlessResizeEnabled 标志设置为 true。对于视频内容,请将其设置保留为 true;对于非视频内容,请将其更改为 false。
如需停用非视频内容的无缝大小调整,请编写以下代码:
setPictureInPictureParams(new PictureInPictureParams.Builder() .setSeamlessResizeEnabled(false) .build());
5.2.3 退出画中画模式时支持更流畅的动画
在 Android 12 中,SourceRectHint标志现在重用于在退出画中画模式时实现更流畅的动画。退出时,系统会使用当前可用的 sourceRectHint 创建动画,无论它是用于进入画中画模式的原始 Rect,还是应用提供的更新后的 Rect。
如需实现此功能,请按以下方式更新您的应用:
1.继续使用sourceRectHint和aspectRatio构造 PictureInPictureParams,以实现流畅的进入动画。
2.如有必要,请在系统开始退出过渡之前更新sourceRectHint。当系统即将退出画中画模式时,activity 的视图层次结构会布局成其目标配置(例如全屏)。应用可以将布局更改监听器附加到其根视图或目标视图(如视频播放器视图),以检测事件并在动画开始前更新 sourceRectHint。
// Listener is called immediately after the user exits PIP but before animating. playerView.addOnLayoutChangeListener { _, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> if (left != oldLeft || right != oldRight || top != oldTop || bottom != oldBottom) { // The playerView's bounds changed, update the source hint rect to // reflect its new bounds. val sourceRectHint = Rect() playerView.getGlobalVisibleRect(sourceRectHint) setPictureInPictureParams( PictureInPictureParams.Builder() .setSourceRectHint(sourceRectHint) .build() ) } }
5.2.4 支持新手势
Android 12 现在支持对画中画窗口使用隐藏和“双指张合即可缩放”手势:
如需隐藏窗口,用户可以将窗口拖动到左侧或右侧边缘。如需取消隐藏窗口,用户可以点按已隐藏窗口的可见部分或将其拖出。
用户现在可以使用“双指张合即可缩放”手势调整画中画窗口的大小。
6 存储
Android 12 引入了对存储管理 API 的几项变更,下面几部分对此进行了介绍。
6.1 语音录音的新目录
系统会将存储在新Environment.DIRECTORY_RECORDINGS文件夹中的音频文件识别为录音。当您的应用对系统的媒体库执行查询时,您可以使用IS_RECORDING标志来检索录音。
6.2 媒体管理访问权限
用户可能会信任特定的应用来执行媒体管理,如频繁地修改媒体文件。如果您的应用以 Android 11(API 级别 30)或更高版本为目标平台且不是设备的默认图库应用,则每次您的应用尝试修改或删除文件时,您都必须向用户显示一个确认对话框。
如果您的应用以 Android 12 为目标平台,您可以请求用户向您的应用授予执行以下各项操作的权限,而无需针对每项文件操作提示用户:
使用 createWriteRequest() 修改文件。
使用 createTrashRequest() 将文件移入和移出回收站。
使用 createDeleteRequest() 删除文件。
为此,请完成以下步骤:
1.在应用的清单文件中声明新的MANAGE_MEDIA权限和READ_EXTERNAL_STORAGE权限。
为了调用 createWriteRequest() 而不显示确认对话框,请同时声明ACCESS_MEDIA_LOCATION权限。
2.在您的应用中,向用户显示一个界面,说明为什么他们可能需要向您的应用授予媒体管理访问权限。
3.调用ACTION_REQUEST_MANAGE_MEDIA intent 操作。这样会将用户引导至系统设置中的媒体管理应用屏幕。在此处,用户可以授予特殊应用访问权限。
6.3 应用存储访问权限
应用可以声明并创建一个自定义 activity,该 activity 在启动后可让用户管理应用存储在用户设备上的数据。应用可以在清单文件中使用android:manageSpaceActivity
属性声明此自定义“管理空间”activity。文件管理器应用可以启动此“管理空间”activity,即使应用未导出该 activity(即,该 activity 将android:exported设置为 false)也是如此。
在 Android 12 中,同时具有MANAGE_EXTERNAL_STORAGE权限和QUERY_ALL_PACKAGES权限的应用(如文件管理应用)可使用新的getManageSpaceActivityIntent()
将用户引导至其他应用的自定义“管理空间”activity(如果为该应用定义了一个)。
getManageSpaceActivityIntent() 方法接受软件包名称和请求代码,它返回以下某一项:
PendingIntent – 如果具有指定软件包名称的应用已定义自定义“管理空间”activity。调用 getManageSpaceActivityIntent() 方法的应用随后可以调用返回的 intent 以将用户引导至该自定义 activity。
null – 如果具有指定软件包名称的应用未定义“管理空间”activity。
6.4 扩展的文件访问权限支持
除了对 ExternalStorageProvider URI 的现有支持以外,getMediaUri()方法现在还支持 MediaDocumentsProvider URI。现在,系统在返回这些 URI 之前将其授予调用方。
此外,由createWriteRequest()授予的媒体 URI 现在支持File 类中的 API。这些 API 提供了读取、写入、重命名和删除文件的功能。