小米大屏设备适配说明2

小米大屏设备适配说明2

2.3 不同类型应用适配建议

针对不同行业,不同类型应用,我们推荐以下五种布局方案:多列布局,Tab拉伸布局,分栏布局,左右布局和横屏拉宽。

适配方式应用所属行业
多列布局金融理财,居家生活,时尚购物,影音视听,医疗健康,学习教育
Tab拉伸布局新闻资讯,图书阅读,摄影摄像,短视频
分栏布局聊天社交,效率办公
左右布局实用工具,音乐,时钟,天气,体育运动
横屏拉宽游戏,旅游交通

针对以上适配方式,具体描述和详细示意图如下(左图为APP在手机上的效果,中图为APP在折叠屏内屏上的效果,右图为APP在大屏设备上的效果)

A.多列布局

在应用首页,此类适配下顶部和底部的Tab拉伸到适合大屏设备页面的大小,同时首页主要功能键/APP推荐位置数量增加。

上图为应用商店类APP手机&大屏设备适配示意

上图为视频类APP手机&大屏设备适配示意

B.Tab拉伸

Tab拉伸适配下,页面主要内容不变,页面两侧加上与主要页面背景色同色/相近的背景,同时顶部和底部的Tab拉伸到适合大屏设备页面的大小。顶部按键数量相比于手机可有相应提升。

上图为新闻资讯类APP手机&大屏设备适配示意

上图为短视频类APP手机&大屏设备适配示意

C.分栏布局

分栏适配下,大屏设备上的页面分栏模式与电脑端的IM社交应用保持一致。

上图为社交IM类APP手机&大屏设备适配示意

D.左右布局

左右布局下,顶部和底部的Tab拉伸到适合大屏设备页面的大小,内部页面左右分列,左侧展示主要的文字性内容,右侧展示相应的图片,视频等。

上图为音乐类APP手机&大屏设备适配示意

E.横屏拉宽

此类适配下,APP界面在大屏设备内横屏时,横向铺满屏幕

3、通用开发适配指南

3.1 屏幕尺寸兼容性

应用resizeable 能力支持

运行 Android 的设备多种多样,尤其是当前已出现或未来可能出现的折叠屏设备,它们有着不同的屏幕尺寸和像素密度,而且在折叠屏设备从展开到折叠的切换过程中,同一个设备可能出现多种屏幕尺寸的使用状态。您在做app设计和开发的时候应该考虑支持不同屏幕尺寸和像素密度,以确保界面能够在各类屏幕上美观地呈现。

折叠屏之所以需要适配,是因为我们的应用有可能在运行的过程中,所在的屏幕尺寸发生了变化,这种情况对现有项目多少都会产生一些问题。

所以折叠屏适配的本质是:

当应用运行时,屏幕的尺寸、密度或比例发生了变化,应用能够继续在变化后的屏幕上正常显示和正常运行

允许改变应用尺寸

要适配折叠屏,首先是要让应用支持动态改变尺寸,我们需要根据应用面向的API Level (targetSdkVersion)进行支持resizeable能力在 manifest 中的 Application 或对应的 Activity 下声明:

  • 如果应用程序面向API Level 24以上(targetSdkVersion>=24),系统将默认应用支持resizeable能力。
  • 如果应用程序面向API Level 24以下(targetSdkVersion< 24),需要应用在manifest中显式的声明android:resizeableActivity=true, 才可以支持resizeable能力 。
android: resizeableActivity = ["true" | "false"]

备注:虽然安卓提供了申请受限屏幕能力,但强烈建议您为应用设计resizeable能力,因为一旦您声明了受限屏幕比例(最大或最小)这意味着,当您的app运行在一个屏幕比例超出了您声明的范围,您的应用程序在屏幕上将出现黑边等现象。

详细信息请参阅Android开发者指南

3.2 横竖屏状态获取

需要通过判断AppWindow的宽高比判断是横屏还是竖屏。

(1)宽:高 >= 1为横屏

(2)宽:高 < 1为竖屏

传递给应用的Configuration中的orientation会标识当前是横屏还是竖屏

(1)Configuration.ORIENTATION_LANDSCAPE为横屏

(2)Configuration.ORIENTATION_PORTRAIT为竖屏

3.3 横屏适配情况标记

应用主动适配大屏设备横屏,采用适配全屏的多列布局、Tab拉伸、分栏布局、左右布局、横屏拉宽等方式,需要在AndroidMainfest.xml文件设置标记。设置了此标记,横屏下会优先使用应用自己适配的横屏布局,而不是进入平行窗口下的横屏模式。

<!-- support landscape for large screen(PAD) -->
        <meta-data
            android:name="LandscapeForPad"
            android:value="true" />

3.4 折叠屏连续性支持

折叠连续性的定义:应用在可折叠设备上运行时,当屏幕发生折叠/展开动作,处于前台的应用会从一个屏幕转换到另一个屏幕,为提供出色的用户体验,确保当前应用能在转换后能连续无缝、正常地运行,应用应在同一位置以相同状态恢复,并且依然保持可用良好的UI布局。

三方应用需在 AndroidManifest.xml 文件的 application层级中添加 android.supports_size_changes或者

miui.supportAppContinuity

属性,以便声明应用支持连续性:

<application>      
    <meta-data
        android:name="android.supports_size_changes"
        android:value="true" />
</application>
<application>      
    <meta-data
        android:name="miui.supportAppContinuity"
        android:value="true" />
</application>

Activity的配置应该尽量支持resizeableActivity。应用需要在 AndroidManifest.xml 文件的 application 或者 actvivity 标签中添加 resizeableActivity=true 的属性。若应用不支持多窗口(例如小窗、分屏等)而声明了resizeableActivity=false,则在折叠屏折叠/展开场景中,可能出现UI布局问题、或者进入到原生的安卓兼容模式(SizeCompatMode)。

<application
    android:resizeableActivity="true">
 
    <activity
          android:resizeableActivity="true" />
</application>

若应用的 targetSDK 为 24 或以上,即便不设置 resizeableActivity 属性,其默认值也为 true。

在设备发生屏幕切换后,应用应能妥善地保存界面状态或者支持配置变更。

  • 保存界面状态

在应用Activity 被系统销毁重走生命周期时,应用需要及时保存和恢复 Activity 的界面状态,应用可以在 onSaveInstanceState 和 onRestoreInstanceState 方法中保存和恢复数据状态:

@Override
public void onSaveInstanceState(@NonNull Bundle outState, @NonNull PersistableBundle outPersistentState) { 
   super.onSaveInstanceState(outState, outPersistentState);
} 

@Override
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {    
   super.onRestoreInstanceState(savedInstanceState);
}
  • 支持配置变更

应用也可以支持配置变更,保持 Activity 不重走生命周期,这时需要应用在 AndroidManifest.xml 中配置如下 configChanges 属性:

<activity android:configChanges="screenSize|smallestScreenSize|screenLayout" />

添加 android:configChanges 后,Activity 或者 Fragment 就会收到 onConfigurationChanged() 的回调,此方法会收到传递的Configuration对象,从而指定新设备配置。通过读取

Configuration中的字段确定新配置,然后通过更新界面所用资源进行适当的更改。调用此方法时,Activity 的Resources对象会相应地进行更新,并根据新配置返回资源,以便您在系统不重启 Activity 的情况下轻松重置界面元素。

代码示例:

@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {         
   super.onConfigurationChanged(newConfig); 
   Toast.makeText(this, "onConfigurationChanged newWidth="
   + newConfig.screenWidthDp  + ", newHeight=" 
   + newConfig.screenHeightDp, Toast.LENGTH_SHORT).show();
}

MIUI针对折叠屏设备新增了针对app侧的wm_on_configuration_changed_called回调EventLog,结合系统侧configuration_changed回调EventLog日志和”Config changes”调用的SystemLog,可以方便您快速定位、排查与onConfigurationChanged回调有关的问题。

Log示例:

// 系统侧触发configurationChanged事件日志(EventLog)
07-20 12:43:31.521 1000 2677 2778 I configuration_changed: 536874240
// App侧触发configurationChanged回调事件日志(EventLog),4个参数分别代表userId、目标Activity、Configuration、耗时。
07-20 12:43:31.701 10086 5585 5585 I wm_on_configuration_changed_called: [0,com.miui.home.launcher.Launcher,{1.0 ?mcc?mnc [zh_CN] ldltr sw696dp w696dp h738dp 440dpi lrg hdr widecg port ?pc finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1914, 2160) mAppBounds=Rect(0, 0 - 1914, 2116) mMaxBounds=Rect(0, 0 - 1914, 2160) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=home mAlwaysOnTop=undefined mRotation=ROTATION_0 mInSplitScreen=false} as.2 s.24 fontWeightAdjustment=0 themeChanged=0 themeChangedFlags=0 extraData = Bundle[{}] dt/m/d},96]
// Config changes关键字SystemLog
2022-07-14 09:54:11.891 2485-2638/system_process I/ActivityTaskManager: Config changes=20000d00 {1.0 ?mcc?mnc [zh_CN] ldltr sw696dp w696dp h738dp 440dpi lrg hdr widecg port ?pc finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 1914, 2160) mAppBounds=Rect(0, 0 - 1914, 2116) mMaxBounds=Rect(0, 0 - 1914, 2160) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_0 mInSplitScreen=false} as.2 s.8214 fontWeightAdjustment=0 themeChanged=0 themeChangedFlags=0 extraData = Bundle[{}] dt/s/d}
  • 适配调试命令

