御坂初琴
Articles132
Tags48
Categories12
【原创教程】MIUI平行视界全探索

【原创教程】MIUI平行视界全探索

查看如何打包到 ROM 包的请直接跳转到第 4 章

# 1.MIUI 的平行视界概述

MIUI 的平行视界分为两个时代,基于安卓 11 的魔窗版时代和基于安卓 12 / 安卓 13 的官改时代。

所谓魔窗版时代,MIUI 的平行视界使用了 magicWindows 方案,该方案同样用在了安卓 10,安卓 11 和安卓 12 版的鸿蒙,ColorOS 以及 OriginOS 上。魔窗方案的优点是,即使应用厂商什么都不做,依旧可以实现两种预设模式的平行视界逻辑。对于应用厂商来说,这是最方便的。

所谓官改版时代,MIUI 使用了安卓 12L 的 Activity Embedding 方案。官方方案的特点是,厂商必须主动适配,自己写配置文件才能实现平行视界。

小米过于相信谷歌的号召力,而事实上根本没几家 APP 适配官方方案,所以无奈之下,小米只能亲自下场,替常见的 APP 写配置文件,并增加一些默认的窗口逻辑。这种在官方接口上自己再修改补全的方案,我称之为官改方案。

关于魔窗方案和官方方案的更多区别,我将会在接下来的时间制作一期视频,通过视频的方式进行更直观的演示。读者可以关注我的 B 站账号。

# 2. 魔窗版的实现方法

魔窗版的平行视界由系统中的两个文件进行控制,分别是应用列表的 magic_window_setting_config.xml,和配置应用横屏属性的 magicWindowFeature_magic_window_application_list.xml 。

这里我们简单解析一下两个文件的内容。

应用列表位置 /data/system/users/0/magic_window_setting_config.xml

1
2
3
<setting name="com.yjs.android" miuiMagicWinEnabled="true" miuiDialogShown="false" miuiDragMode="0" />
<setting name="com.kwai.thanos" miuiMagicWinEnabled="true" miuiDialogShown="false" miuiDragMode="0" />
...

参数详解:

1
2
3
4
name="" //应用包名
miuiMagicWinEnabled="true" //是否开启魔窗模式。推荐为"true"
miuiDialogShown="false" //是否开启魔窗适配对话框。推荐为"false"
miuiDragMode="0" //是否支持分屏窗口拖拽,亲测无效。推荐为"0"

魔窗适配对话框长这样:

image-20230212114621811

应用配置位置 /data/system/magicWindowFeature_magic_window_application_list.xml

1
2
3
<package window_mode="1" support_multi_resume="false" support_fullscreen_video="true" support_camera_preview="true" is_scaled="true" need_relaunch="false" default_setting="" is_dragable="false" is_left_window_one_third="" notch_adapt="false" version="8.0.9" home="" name="com.tencent.mm" />
<package window_mode="2" support_multi_resume="false" support_fullscreen_video="true" support_camera_preview="true" is_scaled="true" need_relaunch="false" default_setting="" is_dragable="true" is_left_window_one_third="" notch_adapt="false" version="" home="" name="com.xunmeng.pinduoduo" />
...

这里的参数就需要详解一下了:

1
2
3
4
5
6
7
8
9
10
11
12
window_mode="1" //理论上1为普通模式,即主页始终在左侧,2为购物模式,即左侧不断被新产生的右侧界面覆盖,4为全屏拉伸模式,-1为先纵向全屏拉伸然后再旋转到横向,0为信箱模式。但是实际测试发现1和2区别不大,推荐为2,大部分应用都能设置为平行视界
support_multi_resume="false" //保持左右窗口都处于活动状态,适合两侧窗口需要同时播放视频的应用。推荐为"false",减小内存占用
support_fullscreen_video="true" //播放视频时全屏。推荐为"true"
support_camera_preview="true" //是否支持在半屏预览拍照界面。推荐为"true"
is_scaled="true" //重绘界面大小。亲测效果一般,如果图标过大或者过小时可以改一下试试
need_relaunch="false" //在调整页面大小时重新加载窗口,亲测无效。推荐为"false",或者在调整左右大小后界面变形时选为"true"
default_setting="" //无用属性。推荐为""
is_dragable="false" //重要属性,是否支持左右大小调节。需要每个应用测试能否支持该功能,以及调节后界面是否变形。
is_left_window_one_third="" //左半屏是否为整个屏幕的1/3大小,亲测无效。推荐为""
version="" //支持的软件版本。推荐为""
home="" //应用主页,需要填写activity。推荐不填,默认为""
name="" //应用包名

