VIVO应用商店APP侵权投诉反通知流程

VIVO应用商店APP侵权投诉反通知流程

侵权投诉及反通知流程图

1.被投诉方向vivo开放平台提交反侵权通知 

平台对投诉方提交的材料审核,满足基本要求后,会在法律规定要求的情形下将投诉内容转送给被投诉方;若被投诉方收到通知后认为并未侵权其合法权益,请按照以下要求提供相关材料通知vivo开放平台

(1)被投诉方信息要求

i.被投诉方主体信息

被投诉方的联络信息,包括姓名、身份证或护照复印件(对自然人)、单位登记证明复印件(对单位)、通信地址、电话号码和电子邮件。

ii.被投诉方要求

被投诉方要求恢复的内容或链接对应的名称和具体网络地址以及截图。

iii.被投诉方保证

被投诉人的反通知书应包含以下保证:被投诉人在本反通知书中的陈述和提供的相关材料皆是充分、真实、和准确的,如本反通知书内容不完全属实,被投诉方自愿承担由此产生的一切法律后果。因被投诉方的反通知书而给vivo公司造成的任何损失,包括但不限于vivo因向权利方或用户赔偿而产生的包括律师费在内的各种损失,此外,被投诉方还应赔偿因此给vivo造成的名誉、商誉等损失。

(2)不构成侵权的初步证明材料

i.被投诉人拥有权利的证明材料

包括但不限于相关有权机构颁发的版权证书、商标权证书、专利权证书、作品首次公开发表或发行日期证明材料、创作手稿、经权威机构签发的作品创作时间戳、作品备案证书等能证明权利人拥有相关权利的有效权属证明。

ii.被投诉人提供的服务不构成侵权的材料

包括但不限于被投诉方提供的服务对权利人的服务构成版权、商标权或专利权等侵权的有效证明材料等。

(3)被投诉方主体资质材料

被投诉方有效主体信息,若为代理,请提供代理主体营业执照及代理授权证明材料。

2.邮件提交申请

撰写邮件,以贵公司企业邮箱发送邮件至devtousu@vivo.com。格式如下:

标题:【侵权投诉反通知】xxx(被投诉方应用名)投诉侵权反通知

邮件正文:被投诉方要求恢复的内容或链接对应的名称和具体网络地址

附件提交:包含营业执照、《vivo开放平台侵权投诉反通知书》盖章扫描件、相关有权机构颁发的版权证书、商标权证书。可提供补充材料如作品首次公开发表或发行日期证明材料、创作手稿、经权威机构签发的作品创作时间戳、作品备案证书等能证明被投诉人拥有相关权利的有效权属证明。若为代理,还请提供代理主体营业执照及代理授权证明材料。

3.平台回复处理结果

(1)vivo开放平台根据收到满足基本要求的反通知后,会转发给投诉方,如投诉方不认可被投诉方的反通知内容,vivo开放平台建议双方自行协商解决。如需通过法律途径,vivo开放平台会配合国家机关的取证工作,争议内容的最终处置以双方协商结论或者国家机关的决定或判决为准。

(2)如被投诉方未能在规定时效内提供有效反通知,或者反通知的内容不能初步证明其提供的内容不侵权,vivo开放平台会有权决定下架或者处理其应用。

(3)被投诉方若为应用,需在3个工作日内提供有效反侵权通知;被投诉方若为游戏,需在5个工作日内提供有效反侵权通知。

(4) 对于同一帐号多次被投诉侵权,且被投诉人不提供有效反通知或者反通知的内容不能初步证明其提供的内容不侵权的,vivo开放平台有权对其帐号进行冻结等处理。

4.注意事项

为了确保被投诉方提供相关材料的真实性、合法性和有效性,被投诉方的反通知书(包括相关证明材料等)及其他相关证明材料,原则上应提供原件,不能提供原件的,应提供有被投诉方签字盖章的复印件,若材料涉外的,应按照法律的规定进行公证转递,并同时提供相应的公证转递材料。

反通知的方式:

将前述全部电子材料以及纸质版本材料扫描后通过电子邮件至vivo指定电子邮箱:devtousu@vivo.com

(1)本流程中的被投诉方的反通知书,均应包括反通知书本身及相关的主体资格证明、权属证明、不构成侵权证明等材料。

(2)若被投诉方已经就投诉内容与权利人存在行政投诉或诉讼的,请在提交反通知时,将相关受理证明及提交行政机关或法院的证据材料以及行政机关或法院的意见或决定一同提交给vivo,这将有利于投诉的处理。

VIVO应用商店APP侵权投诉流程

VIVO应用商店APP侵权投诉流程

鉴于vivo开放平台中第三方提供的服务数量庞大,为了建立良好的市场秩序和保护权利人的合法权益,根据相关法律规定,制定以下处理流程,以尽可能保护权利人的合法权益。

侵权投诉流程

1.收集证据填写材料

若权利人认为第三方在vivo开放平台提供的服务侵犯其合法权益,请按照以下要求提交相关材料,通知vivo。

