天天看點

Flutter啟動頁(閃屏頁)的具體實作及原理詳析

Flutter啟動頁(閃屏頁)的具體實作及原理詳析

為什麼要有啟動頁?

在以下文章中,啟動頁就是閃屏頁。

現在大部分App都有啟動頁,那麼為什麼要有啟動頁?這是個值得思考的問題,如果沒有啟動頁會怎樣,大部分的App會白屏(也有可能是黑屏,主題設定有關系)非常短的時間,然後才能展示App的内容。

那麼問題來了,一定要有啟動頁嗎?答案:不是,而且是盡可能不要有啟動頁,因為啟動頁會讓使用者體驗不夠連貫,甚至IOS在開發手冊上就不推薦使用啟動頁。

我們深入思考一下,既然不推薦為什麼這樣流行,答案非常簡單,啟動頁的成本非常低,如果你想把的App啟動優化到一個非常短的時間,還是有一定成本的。

Android啟動流程

為什麼要談Android的啟動流程呢?因為Flutter啟動的時候,依賴的是Android的運作環境,其本質是Activity上添加了一個FlutterView,FlutterView繼承SurfaceView,那麼就容易了解了,Flutter的全部頁面都是渲染到了FlutterView上,如果不熟悉Flutter的啟動流程可以參考Flutter啟動流程 這篇文章,下面是對Flutter啟動的一個簡單描述。

Flutter啟動頁(閃屏頁)的具體實作及原理詳析

在Flutter中,啟動頁的作用是在FlutterView顯示第一幀之前,不要出現白屏,在FlutterView顯示第一幀之前,我們分成兩個階段,Android啟動階段和Flutter啟動階段,Android啟過程添加啟動頁非常容易,在主題xml中添加android:windowBackground屬性,Flutter怎麼添加啟動頁呢?其實架構已經幫助咱們實作好了,我下面就給大家說一下原理。

Flutter啟動頁具體實作和原理

建立一個SplashActivity,這Activity繼承FlutterActivity,重寫onCreate()方法,在onCreate()方法中調用

GeneratedPluginRegistrant.registerWith()

,下面是啟動頁的代碼。

public class SplashActivity extends FlutterActivity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 GeneratedPluginRegistrant.registerWith(this);
 }
}           

複制

在Manifest中添加SplashActivity作為App的啟動Activity,設定SplashActivity的主題是LaunchTheme。下面是Manifest的配置檔案。

<activity
 android:name=".SplashActivity"
 android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
 android:hardwareAccelerated="true"
 android:launchMode="singleTop"
 android:theme="@style/LaunchTheme"
 android:windowSoftInputMode="adjustResize" 
 <meta-data
 android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
 android:value="true" / 
 <intent-filter 
 <action android:name="android.intent.action.MAIN" / 
 <category android:name="android.intent.category.LAUNCHER" / 
 </intent-filter 
 </activity            

複制

meta-data的

name = "io.flutter.app.android.SplashScreenUntilFirstFrame"

的value一定要設定成true,一定要設定成true,一定要設定成true重要的事情說三遍,如果這個屬性設定成false,效果是這樣的。

Flutter啟動頁(閃屏頁)的具體實作及原理詳析

從現象觀察,啟動頁中間有一段時間黑屏,這個為什麼呢?前面我們說過,Flutter的啟動流程分成兩部分,一部分是Android啟動階段,一個是Flutter的啟動階段,這個黑屏就是Flutter的啟動階段沒有啟動頁所造成的。我們從源碼入手,詳細分析一下,下面是FlutterActivityDelegate的部分源碼。

