小米开发平台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设置音量。

小米开发平台联系人信息使用规范

小米开发平台联系人信息使用规范

1. 背景

曾经 MIUI 系统用户反馈过联系人丢失问题,其中一部分的原因是第三方应用不遵循 Android 标准规范,进行了非自身帐号的联系人删除,导致在第三方应用同步或者恢复联系人数据时,出现了小米帐户联系人被删除而丢失的情况。

显然,这样的情形无论对于用户还是系统,都是无法接受的。

2. 规范

Android 平台上联系人数据是有帐户归属的概念,各个应用或者服务管理各自帐户的联系人数据。虽然可以跨帐户读取联系人数据,但却不应该跨帐户删除联系人数据,否则,多个应用或者服务如果对数据处理逻辑不一致(大多数情况会是这样),就会出现数据丢失,或频繁修改数据。

联系人资料作为用户的核心数据之一,不允许也不应该被第三方应用删除。因为无法直接一一规范第三方应用,也无法预判可能即将进行不规范操作的应用,所以只能在系统层面进行第三方应用行为约束,保证小米帐户联系人不会被第三方应用后台直接删除,保证用户的数据安全。

3. 限制

如果第三方应用在 MIUI 系统上出现了不规范的删除行为,会弹窗提示:“XXX 正在删除小米帐户上的联系人,为了保护信息安全,本次删除已被禁止。您可使用系统通讯录删除联系人。”。

那么结果就是,要么用户一次次点击确认以继续不规范的删除(基本不可能),要么用户无法使用对应的服务(不利于应用和用户)。

因此,建议第三方应用不要删除小米帐户的联系人数据,在合理的操作范围内一同给用户最好的体验。

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

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

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”));

        }

小米开放平台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. 我的应用被用户主动杀死后,怎么样可以让应用进程自动重新启动呢

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

小米开发平台MIUI无极音量适配说明

小米开发平台MIUI无极音量适配说明

1.MIUI无极音量综述

MIUI为了提升用户的音频体验,将媒体音音量范围从[0,15]修改为[0,150]。用户在滑动音量条时,每一次轻微的滑动都能改变音量,细微的音量调节让用户能找到更加合适的音频响度。

2.App适配建议

2.1.若App无调节音量功能或不拦截音量上下按键,此时无需单独适配

2.2.若App通过onTouchEvent等接口拦截volume up或down事件

  • 拦截事件后,做其他的事件处理,例如按键拍照等,此时App无需单独适配;
  • 拦截事件后,通过AudioManager的adjustSuggestedStreamVolume接口调节音量,此时MIUI 系统会内部计算每次按键应该调节多少index,此时App无需单独适配;
  • 拦截事件后,通过AudioManager的setStreamVolume接口,此时接口要传递具体的index值,此时App需适配。

App应内部自己定义步长step,进而算出index。

若支持无极音量step = 10,若不支持step = 1;原因是为了兼容不支持无极音量的机型,统一定义:按键15次将音量调满。

统一计算公式  step = MaxVolume /15;

MaxVolume = AudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)。

总结:简单来说,应用App如果有自己的音量调节逻辑,应该内部定义步长step。通过以上方式可兼容任意机型。       

3.未适配的应用具体表现

  • 具体表现为:按键150次,才能调满音量,调节缓慢。

4.联系我们

MIUI 期待您的适配,若有任何问题,请及时与我们沟通:liuxiaoyu7@xiaomi.com。

小米应用商店MIUI小部件提交审核与上传操作指南

小米应用商店MIUI小部件提交审核与上传操作指南

“MIUI小部件”非安卓原生小部件,若仅适配安卓原生小部件则无需通过MIUI审核,可自行按照安卓规范适配。安装适配安卓原生小部件的APP版本后,可按照下图所示步骤找到原生小部件入口进行添加:

适配“MIUI小部件”请先阅读MIUI产品设计规范MIUI小部件技术规范与系统能力说明,按照规范进行产品设计和开发。通过审核的小部件可在小部件中心上线展示,提交审核与上传操作步骤见本文档正文部分。

如果确认要适配MIUI小部件,且没有和我们联系过,请按照以下模板发送邮件至“miui-widget@xiaomi.com”:

开发者微信:

开发者联系电话:

应用包名:

我方已阅读MIUI小部件产品设计规范与技术规范,准备按照审核要求适配MIUI小部件,希望进一步沟通审核流程和相关规范。

我们会在看到邮件后的10个工作日内与您联系,感谢您适配MIUI小部件。

1.MIUI小部件创建流程

本文档介绍了小部件创建与提交的全流程,您可在了解文档内容后进行小部件的创建与提交。

同时,因为只有符合审核规范的小部件才会被【小部件中心】收录,我们建议您在上传您的小部件之前,先阅读我们的MIUI小部件审核规范

1.1.小部件上传入口

因MIUI小部件是包含在APP中的,所以需要先确保您包含小部件的APP已经在应用商店进行上传/更新,并通过应用商店的审核。

您可直接进入小部件开放平台。https://widget.xiaomi.com/

当您的APP在应用商店开发者平台上传/更新成功后,可以在提交审核成功页面进入小部件开放平台,上传/更新您该应用的小部件。

注:如果您是首次上传有小部件版本的应用,请先邮件联系miui-widget@xiaomi.com,我们进行相应的操作后,才可在应用商店开发者平台看到小部件上传入口。

在应用商店开发者平台,您APP的管理页面内,可以进入小部件开放平台,上传/更新您该应用的小部件。

注:如果您是首次上传有小部件版本的应用,请先邮件联系miui-widget@xiaomi.com,我们进行相应的操作后,才可在应用商店开发者平台看到小部件上传入口。

当您登录开发者账号进入小部件开放平台首页时,您需要阅读并同意《小部件开放平台协议》。

1.2.上传小部件信息

登录开发者账号(企业开发者账号注册流程),进入小部件开放平台首页,点击“我的应用”或者鼠标向下滑动至“我的应用”,我们将会展示您账号下所有应用列表,选择需要上传小部件所属的应用,点击“管理小部件”,即可开始该应用小部件的首次创建。

当您点击“管理小部件”时,我们将从小米应用商店拉取您最新版本的APP,并自动解析该版本APP,解析后将在“上传页面”展示该版本APP中的所有小部件,每一张小部件的名称、小部件ID、小部件尺寸和小部件的状态都会按照您适配的情况进行显示。

如您的小部件展示遇到问题,可以发送邮件至miui-widget@xiaomi.com 联系我们,我们的开发者关系团队会尽快为您解决。

“上传页面”说明:

“小部件详情”说明:

完善资料页,“本地化语言信息”说明

完善资料页,“发布设备”说明:完善资料页,“小部件介绍”、“预览图”说明:

填写完成后,您可以鼠标点击“查看商店展示预览”,进行展示效果查看。

因填写信息过多,您可在填写过程中选择保存,保存成功后,小部件的状态变为“已填写”,保存后的信息可再次进行编辑。

1.3.排序

当所有小部件的信息填写完毕后,您可以在“小部件详情页”通过上下拖拽小部件,或者点击小部件前面的数字序号进行排序。该排序为小部件在小部件中心应用详情页的展示顺序,排序靠前的小部件可以获得更多曝光哟!

1.4.提交审核

当所有小部件的信息填写完毕后,可点击提交审核。提交审核后应用状态更新为等待审核,我们会在1-3个工作日内为您审核。如果超过3个工作日还未有审核结果反馈给您,可以发送邮件至miui-widget@xiaomi.com 联系我们,我们的开发者关系团队会尽快为您解决。

2.MIUI小部件更新流程

2.1.小部件版本更新

已发布的小部件,如需更新版本,则需要优先在应用商店更新您的APP,应用商店操作流程可参考:应用创建与更新,当您的APP更新后,可进入小部件开放平台,与上述小部件创建流程一致。

如您新版APP中有新添加的小部件,该小部件会有“新增”标签提示。

2.2.小部件仅资料更新

已发布的小部件,如需更新资料(小部件介绍文案/小部件预览图),可在应用管理界面,点击“管理小部件”按钮进入小部件详情页,针对不同状态的小部件操作如下:

  • 已上架的小部件:可点击“更新资料”按钮,进行资料更新,与创建小部件的流程一致
  • 审核中的小部件:仅支持操作“查看详情”
  • 未提交审核的小部件/未发布成功的小部件:可点击“重新编辑”按钮,进行资料更新,与创建小部件的流程一致。

3.MIUI小部件管理流程

3.1.排序

当您的小部件已经上线,您进入小部件管理页面后,仍可在“小部件详情页”通过上下拖拽小部件,或者点击小部件前面的数据序号进行排序。

⚠️注意:更改排序无需提交审核,平台每五分钟定时向小部件中心推送最新排序

3.2.驳回原因展示

3.2.1.审核未通过

如您提交的小部件审核未通过,您可在小部件详情页,鼠标滑至该小部件状态,即可查看审核反馈,同时还可以下载附件,直接查看我们提供的图片反馈。

3.2.2.小部件被下架

如您在线的小部件因违规等原因被下架,您可在小部件详情页,鼠标滑至该小部件状态,即可查看下架原因,您可以待问题解决后重新提交审核。

如您对我们的下架有异议,可以发送邮件至miui-widget@xiaomi.com 进行申诉,我们的开发者关系团队会尽快与您联系。

3.2.3.APP被下架

如您的APP在应用商店被下架,您该版本的小部件将同样在小部件中心被下架。

该情况,我们将在您进入小部件开放平台后进行弹窗提示,您可以选择到应用商店处理该版本APP下架问题,当您的APP在应用商店重新提交审核后,您可以在小部件开放平台重新上传小部件。

如您点击“返回”按钮,您可进入小部件详情页面管理历史版本的小部件,被下架的小部件仅支持查看,无法进行其他操作。

4.MIUI小部件下架流程

4.1.适用范围

当您的小部件在上架状态,可申请小部件下架。当您的小部件申请下架后,或在未发布状态下,可以重新编辑资料并进行提交,但不支持删除小部件。

4.2.注意事项

小部件下架:小部件下架后还是存在后台的数据库中,只是未上线,但下架的小部件将无法在小部件中心中进行展示。

4.3.操作流程

  • 登录小部件开放平台,在“我的应用”列表模块,选择要需要管理的应用,并点击“管理小部件”,进入小部件详情页,将展示最新版本的小部件。
  • 当您需要下架的小部件在多个版本均有上线,点击“申请下架”时,可先确认需要下架的版本,确认下架后,小部件状态和操作由[已发布——申请下架]变为[待填写——编辑]。

小米MIUI小部件审核规范

小米MIUI小部件审核规范

本文档介绍了MIUI收录小部件的一些原则,您可通过阅读本文档,对MIUI小部件审核规范有一定了解。本文档会及时更新并完善,每一次的修改都是基于优化用户的体验、公平对待所有开发者出发。我们重视用户体验如同珍视生命一般,希望您也如我们一样。

1.MIUI 小部件所属应用规范

  • 确保小部件所属应用遵循对应的审核规范 https://dev.mi.com/distribute/doc/details?pId=1080
  • 确保应用信息及开发者账号信息填写完整和正确
  • 确保应用正常运行且不会发生崩溃或错误
  • 确保您在应用商店提交的联系信息真实有效,以便小部件管理人员在需要时与您取得联系

2.MIUI 小部件视觉规范

2.1.尺寸规范

2.1.1.设计稿尺寸

小部件需适配不同的设备和屏幕(1080p屏和2k屏),并兼容负一屏和不同布局的桌面(4*6和5*6)。对应的尺寸像素值倍数关系:1080:2k=1:1.333,详细尺寸如下表:

2.1.2.自适应布局的实际尺寸

因小部件采用的是自适应布局,在不同设备屏幕及不同样式的桌面上,小部件的尺寸会相应变化。以下是需考虑的小部件适配兼容场景(以1080*2340分辨率为基础):

  • 桌面支持4*6和5*6布局,需保证4*6布局下完美显示,5*6布局下无显示缺陷问题 (设置方式:设置—桌面—桌面布局规则)
  • 需保证有搜索框下完美显示,无搜索框下无显示缺陷问题(设置方式:设置—桌面—桌面搜索框)
  • 需保证无虚拟键下完美显示,有虚拟键下无显示缺陷问题(设置方式:设置—桌面—系统导航方式)
  • 需保证1080p与2k分辨率的屏幕上无显示问题

基于以上场景,我们列出了各尺寸的小部件在不同设备屏幕上实际显示的最小尺寸和最大尺寸,供设计师参考。(无需单独输出设计方案,设计和验收时注意不同布局下显示是否正常即可)

2.2.其他规范

  • 圆角:1080p屏下的交付资源圆角为38px,2k屏下的交付资源圆角为50px
  • 内容安全区:1080p屏下小部件的主要内容需远离边缘约40px,2k屏下为54px
  • 小部件在浅色模式和深色模式下需能完美显示
  • 小部件在无内容场景下需使用正确的占位符和文字说明
  • 小部件的字号大小设计需让界面信息清晰易读、层次分明
  • 小部件的字色层级需符合规范
  • 小部件的字高和行高需符合规范
  • 小部件预览图: 预览图圆角为38px;若应用适配了深色模式,请同时提供深色模式的预览图