(1)投诉侵权通知书(《vivo开放平台侵权投诉通知书》点此下载

投诉侵权通知书需详细填写好以下内容:

i.权利人主体信息

提供权利人具体的联络信息,包括姓名、身份证或护照复印件(对自然人)、单位登记证明复印件(对单位)、通信地址、电话号码、传真和电子邮件。

ii.权利人的要求

权利人要求删除或者终止的服务的准确名称及截图

iii.构成侵权的证明材料

权利人提供包括但不限于被投诉方提供的服务构成对权利人的版权、商标权或专利权等侵权的有效证明材料等    

为保证侵权有效进行,请按照以下要求补充对应证明文件

iv.权利人保证

权利人的通知书应包含以下保证:权利人在本通知书中的陈述和提供的相关材料皆是充分、真实、和准确的,如果本通知书内容不完全属实,权利人自愿承担由此产生的一切法律后果,承担和赔偿vivo因根据权利人的通知书而删除或者断开被投诉侵权服务而给vivo公司造成的任何损失,包括但不限于vivo因向被投诉方或用户赔偿而产生的包括律师费在内的各种损失,此外,权利人还应赔偿因此给vivo造成的名誉、商誉等损失。

(2)构成侵权的初步证明材料

该初步证明材料应包括:

i.权利人拥有权利的权属证明材料:

包括但不限于相关有权机构颁发的版权证书、商标权证书、专利权证书、作品首次公开发表或发行日期证明材料、创作手稿、经权威机构签发的作品创作时间戳、作品备案证书等能证明权利人拥有相关权利的有效权属证明;

ii.被投诉方提供的服务构成侵权的证明:

包括但不限于被投诉方提供的服务对权利人的服务构成版权、商标权或专利权等侵权的有效证明材料等。

(3)权利人主体资质材料

权利人有效主体信息,若为代理,请提供代理主体营业执照及代理授权证明材料。

2.侵权投诉提交流程

①工单系统提交反馈

适用类型:app名称侵权、logo/icon侵权、APP卡通形象侵权、页面设计侵权、文学作品侵权、影视作品侵权、音乐作品侵权、ICP/IP地址/域名侵权、游戏元素侵权

提交方式:【管理中心】-【应用与游戏】-【工单系统】,选择“投诉举报类”—“应用或者游戏侵权投诉”,烦请按照要求填写信息,并提供证明材料。

②邮件提交反馈

适用类型:app软件代码侵权、游戏版号侵权等

提交方式:请按以下邮件格式,以贵公司企业邮箱发送邮件申请至devtousu@vivo.com

                标题:【侵权投诉】xxx(应用名)投诉侵权应用

                邮件正文:描述侵权情况以及下架需求。

                附件提交:

                包含营业执照、带有签章的《vivo开放平台侵权投诉通知书》(盖章扫描件),同时,附件需提交以下材料,相关有权机构颁发的版权证书、商标权证书。可提供补充材料如作品首次公开发表或发行日期证明材料、创作手稿、经权威机构签发的作品创作时间戳、作品备案证书等能证明权利人拥有相关权利的有效权属证明。若为代理,还请提供代理主体营业执照及代理授权证明材料。

3.平台回复处理结果

应用侵权投诉处理时效为6个工作日内,游戏侵权投诉处理时效为6~8个工作日内。vivo开放平台会根据权利人提供的相关资料进行核实验证,并回复处理结果,敬请耐心等待。

4.注意事项

(1)本文中的权利人,指拥有版权、商标权等合法权益的原始所有人或经原始所有人合法授权的代理人,包括自然人、法人或其他组织等。

(2)为了确保投诉的真实性和有效性,权利人的书面通知书及相关证明材料,原则上应提供原件(扫描件),不能提供原件的,应提供复印件(在复印件上应有权利人的签章)。

VIVO应用商店APP应用认领教程

VIVO应用商店APP应用认领教程

开发者要求拥有非自己账号下产品上传权限,而该应用为该开发者所属,因此申请包名归属权认领。接入支付的游戏暂不支持认领,如需认领,请联系您的对接商务。

一、应用认领

1. 发起应用认领

顶部导航选择【产品】-【应用分发】-【维护-认领】,点击认领

2. 应用认领步骤

步骤一:请填写需要认领的应用包名

步骤二:按照页面指示操作,上传打上签名的签名包。

       注:“签名空包”是平台提供的空包文件,您需要将下载的空包文件及签名说明传给您的技术人员并将要认领应用的签名打入空包。

步骤三:如需要认领的应用属于特殊行业,需提供认领账号的对应特殊行业资质,可参考《特殊行业资质规范》。

3. 查看认领结果

二、应用认领申诉

若无法通过签名包方式认领,请按照以下方式进行应用认领申诉,发送邮件至open@vivo.com。

标题:【认领申诉】xxx(权利人应用名)应用认领申诉

邮件正文:(请提交以下信息)

认领需求:

无法通过签名认领的原因:

认领应用包名:

认领应用名:

认领账号:

认领主体:

附件提交:

(注:

1.附件需提交认领者上传该应用对应的域名的权属证明,当无法提供域名权属证明时则优先提供营业执照及主流市场上架截图。如未在主流市场上架应用时,则补充提供软著或版权证明及其他市场上架截图。

2. 如需要认领的应用属于特殊行业,需提供认领账号的对应特殊行业资质,可参考《特殊行业资质规范》。)

PS:若被认领者因违规行为导致账号冻结,则旗下应用不予以认领,如被认领者存在未经授权上传他人应用、涉嫌侵权行为,认领者可进行线下申诉。

VIVO应用商店32/64分包上传说明

VIVO应用商店32/64分包上传说明

传包须知

前言

为提升终端用户使用体验,降低功耗影响,同时保证APP在手机上的运行效率和兼容性;vivo应用商店将针对不同机型下发合适的包体,推进国内安卓生态对64位架构的支持。

开发者可以在了解本文档描述的规则后,选择适合您的传包模式。

基本规则

  • 同一应用的包名、应用名称、签名需要保持一致;
  • 双包上传时,两个包体的md5值不能相同;
  • 新版本上架后,上一版本同一架构的安装包将被下架,不同架构的安装包不受影响。

单包上传规则

  • 2022年4月1日后新上传的应用,单包上传入口仅支持上传32/64位兼容包和64位架构包,不支持32位架构包;
  • 2022年10月12日起,在架包更新时,单包上传入口仅支持32/64位兼容包和64位架构包,不支持32位架构包(游戏包体更新则于12月1日起不支持32位架构包)。

注:为避免应用分发受影响,建议开发者尽快完成64位适配。

双包上传规则

  • 首次使用“双包上传”,或从“单包上传”更换为“双包上传”时:必须同时上传32位、64位架构包;
  • 通过双包上传并审核通过后,更新时允许在双包上传入口单独维护某个架构包;
  • 双包同时上传时,两个包体的versioncode需保持一致,且两个包体需要同时审核通过后才可上架。
  • 2022年4月1日后,64位架构包上传入口仅上传64位架构包,不支持兼容包。

操作方法/流程

一、创建流程

1、登录vivo开放平台账号→进入管理中心→应用与游戏→应用与游戏列表→点击【创建应用】;

2、录入基础信息后,点击【完善信息】,进入信息完善页面;

3、在该页面可选择APK上传方式为“单包上传”或“双包上传”;

4、包体上传后,等平台审核通过后则可发布。(注:选择“双包上传”则详见上述双包上传规则)

二、单双包切换限制

1、单双包入口可自由切换,但每次单包切换双包时,均需将32位、64位两个包同时上传,双包审核通过后,方可在双包入口单独维护某个架构包。

三、双包维护说明

1、首次使用“双包上传”,或从“单包上传”更换为“双包上传”时:必须同时上传32位、64位架构包;

2、已使用“双包上传”并审核通过后,更新时可单独维护“32位架构”或“64位架构”包体;

3、单独维护某个架构包体时,仅对新上传包体进行审核,未更新的架构会使用上一版本包体进行分发。

VIVO应用创建/更新流程 VIVO应用商店上架全流程教程

VIVO应用创建/更新流程 VIVO应用商店上架全流程教程

一、应用提交流程

1、创建应用

填写应用包名、名称,上传应用icon,完成应用创建。

2、上传APK包,完善信息

填写应用信息、推广信息、资质信息、联系人信息(如下参见部分截图)。

3、点击提交,等待审核

4、审核通过

审核通过上架后,在vivo应用商店进行展示,可操作版本升级或修改资料。

注:请将APP内置的图标替换为高清图标

为了提升用户安装后的视觉体验,请您将APP内置的图标替换为高清图标,以便在vivo手机安装APP后显示清晰美观的应用图标。请于APK内部图标路径替换相应图标,具体每个文件夹中对应的图标尺寸规范如下(原则是不低于规范尺寸,单位:px):

hdpi文件夹 —— 192×192

xhdpi文件夹 —— 256×256

xxhdpi文件夹 —— 384×384

xxxhdpi文件夹 —— 512×512

注:分辨率适配目录参考

480*800    drawable-hdpi

540*960    drawable-sw360dp-hdpi

720*1280   drawable-sw360dp-xhdpi

1080*1920  drawable-sw360dp-xxhdpi

1440*2560  drawable-sw360dp-xxxhdpi

二、应用更新流程

1、版本升级

版本升级,需上传apk包进行更新。

2、资料更新

若只更新应用的相关资料,可无需上传apk包。

3、点击提交,并等待审核通过

Logcat使用限制

Logcat是Android 系统提供的实时查询系统日志的命令工具,帮助开发者在开发调试阶段定位问题,优化自身功能。然而对logcat日志使用不当也会导致一些问题,如个人信息泄漏/功耗等,而开发者对这些问题则关注的少,为此vivo针对logcat做了部分的定制,以防止出现类似的问题。

1. 打印行数限制

vivo限制了logcat输出日志的频率,限制单个进程每秒只能打印 250行,一秒内超出的日志将会直接丢弃(每一秒的日志都是重新计数),并且打印以下的日志:

该日志是对应的进程中打印的,drop 关键字后面表示了当前丢失了多少行的日志,行数限制不区分日志等级。

应用开发者需要控制自身的日志打印量,不能无休止的刷日志。

2. 敏感数据过滤

任何的Android手机只要打开了开发者选项,都可以通过logcat打印系统日志,若日志中有个人数据打印则非常危险,为此vivo在logcat的输出增加了敏感信息脱敏功能,输出的日志中有敏感的数据(如:手机号,设备ID,经纬度,ip地址,URL等),都会全部转换为星号。

未脱敏日志

脱敏后日志

备注:在新的机型上,系统做了区分处理,debug版本应用打印的内容不会脱敏,release版本的会脱敏,满足开发者的调试需要。

3. Logcat调用限制

APP若在运行时需要读取系统日志定位自身发生的异常,调用 logcat 命令时必须增加 -d 参数,即调用logcat不能阻塞进程一直等待;若应用未增加此参数,则系统会特殊处理,实现同等效果(增加参数-d)。如下格式:logcat -d 

增加 -d 参数后,logcat 仅仅会读取当前系统缓存的日志,然后直接退出,不会一直阻塞住。未增加 -d 参数则会持续输出系统日志,对系统负载影响大,特别是系统日志量多的时候,对功耗的影响会非常的大。

VIVI消息推送OriginOS通知渠道 (Channel)规范

1.1  什么是通知渠道?

通知渠道 (Channel) 是 Android O 引入的新功能,旨在解决以下问题:

  • 应用的通知越来越多,给用户造成明显打扰。
  • 在用户有屏蔽应用的部分通知需求时,只能全局屏蔽这个应用的全部通知,不能屏蔽部分,然后留下对自己有用的。

从 Android8.0(API ≥ 26)开始,Android要求开发者必须为所有的通知分配渠道,不同类的消息走不同的渠道发送。用户可关闭单个渠道的消息通知,不会影响其它渠道的消息推送。以原子笔记(v1.0.3.02) 为例,通知分成了5种渠道,1个渠道分组(Channel Group),如下图:

  • 渠道:实际的通知类别,原子笔记分成了5类通知。
  • 渠道分组:一组渠道,仅用于做分组区分,没有更多的逻辑。
  • 每个通知渠道的权限互相独立,互不影响。例如,原子笔记的“录音”类通知为关闭,“提醒”类通知为优先显示。

1.2  如何适配通知渠道?

1. 在产品发布之前,需由产品和运营提前规划好该应用的通知渠道,如“订单通知”、“评论回复提醒”、“新资讯提醒”等,不同类别的消息走不同渠道发送。规划好的渠道不应该在不同版本频繁变更,以免用户设置发生变化。

2. 合理控制渠道数量,建议在2~7个之间;根据需要可以使用渠道分组来对渠道进行分组归类,方便用户区分识别;避免使用1个渠道发送多种类型的通知。

3. 渠道名称是对用户可见的,因此需要使用方便用户理解的文本,因此,渠道命名应符合通知内容特征,例如,发送快递接收信息的专用渠道,命名为“快递信息”,便于用户理解,同时避免名字重复。

4. 渠道名称需要做多语言适配(默认直接显示名称,如果要跟随系统语言的变化而变化,要提翻译),手机重启后才会切换其他语言。

1.3  通知渠道的优先级如何定义

渠道的优先级会影响通知的悬浮、响铃和振动。为了平衡业务发展的需求和通知栏的整体体验,通知栏对渠道优先级的定义做出如下规范:

优先级铃声振动在通知栏显示悬浮显示锁屏显示使用原则
max(以下简称“5”)允许允许允许允许允许(上线中)(用户设置不了)关系到用户生命和财产安全、需要用户立即作出反应否则会造成设备不可用或个人生命/财产受到威胁的通知,如台风预警、暴雨预告、流量预警等,通知栏会视应用使用情况决定是否收紧该能力
high(以下简称“4”)允许允许允许允许允许(上线中)用户短时间内必须了解或采取行动的通知,如信息、电话、账户变动等
default(以下简称“3”)允许允许允许用户在方便时需要尽早查看的通知,如好友申请等;
low(以下简称“2”)允许跟用户相关但可以等待的,或无需用户处理的通知,如关注的作者更新、有更新的内容等;
min(以下简称“1”)允许与用户完全无关的非必要信息,如促销内容、活动信息等;

具体到业务场景,本规范根据消息的类型,给出了每种消息优先级的可设置范围,不在以下范围内的,由各项目组与通知规范小组共同决策:

分类优先级上限类别举例
系统消息系统通知验证码4验证码信息
软件更新3APP版本更新提示信息
邮件3邮件的发送、接收状态信息
短信4短信,彩信等信息
语音通话4语音通话相关的信息
软件状态5与应用的运行、性能、权限、流量等通知信息
常驻信息4音乐类、工具类、天气类等常驻通知栏信息
用户订阅社交动态2用户之间的社交互动提醒,如:被赞、被@、评论、留言、关注、转发
非社交动态2非社交属性的且与用户相关的实时信息更新
订阅4用户主动订阅关注的内容更新提醒,如:你关注的xxxx更新
日程4用户设置的行程通知,提醒
物流4物流节点信息,包括收发货,派送,签收,取件通知等
订单4生成订单相关信息  如:下单成功  卖家收到新订单 订单详情
特殊关注4基于普通的动态、订阅消息之上的特别关注内容
待办4用户在app内设置的提醒信息
财务4包括收付款,红包,涉及金额、账单、交易等信息
出行4用户使用相关app时的导航、路线相关提醒
营销消息新闻资讯阅读推荐2非用户主动订阅,APP向用户推送的文字内容。如:微博、资讯、新闻、点评、小说、公告
音频推荐2非用户主动订阅,APP向用户推送的视频、音频、直播、
陌生人推荐2大V、主播、异性、可能认识的人等
理财2金融理财产品推荐,如:投资、贷款、股票、基金、贵金属。
天气2天气相关的推送
产品推荐广告促销2商品推广、宣传,或者折扣、红包、领劵优惠信息
产品推荐2所有商品、商家、店铺推荐
活动推送运营活动2各类APP内的活动、小游戏提醒,如:抽奖、积分、签到、任务、分享、偷菜、领金币、限时折扣、红包、返现、优惠卷
邀请2APP基于自身产品功能对用户发起的各类邀请,如:邀请用户点评、答题、发布视频、撰文等
IM消息即时聊天信息即时消息4用户与用户或商家进行交流时产生的文字推送
消息提醒4不展示具体消息内容的IM消息提醒,如:你收到一条新消息(伪装成消息提醒的内容推荐类不算)
其他消息/不在以上分类的,由各业务方与SQA共同决策

“社交动态”类需满足“交互要求”:1. 弹窗说明将收到该类别的PUSH通知。2. 用户可选择“同意”或“取消”,用户同意后可加入“系统消息”分类。

“订阅更新”类需满足“交互要求”:1. 弹窗说明将收到该类别的PUSH通知。2. 告知用户关闭通知的完整路径,可加入“系统消息”分类。

FAQ

1、一定要适配通知渠道吗?

取决于你的应用的API:

  • API ≥ 26(Android 8.0):必须适配,而且必须给每条通知指定一个渠道,否则无法发出通知
  • API ≤ 25(Android 7.1):可以不适配。在8.0及以上的设备,通知也能正常发出

2、一定要有渠道分组吗?

不是必须的。没必要强行设置渠道分组,应该视自己的业务需求而定。

3、我的通知是通过Vpush发的,我也能自己设置渠道吗?

目前,Vpush只有固定的渠道,不支持应用自定义Vpush渠道哦~

4、适配渠道后,在 Android 8.0 以前的设备会怎么表现

Android 8.0 以前的设备,会完全无视这个功能,因此不会带来任何兼容性问题。

VIVO手机OriginOS通知样式规范

1.1 前言

  • 规范中的说明和图示仅为示意,通知能包含的行数及每行字数,视系统当前所使用的字体和语言、当前设备的分辨率、全/半角符号、字/行间距等而定;通知的按钮位置、图片大小等会跟随OS整体视觉风格进行优化或调整,恕不另行通知,建议实际开发时在真机上调试和验证。
  • 通知自行设计和开发,系统仅提供基础能力,具体使用由开发者决定需要开发者。
  • OS版本覆盖需要较长的时间周期,且Android的接口存在版本差异,请注意通知在各个目标机型上的样式表现,做好兼容。
  • 规范中的说明和图示仅适用于OriginOS中国大陆版本。
  • 图示文案仅为示例,不代表真实的应用通知内容。

1.2 概述

1.2.1  通知的使用原则

1. 适合发送通知的事件:

(1)  对时间敏感的(例如日程通知)。

(2)  与他人有关的(例如新信息通知)。

(3)  涉及用户财产或数据安全的(例如支付宝转账通知)。

(4)  通知不应该成为主要的与用户沟通的渠道,因为频繁的中断可能会造成影响。

2. 不适合发送通知的事件:

(1)  与用户没有直接关系或时间不敏感的(例如朋友圈的更新)。

(2)  已经显示在屏幕上的(此时应在应用界面中告知用户,例如用户正在聊天界面聊天时,有新的消息,应直接显示在界面上,无需发通知)。

(3)  一些应用可以自行处理的技术细节(例如保存,同步或升级的过程)。

(4)  应用可以自行快速恢复的错误信息,不需要用户参与操作。

(5)  没有提供直接价值,仅仅为了让用户返回到应用程序的消息。

(6)  对用户当前操作的反馈,最好使用弹出框或Toast提示,而非通知。

1.3 通知提醒方式

通知发出后,可以通过以下方式提醒用户,各应用在定义通知提醒方式时,需要充分考虑用户使用场景及可行性:

1. 通知栏显示通知(默认,如果同一应用的通知条数达到2条,会分组)。

2. 状态栏图标(受开关、channel优先级、应用类型等控制)。

3. 悬浮通知(受开关和channel优先级控制):对用户设定的在顶部预览显示的通知,会在通知发出时在屏幕上方以悬浮框形式显示通知内容,并在5s后自动消失,可上滑收起,横滑删除。

4. 锁屏通知(受应用“锁屏通知”开关控制):对用户允许的、在锁屏之后收到的通知,会在锁屏状态下显示。

5. 另外,部分通知还可发出声音或振动(受channel优先级和开关控制)。

6. 在有闪烁LED的设备上,收到部分通知时还可闪烁LED。

1.4 核心的通知组件

1.标题区域

2.内容区域

3.动作区域

1.5 推荐使用的通知样式

1.5.1 安卓原生的标准样式 (Standard template)

使用说明:最常见的样式,也是通知的默认样式,用于传递信息。

设计要点:

1. 应用图标(必选):使用应用图标而非功能图标,因为该图标不能承载具体的功能。

2. 应用名称(必选):与发通知的应用名称保持一致。

3. 辅助信息(可选):可显示应用中的通知来源,如登陆了多个账户的电子邮件app,可在此处显示该通知所指向的账户。

4. 时间(必选):若非必要,请保证时间显示与通知实际发送时间一致。

5. 通知标题(可选,推荐显示):避免与应用名称重复。

6. 通知详情(可选):避免重复标题中的内容。

7. 附图(可选):应与通知内容有关;避免与应用图标重复。

注意:

1. 建议同时具备通知标题和通知详情,若因内容所限,请保证显示通知详情。

2. 目前vpush仅支持本样式,不支持配置“附图”。

1.5.2 安卓原生的大文本样式 (Big text template)

使用说明:用以展示更多的文本内容。

使用条件:需同时满足API≥16和Android ≥4.1。

设计要点:

• 基本视图

用户可点击右上角箭头按钮展开至扩展视图。

• 扩展视图

版本差异:

1、在OriginOS 1.0以前的版本中,不显示右上角箭头按钮、基本视图显示2行通知详情内容,用户需要双指下滑展开至扩展视图。

2、从OriginOS 1.0开始,显示右上角箭头按钮、基本视图显示1行通知详情内容,用户通过点击箭头按钮展开至扩展视图。

1.5.3 安卓原生的媒体样式 (Media template)

使用说明:为音乐、广播等媒体播放设计的通知样式,可以展示媒体内容,并且可以对媒体内容进行控制。

使用条件:需同时满足API≥21 和Android ≥5.0。

设计要点:

可设置on going属性,让用户无法删除该条通知。

• 基本视图

• 扩展视图

版本差异:

1. 在OriginOS 1.0以前的版本中,不显示右上角箭头按钮,用户需要双指下滑展开至扩展视图。

2. 从OriginOS 1.0开始,显示右上角箭头按钮,用户通过点击箭头按钮展开至扩展视图。

1.5.4 进度条样式 (Progress template)

使用说明:用以表示上传/下载进度。

设计要点:

• 有两种类型:无明确进度和有明确进度,有明确进度的可以预估结束的时间;(不强制使用,模块自行评估,建议短时间可结束的场景使用百分比,长时间可结束的场景使用预估结束时间。)

• 可设置ongoing属性,让用户无法在一键清空通知栏时删除该条通知(用户单独滑动本条通知时,仍可响应删除操作)。

有明确进度

• 基本视图

1. 预估结束时间(可选):有明确进度的可以预估结束的时间

• 扩展视图

无明确进度

• 基本视图

• 扩展视图

1.6 不宜使用的通知样式

vivo不推荐使用本规范之外的通知样式,尤其是涉及到虚假内容、诱导、欺骗一类的。我们会在此处不定期公开不规范的通知使用样式,请各位开发者予以关注:

近期不规范样式(2022年2月22日更新):

1、通知中包含分割线,使一条通知视觉呈现上如同两条通知。

2、通知中包含异型大按钮,且按钮点击无效。

3、伪装为来源于其他应用的通知,甚至伪装为系统通知。

1.7 为通知添加其他属性

1.7.1 添加附图 (Large icon)

使用说明:通知默认没有 large icon,增加 large icon可丰富通知的内容,尤其适用于 IM / 邮件等场景。

使用条件:需同时满足API≥11 和Android ≥3.0。

设计要点:

1.附图

• 应与通知内容有关,避免与应用图标重复。

• 请不要滥用 large icon,我们后续会视情况而定是否收紧这个能力。

1.7.2 添加按钮 (Action)

使用说明:Actions 指的是通知的快捷按钮。

使用条件:需同时满足API≥16 和Android ≥4.1。

设计要点:

添加了按钮的样式如下(除媒体通知外,最多可以设置3个 Actions)。

注意:

• 通知默认不会有按钮。

• 任何通知样式都可以添加按钮,开发者可以自由搭配。

• 按钮只能在双指下滑展开至大视图时显示,系统应用默认显示按钮是因为做了特殊处理(历史遗留问题)。

1.7.3 ongoing属性

用于表示“会持续一段时间的、正在进行的任务,且提供了用户暂停/停止/关闭该任务所必须的操作选项”的通知。

 点击跳转落地页,落地页上可关闭任务 不是表示进行中的任务,点击不能快速关闭

1.7.4 通知分组

自Android 7版本开始增加了通知的分组功能,将同一应用的多个通知合并为可展开的层次结构。

未分组
分组未展开
分组已展开

根据触发方式,分组可分为自动分组和自定义分组:

1、自动分组:如果同一应用发出2条或更多条通知且未指定分组,则系统会自动将这些通知分为一组。

2、自定义分组:应用在发送通知时,发送指定分组id的头通知或子通知,则指定分组的通知将被分为一组,一组中有一个头通知和多个子通知。

注意:

1、请开发者不要滥用自定义分组,包括但不仅限于:一条通知一个分组、同一应用有两个以上的自定义分组。

2、在某条通知与其他通知具有区别较大的功能、且样式与控件与常规通知相差较大时,可以使用自定义分组;运营消息不可以使用自定义分组。

3、同一应用,使用自定义分组的头通知不可以超过2条,即同一应用最多只能有自动分组+2个自定义分组。

1.8 管控及惩罚规则

vivo会定期巡查通知样式,发现有不合理使用通知的行为时,会给予邮件警告,必要时将会采取限制。

1.9 FAQ

1、怎么判断API版本?

各 Android 平台版本所支持的 API 级别请查看文档:https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels

2、API和Android需要同时判断吗?

是的,取API和Android中的较小值,因此,我们强烈建议业务方积极升级API,享受安卓新特性(API关系到的不止是通知,可能也会影响业务中的其他功能,详情请咨询业务中的安卓开发工程师~)

3、章节1.5之外的通知样式,算是符合规范的吗?

我们强烈建议:开发者在通用样式能满足需求的情况下不要去“自创”通知样式;如果当前的通知样式的确不能满足需求,请基于用户体验原则考虑通知是否为最好的呈现方式。另外,我们会使用“巡查”的方式进行监管,严重的话会收紧该应用在vivo手机上的通知能力。

4、什么影响通知栏的排序?应用自己能定义吗?

OriginOS按时间排序;OriginOS之前使用安卓原生的排序规则。

更多FAQ后续补充。

VIVO【桌面图标长按快捷方式】接入指导

一、概述

1、Shortcuts是android7.0的新特性,能配置shortcuts的activity必须符合action是android.intent.action.MAIN且category是android.intent.category.LAUNCHER,;Shortcuts实现方式分为静态和动态注册。

2、vivo桌面v9.3.0以后的版本已经开放三方应用自定义快捷方式能力。

二、接入后效果

1、接入后展示效果

自定义功能由应用自身定义,最多显示4个,如上图中“微博”自定义的“热门微博”、“扫一扫”、“发微博”快捷功能。

系统提供默认的功能,由系统确定,不支持自定义,如上图中系统默认的“卸载”、“应用信息”、“编辑桌面”功能。

2、具体实现

参数说明:

shortcutId:快捷方式的唯一标示,必传属性

shortcutLongLabel:桌面优先显示此字段配置的名字

shortcutShortLabel:没有shortcutLongLabel字段或者为空时展示此字段

shortcutDisabledMessage:禁止使用快捷方式的显示字段

icon:桌面显示快捷方式需要的图标

action:通常是android.intent.action.VIEW,也可以根据自己需要更改

targetClass:对应跳转activity的全路径名

targetPackage:对应跳转activity的包名

静态注册方式:

在res文件夹下创建xml文件夹,其中放入shortcuts.xml文件,同时在AndroidManifest.xml中action符合上述2个要求的activity标签中添加<meta-data android:name=”android.app.shortcuts” android:resource=”@xml/shortcuts”/>即可,具体shortcuts.xml和AndroidManifest中的配置如下图:

特别注意其中的shortcutLongLabel,shortcutShortLabel,shortcutDisabledMessage中的string必须写在string.xml中,不然无编译

动态注册方式:

动态注册相对于静态注册灵活性更高,只要是在符合上述2个条件的Activity中可以根据需求进行添加和移除,此添加和移除原生代码是在主线程中执行,实测可以放到子线程,一般正常添加快捷方式都是在onCreate()中第一次进入直接添加即可,重复添加和多次删除同一个ID,测试无异常;

写法如下图:

VIVO【桌面图标角标】适配说明

一、概述

桌面图标角标,接入完成后需要用户手动开启,开启完成后收到新消息时,在已安装的应用桌面图标右上角显示“数字角标”。

二、接入后效果

1、接入后展示效果

应用有新消息需要告知用户时,在桌面显示数字角标。

显示效果同上图中的“应用商店”、“vivo官网”右上角数字角标。

2、“桌面图标角标”默认关闭

接入成功后,“桌面图标角标”默认关闭,需要用户手动开启。

开启路径:“设置”-“通知与状态栏”-“应用通知管理”-应用名称-“桌面图标角标”。

未成功接入“桌面图标角标”的应用,无“桌面图标角标”选项。

备注:视OS版本差异,“桌面图标角标”名称可能为“应用图标标记”或“桌面角标”。

3、具体实现

a. 添加权限:

<uses-permission android:name=”com.vivo.notification.permission.BADGE_ICON” />

b. 应用在需要显示桌面角标的场景,通过广播将信息发送给vivoLauncher:

广播参数:

action:launcher.action.CHANGE_APPLICATION_NOTIFICATION_NUM

packageName:应用包名

className:主类名

notificationNum:未读消息数目

简单示例:

Intent intent = new Intent();

int missedCalls = 10;

intent.setAction(“launcher.action.CHANGE_APPLICATION_NOTIFICATION_NUM”);

intent.putExtra(“packageName”, “com.android.xxxx”);

intent.putExtra(“className”, “com.android.xxxx.Mainxxxx”);

intent.putExtra(“notificationNum”, missedCalls); 

intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);

