小米开发平台Audio接口使用规范

小米开发平台Audio接口使用规范

1. 关于setSpeakerphoneOn方面的使用

问题描述:

在MIUI的项目开发过程中,经常收到用户反馈通话声音会自动外放,经分析排查发现均是第三方应用在通话期间调用了setSpeakerphoneOn(true)方法引起的,影响用户正常通话。

建议:

音频类应用可以利用AudioFocus机制或者监听电话状态来进行控制处理,在手机通话期间不要调用setSpeakerphoneOn方法,从而避免对正常通话的影响。

2. 关于setMode的使用

问题描述:

当前很多APP,多见于社交或需要队友语音的游戏。使用这个接口来设置手机通话模式,改变手机声音的输出设备。当电话模式处于“AudioManager.MODE_IN_COMMUNICATION”或“AudioManager.MODE_IN_CALL”时,声音会默认从听筒发声。且用户默认音量调节类型会变为通话音量类型。

建议:

开发过程中,setMode(AudioManager.MODE_IN_COMMUNICATION)和setMode(AudioManager.MODE_NORMAL)务必成对使用。需要考虑应用异常的情况下,也能够将电话模式重置为normal状态。

3. 关于getStreamVolume的使用

使用建议:

通过该方法得到的音量值受多方因素影响。需要充分考虑当前设备的audiopolicy route策略,选中不同的设备,得到的音量值也不一样。此外该方法受静音模式影响,如果手机处于静音状态,无论音量值为多少,得到的index均为0。如果需要不想受静音状态影响,建议使用getLastAudibleStreamVolume。

4. setStreamVolume音量设置会导致媒体音退出静音状态

问题描述:

  • MIUI允许用户在静音开启时静止媒体音。但静止状态时,一旦触发音量增操作,媒体音会立刻退出静音状态。有许多视频播放或直播app,会在退出app时保存当前音量值,重开app时将音量值restore。这个操作经常会错误的将用户的静音状态解除。
  • app会catch按键时间,调用setStreamVolume来控制媒体音量。某些情况会导致音量调节异常。

建议:

  • 当应用返回时,通过isStreamMute或getRingerMode方法获取手机当前状态,如果处于媒体音或者铃声状态处于静音,则不执行setStreamVolume操作。
  • 音量的控制尽可能的交由系统处理,少用setStreamVolume设置音量。

小米开发平台用户可拒绝应用获取Android ID说明

小米开发平台用户可拒绝应用获取Android ID说明

一、变更说明

应用下载安装时,默认开启 虚拟身份ID开关,用户可在虚拟身份ID授权管理中关闭应用获取虚拟身份ID(OAID和Android ID)

若想体验该功能,请将小米手机升级至21.8.1及之后的开发版,手机设置-隐私保护-保护隐私-特殊权限设置-虚拟身份管理。

二、系统版本

Android 11及以上版本

三、虚拟身份ID关闭后返回值

若用户关闭了应用获取虚拟身份,通过Settings.Secure.getString(Settings.Secure.ANDROID_ID)获取到Android ID返回值见下表:

MIUI开发版Settings.Secure.getString(Settings.Secure.ANDROID_ID)返回值
21.7.31之前不关注
21.8.1-21.9.15
21.9.16之后0000000000000000
MIUI稳定版
MIUI12.5之前不关注
MIUI130000000000000000 

四、应用适配

不需要应用适配,但需要开发者检查验证是否影响应用正常使用,并进行相应调整。

小米开发平台 禁止应用获取MAC地址说明

小米开发平台 禁止应用获取MAC地址说明

一、变更说明:

在Android 11及以上机型中,应用无法通过

NetworkInterface#getHardwareAddress

二、获取到设备mac地址

对于targetSdkVersion<30的App,将会返回一个占位符02:00:00:00:00:00;

对于targetSdkVersion>=30的App,将会直接返回null(与原生特性一致)。

三、应用适配

本功能自身无需适配,但需要开发者检查验证是否影响应用正常使用,并进行相应调整。