具体规范和示例详见《MIUI小部件规范》中第四部分-设计规范

3.MIUI 小部件功能规范

3.1.必须适配

  • 不支持在小部件内进行文字输入
  • 不支持在小部件内上下滑动或左右滑动
  • 调整字体显示大小后,小部件需正常显示,无遮挡、截断文字的现象
  • 小部件在无网络、无数据、无内容、未授权、未添加情况下需展示相应的异常状态
  • 小部件不得出现内容无法正常显示或无法获取等情况
  • 小部件不得出现按钮或者链接点击无反应或报错的情况
  • 支持编辑的小部件,编辑功能必须能正常使用
  • 小部件不得安装或者运行前提示或者强制用户重启设备
  • 小部件显示的实际状态应与设定的优先级对应状态相符
  • 同一id的小部件不得在前后两个版本承接不同类型的功能
  • 小部件需使用独立的后台进程,且内存未超40M
  • 小部件介绍或更新日志中介绍的功能应与实际跳转相符
  • 用户未同意应用的隐私协议时,小部件应显示兜底图,点击小部件后应跳转应用,应用内出现授权弹窗
  • 已安装的小部件在加载数据时显示占位符内容。可以通过将 UI 的静态部分与代表其内容的半透明形状相结合来创建占位符外观

3.2.建议适配

  • 小部件可选择适配无障碍talkback功能,帮助无障碍人士更好的使用小部件
  • 在无障碍“视觉”功能中调整字体大小后,小部件需正常显示,无遮挡、截断现象
  • 小部件可跟随所属应用适配多语言,选择适配多语言里的部分或全部语言(除简体中文外有繁体中文、英文、维吾尔语、藏语),并保证切换语言后功能仍可以正常使用
  • 小部件可跟随所属应用适配深色模式,并保证切换深色模式后,功能可以正常使用

4.MIUI 小部件内容规范