sendBroadcast(intent);

注意: 

在8.0上,还需要给Intent加上下面的flag

Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND

VIVO手写笔SDK接入指南

业务介绍

vivo手写笔SDK提供了在vivo Pad上使用手写笔进行笔形选择、笔形绘制擦除,笔迹预测等能力,主要包含画笔工具栏和画布两个组成部分,通过SDK的简单集成,即可为应用提供多种书写绘制能力。

功能介绍

1.画笔工具栏
画笔工具栏包含以下内容
画笔工具:铅笔,钢笔,水彩笔,马克笔四种画笔工具,5档粗细调节,同时可通过调色盘进行颜色修改
橡皮工具:包含像素橡皮擦和对象橡皮擦两种橡皮擦类型
套索工具:包含框选,移动,复制,剪切,粘贴,删除的功能
另外还提供了撤销、重做及仅手写笔涂鸦开关功能

2.画布
画布提供了一块区域,用于笔迹的绘制和擦除,同时集成了笔迹预测功能

3.笔迹预测
笔迹预测功能通过笔迹的报点,对笔迹绘制的位置进行预测,减少了笔迹绘制的延迟,优化了书写体验

接入指南

一、SDK接入

开发环境

Android Studio 3.4及以上版本
Gradle 版本 3.4及以上
minSdkVersion 28
targetSdkVersion 30
compileSdkVersion 30

接入流程

vivo手写笔SDK目前仅支持在vivo Pad上使用,开发者需要在本地集成aar以及声明SDK所需权限,即可开始接入SDK进行应用开发,接入步骤如下:
1.首先在本地lib添加penengine.aar文件。

2.在build.gradle文件中添加对应依赖

repositories { 
    flatDir { 
        dirs 'libs' 
    } 
} 
       
dependencies { 
    implementation(name:'penengine', ext:'aar')
}  

3.在AndroidManifest.xml文件中添加SDK所需权限,添加之后便可开始使用SDK进行开发了

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  
<uses-permission android:name="android.permission.INTERNET" /> 
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

4.在proguard-rules.pro文件中配置混淆

-keeppackagenames com.vivo.penengine.impl
-keep class com.vivo.penengine.impl.**.*

二、简单应用介绍

下面介绍sdk最基础的使用方式,在应用页面添加画布和画笔工具栏,添加之后便可使用画笔选择,笔迹绘制和擦除等功能,步骤如下

1.在应用页面对应的layout文件添加VivoCanvasViewImpl

layout

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
 
    <com.vivo.penengine.impl.VivoCanvasViewImpl
        android:id="@+id/canvasView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="100dp"
        android:layout_marginBottom="100dp"
        />
 
</FrameLayout>

2.初始化VivoCanvasViewImpl和VivoToolPickerImpl,并将两者进行绑定

MainActivity

public class MainActivity extends Activity {
 
    private static final String TAG = "MainActivity";
    private VivoCanvasViewImpl mCanvasView;
    private VivoToolPickerImpl mToolPicker;
    private boolean mIsEngineAvailable;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mIsEngineAvailable = PenEngineManager.isEngineAvailable(this);
        if (mIsEngineAvailable) {
            return;
        }
        setContentView(R.layout.activity_main);
        mCanvasView = findViewById(R.id.canvasView);
        mToolPicker = new VivoToolPickerImpl(this);
        mToolPicker.bindCanvasView(mCanvasView);
        mToolPicker.show();
    }
 
    @Override
    protected void onResume() {
        super.onResume();
        if (!mIsEngineAvailable) {
            return;
        }
        if (mToolPicker != null){
            mToolPicker.onResume();
        }
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (!mIsEngineAvailable) {
            return;
        }
        if (mToolPicker != null) {
            mToolPicker.onDestroy();
        }
        if (mCanvasView != null) {
            mCanvasView.release();
        }
    }
}

功能介绍

一、画布回调和笔形选择

1.可通过注册回调监听接口来获取画布事件,包括涂鸦内容修改和笔形切换。

回调接口中

onStepChanged 在每完成一笔绘制之后进行回调,其中StepType表示该笔绘制的类型,分为 NEW(新增笔迹) UNDO (撤销),REDO(重做),CLEAR(清空撤销重做栈)