所以,添加自定义的软件,以及修改已有软件的配置,只需要先在 magic_window_setting_config.xml 中添加一行声明,然后再在 magicWindowFeature_magic_window_application_list.xml 中为它配置一个横屏方案即可。具体请自行实验。

image-20230212114759354

这里得夸一句安卓 11 的 MIUI 工程师,官方版适配就为 3200 余款应用适配了横屏配置,当年雷军发布会上的那句我们为 3000 款应用进行了适配也不是空话。

但侧面也证明了,魔窗平行视界方案的适配简介,方便,快速,成本低,支持的配置项简单有效,功能全面,非常适合扩展平行视界生态。

# 3. 官改版的实现方法

官改版的平行视界和魔窗版类似,也是由系统中的两个文件进行控制,分别应用列表 embedded_setting_config.xml,和应用横屏配置 embedded_rules_list.xml 。

应用列表位置 /data/system/users/0/embedded_setting_config.xml

1
2
3
<setting name="com.yjs.android" embeddedEnable="true" />
<setting name="com.kwai.thanos" embeddedEnable="true" />
...

不需要太多解释,一看就懂。

应用配置列表有两个,一个在 /product/etc/embedded_rules_list.xml,是出厂配置,安卓 12 版的 MIUI 有效;另一个是 /data/system/cloudFeature_embedded_rules_list.xml,安卓 13 版的 MIUI 有效。

1
2
3
4
<package name="com.tencent.mm" fullRule="nra:cr:rcr" />
<package name="com.xunmeng.pinduoduo" activityRule="com.xunmeng.pinduoduo.login.LoginActivity,com.xunmeng.pinduoduo.activity.NewPageFoldActivity" clearTop="false" placeholder="com.xunmeng.pinduoduo.ui.activity.HomeActivity:com.xunmeng.pinduoduo.activity.MagicWindowActivity" relaunch="true" splitPairRule="com.xunmeng.pinduoduo.activity.NewPageFoldActivity:*,com.xunmeng.pinduoduo.ui.activity.HomeActivity:*" transitionRules="com.xunmeng.pinduoduo.activity.NewPageMaskActivity,com.xunmeng.pinduoduo.login.PhoneLoginActivity,com.xunmeng.pinduoduo.ui.activity.MainFrameActivity" />
<package name="com.baidu.searchbox" splitPairRule="com.baidu.searchbox.MainActivity:*" transitionRules="com.baidu.searchbox.SplashActivity,com.baidu.searchbox.CodeScannerActivity,com.baidu.searchbox.ugc.activity.LocalAlbumDelegateActivity,com.baidu.searchbox.permission.PermissionActivity" />
...