4.1.小部件名称

  • 小部件名称与应用名称不得相同
  • 小部件名称必须贴合小部件功能,不得添加与小部件内容无关的词语
  • 概括小部件主要功能信息,中文长度不得超过10个中文字符
  • 小部件名称与其功能相对应,不同功能的小部件应该有不同的名称
  • 小部件名称不得存在占位符文本、空格、乱码等非法字符(如:#、*、& 等)
  • 小部件名称不得包含政治敏感、色情、恐怖、暴力血腥等内容,以及国家法律法规禁止的违法内容
  • 小部件名称不得使用其他热门小部件名称或别称,也不得混有商业化用语或流行用语等与小部件功能无关的词语
  • 小部件名称不得含有小米或MIUI等其他小米元素
  • 小部件名称不得涉及赌博、彩票的内容

4.2.小部件介绍

  • 中文长度不得超过22个中文字符,其他语言长度不超过44个中文字符(1个中文字符 = 2个特殊字符 / 2个非中文字符)

举例:所属“相册”应用的小部件

小部件名称小部件介绍
自选图片添加至桌面后,点击小部件来选择图片
精选回忆为你推荐精彩照片与回忆
  • 小部件介绍应符合小部件功能,不同功能的小部件要对应不同的简介
  • 小部件介绍不得添加其他热门词语或商业化用语等与应用无关的关键字
  • 小部件介绍不得违反国家法律法规,包括但不限于法律法规中禁止使用的词语
  • 小部件介绍不得添加具有侵权内容的关键字
  • 小部件介绍不得存在占位符文本、空格、乱码等非法字符(如:#、*、& 等)
  • 小部件介绍中不得使用极限词或虚假承诺等违反新广告法的内容(如“最”“第一”“唯一”“NO.1”“必备”“免费送”“100%” “全球”“顶尖”“首”等)
  • 小部件介绍不得使用疑问、反问等句式,请用陈述语句进行描述
  • 小部件介绍的末尾禁止添加任何标点符号

小部件展示图片说明

4.3.小部件中心预览素材及跳转落地页

4.3.1.预览素材图

  • 需符合MIUI 小部件设计规范,并与实际使用场景相符合
  • 预览素材图要与实际添加到桌面上的展示保持一致
  • 不得包含任何推荐相关的角标(如“推荐”、“荐”、“热门”、“最新”、“优惠”、“惠”等)
  • 不得涉及抄袭内容
  • 不得含非法金钱交易、赌博、反政府、反社会及其他法律禁止内容
  • 不得含诽谤、人身攻击或者侮辱性的内容
  • 不得含强烈的暴力暗示,包括但不限于虐待,聚众斗殴
  • 不得含过度暴露、情色、低俗内容
  • 不得含种族歧视,破坏民族团结内容
  • 不得过度宣传酒精或者危险物品(如毒药、爆炸物等)或者鼓励未成年人消费香烟和酒精饮料
  • 不得包含具有贩卖、购买违禁物品的内容

4.3.2.跳转落地页

  • 不得涉及抄袭内容
  • 不得含非法金钱交易、赌博、反政府、反社会及其他法律禁止内容
  • 不得含诽谤、人身攻击或者侮辱性的内容
  • 不得含强烈的暴力暗示,包括但不限于虐待,聚众斗殴
  • 不得含过度暴露、情色、低俗内容
  • 不得含种族歧视,破坏民族团结内容
  • 不得过度宣传酒精或者危险物品(如毒药、爆炸物等)或者鼓励未成年人消费香烟和酒精饮料
  • 不得包含具有贩卖、购买违禁物品的内容

MIUI Widget适配建议及示例

1.MIUI Widget布局自适应方案建议及示例

1.1.概述

背景见:《MIUI小部件技术规范与系统能力说明》中「小部件技术规范和要求」第八项。

本模块介绍了常用的几类Widget布局实现自适应的方案。

1.2.方案与示例

1.2.1.快捷卡片的自适应方案实践

快捷功能卡片中每个icon的大小是固定的,但是在widget中无法获取具体显示大小,因此如果只写死一套尺寸,在某些模式下,icon会显示不全。

目前采用的方案是,使用2套布局资源,在布局中写死大小,根据Google小部件开发指南中提到的,使用OPTION_APPWIDGET_MIN_WIDTH或者OPTION_APPWIDGET_MAX_WIDTH返回的数值决定使用哪套布局:

先定义2套不同尺寸的layout资源:

//layout_shortcut_large
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/shortcut_icon"
        android:layout_width="53dp"
        android:layout_height="53dp"
        android:layout_gravity="center"
        android:scaleType="centerCrop" />

    <TextView
        android:id="@+id/shortcut_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="@dimen/dp_4"
        android:textSize="24sp" />
</LinearLayout>


//layout_shortcut_small
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/shortcut_icon"
        android:layout_width="28dp"
        android:layout_height="28dp"
        android:layout_gravity="center"
        android:scaleType="centerCrop" />

    <TextView
        android:id="@+id/shortcut_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="@dimen/dp_4"
        android:textSize="14sp"/>
</LinearLayout>

在代码中动态判断使用哪一套布局:

class SimpleWidgetProvider : AppWidgetProvider() {

    override fun onUpdate(
    context: Context,
    appWidgetManager: AppWidgetManager,
    appWidgetIds: IntArray) {
        appWidgetIds.forEach { appWidgetId ->
        updateAppWidget(context, appWidgetManager, appWidgetId)
        }
    }

    fun updateAppWidget(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetId: Int,
    ) {
        val appWidgetManager = AppWidgetManager.getInstance(context)
        val options = appWidgetManager.getAppWidgetOptions(appWidgetId)
        if (options == null) {
            return null
        }
        val minWidth = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)
        val widgetLayoutRes = if (minWidth >= 300)R.layout.layout_shortcut_large else R.layout.layout_shortcut_small
        val remoteViews = RemoteViews(context.packageName, widgetLayoutRes)
        appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
    }
    
    /**
     * 任何option的更改都会触发,无法通过newOptions来判断是否是大小发生了变化
     * 需要将本次的MAX_HEIGHT/MAX_WIDHT记录下来,在下一次触发时比对
     */
    @CallSuper
    override fun onAppWidgetOptionsChanged(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetId: Int,
        newOptions: Bundle
    ) {
        //需要在options change通知里判断,min和max是否发生了变化,如果发生了变化,需要更新布局
    }
}

1.2.2.股票卡片的自适应方案实践

RemoteViews无法动态设置宽高,但是可以在布局中设置宽高,为了实现卡片的自适应,我们不建议在布局中给layout_width和layout_height设置固定值,但是可以通过设置最大和最小宽高来达到自适应的效果;

例如股票通过调整ListView中每个item的maxHeight和maxHeight,来实现自适应(以下按1080*2340分辨率为例):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/stock_item"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:maxHeight="43.64dp"
    android:minHeight="40.97dp"
    android:background="@drawable/pa_bg_stock_widget_item"
    android:paddingHorizontal="@dimen/dp_7">

    <include layout="@layout/pa_widget_stock_item" />
</RelativeLayout>
  • android:minHeight计算:(418-40*2)/ 3 / 2.75=40.97
  • android:maxHeight计算:(440-40*2)/ 3/ 2.75 = 43.64

计算过程:

在MIUI小部件设计规范中,列出了Widget Host在不同模式下的尺寸:

如表格所示,4*2规格的卡片,其最小显示高度是418px,最大显示高度是440px,我们要求内容区距离卡片外边距最小是40px,因此,对于4*2widget的内容区的最小显示高度为418-40*2;

股票4*2最多显示3条数据,因此每条数据的最小显示高度为(418-40*2)/ 3,换算成dp为40.97dp;最大显示高度类似;

1.2.3.其他方案建议

使用LinearLayout等分布局:

疫情卡片的一种自适应布局实现:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="40px">

    <TextView
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_gravity="bottom">

        <LinearLayout
            android:id="@+id/item1"
            android:layout_width="0dp"
            android:layout_height="100dp"
            android:layout_marginEnd="10dp"
            android:layout_weight="1">

        </LinearLayout>

        <LinearLayout
            android:id="@+id/item2"
            android:layout_width="0dp"
            android:layout_height="100dp"
            android:layout_marginEnd="10dp"
            android:layout_weight="1">

        </LinearLayout>

        <LinearLayout
            android:id="@+id/item3"
            android:layout_width="0dp"
            android:layout_height="100dp"
            android:layout_marginEnd="10dp"
            android:layout_weight="1">

        </LinearLayout>

        <LinearLayout
            android:id="@+id/item4"
            android:layout_width="0dp"
            android:layout_height="100dp"
            android:layout_weight="1">

        </LinearLayout>
    </LinearLayout>

</FrameLayout>

利用LinearLayout的android:layout_height属性,动态分配每个item的尺寸;

注:股票卡片也可以采用该方案进行适配,事实上,所有使用ListView或者GridView的卡片布局,都可以用该方案代替,来达到自适应效果,但是相应地需要进行更多自定义的封装,开发成本更高,但是效果比较理想;

使用RelativeLayout相对布局:

手机管理卡片的一种自适应布局实现:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:padding="40px">

    <RelativeLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1">

        <ImageView
            android:id="@+id/icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <TextView
            android:id="@+id/score_description"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_alignParentBottom="true"
            android:text="手机很安全,继续保持"/>

        <TextView
            android:id="@+id/score"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="100"/>
    </RelativeLayout>


    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:text="手机垃圾"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:text="内存占用"/>
    </LinearLayout>

</LinearLayout>

1.3.总结

  • 以上各个方案只是目前为止,可能的部分自适应实践建议,业务需要根据自身情况来判断如何适配
  • 以上各个方案,部分方案可以通过组合的方式,来实现更多效果
  • 以上示例代码中的数值和布局,只具有演示效果,业务需要根据具体情况调整,请勿原样复制

2.小部件加载态建议及示例

2.1.概述

背景见:《MIUI小部件规范》中「无内容场景」。没有配置加载态手机重启或者桌面/负一屏重启会呈现空白卡片,影响整体体验,本模块介绍了推荐的加载态实现方案。

2.2.方案

默认展示加载态(initialLayout指定加载态布局资源),当收到update广播时更新为实际的布局

# 工程结构
.
└── src
    ├── main
    │   └── java
    └── res
        ├── drawable
        │   └── loading.webp
        ├── drawable-night
        │   └── loading.webp
        ├── layout
        │   ├── layout_example.xml
        │   └── layout_loading.xml
        └── xml
            └── app_widget_simple.xml

layout_example.xml 正常的布局资源

...

layout_loading.xml 加载态布局资源

<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="fitXY"
    android:src="@drawable/loading.webp" />

app_widget_simple.xml 小部件配置资源

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="xxx"
    android:minHeight="xxx"
    ...
    android:initialLayout="@layout/layout_loading.xml"/>

AppWidgetProvider 使用正常布局文件创建RemoteViews

public class ExampleWidget extends AppWidgetProvider {

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        for (int appWidgetId : appWidgetIds) {          
            // 正常布局文件创建RemoteViews 
            RemoteViews remoteViews = new RemoteViews(context.getPackageName(), 
            R.layout.example);
            ...        
        }
    }
}

备注:以上方案仅做参考。

MIUI 9 & 10“状态栏黑色字符”实现方法变更通知

MIUI 9 & 10“状态栏黑色字符”实现方法变更通知