onSetPen 接口会在笔形切换之后进行回调,可用该接口监听笔形的变化

onPathLoaded 接口在画布初始化笔迹加载完成后进行回调

mCanvasView.registerOnCanvasListener(new VivoCanvasViewImpl.OnCanvasListener() {
            @Override
            public void onStepChanged(StepType stepType) {
            }
 
            @Override
            public void onSetPen(Pen pen) {
            }
 
            @Override
            public void onPathLoaded() {
            }
        });

2.默认工具栏提供的笔形类型包括 钢笔、铅笔、水彩笔、马克笔、橡皮、套索,可在初始化toolPicker后通过禁用来实现自定义的笔形组合

Set<PenType> penTypes = new HashSet<>();
penTypes.add(PenType.PENCIL);
mToolPicker.disablePens(penTypes);

3.可在初始化时通过setPen接口设置默认笔形,依次设置笔形类型,透明度(0 ~ 100),颜色,粗细等级(0 ~ 4)

mToolPicker.setPen(Pen.WATERCOLOR_PEN, 60, Color.BLACK, SizeLevel.LEVEL_0);

二、笔迹预测

笔迹预测功能,即通过当前的轨迹,预测出下一个可能的触摸点,用于减少画笔轨迹绘制的延迟。

逻辑如下:
1.通过获取轨迹的点集
2.将点集传入算法中,通过算法计算得到预测点的数据
3.算法返回结果即为根据轨迹点集所得的预测点

以下使用简单demo进行展示,使用一笔完整的绘制轨迹经过的点作为点集,计算出预测点

1.在onTouchEvent中,使用requestUnbufferedDispatch() 方法获取无缓冲的MotionEventsl流
2.对每个ACTION_MOVE和ACTION_UP的报点,获取对应的历史点,得到完整的轨迹点集
3.将点集送入算法引擎中,获取对应的预测点,此后可对此预测点进行渲染
4.view销毁时,需要释放算法引擎

public boolean onTouchEvent(MotionEvent event) {
    // step 1: Request unbuffered dispatch of the given stream of MotionEvents to this View.
    requestUnbufferedDispatch(event);
 
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        mTouchPointDataList.add(ConvertUtil.convertMotionEventToTouchPoint(event));
        ......
    } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
        handleActionMove(event);
    } else if (event.getAction() == MotionEvent.ACTION_UP) {
        handleActionMove(event);
        ......
    }
    invalidate();
    return true;
}
 
private void handleActionMove(MotionEvent event) {
    // step 2: Get the history point set of the current motion event.
    for (int i = 0; i < event.getHistorySize() - 1; i++) {
        MotionEvent historyEvent = MotionEvent.obtain(event.getDownTime(),
                event.getHistoricalEventTime(i), event.getAction(),
                event.getHistoricalX(i), event.getHistoricalY(i), 0);
        mTouchPointDataList.add(ConvertUtil.convertMotionEventToTouchPoint(historyEvent));
    }
    mTouchPointDataList.add(ConvertUtil.convertMotionEventToTouchPoint(event));
 
    // step 3: Use algorithm to calculate prediction point.
    if (mVivoAlgorithmManager == null) {
        mVivoAlgorithmManager = new VivoAlgorithmManagerImpl(getContext());
    }
    if (mVivoAlgorithmManager.isFeatureEnable()) {
        mEstimatePoint = mVivoAlgorithmManager.computeEstimatePoint(mTouchPointDataList);
    }
    ......
}
 
public void release() {
    // step 4: Release algorithm engine.
    if (mVivoAlgorithmManager != null) {
        mVivoAlgorithmManager.release();
    }
    ......
}
public static TouchPointData convertMotionEventToTouchPoint(MotionEvent event) {
    if (event == null) {
        return null;
    }
    TouchPointData touchPoint = new TouchPointData(event.getX(), event.getY());
    touchPoint.setOrientation(event.getOrientation());
    touchPoint.setPressure(event.getPressure());
    touchPoint.setTime(event.getEventTime());
    return touchPoint;
}

三、手写笔按键切换

手写笔按键切换功能,需要在按住按键落笔时切换笔形为橡皮擦,抬笔后切换笔形为原来的笔形

处理逻辑如下:

收到ACTION_DOWN事件后进行判断,事件的buttonState是否为BUTTON_STYLUS_PRIMARY,若是则代表按键事件触发,此时判断当前是否为橡皮擦,如果不是则切换为橡皮擦,同时记录状态;

抬笔后对状态进行判断,如果笔形被切换为橡皮擦,则切换为原来笔形。

@Override
@Override
public boolean onTouchEvent(MotionEvent event) {
    if (event.getPointerId(event.getActionIndex()) == 0) {
        if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_POINTER_DOWN) {
            if (event.getButtonState() == MotionEvent.BUTTON_STYLUS_PRIMARY) {
                if (mErasePen != null && mCurrentPen.getType() != Pen.PenType.POINT_ERASE && mCurrentPen.getType() != Pen.PenType.STROKE_ERASE) {
                    //TODO switch to eraser
                    mIsDownChange = true;
                }
            } else {
                mIsDownChange = false;
            }
        } else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_POINTER_UP) {
            if (mIsDownChange) {
                //TODO switch to last pen
            }
        }
    }
    return super.onTouchEvent(event);
}

VIVO高长宽比设备应用适配指导意见

一、 适配背景

目前vivo有意向采用1080×2400/1080×2460/1080×2520等更高长宽比分辨率,经测试发现有些游戏和应用存在异常边、花边、裁切、黑边过大等情况,为避免以上情况,需要对这些游戏和应用进行相应适配调整。建议游戏或应用适配时考虑更大屏幕比例的情况,提高不同屏幕比例的兼容性;同时建议不要固定屏幕比例来判断适配,这样会导致游戏或应用只能兼容固定屏幕比例。

二、调整意见

1、游戏logo或背景出现异常边

描述:logo或背景两侧,出现黑边、蓝边、白边等异常边。

原因:logo或背景宽高比例过小。

措施:建议采用第一条。

(1)提供宽高比例更大的logo或背景;

(2)优先匹配屏幕高度,宽度可稍微裁切(不影响用户体验);

(3)比例相差不大的情况下,可适度拉伸(不影响用户体验)。

2、游戏出现异常边

描述:游戏两侧出现黑边、蓝边、白边等异常边。

原因:游戏固定了宽高或屏幕比例。

措施:请参见第6条获取屏幕宽高,确保背景和游戏能充分铺满屏幕。

游戏各种按钮、状态栏等需要根据屏幕宽高,调整对屏幕相对位置,确保显示完整、布局美观。

3、游戏两边出现花边

描述:游戏两侧出现杂色边,后面内容覆盖了之前内容。

原因:游戏固定了宽高或屏幕比例,后续某帧超过该比例,造成花边。

措施:请参见第6条获取屏幕宽高,确保背景和游戏能充分铺满屏幕。

游戏各种按钮、状态栏等需要根据屏幕宽高,调整对屏幕相对位置,确保显示完整、布局美观。 

4、视频两边出现裁切

描述:播放视频时,视频宽度上发生裁切,造成显示内容变少。

原因:视频优先匹配高度,造成宽度上内容裁切。

措施:请参见第6条获取屏幕宽高,将优先适配高度,改为优先适配宽度,保证视频内容的完整性,并合理安排其他布局。

5、应用底部黑边过大

描述:应用底部黑边过大,底部文字、对话框等位置偏上,造成显示不美观。

原因:屏幕比例变大时,底部黑边相应变大,但是底部文字、对话框等未按屏幕高度动态调整,而是固定了与视频的相对位置。

措施:请参见第6条获取屏幕宽高,然后根据屏幕高度,向下动态调整文字或对话框的相对位置,使显示更美观。

6、获取屏幕宽高方法

(1)获取屏幕物理宽高

建议游戏或满屏应用使用,获取屏幕真实物理宽高。

    WindowManager manager = this.getWindowManager();
    Point outSize = new Point();

Display display = manager.getDefaultDisplay();
    display.getRealSize(outSize);
    int width = outSize.x; //屏幕物理宽度
    int height = outSize.y; //屏幕物理高度

(2)获取应用宽高

建议非满屏应用使用,在某些情况下,应用宽高和屏幕物理宽高有差异。

如在有导航栏、刘海、非透明状态栏等情况下,可以使用以下方法获取实际应用宽高。

WindowManager manager = this.getWindowManager();

DisplayMetrics outMetrics = new DisplayMetrics();

Display display = manager.getDefaultDisplay();

display.getMetrics(outMetrics);

int width = outMetrics.widthPixels; //应用宽度

int height = outMetrics.heightPixels; //应用高度

三、模拟验证

使用分辨率为1080的屏幕,比例任意的手机:

1、进入开发者选项,打开USB调试;

2、电脑进入cmd命令行,输入”adb devices”,确保能连上手机;

3、Cmd命令行输入:”adb shell wm size 1080×2400/1080×2460/1080×2520”

模拟分辨率为1080×2400/1080×2460/1080×2520的情况;

4、测试应用或游戏,是否出现黑边、花边、裁切等情况;

5、测试完毕,cmd命令行输入:”adb shell wm size reset”,恢复屏幕分辨率。

音频类AudioManager接口使用规范

一、setRingerMode错误使用会使得与铃声相关音频全部被静音

问题描述:

某些APP会调用该接口实现静音铃声播放或将手机切换为振铃模式的功能,用户在使用此类APP进行上述两种操作中任意一种后,会导致手机系统中所有与铃声相关音频都会被静音,用户接收不到铃声、手机提示音等,严重影响用户使用体验。

建议:

非声音设置类型的应用不调用setRingerMode接口,交由手机系统控制。

二、setSpeakerphoneOn可能会导致语音和通话被强制从扬声器输出

问题描述:

语音社交类APP为满足用户语音外放的需求,会调用此接口,但若未在播放结束后,将该接口参数设置为false,会导致之后语音和通话音频流会一直被强制从扬声器输出,影响用户使用其他APP。

建议:

一旦APP调用了setSpeakerphoneOn(此时参数为true),在播放音频流结束后,需要设置setSpeakerphoneOn参数为false,把音频通路还原。

三、setStreamMute这个接口即将废除,建议使用其他接口

问题描述:

该接口会使得用户在使用APP时,静音了某个音频流后,手机中已安装的其他APP,其相应类型的音频流都会被设置为静音。

建议:

setStreamMute接口即将被废除,建议开发者使用相应音频实例对应的调节音量参数来实现静音APP自身播放的音频流的功能,例如:AudioTrack实例中的setVolume;MediaPlayer实例中的setVolume;mPlayer.setVolume(0)。

四、setStreamVolume设置错误的音频流类型,会使用户无法正常调节手机音量

问题描述:

在不同的使用场景,用户会有调节不同音频流音量的需求,APP可以调用接口setStreamVolume(int streamType, int index, int flags)来满足这一需求,但往往开发者并没有将streamType参数与当前场景正在播放的音频流类型设置一致,并且该接口可以截获系统的音量按键,导致用户使用APP时,按音量键调节音量后,当前的声音音量大小却没有变化。

建议:

(1)当前vivo手机系统已经可以实现智能识别用户调节音量的场景,并且精准定位用户需要调节的音频流类型,因此建议音量按键交由手机系统来控制。

(2)如若开发者需要调节特定音量,要使用setStreamVolume接口,请设置好符合用户需求场景的正确音频流类型。

五、如若错误使用setMode,会导致音频输出到错误的设备上

问题描述:

当前很多APP(常见于社交类)使用这个接口来设置手机通话模式,改变手机声音的输出设备。用户在使用APP时,使用“语音通话模式”(AudioManager.MODE_IN_COMMUNICATION)后,手机会默认从听筒输出音频,导致用户听不见手机提示音、来电铃声等音频。

建议:

开发过程中,需要注意在播放音频流结束后,调用接口setMode (AudioManager. MODE_NORMAL),把手机模式还原为正常模式。

六、setBluetoothScoOn使用不当,会导致蓝牙无声音输出

问题描述:

当用户需要声音从蓝牙输出时,某些APP会使用蓝牙Sco-link通路来传送语音数据到蓝牙耳机。如果开发者没有使用startBluetoothSco成功建立 sco-link连接通路后就直接调用setBluetoothScoOn(true

),会使得蓝牙耳机无声音输出。

建议:

开发者应使用startBluetoothSco和stopBluetoothSco接口来建立/断开sco-link连接通路,在建立sco-link连接后,有场景需要将音频从蓝牙切换到手机上时候,调用setBluetoothScoOn(false),再次将音频切换回蓝牙播放时,使用setBluetoothScoOn(true)。

七、setMicrophoneMute没有配对使用,可能导致手机录音或语音发送功能失效

问题描述:

某些社交/直播类APP会调用该接口实现语音静音发送的功能,但有些开发者在APP退出的时候没有设置接口为unmute,导致后续其他APP使用手机麦克风录制出来的音频都是静音状态的,影响用户的使用。

建议:

由于这个接口直接作用在手机的底层麦克风数据通路,影响范围很大,不建议使用这个接口来实现语音静音发送的需求。开发者可以通过将APP获取到的音频数据直接替换为值=0(静音)的方式,或采取把录制上来的数据直接丢弃不处理的方式来实现这种需求。