四、功能体验

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

小米11-21.7.13以上的开发版

小米大屏设备适配说明3

小米大屏设备适配说明3

3.5 H5页面适配

我们推荐项目使用响应式设计,实现同一页面自动适配多种屏幕尺寸的效果。

3.5.1 元标签设置

通常情况下,开发者应使用元标签将视口大小调整为屏幕宽度,以确保元素尺寸按照预期显示。

<meta name="viewport" content="width=device-width,initial-scale=1">

开发者可以提供额外的参数来限制用户的缩放行为,一般来说这样做可以优化普通用户体验,减少误操作,但会影响视力障碍用户进行缩放操作,造成访问性问题。

<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">

3.5.2 使用相对 CSS 长度单位

我们推荐开发者在响应式开发中尽量使用相对长度单位,包括:em、rem、%、vw、vh。以下是这些单位的一般使用场景。

em尺寸与当前字体大小相关
rem尺寸相对固定,基于断点与屏幕宽度成阶梯性相关
%尺寸与父元素相关
vw区别于 rem,尺寸与屏幕宽度成线性相关
vh尺寸与屏幕高度成线形相关

上述的只是一般情况,实际开发中使用哪个单位还需视设计要求而定,合理使用长度单位能起到事半功倍的效果。

3.5.3 布局方式

在响应式开发中,开发者应优先使用以下方式进行布局,构建适应性更强的页面。

A.Flexbox(弹性布局)

使用display: flex 可令容器元素启用弹性布局。即使在不确定容器内元素尺寸的情况下,开发者也可以灵活控制元素的大小比例和对齐方式。

参考资料

B.Grid(栅格布局)

Flexbox 可以很好地解决同一行/列内元素的布局问题,Grid 则更适合用于拥有多行/列的二维布局。使用display: grid 可令容器元素启用栅格布局。开发者可灵活控制行列间距、大小比例、对齐方式。

参考资料

3.5.4 CSS 媒体查询

使用 CSS 媒体查询技术,可以使同一元素的样式在不同设备下有不同的表现。通常,我们使用设备屏幕宽度作为判断设备类型的依据,以下是我们推荐的断点划分规则。

屏幕宽度范围设备类型
320 px < width ≤ 480px手机竖屏、折叠屏小屏竖屏
480 px < width ≤ 680px折叠屏展开竖屏
680 px < width ≤ 960px手机横屏、折叠展开横屏、折叠屏小屏横屏
1200px < width桌面端设备

一般情况下,建议开发者使用媒体查询统一控制根字体大小,简化适配流程。

html {  
  font-size: 16px;
}
@media screen and (min-width: 480px) {
  html {
    font-size: 17px;
  }
}
@media screen and (min-width: 680px) {
  html {
    font-size: 18px;
  }
}

3.5.5 Javascript 媒体查询

在一些特殊情况下,页面需要根据断点进行更加复杂的调整。如页面文案,交互逻辑等需要随断点而变化。此时需要使用 Javascript 进行更加细微的调整,我们推荐开发者使用matchMedia 方法进行断点识别,断点划分方案建议与 CSS 方案保持一致。

参考资料

3.5.6 其他建议

1)使用响应式图片,用于图片进行尺寸匹配的场景。

img {
  /* 按比例拉伸或缩放至父元素宽度 */
  width: 100%  }
img {
 /* 按比例缩放至父元素宽度,不拉伸 */
  max-width: 100%  
}
img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  /* 按比例拉伸或缩放至父元素宽高 */
  object-fit: cover;
  object-position: center;
}

2)为块级元素添加 max-width 而非 width,避免在更小尺寸的显示设备上宽度溢出。

4、平行窗口适配指南

推荐采用Android 12L方案进行适配,您可自主定义activity在左右分屏间的跳转方式。

详细参考Android适配文档SampleActivity Embedding 适配指南

平行窗口适配情况标记

应用主动适配12L平行窗口,需要在AndroidMainfest.xml文件设置标记。