在对三方应用折叠屏连续性支持情况测试的过程中,我们发现部分应用虽自行处置了配置变更,在AndroidManifest.xml中配置了screenSize|smallestScreenSize|screenLayout等configChanges属性,并回调onConfigurationChanged方法对屏幕做了适配,Activity不会重建,也不会重走生命周期。但应用的Activity仍然存在很多控件显示问题。

为了带给用户良好的使用体验,我们在系统中忽略了这部分app的configChanges属性配置,强制让应用的Activity执行重建,并重走生命周期,以达到界面完美显示的效果,我们将这种行为称为relaunch。

为了方便您在我们的设备上进行适配,我们提供了如下命令帮助您屏蔽上述的relaunch行为。

屏蔽relaunch行为,是我们在经过充分测试评估后,认为您的应用即使执行relaunch,也无法达到连续性适配测试标准,甚至因relaunch重走生命周期,导致折叠展开后亮屏变慢、回到桌面、闪退、ANR等问题,因此屏蔽relaunch行为,旨在屏蔽上述亮屏变慢、回到桌面、闪退、ANR等一系列连续性、稳定性问题。

如果您希望通过relaunch重建Activity生命周期来适配折叠屏连续性,您需要关注上述的连续性、稳定性问题,避免在重建Activity生命周期中执行耗时操作,影响折叠展开时的应用性能、亮屏速率等,并避免在onDestroy里调用finish,以免折叠展开后回到桌面。

通过如下命令,您的应用在没有配置screenSize|smallestScreenSize|screenLayout等configChanges属性的情况下,折叠展开操作时仍能正常回调onConfigurationChanged方法,Activity不会重建,生命周期不会重走,但与此同时,在折叠屏折叠展开操作时可能会有通知提醒您应用显示存在异常,需要重启。您无需理会上述系统通知,它不会对您的适配造成任何影响。

adb shell wm dump -setForceDisplayCompatMode 应用包名 blocklist

上述命令会导致您的应用无法执行relaunch重建,即使您的应用没有配置screenSize|smallestScreenSize|screenLayout等configChanges属性,但您的应用仍能正常回调onConfigurationChanged方法。为了您的应用能自行处置screenSize|smallestScreenSize|screenLayout等configChanges属性,您可以直接使用下面的命令,该命令会将您的应用恢复到Android 原生的处置配置变更的流程,便于您进行适配工作。与此同时,该命令会帮助您屏蔽掉重启通知,以免对您的适配工作造成打扰。

adb shell wm dump -setForceDisplayCompatMode 应用包名 allowlist

如果您希望恢复relaunch状态,可执行如下命令(尽管我们不建议您这么做,仍然希望您能通过configChanges进行适配工作):

adb shell wm dump -setForceDisplayCompatMode 应用包名 relaunchlist

请注意:以上命令行为不会在设备中持久化保存,在设备重启后就会失效。如有需要,您可再次执行上述命令。此外,执行命令后,您需要将您的应用从最近任务移除后重新启动,来保证命令生效。

如您已完成应用连续性适配,可以联系我们,在测试您的应用达到连续性标准后,我们会通过云控的方式将您的应用从relaunch名单中移除,待您的设备收到云控更新后,您的应用将不会再执行relaunch行为。

  • 页面布局适配

Android 10 (API 级别 29) 或更高版本支持更多种宽高比。对于可折叠设备而言,设备类型可以是超长、超薄的屏幕(例如屏幕宽高比为 21:9 的折叠设备),也可以是 1:1 的屏幕,如要与尽可能多的设备兼容,需要尽量多针对以下屏幕宽高比测试自己的应用:

如果无法支持上述某些高宽比,可以使用 maxAspectRatio 以及 minAspectRatio 来指明自己应用可以处理的最高宽高比和最低宽高比,此属性需要在 AndroidManifest.xml 文件的 application 或者 actvivity 标签中添加。例如:

<application    
    android:maxAspectRatio="2.4">     
<activity          
        android:maxAspectRatio="2.4" />
</application>

详细信息请参阅Android开发者指南

小米开发平台 MIX前置摄像头适配说明

小米开发平台 MIX前置摄像头适配说明

1. 背景

由于MIX系列手机的前置摄像头在屏幕右下角,使用前置摄像头需要将手机倒过来,所以需要应用开发者在用到前置相机的页面把相机画面和UI都倒转过来。

2. Activity适配方法

MIX系列手机支持页面180度方向倒转,只需配置sensorPortrait属性即可。

2.1. 直接在AndroidManifest.xml里面配置

示例如下:AndroidManifest配置

<activity android:name=".MainActivity"    android:screenOrientation="sensorPortrait">    <intent-filter>        <action android:name="android.intent.action.MAIN" />        <category android:name="android.intent.category.LAUNCHER" />    </intent-filter></activity>

2.2. 在代码里面配置

代码示例如下:代码配置方法

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);    setContentView(R.layout.activity_main);}

3. Camera Device适配方法

3.1. UI上给用户以适当的提示,引导用户倒置手机

3.2. 结合屏幕显示方向来设置Camera的显示方向

主要是调用Camera中的setDisplayOrientation接口代码配置方法

final void setDisplayOrientation(int degrees);// Set the clockwise rotation of preview display in degrees.

示例:

代码配置方法