vivo X21屏下指纹适配指南

vivo X21屏下指纹适配指南,点击此处可下载开发过程中所需的全部代码。文档具体内容如下:

一、FingerprintInsets

public final class FingerprintInsets

extends Object

java.lang.Object    

↳  com.vivo.fingerprint.ud.FingerprintInsets

该类描述屏幕指纹相关信息,例如指纹图标位置,指纹图标显示状态等。该类会连接屏幕指纹服务获取状态信息,应用只有在 isReady() 返回 true 的时候才能通过该类的其它接口获取相关信息。使用本接口,需要应用声明com.vivo.fingerprint.permission.READ_STATE 权限,调用 create 方法后,应用不再使用该系列接口后应该调用 destroy 方法释放相关的资源,避免造成可能发生的内存泄露。

该类的方法都不是线程安全的,这就意味着所有方法都应该在同一线程中调用,create 方法会创建一个 Handler,因此该类的所有方法应该在主线程或者有 Looper 的线程中调用。

使用该类的 minSdkVersion 应该不小于 11(Android 3.0.x)。该类能够在 vivo 手机及其它 sdk version 不小于 11 的非 vivo 设备上使用。本类依赖 android-support-v4。

ICON_INVISIBLE

int ICON_INVISIBLE = 0

图标状态标记,表示指纹图标不可见。

值:0

ICON_VISIBLE

int ICON_VISIBLE = 1

图标状态标记,表示指纹图标可见。

static void setDebugEnable(boolean enable)

打开或者关闭内部日志

enable: 是否打开日志

static FingerprintInsets

create(Context context,FingerprintInsetsListener listener)

该类可以创建对象实例,如果多次调用该方法并且没有调用 destroy,则返回的是同一个对象,如果创建失败,则返回空,这种情况一般是在非 vivo 手机上调用该接口,如果创建成功,该方法会产生一个对 context 的弱引用对象,listener 可为 null。

context:组件上下文

listener:Insets 内部事件监听器

返回值:创建成功返回非空置,失败则为空

boolean isReady()

指纹相关状态是否初始化完毕,只有在初始化完毕后,才能调用其它接口获取正确的参数。

void destroy()

清理当前实例对象。

boolean hasUnderDisplayFingerprint()

当前机型是否有屏幕指纹,非 vivo 机型上始终返回 false

注意:该方法只有在 isReady 和 hasUnderDisplayFingerprint 返回 true 时返回有效值。

void setFingerprintInsetsListener(FingerprintInsetsListener listener)

设置 insets 事件监听器,用于 isReady 和 图标状态变化事件(显示,隐藏)监听。

listener:Insets 内部事件监听器

int getFingerprintIconState()

获取指纹图标状态,返回 ICON_INVISIBLE 或者 ICON_INVISIBLE

注意:该方法只有在 isReady 和 hasUnderDisplayFingerprint 返回 true 时返回有效值。

Rect getFingerprintIconPosition()

获取指纹图标位置,返回指纹图标 left, top, right, bottom四个维度,坐标值为屏幕绝对坐标。

注意:该方法只有在 isReady 和 hasUnderDisplayFingerprint 返回 true 时返回有效值。

int getFingerprintIconLeft()

获取指纹图标 left 值

注意:该方法只有在 isReady 和 hasUnderDisplayFingerprint 返回 true 时返回有效值。

int getFingerprintIconTop()

获取指纹图标 top 值

注意:该方法只有在 isReady 和 hasUnderDisplayFingerprint 返回 true 时返回有效值。

int getFingerprintIconRight()

获取指纹图标 right 值

注意:该方法只有在 isReady 和 hasUnderDisplayFingerprint 返回 true 时返回有效值。

int getFingerprintIconBottom()

获取指纹图标 bottom 值

注意:该方法只有在 isReady 和 hasUnderDisplayFingerprint 返回 true 时返回有效值。

二、FingerprintInsets.FingerprintInsetsListener

public interface FingerprintInsets.FingerprintInsetsListener

com.vivo.fingerprint.ud.FingerprintInsets.FingerprintInsetsListener

监听 FingerprintInsets 事件,包括 Insets 准备就绪,指纹图标显示或者隐藏。

void onReady()

Insets 准备就绪

void onIconStateChanged(int state)

指纹图标状态变化

state:FingerprintInsets.ICON_VISIBLE 可见,FingerprintInsets.ICON_VISIBLE 不可见,-1 状态更新中。

 代码示例 ↓ 

private FingerprintInsets mInsets;


class InsetsListener implements
   FingerprintInsets.FingerprintInsetsListener {
   @Override
   public void onReady() {
       boolean hasUdFeature =
           mInsets.hasUnderDisplayFingerprint();
       if (hasUdFeature) {
           final Rect rect =
             mInsets.getFingerprintIconPosition();
           Log.d(TAG, “iconRect:” +
                   rect.toShortString());
           // TODO: vivo device support
           // under display fingerprint.
       } else {
           // TODO: vivo device don’t
           // support under display 
           // fingerprint.
       }
   }


   @Override
   public void onIconStateChanged(int state) {
       Log.d(TAG, “onIconStateChanged ” + state);
   }
}


mInsets = FingerprintInsets.create(
       this, new InsetsListener());
if (mInsets == null) {
   // TODO: Not vivo device.
}


// Later call destroy if needed.
if (mInsets != null) {
   mInsets.setFingerprintInsetsListener(null);
   mInsets.destroy();
   mInsets = null;
}

移动智能终端补充设备标识服务

Android Q系统限制应用获取IMEI,DEVICE ID等标识。为满足移动应用开发者业务诉求,移动安全联盟(MSA)联合vivo等厂商共同开发了支持多厂商的统一补充设备标识调用SDK。现该统一补充设备标识调用SDK已通过vivo内部测试并开放对接。

更多问题请联系客服或对接商务。

相关附件:

附件1:移动智能终端补充设备标识体系统一调用SDK开发者说明文档v1.10

附件2:团体标准-移动智能终端补充设备标识规范

附件3:移动智能终端补充设备标识体系统一调用SDK+F&Qv1.0

附件4:msa_sdk_v1.0.10

vivo小窗适配指南

1、小窗功能介绍

vivo全局小窗在安卓原生的多窗口功能基础上,进行了一系列的优化和创新。以小窗的形式展现应用,并支持不同窗口形式(小窗、分屏、全屏)之间快速切换。
小窗入口包括但不限于:近期任务卡片“小窗”选项,应用内滑、悬浮通知下拉等。用户可以通过 设置-快捷与辅助-多任务 路径进入多窗口功能设置页,了解相关功能介绍。
(注:不同机型、不同版本表现可能有所差异)。

2、小窗应用适配关键点

应用适配全局小窗时,因一些容易被忽略的适配点,导致应用进入小窗模式后出现兼容性问题。为此,我们整理了全局小窗应用适配的五大关键点,帮助开发者快速适配。

2.1 声明支持多窗口模式
应用适配全局小窗需要先声明支持安卓原生多窗口,和多窗口适配策略相同,都需在 [activity] 或 [application] 节点中添加 android:resizeableActivity 属性。

参考代码:

<application
    android:name=".MyActivity"
    android:resizeableActivity="true" />

安卓官方多窗口适配参考链接:https://developer.android.com/guide/topics/ui/multi-window.html

2.2 正确处理Configuration变化
在应用横竖屏旋转和窗口大小变化时,如需自行处理多窗口配置,请设置android:configChanges属性,之后Activity和 Fragment将收到 onConfigurationChanged()方法回调,而不是先销毁再重建。
在管理视图更新、重新加载资源等工作时,注意不要沿用之前的Config信息,而需重新获取最新的资源信息。参考代码:

<activity
  android:name=".MyActivity"
  android:configChanges="screenSize | smallestScreenSize 
		| screenLayout | orientation" />

2.3 正确启动Activity的task
应用需给Activity设置独立的android:taskAffinity属性,以防因继承rootActivity的多窗口属性导致无法启动到小窗模式。
因为task中的Activity会继承rootActivity的多窗口属性。如果启动该task的rootActivity是unresizable的,则即使要启动的Activity是resizable的也无法启动到小窗模式。
参考代码:

<activity
  android:name=".MyActivity"
  android:taskAffinity="com.test.example" />

2.4 正确使用Context
应用在适配小窗模式时,应使用Activity的Context加载合适的资源和计算相对位置。
在应用进程中,存在Application和Activity两类Context,其中Application Context对应的是屏幕资源信息,而Activity Context对应的是窗口的资源信息。
如果应用在小窗模式下使用的是 Application Context处理UI 相关的操作,会导致加载的资源因无法感知到小窗而显示异常。
Activity如何获取特定窗口类型下的窗口尺寸,参考代码:
java:

int screenWidthDp = getResources().getConfiguration().screenWidthDp;
int screenHeightDp = getResources().getConfiguration().screenHeightDp;

kotlin:

val screenWidthDp: Int = resources.configuration.screenWidthDp
val screenHeightDp: Int = resources.configuration.screenHeightDp

2.5 正确处理小窗下的沉浸式
小窗模式下,应用顶部有个功能bar,应用在沉浸式适配时需为此功能bar预留一定空间。因此应用在布局时,需要监听安卓原生的WindowInsetsListener接口回调,针对captionBarInsets变化来处理view高度,注意不要使用固定高度!!!

参考代码:
java:

getWindow().getDecorView().setOnApplyWindowInsetsListener((v, insets) -> {
    //应用在回调中计算view高度
    return insets;
});

kotlin:

window.decorView.setOnApplyWindowInsetsListener { v: View?, insets: WindowInsets ->
    //应用在回调中计算view高度
    insets
}

2.6 其他界面布局建议
• 布局参数尽量使用相对的布局参数,不要使用固定尺寸
• 避免根据屏幕大小,输入法高度等计算view的布局参数,如设置popwindow、dialog等子窗口的位置时,避免使用整个屏幕的相对位置
• 避免对DecorView,content等根布局进行定制,比如设置DecorView的背景、在DecorView添加子布局等
• 注意生命周期处理,如小窗模式下出现页面点击、拖动无响应问题,可能跟布局或生命周期有关
• 避免根据物理屏幕方向设置当前功能或布局

vivo分屏适配指南

1. 适配背景

vivo分屏在原生分屏的基础上做了定制化处理,比如增加分屏方式(消息\三指\组合)、分屏小桌面列表、输入法边界定制、加密界面定制等

2. 适配指导

2.1 判断分屏的方式 

Activity判断:

isInMultiWindowMode方法,判断mIsInMultiWindowMode变量,该变量在oncreate和模式变化时赋值

onMultiWindowModeChanged方法,在模式发生变化时回调

Services判断:

Q以及之前可以通过

1. WindowManagerGlobal.getWindowManagerService().getDockedStackSide()  !=  WindowManager.DOCKED_INVALID

2. getStackInfo,判断WINDOWING_MODE_SPLIT_SCREEN_PRIMARY(分屏)的stack是否存在,存在处于分屏

R以及以后通过

1. isSplitScreenModeActivated方法判断

2. Settings.system. in_multi_window,  0非分屏,1进入分屏

2.2 配置支持分屏

应用AndroidManifest.xml中可以针对app或activity进行设置。

<application
        android:resizeableActivity="true">

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

当应用targetSdkVersion >=24时,android系统默认设置android:resizeableActivity=”true”,不需要应用声明即可支持应用分屏。当应用targetSdkVersion < 24时,需要应用针对application或activity设置android:resizeableActivity=”true”。

应用设置application android:resizeableActivity=”true”时,会针对app的所有activity生效,因此如果应用只需要声明单独的activity时,可以只针对单个activity进行设置,系统侧建议针对application设置android:resizeableActivity=”true”,避免应用部分页面在分屏下的异常。

2.3 获取Activity实际显示的窗口大小和位置

使用:context.getResources().getDisplayMetrics()

不推荐使用:context.getWindowManager().getDefaultDisplay().getMetrics(outMetrics)

上述接口中,context为Activity对应的上下文句柄,每个Activity应该严格使用自己的context来进行布局,而不是使用application的context。

2.4 获取Activity的窗口布局方向

获取Activity窗口方向的的方法为:

context.getResources().getConfiguration().orientation

注:不要使用width /height的方法来判Activity为横竖屏布局,因为在多窗口状态下,支持应用的拖动,会改变宽高

获取设备的方向context.getWindowManager().getDefaultDisplay().getRotation()

2.5 Activity大小切换时不重启适配

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

应用复写onConfigurationChanged()方法,通过该方法的Configuration参数获得窗口高度等信息,并对界面布局做相应调整,如切换布局、调整控件位置和间距等。

2.6 状态栏位置

当处于分屏下屏,和小窗模式时,应用没有状态栏,此时不应该留出状态栏高度,需要从顶部开始布局。

可以通过configuration中windowingmode判断当前处于的模式,WINDOWING_MODE_FREEFORM和WINDOWING_MODE_SPLIT_SCREEN_SECONDARY不需要空出状态栏

安卓64位应用适配指南

一、应用64位适配

1.1 确定您的应用是否需要适配64位