<!-- support 12L Activity Embedding for large screen -->
        <meta-data
            android:name="embedded"
            android:value="true" />

5、进阶开发适配指南(小窗、迷你窗及分屏)

5.1 小窗迷你窗功能介绍

5.1.1 小窗

在多任务处理场景下,小窗意在解决临时使用某个应用的场景,例如:在使用游戏应用时,不便离开,但想发消息给朋友,此时就可以借助小窗打开第二应用

5.1.2 迷你窗

迷你小窗意在解决应用临时挂机的场景,例如:等待网约车、等待游戏更新、观看直播等

5.2 小窗迷你窗适配指南

小窗适配技术文档

MIUI的小窗是基于Android的多窗口Freeform方案实现的。

小窗目前主要问题是应用兼容性导致的一系列问题,内容显示不全、Touch事件不响应等等,其实根本原因是应用没有很好的支持、适配多窗口、多分辨率,如下是小窗适配的一些参考性适配指南。

多窗口适配支持文档

支持不同屏幕分辨率的开发技巧文档

5.2.1 常用适配方法

(1) 如何支持小窗

支持小窗的方式非常简单,只需要声明一个属性,直接在AndroidManifest.xml中 配置属性android:resizeableActivity=”true” ,不支持小窗直接配置为false,系统默认是支持小窗的。

(2)判断自己是否在小窗  
Activity.onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration  newConfig)  
Activity.isInMultiWindowMode()   判断是否处于多窗口                                                    
Activity.getWindowingMode()  当返回5时表示小窗,需要反射调用
String config = context.getResources().getConfiguration().toString();
boolean isInFreeformWindow = config.contains("freeform");
(3)获取小窗的位置/大小等信息

context.getResources().getConfiguration().windowConfiguration.getBounds(),其中Configuration对象里也有小窗的模式以及bounds,所以我们也需要重写ConfigurationChanged方法,并对界面布局做相应调整,如切换布局、调整控件位置和间距等。

(4)Activity大小切换时不重启适配

强烈建议应用在Activity窗口大小切换时不重启适配,遵循google规范,在android:configChanges 属性增加 screenSize|screenLayout|orientation|smallestScreenSize,并在Activity的onConfigurationChanged回调中更新宽高刷新各个子布局。

(5)特别需要注意的适配点,焦点窗口适配

迷你小窗和小窗都有可能成为不是焦点窗口(Focus Window),其中迷你小窗一定是没有焦点的,小窗和小窗下面的全屏应用是可以随意进行焦点切换的,touch在哪个窗口,哪个窗口就是焦点窗口,所以就要保证应用在没有焦点的前提下也是可以正常运行的,特别是视频类app。关键方法:Activity.onWindowFocusChanged(boolean hasFocus)

(6)获取屏幕宽高有些APP会跟随屏幕大小动态计算View的大小,但是有些接口在多窗口形式下面并不试用,要注意以下几点:
  • 在获取Display 对象 或者获取 Resources对象时使用Activity的Context 不要使用Application的Context;
  • 获取Resources对象时不要用context.getResources().getSystem()用context.getResources()就行;
  • Display下有getMetrics getRealMetrics getSize getRealSize四个方法, getRealMetrics和 getRealSize 永远会返回屏幕的宽高,无论应用是否在多屏下, 而getMetrics和getSize会可以获取Activity的大小。
(7)获取View的位置

获取View的位置有两个方法:

  • View.getLocationInWindow(int[] outLocation) 返回距离View所在的Window左上角的距离;
  • View.getLocationOnScreen 返回距离屏幕左上角的距离(由于在小窗模式下应用的位置可以移动, 所以该返回值有时并非是你想要的,所以要慎用)。

5.2.2 其他说明

全局自由窗口功能在MIUI12安卓Q版本以上支持。

如果应用想要启用小窗或者分屏模式下的拖拽功能,请参考如下google资料进行适配:

Android Developer上的拖放文档

AOSP里的DragAndDrop样例应用

github Demo下载连接

5.3 分屏功能介绍

