在Flutter項(xiàng)目遇到Release模式Android會(huì)黑屏的問題,最大的概率可能是在Flutter項(xiàng)目中使用相關(guān)屏幕適配有關(guān)
在Flutter項(xiàng)目開發(fā)中,界面的適配是一個(gè)常見的問題。設(shè)計(jì)師通常會(huì)提供設(shè)計(jì)稿的尺寸,但是在不同的設(shè)備上展示可能會(huì)出現(xiàn)布局錯(cuò)亂或者顯示不完整的情況。為了解決這個(gè)問題,我們可以使用第三方庫screen_ratio_adapter,它可以幫助我們在界面開發(fā)中直接使用設(shè)計(jì)稿的尺寸,并自動(dòng)適配不同的設(shè)備尺寸。
screen_ratio_adapter庫的作用類似于Spring框架中的AOP(面向切面編程)方式。它允許我們在不修改原有代碼的情況下實(shí)現(xiàn)屏幕適配,而且不會(huì)影響我們的代碼邏輯。
你可以在以下地址找到該庫的源代碼和詳細(xì)文檔:https://github.com/D-meng/screen_ratio_adapter
我們的項(xiàng)目是一個(gè)舊項(xiàng)目的維護(hù)工作,其中使用了screen_ratio_adapter庫來實(shí)現(xiàn)屏幕適配。由于項(xiàng)目規(guī)模較大,并且出于時(shí)間和資源的考慮,我們無法一次性替換掉整個(gè)庫,因此需要繼續(xù)使用它。
然而,在升級至Flutter 3.3.9版本時(shí),我們遇到了一個(gè)奇怪的問題。在Android端,debug模式下運(yùn)行正常,但是如果將應(yīng)用打包成release包,并在Android設(shè)備上安裝后打開應(yīng)用,則只會(huì)顯示黑屏。
問題分析
經(jīng)過分析,我們發(fā)現(xiàn)這個(gè)問題是由于Flutter新版本為了加快啟動(dòng)速度,修改了渲染邏輯。在屏幕尺寸還為空時(shí),即在應(yīng)用程序啟動(dòng)的早期階段,F(xiàn)lutter會(huì)嘗試渲染頁面。而我們使用的screen_ratio_adapter庫需要獲取屏幕尺寸以進(jìn)行比例縮放,因此在應(yīng)用程序啟動(dòng)時(shí)就會(huì)嘗試獲取window.physicalSize的值。然而,在Dart代碼運(yùn)行之前,視圖的大小可能是未知的。如果在應(yīng)用程序生命周期的早期階段觀察到該值,可能會(huì)返回[Size.zero],導(dǎo)致界面無法正常渲染,從而導(dǎo)致黑屏的問題。
解決方案
為了解決這個(gè)問題,我們可以利用Flutter提供的window.onMetricsChanged方法來監(jiān)聽physicalSize的變化,并在有值時(shí)進(jìn)行啟動(dòng)界面的渲染。具體的解決方案如下:
///設(shè)計(jì)稿尺寸,單位應(yīng)是pt或dp
final _uiSize = BlueprintsRectangle(375, 667);
Future<void> main() async {
if(window.physicalSize.isEmpty){
window.onMetricsChanged = () async {
//在回調(diào)中,size仍然有可能是0
if(!window.physicalSize.isEmpty){
window.onMetricsChanged = null;
await waitScreenSizeAvailable(); //引入上面那段代碼
runMyAPP();
}
};
} else{
//如果size非0,則直接runApp
await waitScreenSizeAvailable(); //引入上面那段代碼
runMyAPP();
}
}
Future<void> waitScreenSizeAvailable() async {
FxWidgetsFlutterBinding.ensureInitialized(uiBlueprints: _uiSize);
if (!_hasScreenSize) {
// WidgetsFlutterBinding.ensureInitialized();
var observer = _Observer();
WidgetsBinding.instance.addObserver(observer);
await observer.hasScreenSize;
WidgetsBinding.instance.removeObserver(observer);
}
}
bool get _hasScreenSize => !window.physicalSize.isEmpty;
class _Observer extends WidgetsBindingObserver {
final _screenSizeCompleter = Completer<void>();
Future<void> get hasScreenSize => _screenSizeCompleter.future;
@override
void didChangeMetrics() {
if (!_screenSizeCompleter.isCompleted && _hasScreenSize) {
_screenSizeCompleter.complete();
}
}
}
void runMyAPP(){
runApp(MyApp());
}