public static void setCameraDisplayOrientation(Activity activity,                        int cameraId, android.hardware.Camera camera) {    android.hardware.Camera.CameraInfo info =        new android.hardware.Camera.CameraInfo();    android.hardware.Camera.getCameraInfo(cameraId, info);    int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();    int degrees = 0;    switch (rotation) {        case Surface.ROTATION_0: degrees = 0break;        case Surface.ROTATION_90: degrees = 90break;        case Surface.ROTATION_180: degrees = 180break;        case Surface.ROTATION_270: degrees = 270break;    }    int result;    if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {        result = (info.orientation + degrees) % 360;        result = (360 - result) % 360// compensate the mirror    else // back-facing        result = (info.orientation - degrees + 360) % 360;    }    camera.setDisplayOrientation(result);}


可以在OrientationListener.onOrientationChanged(int orientation)回调中进行check,如果display的rotation与之前不同,则执行上述操作,重新设置Camera的显示方向。

Jpeg拍照方向(通过Camera的Parameters.setRotation设置的rotation)与屏幕是否翻转显示无关,维持原有逻辑即可。

小米开发平台屏幕指纹设备适配说明

小米开发平台屏幕指纹设备适配说明

1. 什么是屏下指纹

小米 8 透明探索版中,我们引入屏下指纹这项新技术,即指纹传感器嵌入屏幕下。

由于是屏下指纹,所以需要出现指纹图标,提示用户指纹传感器的位置,但这样会和部分需要使用指纹验证的业务产生冲突。

2. 屏下指纹相关接口

由于各个开发者的指纹验证逻辑和 UI 样式非常不一样,所以系统不打算统一大家的样式,而是会提供相关接口,由各业务组做针对性地适配。

2.1. 是否为屏下指纹设备

// true means FOD project
PRODUCT_PROPERTY_OVERRIDES += ro.hardware.fp.fod=true

2.2. 指纹传感器的位置、大小(每次指纹验证都需要重新获取)

以下是 Android O 的接口:

//location of FOD sensor's top left corner in pixel, the top left corner of screen is (0,0)
//persist.sys.fp.fod.location.X_Y 表示sensor区域左上角的坐标,以pixel为单位,以物理屏幕左上角为(0,0)计算。
persist.sys.fp.fod.location.X_Y = 453,1640 //sensor的大小(单位也是pixel) persist.sys.fp.fod.size.width_height = 173,173

以下是 Android P 的接口(应谷歌要求,需要加入「vendor」字样)

//location of FOD sensor's top left corner in pixel, the top left corner of screen is (0,0)
// persist.vendor.sys.fp.fod.location.X_Y 表示sensor区域左上角的坐标,以pixel为单位,以物理屏幕左上角为(0,0)计算。
persist.vendor.sys.fp.fod.location.X_Y = 453,1640

//sensor的大小(单位也是pixel)
persist.vendor.sys.fp.fod.size.width_height = 173,173

因为同一款设备,市场上可能会同时存在 Android O 和 Android P 两个版本,所以开发者需要根据 Android 版本调用不同的接口,以获取正确的位置信息。

2.3. 控制指纹图标显示、消失

调用标准的指纹监听接口即可显示指纹图标。

2.4. 指定指纹图标的颜色(黑色或白色)

由于各个业务组的界面不尽相同,我们会提供一个接口,让业务方指定要用「黑色」还是「白色」的指纹图标。

复用原有 flag 中的「0」「1」标志(原flag标志没有使用),0表示黑色,1表示白色。

mFm.authenticate(null, mCancellationSignal, 0, callback, null); // 黑色指纹图标(默认)
mFm.authenticate(null, mCancellationSignal, 1, callback, null); // 白色指纹图标

3. 屏下指纹适配建议

3.1. UI 避开指纹传感器的位置

业务方可以通过上述接口获取指纹图标的位置和大小,建议控件、键盘等元素都避开那个区域。

3.2. 不建议同时支持密码验证和指纹验证

由于指纹图标会挡住键盘,所以不建议开发者同时支持指纹和密码验证,而是做成互斥的模式,类似微信支付。

3.3. 考虑是否需要调整正确、错误的反馈

如果第三方调用,系统不处理指纹验证正确或错误的反馈,交由开发者自行处理。开发者可以根据自身业务逻辑,考虑是否需要针对屏下指纹调整相关反馈。

4. FAQ

4.1. 有几款机型支持屏下指纹

小米机型中,目前支持屏下指纹的设备为「小米 8 透明探索版」和「小米8 屏幕指纹版」。

4.2. 如何测试

拿到设备后,建议同时测试以下两个版本的效果:

  • MIUI 9 稳定版,即出厂自带的版本,截至2018年底仍会是该机型用户的主要版本。
  • MIUI 10 开发版或稳定版,后续 MIUI 10 将成为 MIUI 系统的主流,又因为 System UI 的代码架构在MIUI 10有较多调整,所以建议同时覆盖测试。

顺便附上ROM下载链接如下:

小米开发平台刘海屏、水滴屏 Android O 适配

小米开发平台刘海屏、水滴屏 Android O 适配

1. 背景

1.1. 目前已上市的小米 Notch 设备(俗称刘海屏手机)如下,其宽度、高度和形状均略有差异

机型modeldevice分辨率Notch高度Notch宽度DPI
小米8MI 8dipper1080*224889560440
小米8 SEMI 8 SEsirius1080*224485540440
小米8 透明探索版MI 8 Explorer Editionursa1080*224889560440
小米屏幕指纹版MI 8 UDequuleus1080*224889560440
小米青春版MI8Liteplatina1080*228082296440
小米POCO F1POCO F1beryllium1080*224686588440
红米6 ProRedmi 6 Prosakura1080*228089352440

注:以上设备,由于MIUI调整了DPI值,因此DP值与像素值的转换关系是 1dp = 2.75 px 。

1.2. 含 Notch 往往都是全面屏手机,即屏幕比例可能是18:9、18.7:9 等不同的值

全面屏设备的适配建议详见:https://dev.mi.com/console/doc/detail?pId=1160

1.3. Android P 提供了 刘海屏的标准适配接口

MIUI 也将在 Android P 上采用标准接口,因此,下文提及的接口仅用于 Android O 上。关于 Android P 的接口说明,详见https://dev.mi.com/console/doc/detail?pId=1341

1.4. 如下图,为便于说明,我们将顶部区域定义为 Notch 和耳朵区

上述两种屏幕都可以统称为刘海屏,不过对于右侧较小的刘海,业界一般称为水滴屏或美人尖。为便于说明,后文提到的「刘海屏」「刘海区」都同时指代上图两种屏幕。

2. 系统级适配规则

Notch 机型在界面上会带来两个问题:

  • 顶部内容会被 Notch 遮挡
  • 如何处理耳朵区的显示区域

为了保证绝大部分应用都能正常显示,同时尽可能利用屏幕的显示区域。MIUI System UI 制定了以下全局规则

  • status bar 略高于 Notch 高度,对于应用来说,相当于一个更高的 status bar。
  • 当应用显示 status bar 时(如微信首页),允许应用使用耳朵区(背后的逻辑是:因为 status bar 区域本身不可交互,且会显示信号、电池等信息,因此我们假定应用不会在该区域放置重要的内容和可交互的控件)。
  • 当应用不显示 status bar 时(如全屏游戏),不允许应用使用耳朵区,系统默认填黑。
  • 横屏时,默认均不允许使用耳朵区,系统默认填黑。
  • 不允许应用180度倒转显示。

注:上述规则的模拟效果对比图,可以参见文末的附录“Notch 屏系统默认规则介绍”。

3. 开发者适配

系统规则只能解决最基础的可用性问题,在系统规则下,开发者仍需要检查以下内容:

  • 检查系统默认规则是否有可用性问题,考虑是否做针对性优化。
  • 检查 status bar 的显示策略。重新考虑是否隐藏 status bar
  • 尽量避免某些页面显示 status bar,某些页面又隐藏,否则会出现页面跳变的情况(应用的可用高度变了)。
  • 检查横屏的情况,确定是否需要利用横屏的Notch,若使用,需兼顾 Notch 出现在左边/右边的情况。
  • 检查是否写死了状态栏的高度值。Notch机器状态栏的值是变化的,建议改为读取系统的值(后有相关方法说明)。
  • 检查开启「隐藏屏幕刘海」后,应用是否显示异常(详见后文)。
  • 检查普通屏幕的显示,保证应用在普通屏幕和 Notch 屏幕下都能正常显示 。

4. 系统接口说明

若开发者对系统规则下的效果不满意,可以调用以下接口,做针对性的优化。

4.1. 如何判断设备为 Notch 机型

系统增加了 property ro.miui.notch,值为1时则是 Notch 屏手机。

SystemProperties.getInt("ro.miui.notch", 0) == 1;

4.2. 如何获取 Notch / 凹口 / 刘海 的高度和宽度(截至2018.6.26)

MIUI 10 新增了获取刘海宽和高的方法,需升级至8.6.26开发版及以上版本。

以下是获取当前设备刘海高度的方法:

int resourceId = context.getResources().getIdentifier("notch_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}

以下是获取当前设备刘海宽度的方法:


int
resourceId = context.getResources().getIdentifier("notch_width", "dimen", "android"); if (resourceId > 0) { result = context.getResources().getDimensionPixelSize(resourceId); }

4.3. Application 级别的控制接口

如果开发者认为应用的所有页面统一处理就行,可以使用该接口。在 Application 下增加一个 meta-data,用以声明该应用是否使用耳朵区。示例如下:

<meta-data
 android:name="notch.config"
 android:value="portrait|landscape"/>

其中,value 的取值可以是以下4种:

"none" 横竖屏都不绘制耳朵区

"portrait" 竖屏绘制到耳朵区

"landscape" 横屏绘制到耳朵区

"portrait|landscape" 横竖屏都绘制到耳朵区

注:一旦开发者声明了meta-data,系统就会优先遵从开发者的声明。

4.4. Window 级别的控制接口

如果开发者希望对特定 Window 作处理,可以使用该接口。 在 WindowManager.LayoutParams 增加 extraFlags 成员变量,用以声明该 window 是否使用耳朵区。

其中,extraFlags 有以下变量:

0x00000100 开启配置
0x00000200 竖屏配置
0x00000400 横屏配置

组合后表示 Window 的配置,如:

0x00000100 | 0x00000200 竖屏绘制到耳朵区
0x00000100 | 0x00000400 横屏绘制到耳朵区
0x00000100 | 0x00000200 | 0x00000400 横竖屏都绘制到耳朵区

控制 extraFlags 时注意只控制这几位,不要影响其他位。可以用 Window 的 addExtraFlags 和 clearExtraFlags 来修改, 这两个方法是 MIUI 增加的方法,需要反射调用。

int flag = 0x00000100 | 0x00000200 | 0x00000400;
try {
    Method method = Window.class.getMethod("addExtraFlags",
            int.class);
    method.invoke(getWindow(), flag);
} catch (Exception e) {
    Log.i(TAG, "addExtraFlags not found.");
}

4.5. 状态栏高度获取方法

由于 Notch 设备的状态栏高度与正常机器不一样,因此在需要使用状态栏高度时,不建议写死一个值,而应该改为读取系统的值。

以下是获取当前设备状态栏高度的方法:

int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}

4.6. 如何测试和联系我们

使用 小米 8 等任意有刘海的小米设备,升级到  MIUI 10 最新开发版即可(若开发版为Android P,请参见 Android P 的刘海适配文档),支持的机型和下载链接如下(找到对应机型的最新开发版):

4.7. 如何联系我们

可以邮件给我们的项目组 miuishell@xiaomi.com,会有同事解答相关疑问。

5. “隐藏屏幕刘海”适配

MIUI 针对 Notch 设备,有一个“隐藏屏幕刘海”的设置项(设置-全面屏-隐藏屏幕刘海),具体表现是:系统会强制盖黑状态栏(无视应用的Notch使用声明),视觉上达到隐藏刘海的效果。但会给某些应用带来适配问题(控件/内容遮挡或过于靠边等)。

因此开发者在适配时,还需要检查开启“隐藏屏幕刘海”后,应用的页面是否显示正常。针对有问题的页面,我们建议:

  • 通过以下方法获取系统状态栏高度,然后据此调整布局,而不是写死布局:
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
	result = context.getResources().getDimensionPixelSize(resourceId);
}
  • 如有需要,可以通过查询以下 Global settings 来确定「隐藏屏幕刘海」是否开启了,然后再作针对性优化。
Settings.Global.getInt(mContext.getContentResolver(), "force_black", 0) == 1

6. 关于 Android P 的适配

由于 Android P 已针对异形屏幕提供了标准的接口,因此 MIUI 在后续升级到 Android P 时,将会完全复用 Android P 的接口和逻辑。不过仍有几点值得注意:

  • 由于 MIUI Android O 的接口能力与 Android P 有差异,无法二者完全兼容。所以上述提到的接口,在升级到 Android P 后将不再生效,开发者需要针对 Android P 重新适配,望各位开发者谅解。
  • 同时,由于运行 MIUI Android O 的刘海屏设备还会存在好几年,开发者在代码上,仍需要保留对 MIUI Android O 的适配逻辑。

由于 Android P 的接口公布得较晚,我们没法做到和 Android P 完全兼容。给各位开发者造成不便,再次表示歉意。小米刘海屏 Android P 的适配文档详见 https://dev.mi.com/console/doc/detail?pId=1341。 

7. 附录 – 系统默认规则的说明

如果开发者未做任何声明,系统也会有一套默认的显示规则,下图说的就是这套规则,增加示意图方便大家理解。

小米开发平台 刘海屏、水滴屏、挖孔屏 Android P/Q 适配

小米开发平台 刘海屏、水滴屏、挖孔屏 Android P/Q 适配

1. 背景

  • 小米 8 等刘海设备上市时运行的是 Android O 设备,但由于 Android O 没有标准接口,所以当时适配的规则和接口仅在 MIUI 系统生效。关于小米 Android O 的规则,详见https://dev.mi.com/console/doc/detail?pId=1293
  • 后来 Android P 中新增了刘海屏适配的API,为了与行业标准一致,MIUI 也决定在运行 Android P 的设备上完全采用 Android P 的接口。
  • 但由于 Android P 的接口定义得比较晚,导致 MIUI 接口无法与其完全兼容,开发者需要针对 Android P 的小米设备重新适配。

该文档将结合小米的情况给大家简要介绍 Android P 的刘海屏适配规则及 API,更详细的内容可以直接查看官方文档 https://developer.android.com/guide/topics/display-cutout/

2. 部分小米水滴屏/刘海屏/挖孔屏设备信息如下

机型modeldevice分辨率Notch高度Notch宽度DPI
小米8MI 8dipper1080*224889560440
小米8 SEMI 8 SEsirius1080*224485540440
小米8 透明探索版MI 8 Explorer Editionursa1080*224889560440
小米8 屏幕指纹版MI 8 UDequuleus1080*224889560440
小米8 青春版MI8Liteplatina1080*228082296440
小米POCO F1POCO F1beryllium1080*224686588440
红米6 ProRedmi 6 Prosakura1080*228089352440
红米Note 7Redmi Note 7lavender1080*234079116440
小米CC9 ProMi CC9 Protucana1080*2340​71146​​440
Redmi K30​​Redmi K30​phoenix​1080*2400​92179​440

注意事项:

  • 以上设备,由于MIUI调整了 DPI 值,因此DP值与像素值的转换关系是 1dp = 2.75 px ;
  • 用原生api DisplayCutout就可以直接获取​设备屏幕尺寸和异形的位置大小等信息,以上仅做参考,之后新机将不再罗列。​

3. 概念说明

为了方便讨论,我们明确下以下概念:

上述两种屏幕都可以统称为刘海屏,不过对于右侧较小的刘海,业界一般称为水滴屏或美人尖。为便于说明,后文提到的「刘海屏」「刘海区」都同时指代上图两种屏幕。

4. Android P/Q 刘海屏水滴屏挖孔屏的适配规则 

Android P 提供了 3 种显示模式供开发者选择,分别是:

  • 默认模式(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT)
  • 刘海区绘制模式( LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)
  • 刘海区不绘制模式(LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER)

如果开发者未作任何声明,则会按默认模式处理。以下将具体介绍这三种模式的表现。

4.1. 默认模式(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT)

为了在不影响操作的情况下,尽可能利用刘海屏的显示区域,有以下表现:

非全屏(normal mode)全屏(fullscreen mode)
竖屏(portrait mode)使用耳朵区禁用耳朵区
横屏(landscape mode)禁用耳朵区禁用耳朵区

注:所谓全屏(fullscreen mode),是指隐藏状态栏(status bar),即通过 SYSTEM_UI_FLAG_FULLSCREEN 实现的效果。

默认模式的截图效果如下:

4.2. 刘海区绘制模式(LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)

如上所述,默认模式下某些场景会禁用耳朵区,那是因为这些场景下,系统无法判断开发者是否会把控件放置在耳朵区,所以只好默认禁用。如果开发者想要在那些场景下使用耳朵区,需要主动声明,即使用 LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 来主动声明。

由于各个厂商的刘海或者凹口形状、位置不一, 开发者可以通过 WindowInsets.getDisplayCutout()  来获得 DisplayCutout object,里面包含了几个有用的方法:

开发者根据业务内容,自行判断是否需要根据不同的刘海形状做不同的布局调整。以小米8(刘海高度89px)为例,当开发者选用 SHORT_EDGES 模式时,以上接口会返回以下值:

竖屏横屏(刘海在左边)
getBoundingRects()(201, 0 – 879, 90)(0, 201 – 90, 879)
getSafeInsetLeft() 090
getSafeInsetTop()900
getSafeInsetRight() 00
getSafeInsetBottom()00

上述接口的返回值代表:

  • 小米8有一个刘海,竖屏时,这个刘海所在的矩形区域的左上角、右下角的坐标分别为 (201, 0) 和 (879, 90) —— 左上角为 (0, 0) 原点;横屏时,这个刘海所在的矩形区域的左上角、右下角的坐标分别为 (0, 201) 和 (90, 879) —— 左上角为 (0, 0) 原点。
  • 对于小米8,如果开发者需要将内容避开刘海区域,竖屏时就需要从顶部向下偏移 90 px,左、右和下无需要偏移。

又以红米Note 7(水滴屏设备) 为例,当开发者选用 SHORT_EDGES 模式时,以上接口会返回以下值:

竖屏横屏(水滴在左边)
getBoundingRects()(450, 0 – 630, 80)(0, 450 – 80, 630)
getSafeInsetLeft() 080
getSafeInsetTop()800
getSafeInsetRight() 00
getSafeInsetBottom()00

上述接口的返回值代表:红米Note 7 有一个刘海(水滴),竖屏时,这个刘海所在的矩形区域的左上角、右下角的坐标分别为 (450, 0) 和 (630, 80) ;横屏时,这个刘海所在的矩形区域的左上角、右下角的坐标分别为 (0, 450) 和 (80, 630) 。

又以Redmi K30 (挖孔屏设备)为例子,当开发者选用 SHORT_EDGES 模式时,以上接口会返回以下值:

 竖屏横屏(摄像头在左边)
getBoundingRects()(844, 0 – 1080, 95)](0, 0 – 95, 236)
getSafeInsetLeft()095
getSafeInsetTop()950
getSafeInsetRight()00
getSafeInsetBottom()00