分屏场景意在解决长时间并行使用两个应用时的场景,例如:一边听网课,一边记笔记等;一边看视频,一边逛知乎、微博等

5.4 分屏适配技术文档

https://dev.mi.com/console/doc/detail?pId=1161

小米大屏设备适配说明1

1、概述

1.1 小米大屏设备介绍

1.1.1 折叠屏形态与尺寸

折叠屏是一种柔性屏,可以进行弯曲、折叠,比如上下折叠,左右折叠等。

Xiaomi MIX Fold 和 Xiaomi MIX Fold 2 均为左右内折叠设备,其展开状态和折叠状态屏幕大小和分辨率如下:

外屏内屏
尺寸分辨率尺寸分辨率
Xiaomi MIX Fold6.52英寸2520×8408.01英寸2480×1860
Xiaomi MIX Fold 26.56英寸2520×10808.02英寸2160×1914

判断是否为折叠屏设备的接口为:

//可通过反射调用 SystemProperties 的 persist.sys.muiltdisplay_type 属性值来进行判断
boolean isFoldDisplay = SystemProperties.getInt("persist.sys.muiltdisplay_type", 0) == 2;

折叠/展开态的判断接口为:

// 0 UNKNOWN
// 1 折叠
// 2 半折(MIX Fold 2支持)
// 3 展开
final int posture = Settings.Global.getInt(this.getContentResolver(), "device_posture", 0);

1.1.2 平板设备形态与尺寸

小米平板设备屏幕大小和分辨率信息如下:

尺寸分辨率
小米平板511 英寸2560 x 1600
小米平板5 Pro11 英寸2560 x 1600
小米平板5 Pro 5G11 英寸2560 x 1600
小米平板5 Pro 12.412.4 英寸2560 x 1600

判断是否为平板设备的接口为:

public static boolean isTablet(){    
/**应用需反射调用*/    
     return SystemProperties.get("ro.build.characteristics").contains("tablet");}

应用也可以动态判断当前窗口显示在大屏下:

public static boolean isTablet(Context context) {    
    return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE;}

1.1.3 可扩展性

小米大屏设备品类形态未来将会有更多丰富的产品,因此应用适配后需要支持一定程度拉伸。

1.2 文档适用对象

需要适配小米大屏设备应用的第三方开发者、UX设计师、系统工程师、测试

2、UX设计与软件形态适配建议

2.1 基础体验设计

2.1.1 字体

  • 优先调用系统默认字体来保证整台设备的阅读体验一致性,除非有特殊需求;
  • 排版及字号字重应保证清晰可读;
  • 在折叠屏上,建议保持内外屏字号大小一致,展开大屏字号大小如要放大,最大不能超过外屏小屏的1.2倍。

2.1.2 图片、视频

  • 这些资源不应该出现变形、模糊或者显示不全的问题,影响体验;
  • 在折叠屏上,建议图片、视频等信息内容在内外屏采用动态布局,保持图片视频等内容大小一致;展开大屏的图片视频等内容大小如要放大,最大不能超过屏幕高度。

2.1.3 弹窗

  • 弹窗在应用设计中比较常见,如运营内容、广告营销等,建议弹窗大小适中,且有清晰的供用户关闭的按钮;
  • 在折叠屏上,建议弹窗大小内外屏保持一致;展开大屏的弹窗如要放大,最大不能超过屏幕高度的70%。

2.2 页面布局设计

2.2.1 动态布局

  • 大屏设备能够在竖屏和横屏之间无缝切换,折叠屏设备还能够在内屏和外屏之间无缝切换,拥有强大的功能和内容体验。
  • 应用可能会遇到以下几种窗口情况

基于以上情况,推荐几种布局方式

A.拉伸

仅卡片区域、显示区域等拉伸,文字大小图标大小均保持一致

B.缩放

根据窗口区域等比缩放内容信息

C.拓展/增量/减量

根据窗口区域增加或减少项,项的尺寸大小保持不变

D.重新布局

根据窗口区域重新对内容进行布局,以适应更好的信息阅读