public final class FlutterActivityDelegate
 implements FlutterActivityEvents,
  FlutterView.Provider,
  PluginRegistry {
 private static final String SPLASH_SCREEN_META_DATA_KEY = "io.flutter.app.android.SplashScreenUntilFirstFrame";

 private View launchView;

 @Override
 public void onCreate(Bundle savedInstanceState) {
 String[] args = getArgsFromIntent(activity.getIntent());
 FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), args);
 flutterView = viewFactory.createFlutterView(activity);
 if (flutterView == null) {
 FlutterNativeView nativeView = viewFactory.createFlutterNativeView();
 flutterView = new FlutterView(activity, null, nativeView);
 flutterView.setLayoutParams(matchParent);
 activity.setContentView(flutterView);
 launchView = createLaunchView();//1
 if (launchView != null) {
 addLaunchView();//2
 }
 }
 }

 private View createLaunchView() {
 if (!showSplashScreenUntilFirstFrame()) {//3
 return null;
 }
 final Drawable launchScreenDrawable = getLaunchScreenDrawableFromActivityTheme();
 final View view = new View(activity);
 view.setBackground(launchScreenDrawable);
 return view;
 }

 private Drawable getLaunchScreenDrawableFromActivityTheme() {
 //省略了部分代碼
 try {
 return activity.getResources().getDrawable(typedValue.resourceId);
 } catch (NotFoundException e) {
 return null;
 }
 }

 private Boolean showSplashScreenUntilFirstFrame() {
 try {
 ActivityInfo activityInfo = activity.getPackageManager().getActivityInfo(
 activity.getComponentName(),
 PackageManager.GET_META_DATA|PackageManager.GET_ACTIVITIES);
 Bundle metadata = activityInfo.metaData;
 return metadata != null && metadata.getBoolean(SPLASH_SCREEN_META_DATA_KEY);
 } catch (NameNotFoundException e) {
 return false;
 }
 }

 private void addLaunchView() {
 activity.addContentView(launchView, matchParent);//4
 flutterView.addFirstFrameListener(new FlutterView.FirstFrameListener() {//5
 @Override
 public void onFirstFrame() {
 FlutterActivityDelegate.this.launchView.animate()
  .alpha(0f)
  .setListener(new AnimatorListenerAdapter() {
  @Override
  public void onAnimationEnd(Animator animation) {
  ((ViewGroup) FlutterActivityDelegate.this.launchView.getParent())
  .removeView(FlutterActivityDelegate.this.launchView);//5
  }
  });
 }
 });
 activity.setTheme(android.R.style.Theme_Black_NoTitleBar);
 }
}           

複制

注釋1

這個段代碼很容易了解,建立一個LaunchView,主要邏輯在createLaunchView()中,原理也很簡單,根據主題中的

R.attr.windowBackground

屬性,生成一個Drawable,然後建立了一個View,并且把這個View的背景設定成Drawable。

注釋3

showSplashScreenUntilFirstFrame()是得到Manifet中

io.flutter.app.android.SplashScreenUntilFirstFrame

的屬性的值,如果是false,那麼久傳回一個空的的LaunchView,也就不會執行注釋2的代碼。這就是我們上面說的如果設定成false就顯示黑屏的原因。

注釋2

調用addLaunchView(),這方法也很簡單,首先看注釋4,把LaunchView添加到目前的Activity中,然後添加了一個監聽,在注釋5處,這個監聽是當FlutterView第一幀加載完成後回調,回調做了什麼事情呢?很簡單,把LaunchView删除了,顯示FlutterView的第一幀。

總結一下,就是把Android的啟動頁生成一個Drawable,建立了一個LaunchView,把Drawable設定成LaunchView的背景,目前的Activity添加這LaunchView,如果FlutterView的第一幀顯示了,把LaunchView删除。

設定主題,下面是LaunchTheme的代碼。

<resources 
 <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar" 
 <!-- Show a splash screen on the activity. Automatically removed when
 Flutter draws its first frame -- 
 <item name="android:windowBackground" @drawable/launch_background</item 
 </style 
</resources            

複制

下面是launch_background的代碼。

<?xml version="1.0" encoding="utf-8"? 
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
 android:opacity="opaque" 
 <item 
 <bitmap android:src="@mipmap/ic_launch_bg" / 
 </item 
 <item
 android:width="90dp"
 android:height="90dp"
 android:gravity="center" 
 <bitmap android:src="@mipmap/ic_launch_logo" / 
 </item 
</layer-list            

複制

最終效果如下,沒有黑屏,非常順滑。

Flutter啟動頁(閃屏頁)的具體實作及原理詳析

總結

以上就是這篇文章的全部内容了,希望本文的内容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對ZaLou.Cn的支援。