上述接口的返回值代表:

Redmi K30有刘海/水滴/挖孔,竖屏是,这个刘海在手机的左上角,右下角的坐标分别为 (844,0)和 (1080, 95);左上角为(0,0);横屏时,这个刘海对应的值为(0,0)和(95,236)。对于Redmi K30,如果开发者需要将内容避开挖孔区域,竖屏就需要从顶部向下偏移95px,左、右和下无需偏移。

4.3. 刘海区不绘制模式(LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER )

开发者选用这个模式后,意味着不绘制内容到耳朵区。如非必需,我们不建议采用这种模式,因为那样会浪费不少屏幕空间,用户体验不佳。

当开发者选用 NEVER 模式时, DisplayCutout object 的以下方法都会返回空值,因为 Google 认为既然开发者不使用耳朵区,就不需要关心刘海的大小了。

竖屏横屏(刘海在左边)
getBoundingRects()nullnull
getSafeInsetLeft() null null
 getSafeInsetTop()nullnull
getSafeInsetRight() nullnull
getSafeInsetBottom()nullnull

5. 其他注意事项

5.1. 避免写死状态栏的值

由于 Notch 设备的状态栏高度与正常机器不一样,因此在需要使用状态栏高度时,不建议写死一个值,而应该改为读取系统的值。

以下是获取当前设备状态栏高度的方法:

int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");

if (resourceId > 0) {

result = context.getResources().getDimensionPixelSize(resourceId);

}

5.2. 处理好同一页面,进入与退出全屏模式(fullscreen mode)的过渡

因为在默认模式 / LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 下,系统针对全屏与非全屏的页面,耳朵区的显示逻辑不一样。如果开发者没有处理好,容易出现页面可用区域跳变的问题。针对这种页面,我们建议开发者主动声明是否使用耳朵区,以避免跳变。

6. 常见问题

6.1. 如何测试

有两种方法:

  • 使用小米设备测试,如小米8系列(含标准版、探索版、屏幕指纹版),然后升级至 Android P 的 MIUI 版本,下载地址为:https://www.miui.com/download-345.html
  • 使用运行原生 Android 9 的设备,然后前往「开发者选项 – 模拟“刘海屏”」,选择任一刘海选项。 

若适配中遇到问题,可以发邮件给相关工程师张定昌 zhangdingchang@xiaomi.com、喻伟 yuwei@xiaomi.com 或工程组 miuishell@xiaomi.com。

6.2. 适配过小米 Android O 的刘海屏接口,在小米的 Android P 设备上是否需要重新适配

需要。如文章开头所说,Android P 的接口今年6月才公布,我们在接口设计上和他们有一些出入,所以没法兼容。开发者仍然需要再针对 Android P 做适配,但好消息是,各大手机厂商都支持 Android P 的接口,所以大家只要适配一次就可以了。

6.3. MIUI Android O 的老接口在 Android P/Q设备上是否生效?

P和Q大部份用的是原生Andoid的API,MIUI的接口保留了O里面的Application级别的控制接口

<meta-data
 android:name="notch.config"
 android:value="portrait|landscape"/>

app如果用这个meta-data声明了横竖屏都绘制到耳朵区,相当于每个页面都设成了LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES模式。这就需要app确认自己的所有页面会不会被遮挡,特别是横屏和全屏页面。如果想单独修改某个页面,可以单独修改该页面Window的layoutInDisplayCutoutMode属性