小米开发平台 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)与屏幕是否翻转显示无关,维持原有逻辑即可。

小米开发平台 刘海屏、水滴屏、挖孔屏 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. 原则

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

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

小米关于调整“获取应用列表”权限&新增“调节媒体音量”权限的适配说明

小米关于调整“获取应用列表”权限&新增“调节媒体音量”权限的适配说明

1、获取应用列表权限简介

“获取应用列表”权限是在Android原生 QUERY_ALL_PACKAGES 权限基础上额外的权限管理。

影响 PackageManager#getInstalledPackages和PackageManager#getInstalledApplications 两个接口返回值。

应用没适配的情况下,默认添加权限状是“仅在使用中允许”(应用不声明也会添加),即应用在前台时可以正常获取。也可以按照下面方法适配动态申请。

1.1.适配方法

清单文件声明

<uses-permission android:name="com.android.permission.GET_INSTALLED_APPS"/>

判断MIUI 是否支持动态申请权限

try {
    PermissionInfo permissionInfo =  getApplicationContext().getPackageManager().getPermissionInfo("com.android.permission.GET_INSTALLED_APPS", 0);
    if (permissionInfo != null && permissionInfo.packageName.equals("com.lbe.security.miui")) {//MIUI 系统支持动态申请该权限
        if (ContextCompat.checkSelfPermission(getApplicationContext(), "com.android.permission.GET_INSTALLED_APPS") != PackageManager.PERMISSION_GRANTED) {
            //没有权限,需要申请
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{"com.android.permission.GET_INSTALLED_APPS"}, 999);
        }
    }
} catch (PackageManager.NameNotFoundException e) {
    e.printStackTrace();
}

提示:如果系统侧不支持此权限,应用仍然会保持默认(仅在使用中允许)。

判断权限授予结果(与普通运行时权限没有区别)

public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    // do something
}

1.2.特殊说明:

如果不需要系统添加获取应用列表的权限,可以通过下面代码实现(同时,清单文件中不能申请更改权限)

<meta-data
     android:name="do_not_need_get_installed_apps"
     android:value="true"/>

注意:如果声明此meta-data,等于“获取应用列表”权限关闭!

1.3.MIUI生效版本:

MIUI 13

2、调节媒体音量权限说明

为减少部分应用擅自修改媒体音量,而不主动告知用户或获得用户许可的行为,同时尽可能避免对用户带来相应的困扰。现在单独设立“调节媒体音量”的权限,并将默认状态设置为“仅在使用中允许”。同时,“应用行为记录”功能中将会对应用修改媒体音量的行为进行记录,用户可主动调整相关的权限。您的应用如有调节媒体音量的需求,可能受到影响,建议您自行调整相关的产品策略。

MIUI 11及以下版本将不受该调整的影响。

小米APP相册安全分享适配文档

一、 功能介绍

开启小米相册的安全分享功能后,从相册分享图片会自动抹除位置、手机型号、拍摄参数等信息。

安全分享开关: 相册->右上角菜单->设置->安全分享

二、分享图片路径变化

小米相册中打开安全分享中任意一个开关(默认抹除照片位置、默认抹除照片拍摄信息)后,从相册分享时会将抹除相关信息的照片保存到相册的私有目录,然后通过FileProvider的方式生成content://Uri分享给应用

开启安全分享后分享的照片路径:

storage/emulated/0/Android/data/com.miui.gallery/cache/SecurityShare/XXX.jpg

未开启安全分享功能分享的照片路径:

storage/emulated/0/DCIM/Screenshots/XXX.jpg

三、应用不可以直接访问私有目录

由于从Android11开始,应用的私有目录不能被外部访问,即使获取了“所有文件管理”权限也不行。具体参见https://developer.android.google.cn/about/versions/11/privacy/storage。因此如果在用户打开安全分享的情况下,应用接收到相册传递给应用的URL,获取到绝对路径,再直接通过访问绝对路径的方式来读取照片,会发生读取异常。

例如以下错误

