最近新买了RedMi K50和一加Ace Pro两台机器,一台是Android13,一台是Android12。以前UE4.26打包的程序,不论是刘海屏还是挖孔屏,都是可以全屏的。但安装到这两台机器上竟然不能全屏。正好是刘海和挖孔的位置被留了出来。
在UE4/UE5中,Android应用的全屏需要设置两个位置,如下图中红框部分。
第一, 最大宽高比默认是2.1,随着市场上屏幕越来越多,该值可能要放大,例如我就设置为了2.3.
第二,勾选是否使用cutout。通俗点讲,就是你的APP是否要使用刘海或者挖孔的部分。

设置完成之后在Android的Manifest文件中会生成下面两项:
<meta-data android:name="com.epicgames.ue4.GameActivity.bUseDisplayCutout" android:value="true" />
<meta-data android:name="android.max_aspect" android:value="2.30" /> 不出意外的话,你的App可以全屏了。
通过测试发现,相同的手机,用UE4.26和UE5打包结果完全不同。UE5不会有任何问题。但UE4.26在文中提到的两部手机就不会全屏显示了。
既然通过ProjectSetting修改的是上面提到的两项Manifest配置。那我们就打开Android Studio工程看看。差异是什么。
通过查看Android Studio工程代码,可以看到,是否使用刘海就在于下面这几行代码
if (UseDisplayCutout)
{
// will not be true if not Android Pie or later
WindowManager.LayoutParams params = getWindow().getAttributes();
params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(params);
}
所以,只要UseDisplayCutout为true,应该不会出问题吧?那就去找找该值在哪里设置的。
通过翻查代码,该值有两个地方设置
如果有闪屏界面,是通过闪屏界面传递给GameActivity
如果没有闪屏界面,该值是直接从Manifest获取的
if (SplashScreenLaunch == false && android.os.Build.VERSION.SDK_INT >= 28)
{
if(bundle.containsKey("com.epicgames.unreal.GameActivity.bUseDisplayCutout"))
{
UseDisplayCutout = bundle.getBoolean("com.epicgames.unreal.GameActivity.bUseDisplayCutout");
Log.debug( "Display cutout set to " + UseDisplayCutout);
}
else
{
Log.debug( "Display cutout not found. Leaving as " + UseDisplayCutout);
}
}
_extrasBundle = getIntent().getExtras();
if (_extrasBundle != null)
{
ShouldHideUI = _extrasBundle.getString("ShouldHideUI") != null;
UseDisplayCutout = _extrasBundle.getString("UseDisplayCutout") != null;
} 那闪屏界面又是从哪里获取的呢?截取部分闪屏界面代码,可以看到同样是从Manifest获取的。并且还加了一些手机型号的判断,进而最终确认。
try {
ApplicationInfo ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
Bundle bundle = ai.metaData;
if(bundle.containsKey("com.epicgames.unreal.GameActivity.bUseDisplayCutout"))
{
UseDisplayCutout = bundle.getBoolean("com.epicgames.unreal.GameActivity.bUseDisplayCutout");
}
}
catch (NameNotFoundException | NullPointerException e)
{
Log.error("Error when accessing application metadata", e);
}
// allow certain models for now to use full area around cutout
boolean BlockDisplayCutout = android.os.Build.VERSION.SDK_INT < 30;
if (android.os.Build.MANUFACTURER.equals("HUAWEI"))
{
BlockDisplayCutout = false;
}
else if (android.os.Build.MANUFACTURER.equals("HMD Global"))
{
String model = android.os.Build.MODEL;
if (model.equals("Nokia 8.1"))
{
BlockDisplayCutout = false;
}
}
else if (android.os.Build.MANUFACTURER.equals("samsung"))
{
String model = android.os.Build.MODEL;
if (model.startsWith("SM-G970") || model.startsWith("SM-G973") || model.startsWith("SM-G975") ||
model.startsWith("SC-03L") || model.startsWith("SCV41") || model.startsWith("SC-04L") ||
model.startsWith("SCV42") || model.startsWith("SM-N97") || model.startsWith("SM-F700") ||
model.startsWith("SM-G98") || model.startsWith("SCV47") || model.startsWith("SCG01") ||
model.startsWith("SCG02") || model.startsWith("SC-51A") || model.startsWith("SC-52A") ||
android.os.Build.VERSION.SDK_INT >= 28)
{
BlockDisplayCutout = false;
}
}
else if (android.os.Build.MANUFACTURER.equals("Xiaomi"))
{
String model = android.os.Build.MODEL;
if (model.startsWith("POCOPHONE F1"))
{
BlockDisplayCutout = false;
}
}
else if (android.os.Build.MANUFACTURER.equals("OnePlus"))
{
String model = android.os.Build.MODEL;
if (model.startsWith("KB2000") || model.startsWith("KB2001") || model.startsWith("KB2003") ||
model.startsWith("KB2005") || model.startsWith("KB2007") || model.startsWith("LE2110") ||
model.startsWith("LE2111") || model.startsWith("LE2113") || model.startsWith("LE2115") ||
model.startsWith("LE2117") || model.startsWith("LE2119") || model.startsWith("LE2100") ||
model.startsWith("LE2101") || model.startsWith("LE2120") || model.startsWith("LE2121") ||
model.startsWith("LE2123") || model.startsWith("LE2125") || model.startsWith("LE2127") ||
model.startsWith("IN2020") || model.startsWith("IN2021") || model.startsWith("IN2023") ||
model.startsWith("IN2025") || model.startsWith("IN2010") || model.startsWith("IN2011") ||
model.startsWith("IN2013") || model.startsWith("IN2015") || model.startsWith("IN2017") ||
model.startsWith("IN2019") || model.startsWith("AC2001") || model.startsWith("AC2003") ||
model.startsWith("BE2025") || model.startsWith("BE2026") || model.startsWith("BE2028") ||
model.startsWith("BE2029"))
{
BlockDisplayCutout = false;
}
}
if (BlockDisplayCutout)
{
UseDisplayCutout = false;
}
if (UseDisplayCutout)
{
// only do this on Android Pie and above
if (android.os.Build.VERSION.SDK_INT >= 28)
{
WindowManager.LayoutParams params = getWindow().getAttributes();
params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(params);
}
else
{
UseDisplayCutout = false;
}
} 这段代码UE4和UE5是有区别的。UE5默认值是通过boolean BlockDisplayCutout = android.os.Build.VERSION.SDK_INT < 30;判断,但UE4是默认为true,后面再通过手机型号判断是否需要忽略Cutout的配置。
从代码中可以看到,闪屏同样会设置是否使用刘海后者挖孔。
如果你的项目就是UE4开发的,并且不想升级到UE5。毕竟升级是有代价和风险的。那么可以尝试这两种方法。
通过UPL修改为全屏
通过修改闪屏界面代码
这种方式需要你对UPL了解,前面提到一个教程会告诉你如何在UE4、UE5中进行Android开发。
前面提到了全屏关键的几行代码,我们只需要在UPL里将那几行代码插入即可。但只能修改GameActivity,闪屏界面还是不能全屏,所以在项目设置里取消显示闪屏界面。自己开发一个闪屏界面。
然后在UPL文件中插入以下代码,将java代码插入到GameActivity的OnCreate中。这样游戏界面就可以全屏了。
&lt;!--添加代码到OnCreate函数的Super后面--&gt;
&lt;gameActivityOnCreateAdditions&gt;
&lt;insert&gt;
try {
String packageName = getPackageName();
PackageManager pm = getPackageManager();
ApplicationInfo ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
Bundle bundle = ai.metaData;
if(bundle.containsKey(&quot;com.epicgames.ue4.GameActivity.bUseDisplayCutout&quot;))
{
UseDisplayCutout = bundle.getBoolean(&quot;com.epicgames.ue4.GameActivity.bUseDisplayCutout&quot;);
}
}
catch (NameNotFoundException | NullPointerException e)
{
Log.error(&quot;Error when accessing application metadata&quot;, e);
}
if (UseDisplayCutout)
{
// will not be true if not Android Pie or later
WindowManager.LayoutParams paramsA = getWindow().getAttributes();
paramsA.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(paramsA);
}
&lt;/insert&gt;
&lt;/gameActivityOnCreateAdditions&gt; 这种方法有个缺陷,就是前面提到的闪屏界面需要特殊处理。(如果你会接入SDK,那这也不是个事)
这种方法比较直接和粗暴,但效果甚好。UE4、UE5打包Android生成了这么多代码,总有地方去修改这些代码吧。找到他们不就好了。

生成的Android工程代码
不论是GameApplication也好,GameActivity也好。还是SplashActivity。他们都是在UBT的时候生成。在UBT里有一份Android打包的代码UEBuildAndroid.cs、UEDeployAndroid.cs有兴趣可以去翻翻。
这里直接说结果,Engine\Build\Android\Java\src\com\epicgames\ue4到这里取找SplashActivity可以看到,就是完整的代码,并不是模板。那我们将该代码中关于是否忽略UseDisplayCutout的代码修改成与UE5一样就可以了。boolean BlockDisplayCutout = android.os.Build.VERSION.SDK_INT < 30;

这种方法修改了引擎原本的内容,一定要记好。不然更新或升级的时候出Bug就不好了。
重新打包试试,问题完美解决。