6.4. 原生 Android P 的规则和 MIUI Android O 的规则有什么区别

双方在默认模式下的表现是完全一致的,区别主要体现在:

  • Android P 能通过 DisplayCutout object 获取刘海 / Notch / Cutout 的具体信息,但 MIUI Android O 只能获取刘海的高宽信息。
  • Android P 不能控制仅竖屏(或横屏)使用耳朵区,但 MIUI Android O 可以分别配置横竖屏对耳朵区的使用策略。 

小米开发平台亮度适配说明

小米开发平台亮度适配说明

1.  前言

为了使亮度调节更加细腻, MIUI对原生亮度级别进行了扩展, 由原有的255级调整根据不同屏幕分别支持255/1023/2047/4095级。开发者在进行亮度调整时需要先去确认机型亮度的最大值和最小值,然后进行比例调整。

2.  机型最大和最小亮度确认方法

基于Android P以上的MIUI亮度级别支持超过255, 可通过如下方式确认最大值和最小值:

        public int getMaximumScreenBrightnessSetting() {

            return mContext.getResources().getInteger(mContext.getResources()

                    .getIdentifier(“config_screenBrightnessSettingMaximum”, “integer”, “android”));

        }

        public int getMinimumScreenBrightnessSetting() {

            return mContext.getResources().getInteger(mContext.getResources()

                    .getIdentifier(“config_screenBrightnessSettingMinimum”, “integer”, “android”));

        }

小米开发平台大字体适配说明

小米开发平台大字体适配说明

1.背景

目前MIUI中可调节字体大小的有两处:字体大小 、显示大小。

1.1.字体大小

位置:设置-显示-字体大小。

原理:修改FontScale,仅修改字体大小,当字体档位偏大时,可能会造成布局错乱、重叠等问题。

1.2.显示大小

位置:设置-更多设置-无障碍-显示大小。

原理:修改屏幕显示密度DPI,修改DPI后可整体调整显示比例,包括字体大小和图片大小。

2.现存问题

  • 无论是系统App、还是第三方App,对于大字体的适配效果均可以优化以满足特殊人群对大字体的需求;
  • 部分第三方App不随MIUI系统字体大小的变化而变化(或变化后效果欠佳),且部分App自身有大字体调节设置,未做关联融合,用户体验不佳。

3.三方应用适配MIUI大字体优化方案

3.1.字体大小调整说明

字体大小调整FontScale,并且应用对应的UIMode。FontScale和相应的UIMode档位详情如下:

3.1.1.字体大小适配步骤

如果需要特殊适配相应的UIMode,需要添加对应的资源文件夹,详见以下步骤:

  • Step1: 把需要随字体模式改变而变大小的文字大小用sp单位描述;
  • Step2: 把需要随字体模式改变的资源放在XXX-[YYYui]-ZZZ的目录下,例如drawable-largeui-hdpi,YYY的可选集合为{ smallui, mediumui, largeui, hugeui, godzillaui };
  • Step3:如果需要在代码中根据字体模式执行不同的代码,可通过MiuiConfiguration.getScaleMode()获取,并与MiuiConfiguration.UI_MODE_TYPE_SCALE_LARGE等比较即可。

3.1.2.附加说明

  • 不同UIMode下的资源, 是并列关系, 不存在类似 xxhdpi -> xhdpi 的兜底,意思是在xxhdpi没找到资源并不会再去xhdpi中找;
  • 对于不需要特殊适配 “巨无霸” 模式的app, 只要复制 “超大号” 适配的资源到 “巨无霸” 下即可完成适配。

3.2.显示大小调整说明

显示大小调整修改屏幕显示密度DPI,在系统默认DPI的基础上进行放大和缩小,修改显示大小是通过修改设备configuration中的display density 实现的,具体档位说明如下:

用户修改了显示大小,那就是修改了屏幕显示密度DPI参数,此时应用加载的资源目录可能会发生变化,比如由xxhdpi变为xxxhdpi,具体加载哪个目录的资源由当前显示密度决定。

3.3.三方应用监听字体大小档位变化的方法

  • 监听字体大小变化的方法
IntentFilter filter = new IntentFilter();filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);registerReceiver(mIntentReceiver, filter);
  • 获取当前字体大小档位的方法,通过获取UIMode来获取
static int getCurrentUIModeType() { Configuration config = Resources.getSystem().getConfiguration(); return config.uiMode & Configuration.UI_MODE_TYPE_MASK;}

其中字体大小的小号,标准,中号,大号,超大,巨无霸对应的UIMode值分别是12,1,13 ,14 ,15 ,11

  • 获取当前屏幕显示密度的方法可参考:
static float getCurrentDensityDpi(Context context) {DisplayMetrics metrics = new DisplayMetrics();WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);wm.getDefaultDisplay().getMetrics(metrics);return metrics.densityDpi;}

4.需三方应用支持的具体内容

MIUI大字体的适配,能够照顾到视力有障碍以及老年用户,能够让用户感受到App的用心,提升用户体验;将用户放在首位,建立良好的口碑,让App能够在更广泛的人群中传播,因此在适配过程中,需要三方应用支持下述内容:

  • MIUI修改字体大小的时候修改了FontScale,FontScale的变化针对屏幕上用像素为单位的文本是不起作用的,需要适配的话文本需要使用sp为单位,同时字体变大可能导致布局变化,需要特殊注意;
  • MIUI修改字体大小的同时还修改显示模式(UIMode),需要特殊适配需要添加对应的资源文件夹,具体的UIMode详情和适配步骤详见:3.1字体大小调整说明;
  • MIUI修改了显示大小时是修改了屏幕的DPI值,此时应用加载资源的目录可能会发生变化,具体加载哪个目录由当前的DPI值决定,同时DPI变化会直接导致布局的变化,需要进行一个自查,若出现文字折行、文字和icon重叠等情况,需进行调整适配,有利于用户体验的提升。

5.FAQ

5.1.常见的修改字体大小引起的问题和解决方法

  • 文本内容显示不全, 需要调整布局, 或者调整不同显示密度对应的文本dp和sp值。
  • 图片割裂或显示不全, 原因一般是图片资源和显示密度不匹配, 需要在监听到字体变化后重新加载图片。

5.2.平板应用不需要修改,保持原来的设计

小米开放平台屏幕圆角适配说明

小米开放平台屏幕圆角适配说明

1.背景

目前大部分小米手机的屏幕都是圆角,如下示意图所示。四个黑色角表示屏幕缺失部分。

2.参数说明

MIUI提供以下两个值分别表示屏幕上下方圆角的半径:

  • rounded_corner_radius_top 
  • rounded_corner_radius_bottom

3.使用方法

例如需要取得rounded_corner_radius_top的值,可以参考如下代码:

public static int getCornerRadiusTop(Context context) {
         int radius = 0;
         int resourceId = context.getResources().getIdentifier("rounded_corner_radius_top", "dimen", "android");
         if (resourceId > 0) {
              radius = context.getResources().getDimensionPixelSize(resourceId);
       
         return radius;
}

小米开发平台全面屏及虚拟键适配说明

小米开发平台全面屏及虚拟键适配说明

1. 前言

自2016年小米 Mix 全面屏手机推出时,得到了业界和用户的双重认可,小米也引领了“全面屏”手机的风潮。作为全面屏手机的引领者,小米将推出更多的全面屏手机,追求更大的屏幕比例,更高的屏占比。

这些变化也影响了手机软件的设计,最值得开发者关注的,是以下两点:

  • 更大的屏幕高宽比
  • 虚拟导航键

2. 更大的屏幕高宽比

大部分全面屏设备都是18:9,从下图可以看到,在 1080P 的分辨率下,比标准的 16:9 屏幕,足足多了240像素。开发者需要作一些优化,以充分利用更大的显示空间。

2.1. 声明 Maximum Aspect Ratio

Android 标准接口中,支持应用声明其支持的最大屏幕高宽比(maximum aspect ratio)。具体声明如下,其中的 ratio_float 被定义为是高除以宽,以 16:9 为例,ratio_float = 16/9 = 1.778 (18:9则为2.0)。

<application>
    <meta-data android:name="android.max_aspect" android:value="ratio_float" />
</application>

若开发者没有声明该属性,ratio_float 的默认值为1.86,小于2.0,因此这类应用在全面屏手机上,默认不会全屏显示,屏幕底部会留黑。考虑到将有更多 19.5:9 甚至更长的手机出现,建议开发者声明 Maximum Aspect Ratio ≥ 2.2 或更多。值得一提的是,如果应用的 android:resizeableActivity 已经设置为 true,就不必设置 Maximum Aspect Ratio 了。详见 Android 官方文档 Declaring maximum aspect ratio

2.2. 避免内容拉伸/变形

从16:9变成18:9甚至更长的比例,图片往往被会拉伸变形,此问题常见于应用开屏图。开发者应使用更灵活的布局,以适应不同的屏幕比例。

2.3. 充分利用屏幕空间

开发者应充分利用全面屏显示更多内容。如下图,王者荣耀已修改了 Maximum Aspect Ratio,在全面屏有更宽阔的游戏视野。

3. 虚拟导航键(Navigation bar)优化

3.1. 虚拟导航键样式

为了实现更高的屏占比,屏幕内的虚拟导航键就成了标准功能,如何让其应用界面在视觉上统一,同样需要开发者的积极适配。Android 已经有相关接口允许开发者自定义虚拟键的样式,以下是可供选择的样式。

关于使用哪种样式,我们有以下建议:

建议1:如果页面含有复杂背景/纹理,建议设置为透明,如下图中的桌面和通话界面。

建议2:含「底部Tab」的页面,建议将虚拟键设置为「底部Tab」的颜色,如 MIUI 的相机和小米商城。

建议3:不含「底部Tab」的页面,建议使用背景颜色,如多看阅读。

由于一个应用内含有多种不同的页面,我们希望开发者能当前页面的情况,来选择合适的虚拟键样式,以保证视觉的统一美观。

3.2. 如何修改虚拟键样式

Android 有标准的实现方式, 调用以下接口即可 window.setNavigationBarColor (int color)。在调用该接口时,还需要设置一些flag,详见该接口的注释说明(即下文):

/**
 * Sets the color of the navigation bar to {@param color}.
 *
 * For this to take effect,
 * the window must be drawing the system bar backgrounds with
 * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
 * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_NAVIGATION} must not be set.
 *
 * If {@param color} is not opaque, consider setting
 * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
 * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.
 * <p>
 * The transitionName for the view background will be "android:navigation:background".
 * </p>
 */
public abstract void setNavigationBarColor(@ColorInt int color);​

4. FAQ

4.1. 这些改动是仅针对 MIUI 系统吗

不是。上述提及的均是 Android 标准接口,且早在几年前就已经存在。因此开发者的任何改动,可以在其余 Android 手机中直接生效。我们相信全面屏会是往后手机设计的趋势,做好相关的适配工作,对于开发者来说是非常值得的。

4.2. 如何测试

目前市面上的小米新机均为全面屏手机,测试方法如下:

  • 升级至 MIUI 9.5 及以上版本
  • 前往「设置 > 全面屏 > 应用全屏运行设置」,找到相关应用,然后打开开关,就可以在全面屏比例下运行应用,以观察应用的表现。

有任何问题可以邮件给我们的项目组 miuishell@xiaomi.com ,会有同事解答相关疑问。

小米开发平台剪切板隐私保护功能说明及读写剪切板权限调整说明

小米开发平台剪切板隐私保护功能说明及读写剪切板权限调整说明

1.应用口令规则告知

您的应用若存在通过读取剪切板内容识别并弹出口令的交互逻辑(例如:淘宝读取淘口令),请告知我们您的应用读取的口令规则。

您可将:

  • 开发者名称
  • 应用名称
  • 包名
  • 口令规则(正则patten字符)
  • 口令的使用说明

发送至邮箱miui-security-open@xiaomi.com

我们将在系统中维护您的应用口令,以帮助您的应用能够正确读取剪切板中的必要信息。(注意:我们仅接受在小米应用商店已上架的应用的相关申请)。

2.剪切板权限调整说明

  • 原读写剪切板权限拆分为“读取剪切板”和“写入剪切板”,给予用户更加灵活的隐私权限管控方式;
  • “读取剪切板”权限将默认设置为“智能允许”;“写入剪切板”权限将默认设置为“仅在使用中允许”;
  • 当应用读取剪切板时,若剪切板中的最新内容符合应用读取规则(例如:淘宝读取剪切板中的淘口令),将智能允许应用读取剪切板;智能允许时,将在屏幕顶部通过提示气泡的方式告知用户(用户可见内容为:已智能允许某某应用读取剪切板);
  • 当应用读取剪切板时,若剪切板中的最新内容不符合应用读取规则,且剪切板中的内容符合其他应用读取规则(例如:非淘宝应用读取剪切板中的淘口令),将智能拒绝应用读取剪切板,且用户无感知;
  • 当应用读取剪切板时,若该应用存在读取规则但剪切板最新内容不符合应用读取规则(例如:淘宝读取剪切板中的一段任意文字),将智能拒绝应用读取剪切板,且用户无感知;
  • 当应用读取剪切板时,若剪切板中的最新内容不符合任何应用读取规则且应用不存在读取规则(例如:某地图应用读取剪切板中的一段任意文字),将智能拒绝应用读取剪切板,并弹出通知、气泡告知用户,用户有权利关闭提示。

3.FAQ

3.1.此功能适配哪些Android版本

此功能适配Android的版本:Android 10、 Android 11。

3.2.开发者在哪个MIUI版本可以体验测试

三方开发者可安装MIUI 12开发版20.11.16之后的版本调试。

3.3.智能拒绝的逻辑是否会影响用户主动粘贴剪切板中的内容

不会,只要用户不手动将应用读取剪切板的权限状态手动调整为“拒绝”,用户都可正常手动长按粘贴。此次剪切板隐私保护的功能仅针对应用主动请求读取剪切板的情况。

3.4.读取剪切板权限是否可以使用权限弹窗引导用户授权

读取剪切板权限目前未支持权限询问弹窗,所有应用默认权限状态为“智能允许”。

后台发送本地通知权限管理说明

1.介绍 

安卓系统中,三方应用位于后台运行时会发送大量通知,意图召回用户,严重影响用户体验。该权限可以控制应用在后台运行时发送本地通知的能力。

2.原则

该权限默认拒绝,即应用位于后台时默认不允许发送本地通知。针对特殊应用会提供白名单,例如音乐播放、日程提醒等。白名单应用一旦使用本地通知发送普通消息,或是出现其他有损用户体验等行为时,将永久取消白名单。普通消息定义请参考:https://dev.mi.com/console/doc/detail?pId=2086

3.建议

开发者可以接入小米推送,使用push消息触达并服务用户,相关接入流程请参考:https://dev.mi.com/console/doc/detail?pId=230

小米开发平台后台弹出页面权限管理说明

小米开发平台后台弹出页面权限管理说明

1. 介绍 

安卓系统中,由于三方应用可以随意从后台弹出页面,严重影响用户体验,该权限可以控制应用是否可以在后台启动页面。

2. 原则

该权限默认为拒绝的,既为应用默认不允许在后台弹出页面,针对特殊应用会提供白名单,例如音乐(歌词显示)、运动、VOIP(来电)等;

白名单应用一旦出现推广等恶意行为,将永久取消白名单。

小米手机设备管理器权限管理说明

小米手机设备管理器权限管理说明

1. 介绍 

设备管理器权限是Android提供给(企业)设备管理类应用的设备保护功能,对手机设备进行管理和操作的接口权限。

权限接口涉及对用户数据、密码的操作,安全性风险极高。

部分应用滥用权限声明来进行防卸载保护,应用内并无相关功能,当用户开启后就无法卸载。

2. 原则 

  • 设备管理器是提供给设备管理类应用的系统权限接口,禁止应用滥用设备管理器权限提供非设备管理功能外的其他功能及服务,包括但不限于清除用户数据、防卸载等。
  • 对于引导或提供非正常使用设备管理器权限的应用,按标准执行系统管控策略。包括但不限于:在系统内强提醒用户进行关闭处理、禁止应用获取相关服务或权限接口。
  • 对于引导或提供通过设备管理器权限,对用户的数据、设备使用安全可能产生危害的应用,将严格执行:将该应用在小米应用商店进行下架处理、禁止应用获取相关服务接口 、禁止相关应用在设备管理器应用列表中显示。
  • 禁止一揽子权限授权原则,各APP只能在其核心业务功能需要特定权限,且用户不同意授予该权限时,才允许退出应用。否则,非核心功能的权限调用中,应用不得以用户不授权而强制退出应用,相关权限应当为设计时的默计关闭保护状态。

小米手机默认桌面应用管理说明

小米手机默认桌面应用管理说明

1. 介绍 

安卓系统中,由于第三方桌面类的应用不稳定,会带来手机系统卡顿、手机功耗大、偷偷下载应用、恶意扣费等问题,给用户带来了很大的使用困扰。

2. 原则

桌面作为系统常用、基础并承担着入口安全责任的重要应用,为保证系统的稳定、完整、一致性,小米方强制禁止三方应用设为默认桌面,只允许使用系统桌面。

小米开发平台隐身模式三方应用适配文档

小米开发平台隐身模式三方应用适配文档

说明:若不涉及到使用录音、相机、麦克风权限,则可忽略此项适配

1.MIUI「隐身模式」功能

功能效果:

  • 用户开启该模式后,应用将无法使用ACCESS_FINE_LOCATION、CAMERA、RECORD_AUDIO三项权限
  • 应用请求对应权限时,系统会弹出通知告知用户“隐身模式已开启,应用无法xx”,但为避免频繁打扰用户,该通知有弹出策略,所以并不会每次应用使用权限时都弹出,如下图:

2.需适配的场景:

若三方应用在隐私模式开启状态下,因无法使用定位、相机、麦克风而无任何提示,且用户忽略了系统弹出push,可能使用户产生疑惑,认为应用出现问题,影响用户体验

适配方案:

MIUI提供两项属性值以供业务查询当前隐私模式的开启状态,便于在用户开启隐身模式时弹出“因开启隐身模式故无法使用麦克风相机获取定位”解释文案的弹窗

判断隐身模式为开启状态的属性值:

1. public static final String KEY_INVISIBLE_MODE_STATE = “key_invisible_mode_state”;

Settings.Secure.getInt(getContentResolver(), PermTipsUtils.KEY_INVISIBLE_MODE_STATE, 0) == 1;

2. public static final String KEY_INVISIBLE_MODE_PROP = “persist.sys.invisible_mode”;

SystemPropertiesUtils.get(PermTipsUtils.KEY_INVISIBLE_MODE_PROP) 是 “1”

3.功能体验说明:

若想体验该功能,机型和版本要求如下:

机型:小米10、小米9、红米Redmi K40游戏增强版、红米Redmi K30至尊纪念版、红米Redmi 9、MIX FOLD

MIUI版本:21.6.5之后的开发版rom 

小米开放平台MIUI进程管理适配说明

小米开放平台MIUI进程管理适配说明

1. 介绍

应用进程的存活与否常常受到三方开发者的关注。与原生系统不同,MIUI在Android系统的基础之上,开发了一套进程管理模块,便于系统管理运行中的进程。

此文档将会提供进程相关信息,方便开发者适配MIUI进程管理机制、初步自查应用被杀原因、更精确的向我们的三方团队同事反馈应用异常被杀问题

2. 进程管理功能

MIUI的进程管理功能大致分为两类:用户主动触发、用户被动触发

用户主动触发的功能包含:

 名称 触发入口Reason 
 一键清理 最近任务/悬浮球 OneKeyClean
 强力清理 负一屏 ForceClean
 垃圾清理 安全中心  GarbageClean
  锁屏清理 安全中心LockScreenClean
 游戏清理 安全中心 GameClean
 优化清理 安全中心 OptimizationClean
上滑清理 最近任务SwipeUpClean 

如果发现应用因为以上原因被杀死,那么意味着是用户在触发入口主动杀死这些应用

用户被动触发的功能包含:

 名称 被动触发场景 Reason
 Power异常查杀 应用过度耗电 AutoPowerKill
 Thermal异常查杀 应用使手机发热 AutoThermalKill

如果发现应用因为以上原因被杀死,那么意味着应用出现异常,会影响到系统正常运作,系统将应用清理掉了

3. FAQ

3.1. 我的应用在原生上运行时正常,但在MIUI上频繁被杀,该怎么定位原因

可以在shell中输入命令:

adb logcat -b events | grep am_kill

查看被杀应用的日志,例如:

1494  2963 I am_kill : [0,5253,com.eg.android.AlipayGphone,500,LockScreenClean]

最后一列信息即为被杀的Reason,和上面的表格进行一下对比,即可定位被杀的原因

3.2. 我的应用频繁的被AutoPowerKill/AutoThermalKill杀死,该怎么解决这个问题

当出现应用频繁被被动原因杀时的情况时,开发者首先应该自己检查下自身应用有没有过度耗电、发热的行为

如果确认自身应用质量没有问题,可以打个bugreport并联系我们的三方团队的同事进行反馈,我们内部的对应开发会进行深度分析

3.3. 我的应用被用户主动杀死后,怎么样可以让应用进程自动重新启动呢

开发者可以在用户使用应用时,引导用户在安全中心中打开自启动开关

小米耳返功能SDK适配说明

1、耳返功能sdk简介

小米手机目前高通平台机型上提供K歌低延时耳返功能 ,用户可以在小米手机上体验震撼的K歌效果,诚邀应用开发者适配,感谢支持!

支持耳返功能的设备

itgsa接口小米12S, 小米12S Pro,小米12S Ultra和其他出厂系统为Android 13版本的高通平台机型
小米接口其他高通平台机型

2、sdk接入方法

2.1 权限说明

需要应用权限配置:

android.permission.MODIFY_AUDIO_SETTINGS

android.permission.RECORD_AUDIO

2.2 API使用说明

1)小米12S, 小米12S Pro,小米12S Ultra和其他出厂系统为Android 13版本的高通平台机型获取MediaClient单例,其他高通平台机型获取KaraokeMediaHelper单例