BitmapFactory: Unable to decode stream: java.io.FileNotFoundException: /storage/emulated/0/Android/data/com.miui.gallery/cache/SecurityShare/1655174005823.jpg: open failed: ENOENT (No such file or directory)

四、适配方式推荐FileProvider

针对开启安全分享的情况下,分享照片到应用,建议应用使用fileprovider(https://developer.android.com/reference/androidx/core/content/FileProvider)的方式访问照片,参考代码如下:

Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();

if (Intent.ACTION_SEND.equals(action) && type != null) {
     if (type.startsWith("image/")) {
         Uri imageUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
         InputStream is = null;
        try {
            is = getContentResolver().openInputStream(imageUri);
            Bitmap bmp = BitmapFactory.decodeStream(is);
            imageView.setImageBitmap(bmp);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
     }
    } 

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

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

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;
}

小米开发平台MiHaptic适配说明

小米开发平台MiHaptic适配说明

1.MiHaptic简介

MiHaptic是一套振动波形的生成框架,可以通过参数或者HE格式为你的线性马达机器定制出丰富的振动效果。

1.1.能力

MiHaptic支持接入不同的触感波形生成算法以驱动手机上的线性马达,目前支持小米自研算法和RichTap。

1.2.场景及案例

  • QQ音乐节奏实验室

QQ音乐节奏实验室4D振感,开启音乐随振,让听歌更有节奏感。

  • 和平精英高品质振动

和平精英高品质振动使用MiHaptic方案对不同枪械、载具、脚步声、玻璃破碎等场景提供拟真的触感反馈。

2.使用入门

2.1.开发环境

  • 安卓Studio 4.0 以上

2.2.开发准备

2.2.1.集成MiHaptic SDK

  • miui.os

DynamicEffect_0807_2

提供组合PrimitiveEffect的方式。

  • android.os

DynamicEffect_0128

提供HE文件振动方式。

2.2.2.增加振动权限

2.2.3.使用 MiHaptic SDK文档 中的接口。

DynamicEffect_ZH.docx

2.3.使用MiHaptic

  • 组合PrimitiveEffect的方式

DynamicEffect可组合Transient和Continuous类型的效果,如下图所示

DynamicEffect 数据结构实际上是对一组参数的描述,一个DynamicEffect由若干个PrimitiveEffect和若干Parameter所组成。

PrimitiveEffect 分为两种,一种是描述瞬时振动的Transient,例如哒哒哒的清脆效果;另一种为Continuous,这种振动效果的特点是持续时间长,例如嗡嗡嗡的效果。

对于每种PrimitiveEffect,有两个重要的参数,intensity和sharpness,分别描述它们的强度与锐度。

Parameter与PrimitiveEffect的两个重要属性对应,用来控制PrimitiveEffect的强度或者锐度。

Parameter 可以添加到整个DynamicEffect里,那么它可以控制所有时间范围内的PrimitiveEffect。

Parameter 也可以添加在Continuous效果中,用来作渐变。

例如,下图所示的是一个由两个Continuous与一个Transient所组合成的效果:

DynamicEffect effect = DynamicEffect.startCompose(); 
 
DynamicEffect.PrimitiveEffect continuous1 = DynamicEffect.createContinuous(0.5, 1.0, 3.0); 
DynamicEffect.Parameter param1 = DynamicEffect.createParameter(DynamicEffect.INTENSITY,  
                                                                  new float[]{0.5, 1, 2} , new float[] {0.6 , 0.3, 0.2); 
continuous1.addParameter(1, param1); 
 
DynamicEffect.PrimitiveEffect transient = DynamicEffect.createTransient(0.8, 1.0); 
 
DynamicEffect.PrimitiveEffect continuous2 = DynamicEffect.createContinuous(0.3, 1.0, 6.0); 
DynamicEffect.Parameter param2 = DynamicEffect.createParameter(DynamicEffect.INTENSITY,  
                                                                  new float[]{1.0, 2.0} , new float[] {0.3, 1.0); 
continuous1.addParameter(2, param2); 
 
effect.addPrimitive(0, continuous1); 
effect.addPrimitive(3.5, transient); 
effect.addPrimitive(4, continuous2); 
 
DynamicEffect.Parameter global = DynamicEffect.createParameter(DynamicEffect.INTENSITY,  
                                                                  new float[]{0, 8.0} , new float[] {0.2, 1.0); 
 
effect.addParameter(2.5); 

HapticPlayer player = new HapticPlayer(effect);
player.start();

上图描述了一个DynamicEffect以及intensity属性的渐变示意图。DynamicEffect中包含了两个Continuous和一个Transient。

生成波形的强度在0时刻的强度为0.5,它是由第一个Continuous创建时所指定的。 在第0.5S时强度为0.6,该变化是由Continuous被自己的Parameter所控制引起。第2.5S时,Continuous的强度渐变到了0.04,该变化由于Global参数对其产生了影响,对于强度而言,global参数与PrimtiveEffect参数的强度作乘积,因此变为0.2*0.2=0.04 。而后该Continuous的Intensity无自身Parameter影响,但由于globalParameter存在,从2.5到第3S改变到0.05。

在第3.5S时开始播放Transient,强度为初始值0.8与当前时刻的global参数0.3相乘为0.24。(global起始时间为2.5S,因此在3.5S时参数的值渐变为0.3)。

第二个Continusou起始时间为第4S,从第4s-第6S强度递增,因为当前依旧属于global参数的作用时间,第4S强度为=0.3*0.35 = 0.105,后面的计算方式同理。

  • 使用HE文件
{
 "Metadata": {
 "Version": 1, // 版本号,整形
 "Created": "2020-07-08", // 创建时间,String类型
 "Description": "game haptic" // 震动效果描述,String类型
 },
 "Pattern": 
 [
 {
 "Event": {
 "Type": "continuous", // 事件类型: continuous->持续震动。transient->简短震动
 "RelativeTime": 0, // 相对开始时间, 整形, 单位ms
 "Duration": 300, // 持续震动类型参数:持续时间。整形, 单位ms
 "Parameters": {
 "Intensity": 80, // 震动强度, 整形, [0,100]。0->平台支持的最小值, 100->平台支持的最大值。
 "Frequency": 50, // 震动频率, 整形, [0,100]。0->平台支持的最小值, 100->平台支持的最大值。
 "Curve": [ // 持续震动类型参数:曲线。实现上保证平滑过渡效果
 {"Time": 0, "Intensity": 0, "Frequency": 25}, // 起始点,必须。time为RelativeTime,Intensity必须取值为0。
 {"Time": 100, "Intensity": 0.7, "Frequency": -30}, 
 {"Time": 200, "Intensity": 0.75, "Frequency": 90},
 {"Time": 300, "Intensity": 0, "Frequency": 50} // 结束点,必须。time为Duration,Intensity必须取值为0。
 ]
 }
 }
 },
 {
 "Event": {
 "Type": "transient", // 事件类型: continuous->持续震动, transient->简短震动
 "RelativeTime": 400, // 相对开始时间, 整形, 单位ms
 "Parameters": {
 "Intensity": 80, // 震动强度, 整形, [0,100]。0->平台支持的最小值, 100->平台支持的最大值。
 "Frequency": 40 // 震动频率, 整形, [0,100]。0->平台支持的最小值, 100->平台支持的最大值。
 }
 }
 }
 ]
}

HE文件描述如上面的JSON格式所示,可以将描述文件放在项目中,使用DynamicEffect.create(string)接口创建效果。

InputStream is = getResources().openRawResource(R.raw.demo_he);
try {
    int size = is.available();
    byte[] buffer = new byte[size];
    is.read(buffer);
    is.close();
    str = new String(buffer);
} catch (Exception e){}
effect = DynamicEffect.create(str);
player = new HapticPlayer(effect);
player.start();

注:使用HE文件创建出的DynamicEffect不可再用于添加PrimitiveEffect,HE文件当前仅支持16个以内的event。