我们在开发版 7.7.13(2017年的开发版) 及以后的版本,MIUI 状态栏更新了一处实现逻辑。直接影响就是,部分应用无法显示“状态栏黑色字符”,如下图中的即刻、UC、好奇心日报等应用。

1. 问题出现的原因

  • 在 Android 6.0 以前,Android 没有方法可以实现“状态栏黑色字符”效果,因此 MIUI 自己做了一个接口。
  • 在 Android 6.0 及以上版本,Android 提供了标准的方法实现“状态栏黑色字符”效果,但这个方法和 MIUI 的方法产生了冲突,以致于当开发者使用 Android 标准方法时,没有出现预期的效果,这给很多开发者都造成了困扰,尤其是海外开发者。

2. 如何解决

基于以上背景,我们决定兼容 Android 的方法,舍弃 MIUI 的自己的实现方法。从今天的 7.7.13 开发版生效,之后随 MIUI 9 外发。非常抱歉给各位开发者带来麻烦,但长远来看,兼容 Android 的标准,对 MIUI 和开发者都更为有利。

3. 开发者需要做的

如果开发者需要设置“状态栏黑色字符”的效果, 需要做以下几件事:

3.1. 在新的 MIUI 版本(即基于 Android 6.0 ,开发版 7.7.13 及以后版本)

  • 使用 View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR ,来设置“状态栏黑色字符”效果
  • 同时要设置 WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
  • 并且不设置 WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS

  参考实例:

Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);

3.2. 在旧的MIUI版本

还有大量的用户使用旧的 MIUI 版本,因此仍然需要使用 MIUI 原有的方法,即

public void setStatusBarDarkMode(boolean darkmode, Activity activity) {
        Class<? extends Window> clazz = activity.getWindow().getClass();
        try {
        int darkModeFlag = 0;
        Class<?> layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
        Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
        darkModeFlag = field.getInt(layoutParams);
        Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
        extraFlagField.invoke(activity.getWindow(), darkmode ? darkModeFlag : 0, darkModeFlag);
        } catch (Exception e) {
            e.printStackTrace();
        }
}

对于广大开发者而言,为了保证在新旧版本的 MIUI 都能实现「状态栏黑色字符」的效果,需要开发者同时写上以上两种实现方法。给各位开发者带来麻烦,再次表达歉意,望谅解。文末附上了常见问题,如果在适配过程中遇到问题,可以邮件给我们的项目组 miuix-systemui@xiaomi.com,会有同事解答相关疑问。。

4. FAQ

4.1. 如何调试

如前所述,相关改动将从 7.7.13 开发版(2017年的版本)及以后版本生效,因此开发者升级到  MIUI 10 最新开发版即可,支持的机型和下载链接如下(找到对应机型的最新开发版):

4.2. 如何实现状态栏白色字符效果

int flag = window.getDecorView().getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
window.getDecorView().setSystemUiVisibility(flag);​

面向隐私保护的权限使用行为管理说明

1.背景

自MIUI于2010年8月16日上线以来,用户的隐私及安全都是我们最关注的要点。

小米于2018年起草《移动智能终端补充设备标识规范》,应对IMEI等设备标识被篡改及滥用的信息安全问题,确保用户数据隐私不被泄露。

在用户的隐私方面,MIUI希望可以通过系统权限隐私保护逻辑,在用户的隐私及数据安全方面有进一步的突破与保护措施。

2.权限改动

2.1.禁止获取不可重置的设备标识

  • 安卓P版本中将不再支持通过READ_PHONE_STATE权限获取不可重置的设备标识。具体表现与Android Q一致,获取到空值不抛出异常。
  • 生效时间:开发版-MIUI11 3.12(小米6)。
  • 针对READ_PHONE_STATE权限在Android P各MIUI版本的表现为:
版本/设备标识IMEI/MEIDIMSIICCIDSerialOAID
MIUI 11开发版(3.12之前)可以获取可以获取可以获取可以获取可以获取
MIUI 11稳定版可以获取可以获取可以获取可以获取可以获取
MIUI 11开发版(3.12之后)不可获取不可获取不可获取不可获取可以获取
  • 适配方法:可以通过”1″.equals(SystemProperties.get(“ro.miui.restrict_imei_p”))为true判断Android P上已经开启了设备标识的限制策略。
  • 建议开发者使用OAID替代IMEI等设备标识作为用户标识。OAID获取方法:http://msalliance.icoc.bz/col.jsp?id=120

2.2.CALL_PHONE 及 ANSWER_PHONE_CALLS

  • 除应用被设置为默认拨号应用外,CALL_PHONE权限及ANSWER_PHONE_CALLS将默认禁止获取。
  • 生效时间:开发版-MIUI11 3.12。
  • 适配方法:若业务需要以上两项权限,可从产品逻辑角度引导用户将应用设置为默认拨号应用。

2.3.禁用后台拍照行为

  • CAMERA权限仅支持应用在前台时调用,应用处于后台时将禁止使用该权限进行拍照或录像等行为。
  • 生效时间:开发版-MIUI11 3.12。
  • 适配方法:请开发者检查业务是否有相关行为并修改相关业务逻辑,避免出现异常情况;若出现不可避免的后台使用相机行为,请通过调起前台服务实现。

2.4.定位、录音权限用途描述

当应用申请定位、录音权限时,系统会根据应用是否对权限进行用户说明来判断授权弹窗中出现的选项:

  • 若应用未对权限用途进行说明,则显示“拒绝”“允许本次”及“仅在应用使用中允许”选项;
  • 若应用已进行权限用途说明,则在以上选项的基础上,增加显示“始终允许”选项。

该显示逻辑不影响基础权限选项,若用户进入应用授权管理,仍可对权限设置始终允许。

  • 生效时间:开发版-MIUI11 3.12。
  • 适配方法:请开发者进行权限用途说明,接入权限用途说明请查看:”MIUI权限用途接入说明“。

小米手机深色模式适配说明

小米手机深色模式适配说明

1.深色模式背景与介绍

1.1.安卓原生深色模式介绍

Android 10 (API 级别 29) 及更高版本中提供深色主题背景。深色主题背景具有诸多优势:

  • 可大幅减少耗电量(具体取决于设备的屏幕技术)。
  • 为弱视以及对强光敏感的用户提高可视性。
  • 让所有人都可以在光线较暗的环境中更轻松地使用设备。

深色主题背景同时适用于 Android 系统界面和在设备上运行的应用。在 Android 10 (API 级别 29) 及更高版本中,您可以通过以下三种方法启用深色主题背景:

  • 使用系统设置(Settings -> Display -> Theme)启用深色主题背景。
  • 使用“快捷设置”图块,从通知托盘中切换主题背景(启用后)。
  • 在 Pixel 设备上,选择“省电模式”将同时启用深色主题背景。其他原始设备制造商 (OEM) 不一定支持这种行为。