2)isSupported判断应用是否支持KTV功能使用,小米平台通过应用白名单控制是否支持app使用KTV功能。 若app申请支持KTV功能,请联系我们(gengping@xiaomi.com

3)演唱开始,先打开KTV系统,openKTVDevice

4)设置相关配置:

setMixerSoundType 混响音效类型

setEqualizerType EQ音效类型

setPlayFeedbackParam 耳返开关

setMicVolParam 耳返音量大小

5)再开启播放,最后开启录音 【小米仅支持deep buffer播放方式的KTV效果】

6)演唱结束,先关闭播放、录音

7)再closeKTVDevice 关闭KTV系统

2.3 接口函数列表

  • itgsa结构机型

小米12S、小米12S Pro、小米12S Ultra和其他出厂系统为Android 13版本的高通平台机型

详情请参考 DEMO

接入实例参考 com.example.mediademo

函数名称功能简介
initialize初始化并获取KTV MediaClient单例。
getVersion获取KTV SDK库版本号。
isDeviceSupportKaraoke判断当前机器设备能否支持KTV。
isAppSupportKaraoke应用是否支持KTV。【oppo、vivo返回默认值true,小米手机检测】补充说明:小米平台通过应用白名单控制是否支持app使用KTV功能。 若app申请支持KTV功能,请联系我们(gengping@xiaomi.com),邮件说明应用包名和应用功能简介。
isSupported应用是否支持KTV功能使用,注意此为前三个接口组合判断结果,通常来说三方只使用该接口判断是否支持。
getKaraokeSupportParameters应用获取当前机器支持KTV的JSON参数信息,比如应用设置何种参数(AudioTrack的采样率、flag等,AudioRecord的source等),判断是否可以正常使用KTV功能。
openKTVDevice打开KTV设备,此动作必须是刚发生在播放伴奏前。
closeKTVDevice关闭KTV设备。
setPlayFeedbackParam控制耳返开启/关闭接口,系统默认打开,建议无论怎么app调用都打开调用一次,排除其他app不正确调用的干扰。
getPlayFeedbackParam获取当前耳返开关状态。
setMicVolParam设置人声音量大小。
getMicVolParam获取当前人声音量值。
setMixerSoundType设置混响效果。 ( 0:无、1:KTV、2:剧场、3:音乐厅、4:录音棚 )
setEqualizerType设置EQ均衡器音效。 (0:无、1:标准、2:浑厚、3:清脆、4:明亮)
getExtMixerSoundType扩展混响音效。
getExtEqualizerType扩展EQ均衡器音效。
  • 小米结构机型

详情请参考:KaraokeMediaHelper

接入实例参考 com.miui.media.KaraokeMediaHelper

函数名称功能简介
KaraokeMediaHelper初始化KTV工具类
isDeviceSupportKaraoke判断当前机器设备能否支持KTV。
getKaraokeSupportParameters应用获取当前机器支持KTV的JSON参数信息,比如应用设置何种参数(AudioTrack的采样率、flag等,AudioRecord的source等),判断是否可以正常使用KTV功能
isDeviceSupportMixerSound判断当前机器设备能否支持KTV。
openKTVDevice打开KTV设备,此动作必须是刚发生在播放伴奏前 。
closeKTVDevice关闭KTV设备。
isAppSupportKaraoke应用是否支持KTV。【小米手机检测】补充说明:小米平台通过应用白名单控制是否支持app使用KTV功能。 若app申请支持KTV功能,请联系我们(gengping@xiaomi.com),邮件说明应用包名和 0:无、1:KTV、2:剧场应用功能简介。
setMixerSoundType设置混响效果。( 0:无、1:KTV、2:剧场、3:音乐厅)
setPlayFeedbackParam控制耳返开启/关闭接口,系统默认打开,建议无论怎么app调用都打开调用一次,排除其他app不正确调用的干扰。
setMicVolParam设置人声音量大小。
getPlayFeedbackParam获取当前耳返开关状态。
getMicVolParam获取当前人声音量值。
setEqualizerType设置EQ均衡器音效。(0:无、1:标准、2:浑厚)
getExtMixerSoundType扩展混响音效。
getExtEqualizerType扩展EQ均衡器音效。

小米跑步机传感器数据集成到计步器数据库说明

1.小米跑步机传感器简介

当手机放置在跑步机上时,收集手机中传感器的数据,判断是否在跑步机上运动,若运动一步则跑步机传感器上报一次数据1,不运动则不报。

2.将跑步机数据集成到计步器数据库

2.1.通知系统服务

在注册或者解除注册跑步机传感器的时候通过Binder告知系统服务。 这步骤为必须操作,否则跑步机计步器数据无法同步至系统计步数据库。

代码示例:

public class MainActivity extends AppCompatActivity {

    private SensorManager mSensorManager;
    private Sensor mTreadmillSensor;
    private TreadmillListener mTreadmillListener;
    private static final int TREADMILL_SENSOR = 33171041;
    private static final String SERVICE_NAME = "miui_step_counter_service";
    private Binder mBinder;