使用了原生代码的应用才需要进行64位适配。

1.1.1 64位应用的特征

64位应用必须包含64位库。最简单的方法就是检查APK文件的结构,库文件会被打包在APK,针对ARM架构和x86架构具体目录结构如下:

平台32位库文件夹64位库文件夹
ARMlib/armeabi或者lib/armeabi-v7alib/arm64-v8a
x86lib/x86lib/x86_64

针对ARM架构,APK中库文件被打包在lib/arm64-v8a下面的应用是64位应用。

1.1.2 确定您的应用是否使用了原生代码 

如果您的应用符合以下情况,便是使用了原生代码:

  • 使用了任何 C/C++(原生)代码。
  • 与任何第三方原生库关联。
  • 通过使用原生库的第三方应用构建程序构建而成。

1.2 检验您的应用是否已经支持64位 

1.2.1 利用谷歌商店快速检查您的应用是否支持64位

要检查应用是否已满足 64 位要求,一种快速的方法是前往 Play 管理中心查看现有的版本,确定应用是否合规

如果您的草稿版本存在与 64 位要求相关的问题,Play 管理中心也会显示相应的警告。请看以下示例:

如果您看到提醒,表明您的应用是不支持64位的,需要进行相应的适配。

1.2.2 使用APK分析器查找原生库

APK分析器是一款可用于对构建的 APK 进行各方面评估的工具。针对我们目前所讨论的情况,我们将使用该工具查找原生库,以确定是否具备 64 位库。

1. 打开 Android Studio,然后打开任一项目。

2. 从菜单中依次选择 Build > Analyze APK…

3. 选择您要评估的APK。

4. 查看 lib 文件夹,您可以在其中找到“.so”文件。如果在您的应用中找不到任何“.so”文件,则说明该应用的相应库已准备就绪,您无需采取进一步措施。如果您看到 armeabi-v7a 或 x86,则说明您有 32 位库。

5. 检查是否 arm64-v8a 或 x86_64 文件夹中有类似的“.so”文件。

6. 如果您没有任何 arm64-v8a 或 x86_64 库,则需要更新构建流程以开始构建并打包APK中的这些工件。

7. 如果您看到32位和64位的库均已打包到软件包中,则可以跳至在64位硬件上测试应用。

1.2.3 通过解压缩 APK 查找原生库

APK文件的结构类似于ZIP文件,可以像ZIP文件一样解压缩。 如果您更喜欢使用命令行或任何其他解压缩工具,也可以采用解压缩APK的方法。

只需解压缩APK文件(根据您使用的解压缩工具,您可能需要将其重命名为.zip),然后按照上文中的指南浏览解压缩后的文件,即可确定您的应用是否已经为支持64位设备做好准备了。

例如,您可以从命令行中运行如下命令:

> zipinfo -1 YOUR_APK_FILE.apk | grep .so$
lib/armeabi-v7a/libmain.so
lib/armeabi-v7a/libmono.so
lib/armeabi-v7a/libunity.so
lib/arm64-v8a/libmain.so
lib/arm64-v8a/libmono.so
lib/arm64-v8a/libunity.so

请注意,此示例中存在 armeabi-v7a 库和 arm64-v8a 库,这表明该应用支持 64 位架构。

1.3 如何使用64位库构建应用

1.3.1 使用Android Studio或Gradle 进行构建

大多数Android Studio项目都使用Gradle作为底层构建系统,因此本部分适用于使用这两种工具进行构建的情况。针对原生代码进行构建很简单,只需将arm64-v8a 和/或 x86_64(视您要支持的架构而定)添加到应用

“build.gradle”文件中的 ndk.abiFilters 设置中即可:

// Your app's build.gradle
apply plugin: 'com.android.app'