可参考:https://developer.android.com/guide/topics/ui/look-and-feel/darktheme#top_of_page

1.2.MIUI深色模式介绍

深色模式是一种将屏幕主色调转为深色的模式。如下图MIUI10界面为例,上图是正常浅色背景,下图为MIUI深色模式。

用户在MIUI中有两个方式可以打开深色模式:1.设置-显示-深色模式 2.下拉控制中心 中可以开启,开启后将全局变黑如上图显示:

对于适配深色模式的应用来说,我们会优先启用应用的深色模式。未接入的应用,则会通过算法进行反色。适配方式将在第三部分详细描述。如果用户不希望某个应用被反色,用户可以手动关闭该应用的反色功能,见下方:

目前已有众多知名头部三方应用适配了深色模式,例如微信、QQ、爱奇艺、优酷、知乎、小红书、钉钉等。

2.适配深色模式的好处

我们强烈建议您为您的应用适配深色模式,主要有如下原因:

  • 更酷、更时髦的流行趋势

作为一种全新的潮流,黑色界面受到众多用户、尤其是年轻用户的欢迎。目前,各大安卓系统、iOS系统都已经支持深色模式,众多主流头部应用也已适配或正在适配深色模式。                

  • 让用户更专注于内容

深色背景下,文字、图片、视频都能更清晰地呈现,尤其是暗光环境下。对于浏览器、资讯和视频类app,深色可以让用户沉浸其中,为应用贡献更多的使用时长:对于使用时段是晚上的应用,这一特性更加明显。                                               

  •  降低应用的耗电量                                               

省电是用户最关注的性能之一。低电情况下,用户更青睐使用深色模式的应用。根据小米实验室测试数据:

OLED屏幕100%亮度下,深色模式耗电相比浅色模式,最高降低83%;

OLED屏幕50%亮度下,深色模式耗电相比浅色模式,最高降低50%。

  • 推广优待适配深色模式时可以联系我们,小米可以为应用提供应用商店专题和微博宣传等方式,为应用增加额外的宣传。

3.适配方式

适配方式有两种:

  • 适配深色模式资源:开发者根据自身应用设计深色模式,按照适配规范进行适配。
  • 适配全局反色:利用安卓Q上提供的forcedark能力可以直接反色。

在适配前,开发者需要考虑是否在自有应用内增加深色模式开关。我们建议的方式:

  • 务必提供跟随系统深色模式的选项。可以默认跟随系统,或在监测到系统切换为深色/浅色时提示用户。 
  • 可以为用户提供手动切换的开关。

目前已有三方应用如下(左 小红书 右 QQ),仅供参考:

方式一 适配深色模式资源                                           

适配深色模式资源需要设计师先对所有页面设计深色页面,再由开发完成深色模式资源开发。

优点:在所有安卓版本、所有手机厂商用户均可以使用,且体验较好。

3.1.谷歌适配要求

开发者可基于谷歌深色模式适配标准进行适配:https://developer.android.com/guide/topics/ui/look-and-feel/darktheme#top_of_page

已经适配过的页面要将ForceDarkAllowed()​参数值设置为false,若整个主题均已适配深色模式,则需要将这个主题的ForceDarkAllowed()参数值设置为false。

3.2.小米深色模式配色标准设计

其余适配建议详见设计文档:附件1-小米深色模式配色标准设计文档。

3.3.接入方式

3.3.1.接入介绍

  • 根据谷歌深色模式接入标准,如要支持深色主题背景,您必须将应用的主题背景(通常可在res/values/styles.xml中找到)设置为继承 ​DayNight​ 主题背景:
<style name="AppTheme" parent="Theme.AppCompat.DayNight">

您还可以使用 MaterialComponent 的深色主题背景

<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">

这会将应用的主要主题背景与系统控制的夜间模式标记相关联,并将应用的默认主题背景设置为深色主题背景(如果已启用)。详细内容见:https://developer.android.com/guide/topics/ui/look-and-feel/darktheme#force_dark

从开发的角度说,就是打开夜间模式后,系统会优先从xxx-night资源中寻找资源并替换。
Android系统从2.2开始就已经⽀支持了了DarkMode,其对应接口是名为“uimode”的系统服务,DarkMode所使⽤用到的接口方法是:setNightMode、getNightMode分别对应设置夜间模式和获取设置状态。

// 获取uimode系统服务
UiModeManager uiModeManager = (UiModeManager)
getSystemService(Context.UI_MODE_SERVICE);                                             
// 获取设置状态
int currentMode = uiModeManager.getNightMode();                                               
// 设置夜间状态
uiModeManager.setNightMode(UiModeManager.MODE_NIGHT_AUTO); // ⾃动
uiModeManager.setNightMode(UiModeManager.MODE_NIGHT_YES); // 启⽤
uiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO); // 停⽤
                                             
在日志中的体现:
------------------------------------------------------                                                        
DUMP OF SERVICE uimode:
Current UI Mode Service state:                                                        
mDockState=0 mLastBroadcastState=0                                                        
mNightMode=2 mNightModeLocked=false
mCarModeEnabled=false mComputedNightMode=false
mCarModeEnableFlags=0 mEnableCarDockLaunch=true                                                        
mCurUiMode=0x2d mUiModeLocked=false mSetUiMode=0x2d
mHoldingConfiguration=false mSystemReady=true
mTwilightService.getLastTwilightState()=null                                                        
--------- 0.001s was the duration of dumpsys uimode,ending at: 2019-01-18 00:00:01--------------

从uimode服务dump出来的信息可以判断:

mNightMode的取值:0 → AUTO; 1 → NO; 2 → YES另外,在Android O开始,Android⽀支持adb命令启动和停⽤用DarkMode,具体命令是:adb shell cmd uimode night <auto | yes | no>

O以前的机型,只能通过代码调用uimode接口启动和停用DarkMode。需要注意的是,miui虽然从level 16开始支持DarkMode,但是并不意味着miui之前的版本不能切换DarkMode,用户完全可以通过命令或者代码调用系统接口,启动和关闭DarkMode。而Setting中的开关是阻⽌不了的,除⾮MIUI对这个权限进行收紧。   

MIUI SDK从level 16开始,支持DarkMode,所以的支持DarkMode,是指MiuiSDK增加了新的主题,DayNight,来自适应的适配Light和Dark主题。其实在16之前的SDK上,Light和Dark主题都是一直存在的,只是没有一个自动适配的机制存在,而且Dark主题存在一些Bug,并不是它应该是的样⼦。适配过程中,存在一些APP,之前就是在Dark主题下进行的开发,那么在sdk level 16以后,发现Dark模式存一些问题,比如弹窗变成了⿊⾊,均是因为Dark主题修改造成的。附录中会给出SDK针对Dark和Light进行的改动,包括所有受影响的资源和主题样式修改。