参数解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
name="" //包名
activityRule="" //全屏显示的activity,配置错误会导致平行视界无效!。推荐不配置
clearTop="false" //布尔值,右屏幕不杀死,多实例,默认为false。推荐不配置
placeholder=":" //占位用的activity,比如微信的右半屏。推荐不配置
relaunch="false" //布尔值,调整窗口时重加载。默认为false,推荐为"false"
splitPairRule=":" //左右分隔的activity,右侧可以表示成*。推荐不配置
transitionRules="" //相互覆盖的activity,类似于购物模式,用逗号隔开。推荐不配置
disableSensor="false" //布尔值,禁用传感器。推荐不配置
splitRatio="0.42" //左右分屏大小比例,左边比右边。推荐为"0.33",画面最好看。视频软件推荐小米平板设置为"0.42",红米平板设置为"0.46",折叠屏不设置,可以解决右侧视频全屏后无法退出的问题
isShowDialog="true" //显示应用适配对话框。推荐为"false"
sizecompatRatio="5:4" //大小兼容比例(安卓12有效),用于重绘activity,防止某些界面图标过大,配置错误会导致平行视界无效!推荐不配置
sizecompatRule="*" //哪些activity重绘大小(安卓12有效),配置错误会导致平行视界无效!推荐不配置
scaleMode="1" //大小兼容比例(安卓13有效),相当于上两句的和。如果界面图标和文字显示过大,可以配置为1
finishSecondaryWithPrimary="0" //true时右侧界面跟随左侧界面关闭。推荐不配置
fullRule="nra:cr:rcr" //竖屏时全屏而非分屏,在安卓13上不生效。"*"为无视方向始终开启全屏,"nr"大概率为仅竖屏下开启全屏。推荐不配置
middleRule="*" //开启信箱模式的方向。方向值同上,nra:cr:rcr,配置错误会导致平行视界无效!推荐不配置
supportCameraPreview="false" //是否支持在半屏预览拍照界面。推荐不配置
//2023年5月发布小米平板6后更新
minSupportVersion="1:20230413" //从哪天更新的APP版本后支持。巨离谱的写法,谁想出来的
procCompat="true" //如果厂商支持了平行视界则优先使用厂商方案,默认为"true"
//2023年8月发布小米平板6Max后更新
isShowDivider="true" //是否支持左右调节,默认为"true"。使用该参数建议设置splitRatio为0.3或0.7,得到不撕裂的体验
supportFullSize="true" //是否在支持左右调节的同时支持视频全屏,默认为"true"
flags="forceResumeOnFocus:*" //是否触屏跟随,也就是你点到哪半边屏幕就强制那半边运行

我们不难发现,官改版的配置文件比魔窗版复杂很多,可以详细配置 12L 官方的 embedding windows 中的几乎所有内容。但是这个适配工程量极大,所以就有了这个神奇的一幕:

image-20230212115043851

也就是说,MIUI 工程师在写了不到 73 个软件后,决定对其他软件不再手动写横屏配置了,就非常的简 (tou) 洁 (lan)。当然官方平行视界适配成本高(连购物模式都没有现成接口)也是一个原因。

MIUI 工程师为安卓 12 和安卓 13 版的 MIUI For Pad 共添加了约 1912 款应用,比安卓 11 的 MIUI For Pad 的 3300 余款少了接近一半。

添加自定义的软件,以及修改已有软件的配置,依旧只需要先在 embedded_setting_config.xml 中中添加一行声明,然后再在 embedded_rules_list.xml 配置中为它配置一个横屏方案即可。但是查找每个页面的 activity 工作量巨大,所以我们也可以用简 (tou) 洁 (lan) 的写法来实现平行视界,只给它配置 splitRadio 等几个简单参数即可。

比如在我的模块里,我写成这样:

1
2
3
4
5
6
7
8
9
<!-- MIUI_MagicWindow+ -->
<package name="com.taobao.taobao" />
<package name="com.tencent.mtt" />
<package name="com.ss.android.article.news" clearTop="true" splitRatio="0.33" />
<package name="com.snda.wifilocating" />
<package name="com.taobao.idlefish" />
<package name="com.alibaba.android.rimet" splitRatio="0.33" />
<package name="com.tmri.app.main" splitRatio="0.33" />
...

就可以快速的添加自己想增加平行视的应用了。

# 以下技术分享来自 @一只无言菌:

如果我们有时间也可以对一个软件进行精修,比如像这样:

1
<package name="tv.danmaku.bili" splitRatio="0.42" activityRule="tv.danmaku.bili.MainActivityV2" transitionRules="tv.danmaku.bili.MainActivityV2" splitPairRule="tv.danmaku.bili.MainActivityV2:*,com.bilibili.search.main.BiliMainSearchActivity:*,com.bilibili.bplus.followinglist.quick.consume.QuickConsumeActivity:*,tv.danmaku.bili.ui.videodownload.VideoDownloadListActivity:*,com.bilibili.lib.ui.GeneralActivity:*,tv.danmaku.bili.ui.favorite.FavoriteBoxActivity:*,tv.danmaku.bili.ui.main2.WatchLaterActivity:*,com.bilibili.lib.ui.GeneralActivity:*,com.bilibili.app.preferences.BiliPreferencesActivity:*,com.mall.ui.page.search.SearchFragmentLoadActivity:*" />
# 这里有一个奇技淫巧:

如果应用不支持 activityRule="",则可以考虑把首页和它能跳转到的下一个 activity 加入到 transitionRules 就可以实现类似 activityRule="" 的功能。以知乎 APP 为例:

1
<package name="com.zhihu.android" splitRatio="0.33" activityRule="com.zhihu.android.app.ebook.activity.EBookHostActivity,com.zhihu.android.app.ui.activity.MainActivity,com.zhihu.android.app.ui.activity.LauncherActivity" splitPairRule="com.zhihu.android.app.ui.activity.MainActivity:*,com.zhihu.android.app.SearchActivity:*" transitionRules="com.zhihu.android.app.ebook.activity.EBookHostActivity,com.zhihu.android.app.ui.activity.LaunchAdActivity,com.zhihu.android.app.ui.activity.TransActivity,com.zhihu.android.app.ui.activity.LauncherActivity,com.zhihu.android.panel.ui.attach.PanelAttachActivity,com.zhihu.android.app.ui.activity.SocialOauthActivity,com.zhihu.android.app.ui.activity.DealLoginActivity,com.zhihu.android.app.SearchActivity,com.zhihu.android.app.ui.activity.MainActivity,com.coolapk.market.view.contact.FriendListActivity:*" />

注意,只有将全屏的 Activity 放到 activityRule 和 transitionRules 里,才能保证该 Activity 基本不出现突然半屏显示的情况。

# 这里给出几个比较常见的模板供大家参考:

只有一个 Activity 怎么适配?

1
<package name="应用包名" activityRule="仅有的一个Activity" transitionRules="仅有的一个Activity" splitRatio="0.42" />

首页以及部分页面全屏,但是有的界面分屏怎么适配?

1
<package name="应用包名" activityRule="全屏的Activity" transitionRules="全屏的Activity" splitPairRule="全屏的Activirty:*" splitRatio="0.42" />

首页以及部分页面全屏,但是想要递进关系,怎么适配?

1
<package name="应用包名" activityRule="全屏的Activity" transitionRules="全屏的Activity" splitPairRule="首页的Activirty:界面1的Activirty,界面1的Activirty:界面2的Activirty,界面2的Activirty:界面3的Activirty" splitRatio="0.42" />

强制整个应用横屏怎么适配?

1
<package name="应用包名" scaleMode="1" fullRule="nra:cr:rcr" />

# 4. 如何打包到 ROM 包

解包文件对应分区,修改对应的文件,然后复制到原路径下覆盖原文件,重新打包分区即可。

对于安卓 11 版的 MIUI For Pad,需要覆盖以下两个文件。

/data/system/users/0/magic_window_setting_config.xml

/data/system/magicWindowFeature_magic_window_application_list.xml

对于安卓 12 / 安卓 13 版的 MIUI For Pad,需要覆盖以下三个文件。

/data/system/users/0/embedded_setting_config.xml

/product/etc/embedded_rules_list.xml

/data/system/cloudFeature_embedded_rules_list.xml

所有文件可以通过解包《完美横屏应用计划》模块获得。

# 5. 如何打包成模块

由于这些文件都不在 system 分区,所以传统模块的文件替换法并不会生效。这里我们采用 post-fs-data.sh 来实现这个功能。

在 post-fs-data.sh 中,只需要用简单的移动和复制命令即可实现对文件的替换。

在旧版的模块中,我们使用 mv 语句直接复制这个文件,但是这样会导致云控后文件失效。

1
2
3
4
5
6
7
8
9
10
# For Android 11
mv $MODDIR/common/system/users/0/magic_window_setting_config.xml /data/system/users/0/magic_window_setting_config.xml
mv $MODDIR/common/system/magicWindowFeature_magic_window_application_list.xml /data/system/magicWindowFeature_magic_window_application_list.xml

# For Android 12/Android13
mv $MODDIR/common/product/etc/embedded_rules_list.xml /product/etc/embedded_rules_list.xml
mv $MODDIR/common/product/etc/embedded_rules_list.xml /data/system/cloudFeature_embedded_rules_list.xml