android {
  compileSdkVersion 27
  defaultConfig {
      appId "com.google.example.64bit"
      minSdkVersion 15
      targetSdkVersion 28
      versionCode 1
      versionName "1.0"
      ndk.abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
// ...

1.3.2 使用CMake进行构建

如果您的应用是使用CMake构建的,那么您可以通过将 arm64-v8a 传递到“-DANDROID_ABI”参数来针对 64 位 ABI 进行构建:

    > cmake -DANDROID_ABI=arm64-v8a … or
   > cmake -DANDROID_ABI=x86_64 …

    在使用externalNativeBuild 时,此方法无效。请参阅使用Gradle进行构建部分。

1.3.3 使用ndk-build 进行构建

如果您的应用是使用 ndk-build 构建的,那么您可以使用 APP_ABI 变量修改“Application.mk”文件,从而针对 64 位 ABI 进行构建:

    APP_ABI := armeabi-v7a arm64-v8a x86 x86_64

在使用 externalNativeBuild 时,此方法无效。请参阅使用 Gradle 进行构建部分。

1.3.4 将32位代码移植到 64 位架构

如果您的代码已经可以在桌面或 iOS 平台上运行,那么您无需针对 Android 做额外的工作。如果这是第一次针对 64 位系统构建您的代码,那么您需要解决的主要问题是指针不再适合 int 这样的 32 位整数类型。您将需要更新以 int、unsigned 或 uint32_t 等类型存储指针的代码。在 Unix 系统上,long 对应的是指针大小,但在 Windows 上并非如此,因此您应该改用释意类型 uintptr_t 或 intptr_t。使用 ptrdiff_t 类型来存储两个指针之间的差异。

您应该始终选择使用 <stdint.h> 中定义的特定固定宽度整数类型,而不是 int 或 long 等传统类型,即便对于非指针也应如此。

使用以下编译器标记来捕捉代码在指针和整数之间转换不正确的情况:

    -Werror=pointer-to-int-cast
   -Werror=int-to-pointer-cast
   -Werror=shorten-64-to-32、

具有 int 字段(包含指向 C/C++ 对象的指针)的 Java 类也有同样的问题。在 JNI 源代码中搜索 jint,并确保切换到 long(Java 端)和 jlong(C++ 端)。

注意:因指针被截断而引起的崩溃将表现为 SIGSEGV,其中错误地址的前 32 位全部为零。

对于 64 位代码而言,隐式函数声明的危险性要高得多。C/C++ 假定隐式声明的函数(即编译器未检测到声明的函数)的返回值类型为 int。如果函数的实际返回值类型是指针,那么在 32 位系统上是可行的,因为在 32 位系统中指针的类型为 int,但在 64 位系统中,编译器会丢弃指针的前半部分。例如:

// This function returns a pointer:
// extern char* foo();

// If you don't include a header that declares it,
// when the compiler sees this:
char* result = foo();

// Instead of compiling that to:
result = foo();

// It compiles to something equivalent to:
result = foo() & 0xffffffff;

// Which will then cause a SIGSEGV if you try to dereference `result`.

以下编译器标记会将隐式函数声明警告变成错误,以便您能够更轻松地查找和解决此问题:-Werror=implicit-function-declaration

如果您有内联汇编程序,您需要重新编写该程序或使用普通的 C/C++ 实现。

如果您对类型大小进行了硬编码(例如,8 或 16 字节),请使用等效的 sizeof(T) 表达式(例如 sizeof(void*))来替换它们。

如果需要有条件地编译不同于 64 位的 32 位代码,则对于一般性的 32/64 差异,您可以使用 #if defined(__LP64__);对于 Android 支持的具体架构,可以使用 __arm__、__aarch64__ (arm64)、__i386__ (x86) 和 __x86_64__。

您需要调整类似 printf 或 scanf 的函数的格式字符串,因为如果使用传统的格式说明符,您无法以一种对 32 位和 64 位设备都正确的方式来指定 64 位类型。您可利用 <inttypes.h> 中的 PRI 和 SCN 宏来解决此问题,PRIxPTR 和 SCNxPTR 分别用于写入/读取十六进制指针,PRId64 和 SCNd64 分别用于以可移植的方式写入/读取 64 位值。

在移位时,您可能需要使用 1ULL 来获取要移位的 64 位常数,而不能使用仅支持 32 位的 1。

1.3.5 利用 Android App Bundle 减少大小增加量

为您的应用添加 64 位架构支持可能会导致 APK 的大小增加。我们强烈建议您利用 Android APP Bundle 功能,以尽量减小因在同一 APK 中同时包含 32 位和 64 位原生代码而对 APK 大小产生的影响。

实际上,将应用改为使用 Android App Bundle 可以缩减 APK 的大小,使其比现在更小。

1.3.6 游戏开发者

我们知道,迁移第三方游戏引擎是一个耗费人力的过程,并且需要很长的准备时间。庆幸的是,三大最常用的引擎目前都支持 64 位架构:

  • Unreal(自 2015 年起)
  • Cocos2d(自 2015 年起)
  • Unity(自 2018 年起)

1.3.7 Unity开发者

升级到支持的版本

Unity 自版本 2018.2 和 2017.4.16 开始提供 64 位支持。

如果您发现自己使用的Unity版本不支持64位架构,请确定要升级到的版本,并按照Unity提供的指南迁移您的环境,确保将您的应用升级到可构建 64位库的版本。Unity建议您升级到该编辑器的最新LTS版本,以获取最新的功能和更新。

下面的图表概述了Unity 的各个版本以及您应该采取的措施:

更改构建设置以输出 64 位库

如果您使用的 Unity 版本支持 64 位的 Android 库,那么您可以通过调整构建设置来生成 64 位版本的应用。您还需要使用 IL2CPP 后端作为 Scripting Backend(详见此处)。要为构建 64 位架构而设置 Unity 项目,请按以下步骤操作:

1.转到 Build Settings,然后确认 Unity 标志是否显示在 Platform 下的 Android 旁边,以确保您是在针对 Android 进行构建。

   a.如果 Unity 标志未显示在 Android 平台旁边,请选择 Android,然后点击 Switch Platform。

2.点击 Player Settings。

3.依次转到 Player Settings Panel > Settings for Android > Other Settings > Configuration

4.将 Scripting Backend 设为 IL2CPP。

5.依次选择 Target Architecture > ARM64 复选框。

照常构建!

请注意,针对ARM64进行构建需要您专门针对该平台构建您的所有资产。请按照 Unity 的指南来缩减 APK 大小,同时考虑利用 Android App Bundle 功能来减小大小增加量。

1.4 应用64位合规性检验

1.4.1 合并 APK 和 64 位合规性

如果您要使用 Google Play 的合并 APK 支持来发布应用,请注意在版本层面评估是否符合 64 位要求。不过,如果 APK 或 app bundle 不会分发给搭载 Android 9 Pie 或更高版本的设备,则不适用 64 位要求。

如果您的某个 APK 被标记为不合规,但该 APK 比较老旧且无法使其合规,一种策略是在该 APK 清单的 uses-sdk 元素中添加 maxSdkVersion=”27″ 属性。这样一来,此 APK 将不会被分发给搭载 Android 9 Pie 或更高版本的设备,因而也就不会再妨碍合规。

1.4.2 RenderScript和64位合规性

如果您的应用使用 RenderScript 并且是通过较低版本的 Android 工具构建的,该应用可能会存在 64 位合规性问题。使用版本低于 21.0.0 的构建工具时,编译器可能会将生成的位码放到外部 .bc 文件中。64 位架构不再支持这些旧的 .bc 文件,因此,如果您的APK 中有这类文件,就会造成合规性问题。

要解决此问题,请移除项目中的所有 .bc 文件,将环境升级到 build-tools-21.0.0 或更高版本,并将 Android Studio 中的 renderscriptTargetApi 设为 21+,以指示编译器不要生成 .bc 文件。然后,重新构建您的应用,检查是否有 .bc 文件,再将应用上传到 Play 管理中心。

二、64位应用测试

1.1 vivo远程真机调试

vivo云测平台提供Android 5.1及以后的部分真机调试,开发者可以直接通过以下链接进入远程真机进行试用体验:https://vcl.vivo.com.cn/#/home/index

vivo Android 7.0以后的大部分机器支持64位。

1.2 在64位设备上测试应用

64 位版本的应用应提供与 32 位版本相同的质量和功能集。请对您的应用进行测试,以确保使用最新的 64 位设备的用户能够在您的应用中获得优质的体验。

最简单的 APK 测试方法就是使用 adb 安装该应用。大多数情况下,您可以提供 –abi 作为参数,用以指示要将哪些库安装到设备上。这样在设备上安装该应用时便会仅包含 64 位库。

# A successful install:
> adb install --abi armeabi-v7a YOUR_APK_FILE.apk
Success

# If your APK does not have the 64-bit libraries:
> adb install --abi arm64-v8a YOUR_APK_FILE.apk
adb: failed to install YOUR_APK_FILE.apk: Failure [INSTALL_FAILED_NO_MATCHING_ABIS: Failed to extract native libraries, res=-113]

# If your device does not support 64-bit, an emulator, for example:
> adb install --abi arm64-v8a YOUR_APK_FILE.apk
ABI arm64-v8a not supported on this device

安装成功后,请照常对应用进行测试,以确保其质量与 32 位版本相同。

三、64位应用发布

如果您觉得应用已准备妥当,请照常发布。与往常一样,请继续遵循部署应用的最佳做法。也务必要先在支持 64 位的设备上进行全面测试,然后再面向更广泛的受众群体发布。

四、参考链接

(1)Google指南:支持64位架构

https://developer.android.com/distribute/best-practices/develop/64-bit?hl=zh-cn

(2) 64-bit and Data Size Neutrality

https://unix.org/whitepapers/64bit.html

(3)20 issues of porting C++ code on the 64-bit platform

https://pvs-studio.com/en/a/0004/

vivo 折叠屏三方应用开发适配指南

一、vivo 多窗口(分屏小窗)功能适配指南

1.1 适配背景

随着大屏手机的普及,用户对于多窗口功能的使用越来越频繁,尽管google 已经推出了多窗口功能,但是很多应用还没有适配多窗口功能,因此,我们输出《vivo 多窗口(分屏小窗)功能适配指南》提供给应用侧进行适配,让三方应用开发者能够更清楚多窗口功能的适配开发流程。

1.2 适配指导

1.2.1 配置支持多窗口功能

应用AndroidManifest.xml 中可以针对 app 或 activity 进行设置。

代码实例:

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

当应用 targetSdkVersion >=24 时,android 系统默认设置 android:resizeableActivity=”true”,不需要应用声明即可支持应用使用多窗口功能。当应用 targetSdkVersion < 24 时,需要应用针对 application 或 activity 设置 android:resizeableActivity=”true”。

1.2.2 获取Activity 实际显示的窗口大小和位置

Android11 及以上(API 级别为30 及以上)推荐使用:

WindowManager#getCurrentWindowMetrics() : 针对系统的当前窗口状态, 返回WindowMetrics 对象

WindowManager#getMaximumWindowMetrics():针对系统可能的最大窗口状态,返回 WindowMetrics

代码实例:

获取当前activity 的窗口指标

WindowMetrics windowMetrics = activity.getWindowManager().getCurrentWindowMetrics();

Android10(API 级别为29)推荐使用:

context.getResources().getDisplayMetrics()

不推荐使用:

context.getWindowManager().getDefaultDisplay().getMetrics(outMetrics)

上述接口中,context 为 Activity 对应的上下文句柄,每个 Activity 应该严格使用自己的context 来进行布局,而不是使用 application 的 context。

备注:

Display 方法 getSize() 和 getMetrics() 在 API 级别 30 中已被弃用,取而代之的是新的 WindowManager# getCurrentWindowMetrics() 方法。

Android 12(API 级别 31)弃用了 Display 方法 getRealSize() 和 getRealMetrics(),并更新了它们的行为以使其更接近 getMaximumWindowMetrics() 的行为。

getSize():获取context 显示的尺寸,不包括状态栏等系统显示区域的尺寸

getRealSize():获取手机屏幕的真实尺寸,包括状态栏等系统显示区域的尺寸

getMetrics():获取context 显示区域的屏幕元素,例如屏幕尺寸、屏幕密度等屏幕元素信息,不包括状态栏等系统显示区域的屏幕元素信息

getRealMetrics():获取context 显示区域的屏幕元素信息,例如屏幕尺寸、屏幕密度等屏幕元素信息,包括状态栏等系统显示区域的屏幕元素信息

Android10 上,我们推荐使用getSize 和getMetrics 来获取显示区域的大小。在多窗口(分屏小窗)模式下,getSize 和getMetrics 获取出来的是context 显示区域的大小,getRealSize和getRealMetrics 获取出来的是手机屏幕的真实大小,getSize 和getMetrics 获取出来的尺寸值要小于getRealSize 和getRealMetrics 的尺寸值。

1.2.3 获取Activity 的窗口布局方向

获取Activity 窗口方向的的方法为:

context.getResources().getConfiguration().orientation

注:不要使用width /height 的方法来判Activity 为横竖屏布局,因为在多窗状态下,支持应用的拖动,会改变宽高。

获取设备方向的方法如下:

context.getWindowManager().getDefaultDisplay().getRotation()。

1.2.4 Activity 大小切换时不重启适配

1. 应用在Activity 窗口大小切换时不重启适配,在android:configChanges 属性增加 screenSize|screenLayout| orientation|smallestScreenSize

代码示例:

<activity
 android:name=".MyActivity"
 android:configChanges="screenSize | smallestScreenSize | screenLayout | orientation" />

2. 在Activity 的onConfigurationChanged 回调中更新宽高刷新子布局。

代码示例:

@Override
public void onConfigurationChanged(Configuration newConfig) {
   super.onConfigurationChanged(newConfig);
   ……
}

应用复写 onConfigurationChanged() 方法,通过该方法的 Configuration 参数获得窗口高度等信息,并对界面布局做相应调整,如切换布局、调整控件位置和间距等。

1.2.5 状态栏位置

当处于分屏下屏,和小窗模式时,应用没有状态栏,此时不应该留出状态栏高度,需要从顶部开始布局。

可以通过configuration 中windowingmode 判断当前处于的模式,WINDOWING_MODE_FREEFORM 和WINDOWING_MODE_SPLIT_SCREEN_SECONDARY 不需要空出状态栏。

1.2.6 分屏下ActivityRecord 动画问题

很多应用会重写

<item name="android:activityOpenEnterAnimation">@null</item>
<item name="android:activityOpenExitAnimation">@null</item>
<item name="android:activityCloseEnterAnimation">@null</item>
<item name="android:activityCloseExitAnimation">@null</item>

分屏下containingWidth/containingHeight 为分屏窗口的尺寸, width/height 为displaycontent 尺寸(全屏)

initialize(containingWidth, containingHeight, width, height);

对于动画类型有 ABSOLUTE/RELATIVE_TO_SELF/RELATIVE_TO_PARENT

如果应用配置的xml 中带了p,使用的displaycontent 尺寸,会导致分屏下位移动画问题。没有p 为 RELATIVE_TO_SELF 类型

1.2.7 正确处理分屏小窗的沉浸式

分屏或小窗模式下,应用顶部有个功能bar,应用在沉浸式适配时需为此功能bar 预留一定空间。因此应用在布局时,需要监听安卓原生的WindowInsetsListener 接口回调,针对captionBarInsets 变化来处理view 高度,注意不要使用固定高度!!!

参考代码:

java:

getWindow().getDecorView().setOnApplyWindowInsetsListener((v, insets) -> {
  //应用在回调中计算view 高度
  return insets;
});

kotlin:

window.decorView.setOnApplyWindowInsetsListener { v: View?, insets: WindowInsets ->
  //应用在回调中计算view 高度
  insets
}

1.2.8 其他布局建议

•  布局参数尽量使用相对的布局参数,不要使用固定尺寸

•  避免根据屏幕大小,输入法高度等计算view 的布局参数,如设置popwindow、dialog 等子窗口的位置时,避免使用整个屏幕的相对位置

•  避免对DecorView,content 等根布局进行定制,比如设置DecorView 的背景、在DecorView 添加子布局等

•  注意生命周期处理,如小窗模式下出现页面点击、拖动无响应问题,可能跟布局或生命周期有关

•  避免根据物理屏幕方向设置当前功能或布局

二、折叠屏前后屏切换适配指南

2.1 屏幕兼容性

2.1.1 应用布局优化

折叠屏有主屏(展开态)和副屏(折叠态)两种形态,以X Fold 为例,主屏和副屏的分辨率分别是1916×2160 和1080×2520,densityDpi 是480,应用程序需要针对不同的屏幕尺寸进行布局适配和优化。

2.1.1.1 屏幕兼容显示

在对不同尺寸屏幕适配过程中,为了确保在折叠屏各个屏幕形态下获取最佳的布局显示效果,例如页面无明显的拉伸放大、布局错位、重叠和显示更多更清晰的内容,建议您对布局进行优化。应用界面正确、美观的布局和显示,包含如下:

•  确保您的布局能够根据屏幕适当地调整大小

•  根据屏幕配置提供合适的 UI 布局

•  确保对正确的屏幕应用正确的布局

•  提供可正常缩放的位图

    详细适配方法请参考Android 开发者适配指导:https://developer.android.com/training/multiscreen/screensizes?hl=zh-cn

    基于折叠屏的特性,用户经常会在主屏(大屏)玩游戏或全屏看视频,以X Fold 为例,主屏有前置摄像头,因此应用程序页面需要针对打孔屏做适配,避免页面显示时摄像头区域出现白边或黑边,影响显示效果。参考vivo 手机全屏适配开发指导:https://developers.vivo.com/doc/d/52a902cf33fa72efeeeaf5197d60efa5

2.1.1.2 支持横竖屏切换

以X Fold 为例,主屏(展开态)屏幕尺寸接近4:3,桌面应用支持横竖屏显示,因此用户会在任何形态下打开应用。因此我们建议应用程序支持横竖屏切换,提高用户体验。横竖屏切换适配建议如下:

1、 横竖屏切换涉及到方向、尺寸和键盘隐藏/ 显示, 因此需要在Activity 配置

android:configChanges=”orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden”

2、 在onConfigurationChanged 函数收到config 变化后,动态加载横竖屏布局以及完成其他任务

public void onConfigurationChanged(Configuration newConfig) {
   super.onConfigurationChanged(newConfig);
   if(newConfig.orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT){
      //switch to port and load layout
      setContentView(R.layout.activity_main);
      //TODO something
   }else{
      //switch to land and load layout
      setContentView(R.layout.activity_main_land);
      //TODO something
   }
}

2.1.2 应用resizeable 能力支持

折叠屏状态切换时,涉及到分辨率和像素密度的动态变化。若应用程序界面不支持resizeable 能力,在切屏时会存在应用程序界面未全屏显示的问题,如下图所示:

展开态

折叠态

因此我们期望应用程序界面在切屏后可以全屏显示,应用程序支持resiszeable 需要在 AndroidManifest.xml 针对Application 或Activity 设置android:resizeableActivity。

<application
  android:resizeableActivity="true"> ->对应用内所有activity 生效
  <activity
      android:resizeableActivity="true" /> ->仅对此activity 生效
</application>

开发者需要根据应用面向的API Level (targetSdkVersion)进行支持resizeable 能力的申明。

•  如果应用程序面向API Level 24 以上(targetSdkVersion>=24),系统将默认应用支持resizeable 能力。

•  如果应用程序面向API Level 24 以下(targetSdkVersion< 24),需要应用在manifest 中显式的声明android:resizeableActivity=true, 才可以支持resizeable 能力。

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

    受限屏幕支持开发者适配指导:https://developer.android.com/guide/practices/screens-distribution?hl=zh-cn

2.2 应用连续性

为了保证应用程序在折叠屏展开/折叠过程无缝切换,开发者需要做应用连续性的设计,以确保应用程序任务不中断。以X Flod 为例,最佳的体验为,应用在展开切换过程中,不发生应用的重启,且切换之前的任务和应用相关状态得以保存和延续。

折叠屏切屏时会触发对smallestscreensize、screensize 和screenlayout 的配置更改。每当发生配置更改时,默认情况下会销毁并重新创建整个activity。开发者可以通过注册监听系统configchanges 消息,不重启应用的情况下处理配置更改,应用程序需要向manifest 中添加android:configchanges 属性,其中至少包含以下值:

android: configChanges = “screenSize|smallestScreenSize|screenLayout” 应用程序需要复

写 onConfigurationChanged() 方法,通过该方法的Configuration 参数获得屏幕的分辨率等信息,就可以针对不同比例屏幕下的应用界面布局做相应调整,如切换布局、调整控件位置和间距等。

public void onConfigurationChanged(Configuration newConfig) {
   super.onConfigurationChanged(newConfig);
if(newConfig.screenWidthDp > xxx) //判定屏幕尺寸
  //load layout
    setContentView(R.layout.activity_main);
    ...
}

如果您的应用必须进行重新走生命周期来以响应屏幕切换,需要进行状态的保存和恢复。

您可以通过OnSaveInstanceState()和ViewModel 对象来进行之前状态保存和后续的恢复。即,在销毁activity 之前,通过onSaveInstancesState()存储状态, 在onCreate() or onRestoreInstanceState()进行状态的恢复。

Note:不要在OnDestroy()中调用finish()或其他自行终止进程。这将导致应用程序在设备折叠或展开时关闭、闪退等问题。

其他适配事项可以参考Android 开发者适配指导(例如链接中提到的 保存界面状态和支持配置变更 部分):

https://developer.android.com/guide/topics/ui/foldables?hl=zhcn#%E5%BA%94%E7%94%A8%E8%BF%9E%E7%BB%AD%E6%80%A7

2.3.判定折叠屏设备方法

根据应用程序判定折叠屏设备的需求,提供了两种判定折叠屏设备的方法。

2.3.1 根据model 机型判定

此方法适用于应用程序已经针对部分折叠屏设备适配且支持新折叠屏设备在线配置生效场景,否则强烈建议开发者通过方法2 判定折叠屏设备。

以X Fold 为例,可以获取如下属性获取model 名

λ adb shell getprop ro.product.model

V2178A

2.3.2 通过接口判定

建议开发者使用此方法判定设备类型,接口说明如下:

类 :android.util.FtDeviceInfo

public static String getDeviceType

返回目前是三种类型:phone、tablet 和foldable

可以通过反射的方式获取设备类型,返回值foldable 表示折叠屏设备。

反射代码示例:

private static boolean isVivoFoldableDevice(){
     try{
       Class<?> c= Class.forName("android.util.FtDeviceInfo");
       Method m = c.getMethod("getDeviceType");
       Object dType = m.invoke(c);
       Log.d("fold","getDeviceType="+dType);
       return "foldable".equals(dType);
     }catch(Exception e){
       e.printStackTrace();
     }
     return false;
}

2.4.调试和验证

2.4.1 通过命令模拟调试

开发者可以在非折叠屏手机上面通过命令修改手机的屏幕分辨率和densityDpi 来进行模拟调试,如下以X Fold 屏幕配置为例模拟切屏(主屏->展开态,副屏->折叠态):

副屏切主屏模拟方法:

(1)预先在手机设置如下屏幕配置:

  adb shell wm size 1080×2520

  adb shell wm density 480

(2)adb shell wm size 1916×2160

主屏切副屏模拟方法:

(1)预先在手机设置如下屏幕配置:

  adb shell wm size 1916×2160

  adb shell wm density 480

(2)adb shell wm size 1080×2520

配置恢复方法:

adb shell wm size reset

adb shell wm density reset

2.4.2 通过模拟器调试

2.4.2.1 Android 折叠屏模拟器

除了通过手机动态切换分辨率的方式来进行调试外,也可以通过模拟器的方式进行调试和验证,Android Studio 模拟器Phone 分类支持8 英寸和7.3 英寸的折叠屏调试,但是无法克隆,即无法修改模拟器的物理尺寸和分辨率,且模拟器System image 如果是x86 的系统,只支持32 位应用的安装和运行,如果是纯64 位的应用,模拟器需要选择arm64-v8a,否则无法安装和运行应用。

Note:

1. Android Studio Foldable 模拟器无法修改物理尺寸、分辨率和densityDpi,因此在适配时,无法完全模拟vivo 折叠屏手机,在app 或activity resizeableActivity = true 时,为了验证切屏是否有黑边可以在Android 原生模拟器查看效果。

展开态
折叠态

2. 64 位模拟器需要PC 主机支持,部分PC 无法正常启动64 位模拟器。

2.4.2.2 折叠屏展开态模拟器

Android 折叠屏模拟器无法修改物理尺寸、分辨率和densityDpi,若应用在进行《2.2 不同分辨率布局适配》时,需要100%模拟折叠屏展开态的屏幕进行调试时,可以通过克隆Tablet分类的Nexus 10,然后修改vivo 折叠屏手机对应的物理尺寸、分辨率和DPI 进行调试和验证。

Note:

1. 应用UI 兼容性适配对物理尺寸有强关联时,请务必修改Screen size 为8.03,分辨率是展开态分辨率

2. 64 位模拟器是否可以运行需要PC 主机支持

3. 若手机模拟densityDpi 和分辨率调试无问题后,无法在模拟器运行,可以发包给vivo 接口人,在vivo 折叠屏真机运行点检。

4. 模拟器无法修改densityDpi,需要启动模拟器后,通过adb shell 命令设置。

此时打开终端,可以看到模拟器设备已经挂载和连接,可以进行adb 操作。

λ adb devices

List of devices attached

emulator-5554 device

模拟器无法直接修改densityDpi,需要启动模拟器后单独设置。

adb shell wm density 480

2.4.3 远程真机调试

目前vivo 已经支持X Fold 远程真机调试,开发者可以访问如下链接使用远程真机

https://vcl.vivo.com.cn/#/machine/picking

远程真机主屏效果图

远程真机副屏效果图

2.4.4 测试用例建议

2.4.4.1 用例1

应用在展开态下页面显示正常

测试步骤:

(1)在屏幕展开的状态下,打开应用,查看应用各个页面显示效果;

预期结果:

(1)应用的所有页面可以全屏显示,页面没有发生截断、拉伸变形、放大模糊,按钮缺失等问题;

2.4.4.2 用例2

应用在展开态下核心页面功能正常

测试步骤:

(1)在屏幕展开的状态下,打开应用;

(2)遍历应用核心页面的控件按钮,所有功能按钮可用,无失效/crash/anr 等问题;

预期结果:

(1)应用在折叠屏展开后,核心页面功能可用

2.4.4.3 用例3

应用在折叠和展开状态切换时业务不中断,应用页面显示和控件点击正常。

测试步骤:

(1)在展开态下,打开应用的测试页面;

(2)在应用的测试页面切换到折叠态,观察页面显示;

(3)点击测试页面的所有控件和按钮;

(4)在折叠态下,打开应用的测试页面;

(5)在应用的测试页面切换到展开态,观察页面显示;

(6)点击测试页面的所有控件和按钮。

预期结果:

(1)应用页面在状态切换过程中,不要出现页面重启、闪退;页面显示正常,不要出

现页面截断、拉伸变形、放大模糊,按钮缺失等问题;

(2)测试页面按钮和控件点击都能正常响应;

(3)应用页面在状态切换过程中,不要出现页面重启、闪退;页面显示正常,不要出

现页面截断、拉伸变形、放大模糊,按钮缺失等问题;

(4)测试页面按钮和控件点击都能正常响应。

三、vivo 多窗模式适配指南

3.1 适配规则

窗口在左右两边拆分显示以Activity 为基本单位,如果要实现拆分,应用窗口需要用Activity 实现和启动,子布局或者非Activity 实现的窗口,无法拆分。以下提供Pad 设备上在横屏时应用不同界面分屏显示的适配方法和规则

3.1.1 AndroidManifest 增加meta-data

在app 的AndroidManifest.xml 文件中 <application>标签内新增

<meta-data android:name=”VivoMultiWindow” android:value=”true” />

3.1.2 Assert 目录新增vivo_multiwindow.json 配置文件

“vivo_multiwindow.json”模板

在 assert 目录下新建配置文件 “vivo_multiwindow.json”,模板示例如下:

{         “funtouchPlugVersion”: “1.1”,       “package”:   “com.vivo.test”,       “elements”:   [                 {                        “fun”: {                                 “name”: “multi-landscape”,                                 “enable”: “true”                        },                        “config”: {                                 “mode”: 1,                                 “action”: [                                         {                                                  “pages”: “com.vivo.test.MainActivity”,                                                  “attachedPage”: “com.vivo.test.primaryActivity1”                                         },                                         {                                                  “from”: “com.vivo.test.FromActivity1”,                                                  “to”: “com.vivo.test.ToActivity”                                         },                                         {                                                “from”:   “com.vivo.test.FromActivity2”,                                                  “to”: “*”                                         }                               ],                               “Activities”:   {                                         “fullscreenActivities”: [                                                  “com.vivo.test.fullScreenActivity1”,                                                  “com.vivo.test.fullScreenActivity2”                                         ],                                         “transitActivities”: [                                                  “com.vivo.test.transitionActivity1”,                                                  “com.vivo.test.transitionActivity2”,                                                  “com.vivo.test.transitionActivity3”                                         ],                               },                               “EX”:   {                                         “draggable”:   “true”,                                         “statusBarVisible”:   “true”,                                         “videoAutoFullscreen”:   “true”,                                         “isRelaunchForResizing”:   “false”,                                         “supportDoubleResume”:   “true”,                                         “windowsGravity”: [                                                {                                                         “deviceType”: “FOLD”,                                                         “gravity”: “1|2”                                                },                                                {                                                         “deviceType”: “PAD”,                                                         “gravity”: “900|1654”                                                }                               ],                               “splitBarColor”:   “0xffe5e5e5”                        }                  }           }      ]}

“vivo_multiwindow.json”字段描述

“vivo_multiwindow.json”具体字段描述如下:

参数描述
funtouchPlugVersion1.0vivo 多窗口功能版本,目前固定1.0
package应用包名
fun.namemultilandscapevivo 多窗口功能名称,固定为multi-landscape
fun.enablefalse/是否启用该配置
false 关闭
true 启用
config.mode0/1默认模式
0 购物模式
1 自定义模式
config.action应用冷启动时默认打开的双界面配置
config.action.pages默认双屏主页面列表
注:可以指定多个,分号隔开,应用冷起动启动该页面,自动在副页面位置启动attachedPage页面
config.action.attachedPage默认双屏副页面
config.action.from(自定义模式生效)触发多窗口的源activity
config.action.to(自定义模式生效)触发多窗口的目标activity
“*”表示任意activity
注:当从源Activity启动目标Activity,自动在主副位置打开对应Activity
config.Activities.fullscreenActivities0/1是否需要全屏启动
0 非全屏
1 全屏
config.Activities.transitActivities过渡页面列表
注:无固定窗口位置,跟随启动时上一级页面窗口位置
config.EX特殊属性配置
config.EX.draggable0/1是否支持窗口拖动
0 不支持
1 支持
config.EX.statusBarVisible0/1是否支持状态栏可见
0 不支持
1 支持
config.EX.videoAutoFullscreen0/1是否支持默认视频全屏播放
0 不支持
1 支持
config.EX.isRelaunchForResizing0/1是否支持重启
0 不支持
1 支持
config.EX.windowsGravity窗口比例
config.EX.windowsGravity.deviceTypeFOLD/PAD设备类型
FOLD 折叠屏
PAD 平板
config.EX.windowsGravity.gravity1|2 /
900|1654
窗口比例
FOLD 默认1|2
PAD 默认900|1654
config.EX.supportDoubleResume0/1是否支持两个界面同时resume
config.EX.splitBarColor分隔条的颜色

3.2 适配建议

3.2.1 状态判断

获取Activity 是否运行在模式下:

String config = context.getResources().getConfiguration().toString();

boolean isInMagicWindow = config.contains(“multi-landscape”);

context 为Activity 的context

配置了Fullscreen 的页面,显示为全屏,但也运行在模式下

3.2.2 获取Activity 显示大小

推荐使用:context.getResources().getDisplayMetrics()

不推荐使用:context.getWindowManager().getDefaultDisplay().getMetrics(outMetrics)

context 为Activity 对应的上下文句柄,每个Activity 应该严格使用自己的context 来进行布局,而不是使用application 的context。

3.2.3 获取Activity 窗口布局方向

获取Activity 窗口方向的方法为:context.getResources().getConfiguration().orientation

当Activity 处于横屏应用显示状态下时,获取的orientation 统一为竖屏;如果Activity是在状态下全屏显示,其orientation 为值为横屏。

获取设备的方向context.getWindowManager().getDefaultDisplay().getRotation()。

3.2.4 指定Activity 全屏显示

横屏应用显示状态下,Activity 希望以全屏展示,有三种方法:

方法一:动态全屏显示,Activity 首先以横屏应用显示非全屏显示,调用Activity 类的如下接口申请横屏方向可进入全屏显示状态:

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)

在此状态下,调用Activity 类申请竖屏方向即可退出全屏状态:

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)