3.3.2.接入方式详细说明                                              

重点说明⼀下接⼊和使⽤⽅式。这⾥除了Theme.Light.DarkActionBar这个主题以外,所有Light(Dark)主题均有对应的DayNight主题。

  • 对于非解耦应⽤,可以直接使⽤Miui SDK的public的资源和属性。

代码接⼊方式:可以直接将使⽤MiuiSdk的Light或者Dark主题的地方,替换成DayNight主题,⽐如:将

<style name="MiuiDemo.Theme.Test" parent="miui:Theme.Light">
或者<style name="MiuiDemo.Theme.Test" parent="miui:Theme.Dark"> 
替换成<style name="MiuiDemo.Theme.Test" parent="miui:Theme.DayNight">

Java代码中:将使⽤Light主题的所有地⽅miui.R.style.Theme_Light替换成miui.R.style.Theme_DayNight

  • (如果本身就是⾃升级应用,就不⽤看这⼀步了)对于解耦自升级应⽤,需要在gradle中进行如下操作:   
1)修改build.gradle中的dependencies,使⽤用miui插件,如下:(以gradle 3.3.0为例)
dependencies {                                               
classpath 'com.miui.tools.build:gradle:3.3.0'
}
2)gradle/wrapper/gradle-wrapper.properties⽂文件中的gradle修改到5.0,即:
distributionUrl=http://sdk.pt.miui.com/miuisdk/software/gradle/gradle-5.0-all.zip
3)此步,3.2.0可以忽略略,在3.3.0下必须将:                                                
apply plugin: 'com.android.application'
改成
apply plugin: 'com.miui.application'                                       
4)如果gradlew的⽇日志中有如下输出:
add property to gradle.properties of this project:                                                
android.aapt2FromMavenOverride=/home/{username}/.miuisdk/build-tools/aapt/28/aapt2
则修改gradle.properties,最后加一行:
android.aapt2FromMavenOverride=//home/{username}/.miuisdk/build-tools/aapt/28/aapt2
5)增加依赖:
compileOnly 'com.miui:core:alpha-SNAPSHOT'

于是,miui sdk中的Theme.DayNight就接入完成,在APP中可以直接使⽤Theme.DayNight.<…>主题。针对之前的miui:Theme.Light主题或者miui:Theme.Dark主题,均有对应的Dark或者Light主题。所以,如果需要适配自动切换夜间和日间模式,只要替换成对应的DayNight主题即可,DayNight主题负责⾃动切换两种(Dark和Light)主题。

  • (非单发应⽤请忽略)对于单发应用:单发应用的接⼊⽅式,可以参考:MIUI SDK support包的使用指南将依赖由:compileOnly ‘com.miui:core:alpha-SNAPSHOT’改成 implementation ‘com.miui.support:core-compat:alpha-SNAPSHOT’
  • 对于单发或者⾃升级应⽤,在代码接入上完全可以参考1中的代码接入方式。即,替换Light或者Dark为DayNight。但是,对于⾃自升级来说(单发应⽤不会有这个问题,因为单发应用已经将DayNight主题打包进APK),由于不确定⾃升级的目标ROM是否⽀持DayNight主题,所以如果在没有DayNight主题的ROM上⾃自升级,就会崩溃。这⾥需要看下⾯的注意事项1,可以找到解决方案。详见开发文档:附件2-深色模式开发文档

方式二 适配全局反色

全局反色利用安卓Q上提供的forcedark能力可以直接反色,对于开发者来说较为友好。

  • 优点:开发工作量小,适配较快。
  • 缺点:仅安卓Q手机可以使用该功能,复杂页面用forcedark适配难度较大;非原生的Webview无法反色,图片无法反色。

可以在安卓Q手机上,打开以下开关即可体验安卓Q反色能力,查看本应用反色后的情况。开关位置如下:

3.4.谷歌Force Dark适配要求

Android 10 提供 Force Dark 功能。一如其名,此功能可让开发者快速实现深色主题背景,而无需明确设置 ​DayNight​ 主题背景。如果您的应用采用浅色主题背景,则 Force Dark 会分析应用的每个视图,并在相应视图在屏幕上显示之前,自动应用深色主题背景。有些开发者会混合使用 Force Dark 和本机实现,以缩短实现深色主题背景所需的时间。应用必须选择启用 Force Dark,方法是在其主题背景中设置 ​android:forceDarkAllowed=”true”​。此属性会在所有系统及 AndroidX 提供的浅色主题背景(例如 ​Theme.Material.Light​)上设置。使用 Force Dark 时,您应确保全面测试应用,并根据需要排除视图。如果您的应用使用深色主题背景(例如​Theme.Material​),则系统不会应用 Force Dark。同样,如果应用的主题背景继承自 DayNight​主题背景,则系统不会应用 Force Dark,因为会自动切换主题背景。

3.5.MIUI深⾊模式适配的目标和方法

在MIUI12上推出全局的深色模式,所有系统应用和主流三方应用适配。在MIUI12上用户开启深色模式后将默认开启全局反色。要达到的效果:系统切换到深⾊模式以后,应⽤也随之切换到深色模式,这是我们这次MIUI适配深色模式要达到的目标,也是我们对每个系统应⽤的要求。

适配的⽅法有三种:                                          

  • Android原生的深色主题
  • 全局反⾊
  • 第三方框架或者应用⾃自己实现深色模式  

目前只有Android Q深色模式支持全局反色:Android Q上提供了两种实现深色模式的⽅式:深色主题和全局反⾊。

深⾊主题即Theme.AppCompat.DayNight或者Theme.MaterialComponents.DayNight。深⾊主题是Android官⽅提供的实现深色模式的一种方法,应用也可以其它自己的方法实现深色模式。深⾊主题起作用后,将会加载-night⽬目录下的资源,如果没有找到,那么加载默认资源。对于标准控件,如果应用不提供深色模式的资源,系统也会提供一套默认的深色资源(不过比较难看,基本不可用)全局反色即ForceDark,系统会根据一套算法,计算出每个View属于前台还是后台。对于前台View,会使其变亮,对于后台View,会使其变暗,⽽不需要开发者提供两套资源。具体的原理可以移步Android Q Force Dark调研。如果一个activity使⽤了深色模式,那么全局反色对其将不生效。    

3.6.接入方式介绍

3.6.1.建议的整体适配步骤                                              

  • 现状评估

打开系统的深⾊模式,看看⾃⼰应用中那些⻚面有没有问题,一般可以分为三类:

A.适配了深色模式没有问题

B.没有适配深色模式的

C.适配了深⾊模式但是有问题的

  • 全局反色过滤

对于所有的A,请务必禁用全局反色,否则全局反色可能会和已有的深⾊模式效果冲突。对于所有的B,可以设置 <item name=”android:forceDarkAllowed”>true</item> ,然后可以打开全局反色,观察有没有问题。没有问题的,那就适配完成了,问题⽐较⼤的归为D。

  • 全局反色问题分析                                       

对于所有的D,分析问题的原因,是反色本身的问题还是⾃己应⽤导致的,⽐如布局存在有问题,有图片、webVIew等。其他的看下能不能通过调整布局等最后还是通过全局反⾊简单解决,剩下统一归为C。

  • 重新适配

对于C,看看是不是原来的适配方法有问题,有没有必要采用新的适配⽅法、框架,可不可以采用系统的深色主题等重新适配。                                              

  • 验证

打开系统深色模式,重新测试所有页⾯。

比较理想的做法是:建议采用深色主题和全局反色相结合的方式。整个应用用的主题使用DayNight,设置<item name=”android:forceDarkAllowed”>false</item>。对于已经⽀持深色主题或者用其他方法实现了深⾊模式的⻚面,没必要再支持全局反⾊,务必禁⽤。对于需要使用反⾊的⻚面,使⽤Light主题,设<itemname=”android:forceDarkAllowed”>true</item>。其他⻚面单独适配。                                             

3.6.2.深色主题的适配

深色主题在Android Q之前就是已经有了的,只不过其在Android Q上得到了了加强。一个activity如果想使用深色主题,让其主题继承于DayNight主题或者让整个应用的主题继承于DayNight主题即可。不过,上⾯的做法只适合跟随系统变化的需求,如果有高级的需求,⽐如应用想要主动切换到深⾊色主题或者不跟随系统变化,而是让⽤户选择,那么需要这样做:

1.监听UImode的变化。manifest中 activity 设置属性 android:configChanges=”uiMode”,并重写onConfigurationChanged。注意,声明了configChanges系统将不不会重建对应的activity,⽽而是回调onConfigurationChanged,需要activity⾃己处理变化的情况

2.让Activity继承于AppCompatActivity,这样子可以通过AppCompatDelegate.setDefaultNightMode(mode) 设置当前activity的mode,通过AppCompatDelegate.getDefaultNightMode()获取当前设置的mode。系统可以选择的mode有四种,具体解释可以看下面第1篇文章。

3.用户选择夜间模式的时候,保存用户选择的模式到SharedPreferences,调用recreate()重启activity,注意recreate()是重新创建activity,会回调所有生命周期方法。

4.activity启动的时候,取出之前保存的模式,通过AppCompatDelegate.setDefaultNightMode(mode) 进行当前activity的mode。                                            

5.系统UImode发⽣变化的时候,即onConfigurationChanged时,获取系统当前的UImode,根据应用⾃身的逻辑判断需不需要改变当前activity的mode,如果需要改变重复第3、4步。

切换到深⾊模式时,资源应用的顺序:                                  

  • 应用提供的-night资源
  • 应⽤设置的默认资源(通常是亮色下的资源,也可以是应用主动在深⾊模式下设置的主题)
  • 系统的默认深⾊资源
  • 系统默认的亮色资源

所以,凡是应用⾃己提供资源了的,那就提供两套,要么就全部⽤用系统的默认资源。如果一个应⽤没有使用DayNight主题,那么只会变化应用提供的-night深⾊资源,其他不会变化,系统也不会应⽤标准控件的默认深色资源。

具体实现,可以参考:  

https://segmentfault.com/a/1190000011472198
https://blog.csdn.net/xiaoxiaocaizi123/article/details/90370275

有两种方式,⼤同小异,相信大家能看得懂。

3.6.3.全局反色的适配      

⾸先来看下全局反色生效前提:

系统端:1.开启深色模式

应用端:1.activity对应的主题是Light的 2.对应的控件允许全局反色,默认是forceDarkAllowedDefault,这个值由系统属性debug.hwui.force_dark决定,debug.hwui.force_dark⼜是由全局反⾊的开关控制,默认是关闭的。         

以上几点缺⼀不可,需要详细说明几点是:

1.实现了深色主题的activity是不会受全局反色的影响,前面说了,深⾊主题是DayNight,不属于Light

2.如果一个控件没有申明forceDarkAllowed,那么forceDark是否⽣效取决于全局反色是否开启,如果全局反色开启,那么forceDark生效,否则不生效;

如果一个控件声明了forceDarkAllowed = false,那么无论如何都不会生效;

如果一个控件声明了forceDarkAllowed = true,那么只要开启深色模式,全局反色就会⽣效。             

3.forceDarkAllowed可以在主题xml中申明,也可以在代码中动态设置,动态设置可以具体到某⼀个view,后者会覆盖前者。遵循覆盖原则,即子View的声明会覆盖父View的声明,activity的会覆盖这个应⽤的申明。

适配全局反色的步骤:                                                

1.compileSdkVersion 设为29, 否则会编译失败,targetSdkVersion 貌似没有要求。

2.对于需要使用forceDark的activity,务必让其实现Light主题,Dark主题、DayNight主题都不会⽣效。

3.在activity的主题中申明<item name=”android:forceDarkAllowed”>true</item> 或者 在onCreate中,在setContentView之前,调⽤getWindow().getDecorView().setForceDarkAllowed(allowed)。

最后提供demo,需要的可以参考一下,源码:https://github.com/CQULittleMing/AndroidDemo/tree/master/demo

操作路径:  View – View 下面最后的两个。     

4.FAQ

4.1.全局反色的局限

  • 目前全局反色只是实验性功能,存在很多bug,不可过于依赖,更多可以参考Force Dark目前存在的问题。
  • 不会反色图⽚。
  • WebView/Flutter⽀持不完善所以如果一个⻚面存在Flutter书写、WebView或者亮色的图片,建议还是使⽤深色模式进行适配。

4.2.如何开启和禁用全局反色

设置是否⽀持全局反色有静态和动态两种方法:                                              

静态方式就是对应用或者activity的主题中声明<item name=”android:forceDarkAllowed”>true</item> 或者 <item name=”android:forceDarkAllowed”>false</item>;

动态⽅法是调用View.setForceDarkAllowed(true)或者View.setForceDarkAllowed(false),对DecorView设置可以对整个窗口起作⽤。

5.联系我们

有任何问题可以随时联系我们:

商务合作:liushuo3@xiaomi.com

产品问题:zhangyanan10@xiaomi.com

研发问题:darkmode@xiaomi.com