# For Android 12
mv $MODDIR/common/system/users/0/embedded_setting_config.xml /data/system/users/0/embedded_setting_config.xml

以上文件会被 MIUI 服务器云控,所以我尝试一些方法来干掉云控。比如最简单的权限法,给这些文件的权限限制为 440,使 system 不再对它有修改的权限,以为从一定程度上解决云控的问题。事实证明云控直接替换文件,关闭它的写入权限几乎没有用。

1
2
3
4
5
6
7
# Disable Cloud Feature
chmod 440 /product/etc/embedded_rules_list.xml
chown system /product/etc/embedded_rules_list.xml
chmod 440 /data/system/cloudFeature_embedded_rules_list.xml
chown system /data/system/cloudFeature_embedded_rules_list.xml
chmod 440 /data/system/users/0/embedded_setting_config.xml
chown system /data/system/users/0/embedded_setting_config.xml

所以我们重新分析一下云控原理。在用户使用平板时,云控 APP 下载新的配置文件到你的平板电脑,在下次启动时加载这个新文件,实现云控。也就是说,我们只要赶在加载前,把文件替换掉,就可以了。新版模块里我的文件改成这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
# For Android 11
cp $MODDIR/common/system/users/0/magic_window_setting_config.xml /data/system/users/0/magic_window_setting_config.xml
cp $MODDIR/common/system/magicWindowFeature_magic_window_application_list.xml /data/system/magicWindowFeature_magic_window_application_list.xml

# For Android 12/Android13
rm /product/etc/embedded_rules_list.xml
cp -l $MODDIR/common/product/etc/embedded_rules_list.xml /product/etc/embedded_rules_list.xml
rm /data/system/cloudFeature_embedded_rules_list.xml
cp -l $MODDIR/common/product/etc/embedded_rules_list.xml /data/system/cloudFeature_embedded_rules_list.xml

# For Android 12
rm /data/system/users/0/embedded_setting_config.xml
cp -l $MODDIR/common/system/users/0/embedded_setting_config.xml /data/system/users/0/embedded_setting_config.xml

在每次系统启动前,用 cp -l 文件实现从模块内文件到配置文件的软链接,等待系统启动时,就会加载我们想要的它加载的文件,从而实现干掉云控。

当然这样也带来一个问题,如果需要修改这个配置文件,你需要修改的是模块内的文件而不是模块外的文件,路径为 /data/adb/modules/MIUI_MagicWindow+/common/

这样替换的文件不会在模块卸载时被替换回去,所以我们需要写一个 uninstall.sh,并在其中加入文件替换回去的命令,以及还原文件读者权限的命令(魔窗配置文件会在删除后重启自动生成,直接删除就行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Remove Files
rm /data/system/users/0/magic_window_setting_config.xml
rm /data/system/magicWindowFeature_magic_window_application_list.xml

# Move Back Files
rm /product/etc/embedded_rules_list.xml
cp $MODDIR/common/product/etc/embedded_rules_list_bak /product/etc/embedded_rules_list.xml
rm /data/system/cloudFeature_embedded_rules_list.xml
cp $MODDIR/common/product/etc/embedded_rules_list_bak /data/system/cloudFeature_embedded_rules_list.xml
rm /data/system/users/0/embedded_setting_config.xml

# Enable Cloud Feature
chmod 660 /product/etc/embedded_rules_list.xml
chown system /product/etc/embedded_rules_list.xml
chmod 660 /data/system/cloudFeature_embedded_rules_list.xml
chown system /data/system/cloudFeature_embedded_rules_list.xml
chmod 660 /data/system/users/0/embedded_setting_config.xml
chown system /data/system/users/0/embedded_setting_config.xml

至此,一个模块就写好了。

Author:御坂初琴
Link:https://ybcq.github.io/2023/02/12/%E3%80%90%E5%8E%9F%E5%88%9B%E6%95%99%E7%A8%8B%E3%80%91MIUI%E5%B9%B3%E8%A1%8C%E8%A7%86%E7%95%8C%E5%85%A8%E6%8E%A2%E7%B4%A2/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可
×