此场景广泛应用于视频全屏播放场景。

方法二:在配置文件fullscreenActivities 中添加想要全屏的Activity,即可实现Activity默认以全屏启动,此方法中,Activity 会一直以全屏状态显示

方法三:在“AndroidManifest.xml”文件中将Activity 的方向配置为横屏,此方法中,Activity 会一直以全屏状态显示

android:screenOrientation = “landscape”

或android:screenOrientation = “sensorLandscape”

3.2.5 开发建议

•  应用主界面Activity 的启动以singleTask 的模式来启动,保证其在栈内的唯一性。

•  应用内不要存在两个及以上组件名不一样的主界面Activity,例如某个应用在登录前的主界面Activity 组件名与登录后的Activity 组件名不同,会导致新的主界面Activity显示在右半边

•  尽量避免或减少过渡Activity 的使用,尽量避免或者减少使用过渡Activity 来实现Activity 的启动,如果一定要使用,建议使用ActivityA->ActivityB->ActivityC 的启动方式,而不是(ActivityA->ActivityB) + (ActivityA->ActivityC)的方式(示例中,ActivityB 为过渡Activity ) 。过渡Activity 的数量不能超过1 个, 例如ActivityA->ActivityB->ActivityC->ActivityD,其中ActivityB 和ActivityC 是过渡窗口,建议从ActivityA 直接启动ActivityC。

•  Activity 大小切换时不重启适配,建议应用在Activity 窗口大小切换时不重启适配,在android:configChanges 属性增加screenSize|screenLayout|orientation|

smallestScreenSize,并在Activity 的onConfigurationChanged 回调中更新宽高刷新子布局。

•  避免Activity 子布局的缺省重用,由于左右Activity 同时显示,左右显示的两个Activity中不能同时存在单实例的公共布局模块

•  避免左右两个Activity 共用一个播放器,左右两个Activity 同时显示,建议两边使用的播放器资源相互独立,互不影响。

•  建议Camera 相关的Activity 界面配置为全屏显示

3.3 启动返回显示逻辑

基于目前横屏应用显示模式,在模式中启动或者回退链条展示。

3.4 三方适配多窗模式过程中需要注意:

应用更新json 文件后安装不生效,可以通过下面两种方式更新:

1. 修改完之后,升级应用版本号,重新安装应用

2. 把数据库都删除,重新创建,需要root 权限删除数据库之后重启。

adb vivoroot

adb shell rm -rf data/data/com.vivo.smartmultiwindow/*

adb shell rm -rf data/user_de/0/com.vivo.smartmultiwindow/*

建议使用第一种。