    @RequiresApi(api = Build.VERSION_CODES.Q)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        mTreadmillSensor = mSensorManager.getDefaultSensor(TREADMILL_SENSOR,true);
        mTreadmillListener = new TreadmillListener();
        //监听跑步机Sensor
        mSensorManager.registerListener(mTreadmillListener,mTreadmillSensor,mSensorManager.SENSOR_DELAY_NORMAL);
        //通知系统服务,注册的时候发送true
        sendMessage(true);
    }

    @RequiresApi(api = Build.VERSION_CODES.Q)
    public void sendMessage(boolean is){
        //获取系统服务,ServiceManager报错底下有解决方案
        IBinder binder = ServiceManager.getService(SERVICE_NAME);        
        if (mBinder == null) {
            mBinder = new Binder();
        }
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken("miui_step_counter_service");
        data.writeBoolean(is);
        //传过去一个全局Binder(为了感知本类是否被销毁)
        data.writeStrongBinder(mBinder);
        try {
            binder.transact(0,data,reply,0);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private class TreadmillListener implements SensorEventListener{

        @Override
        public void onSensorChanged(SensorEvent sensorEvent) {

        }

        @Override
        public void onAccuracyChanged(Sensor sensor, int i) {

        }
    }

    @RequiresApi(api = Build.VERSION_CODES.Q)
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解注册的时候,通知系统服务,发送false
        mSensorManager.unregisterListener(mTreadmillListener,mTreadmillSensor);
        sendMessage(false);
    }
}

2.2.ServiceManager拿不到解决方案

在代码中创建一个包名为android.os,类名为ServiceManager的类。

代码示例:

package android.os;

public class ServiceManager {
    private ServiceManager(){}

    public static IBinder getService(String name){
        return null;
    }
}

2.3.说明

目前跑步机计步传感器支持小米12、小米12 Pro、小米12S Pro、小米12S Ultra四款机型,如果您的应用在注册下面这个特定传感器type时返回的sensor对象为空,说明当前机型不支持跑步机计步传感器,应用可通过此sensor对象的返回值来判定该功能是否生效。

  mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        mTreadmillSensor = mSensorManager.getDefaultSensor(TREADMILL_SENSOR,true);
        //根据mTreadmillListener是否为空来判定当前手机是否支持跑步机计步的功能
        mTreadmillListener = new TreadmillListener(); 

小米开发平台小米计步器接口适配说明

小米开发平台小米计步器接口适配说明

1.小米计步器简介 

收集手机中传感器的数据,通过机器学习算法判断步数。 

1.1 算法说明 

  • 计步器Sensor 5分钟上报一次数据,后台Service将计步数据记录插入本地数据库,若没有数据上报, 则不插入; 
  • app第一次请求数据会立即响应, 后台Service会立即返回给app最新的记录数据;
  • app在1分钟内多次请求数据, 则只有第一次得到的数据是最新的, 后面的请求结果和第一次相同; 
  • 一条记录只有一种计步模式.。例如, 用户在10分钟内有600步数据, 400步走路, 200步跑步, 则这10分钟会分拆成两条记录, 400步走路和200步跑步;
  • 只传给应用层3种计步模式: 0: 不支持(在不支持计步的手机上不会得到数据), 2: 走路, 3: 跑步。 

2.计步器接入 

2.1 判断本机是否支持计步 

使用miui.util.FeatureParser提供的接口去判断是否支持stepsProvider功能

//示例code
//在项目中新建一个工具类,FeatureParser,通过反射机制来获取miui.util.FeatureParser
public class FeatureParser {
    public static boolean getBoolean(String name, boolean defaultValue) {
        try {
            Class featureParserClass = Class.forName("miui.util.FeatureParser");
            Method method = featureParserClass.getMethod("getBoolean", String.class, boolean.class);
            return (Boolean) method.invoke(null, name, defaultValue);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return defaultValue;
    }
}

//在功能开始之前判断是否支持stepsProvider功能
boolean isSupport= FeatureParser.getBoolean("support_steps_provider",false); 

2.2 App采用ContentProvider的query请求获取计步数据 

接口格式: 

Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
参数必选类型范围说明
uritrueandroid.net.UriUri.parse(“content://” + “com.miui.providers.steps” + “/item”);取固定值
projectionfalseString[]可选列: “_id”, “_begin_time”, “_end_time”, “_mode”,  “_steps”获取指定列, 若为null则获取所有列
selectionfalseString从可选列中选取自定义条件获取满足条件的记录行, 若为null则获取所有行
selectionArgsfalseString[]一般为可选列的值Selection中带?的格式化参数
sortOrderfalseString从可选列中选取列升序(asc)或降序(desc), 可多个并列返回结果的记录行排序方式, 若为null则按id大小排序

2.3 返回结果: 满足查询条件的记录行

记录行的数据结构如下 

public class Step {
    private int mId; // 记录在sqlite的id
    private long mBeginTime; // 计步开始时间
    private long mEndTime; // 计步结束时间
    private int mMode; // 计步模式: 0:不支持模式, 1:静止, 2:走路, 3:跑步, 11:骑车, 12:交通工具 
    private int mSteps; // 总步数
}

3.代码示例

3.1 AndroidManifest.xml中声明权限 

 <uses-permission android:name=”miui.permission.READ_STEPS” />(不声明权限无法读取计步数据)。 

3.2 Query用到的数据结构 

public class Steps {
/* Data Field */
public static final String ID = "_id";
public static final String BEGIN_TIME = "_begin_time";
public static final String END_TIME = "_end_time";
// 0: NOT SUPPORT 1:REST 2:WALKING 3:RUNNING
public static final String MODE = "_mode";
public static final String STEPS = "_steps";
/* Default sort order */
public static final String DEFAULT_SORT_ORDER = "_id asc";
/* Authority */
public static final String AUTHORITY = "com.miui.providers.steps";
/* Content URI */
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/item");
}
public static String[] projection = new String[] {
Steps.ID,
Steps.BEGIN_TIME,
Steps.END_TIME,
Steps.MODE,
Steps.STEPS
};

3.3 返回结果的数据结构 

public class Step {
private int id;
private long mBeginTime;
private long mEndTime;
private int mMode;
private int mSteps;
}

3.4 查询方法示例 

public LinkedList<Step> getAllSteps(String selection, String[] args) {
LinkedList<Step> steps = new LinkedList<Step>();
Cursor cursor = resolver.query(Steps.CONTENT_URI, projection, selection, args,
Steps.DEFAULT_SORT_ORDER);
if (cursor.moveToFirst()) {
do {
Step s = new Step(cursor.getInt(0), cursor.getLong(1), cursor.getLong(2),
cursor.getInt(3),
cursor.getInt(4));
steps.add(s);
} while (cursor.moveToNext());
}
return steps;
}

小米开发平台小米妙播适配说明

小米开发平台小米妙播适配说明

一、小米妙播背景与介绍

1.1 安卓原生Media Session介绍

Android 框架定义了两个类(媒体会话和媒体控制器),它们为构建媒体播放器应用提供了一个完善的结构。

媒体会话和媒体控制器通过以下方式相互通信:使用与标准播放器操作(播放、暂停、停止等)相对应的预定义回调,以及用于定义应用独有的特殊行为的可扩展自定义调用。

更多信息详见:https://developer.android.com/guide/topics/media-apps/media-apps-overview

1.2 MIUI小米妙播介绍

小米妙播支持基于Media Session的音频播控、基于Wi-Fi的跨设备音乐接力。

用户可以在小米手机和平板的系统控制中心、通知栏/锁屏媒体中心使用该功能。

控制中心内的小米妙播播控,基于安卓原生Media Session能力实现,音频应用适配Media Session即可,众多知名三方应用均已适配。

二、适配小米妙播的好处

2.1 系统全局常驻播控

小米妙播作为控制中心常驻的播控、互联入口,用户可以在手机全局操作音频,是Android厂商中全面媲美苹果 AirPlay2 的系统级功能。

2.2 覆盖更多场景的播放

各音频app的用户活跃场景不再局限于手机,用户用音箱等设备听歌时,也可使用各音频app,通过小米妙播互联播放。

三、适配方式

基本适配方式请参考谷歌官方文档:

https://developer.android.com/guide/topics/media-apps/working-with-a-media-session

https://developer.android.com/guide/topics/media-apps/audio-app/building-a-mediabrowserservice

适配要点:

及时更新Meta信息

// 构建MediaSession
MediaSessionCompat mMediaSession;
//构建MediaMetadata,并传入媒体meta信息(歌曲名、专辑名、歌手名、歌曲时长等)
MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
builder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, getTrackName())
        .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, getAlbumName())
        .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, getArtistName())
        .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration());
MediaMetadataCompat metadata = builder.build();
mMediaSession.setMetadata(metadata);
// 构建并传入歌曲封面
Bitmap result;
// 获取封面BitMap...
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, result);
mMediaSession.setMetadata(builder.build());

及时更新播放状态信息

// 构建PlaybackState,传入播放状态、播放进度等信息。
PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder(getSourcePlaybackState())
        .setBufferedPosition(getBufferedPosition())
        .setState(PlaybackStateCompat.STATE_PLAYING, position(), getSpeedRatio());
mMediaSession.setPlaybackState(builder.build());

处理播放回调 

// 构建MediaSessionCompat.Callback回调,处理播放、暂停、上一首、下一首、调整播放进度等操作
private class MediaPlaybackSessionCallback extends MediaSessionCompat.Callback {
    @Override
    public void onPlay() {
        play();
    }
    @Override
    public void onPause() {
        pause();
    }
    @Override
    public void onSkipToNext() {
        next();
    }
    @Override
    public void onSkipToPrevious() {
        prev();
    }
    @Override
    public void onFastForward() {
        forward();
    }
    @Override
    public void onRewind() {
        backward();
    }
    @Override
    public void onStop() {
        stop();
    }
    @Override
    public void onSeekTo(long pos) {
        seek(pos);
    }
}
mMediaSession.setCallback(new MediaPlaybackSessionCallback());