FlutterApp啟動流程分析

拙峰發表於2020-06-09

程式碼是基於io.flutter.embedding.android包下,看網上好多程式碼都是基於io.flutter.app目錄下的。其實在flutter1.12之後就建議使用前者了。

下面講的是純Flutter專案,不是混合開發,混合的話是有區別的。以Android應用為例,啟動還是先走Application和指定作為Launcher的Activity。而系統預設的會使用繼承FlutterAppication的一個Application和繼承FlutterActivity的MainActivity。所以app的啟動可以先分析下FlutterApplication和FlutterActivity 。

FutterApplication

package io.flutter.app;

import android.app.Activity;
import android.app.Application;
import androidx.annotation.CallSuper;
import io.flutter.view.FlutterMain;

/**
 * Flutter implementation of {@link android.app.Application}, managing application-level global
 * initializations.
 *
 * <p>Using this {@link android.app.Application} is not required when using APIs in the package
 * {@code io.flutter.embedding.android} since they self-initialize on first use.
 */
public class FlutterApplication extends Application {
  @Override
  @CallSuper
  public void onCreate() {
    super.onCreate();
    FlutterMain.startInitialization(this);
  }

  private Activity mCurrentActivity = null;

  public Activity getCurrentActivity() {
    return mCurrentActivity;
  }

  public void setCurrentActivity(Activity mCurrentActivity) {
    this.mCurrentActivity = mCurrentActivity;
  }
}

複製程式碼

FlutterApplication是繼承於Application,主要是在onCreate()中調了FlutterMain.startInitialization(this)

FlutterMain

/**
 * A legacy class to initialize the Flutter engine.
 *
 * <p>Replaced by {@link io.flutter.embedding.engine.loader.FlutterLoader}.
 */
public class FlutterMain {

 ...
	
}
複製程式碼

看上面的註釋,一個初始化FLutter引擎的舊類,現在被FlutterLoader給替換了。我們看裡面程式碼,確實FlutterMain就是一個空殼,具體都是呼叫FLutterLoader實現的。 初始化:


  
  public static void startInitialization(
      @NonNull Context applicationContext, @NonNull Settings settings) {
    if (isRunningInRobolectricTest) {
      return;
    }
    FlutterLoader.Settings newSettings = new FlutterLoader.Settings();
    newSettings.setLogTag(settings.getLogTag());
    FlutterLoader.getInstance().startInitialization(applicationContext, newSettings);
  }
複製程式碼

這方法將載入Flutter引擎的本機庫以啟用後續的JNI呼叫。也開始查詢和解壓縮應用程式APK中打包的Dart資源。

FlutterLoader

我們看下FlutterLoader的startInitialization方法,其實它主要做了以下幾件事:

  • 初始化配置
  • 初始化資源
  • 載入Flutter.so
  • 註冊VsyncWatcher
  • 記錄初始化的耗時

下面看下原始碼

 
  public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
    // Do not run startInitialization more than once.
    if (this.settings != null) {
      return;
    }
    if (Looper.myLooper() != Looper.getMainLooper()) {
      throw new IllegalStateException("startInitialization must be called on the main thread");
    }

    // Ensure that the context is actually the application context.
    applicationContext = applicationContext.getApplicationContext();

    this.settings = settings;

    long initStartTimestampMillis = SystemClock.uptimeMillis();
    initConfig(applicationContext);
    initResources(applicationContext);

    System.loadLibrary("flutter");

    VsyncWaiter.getInstance(
            (WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE))
        .init();

    // We record the initialization time using SystemClock because at the start of the
    // initialization we have not yet loaded the native library to call into dart_tools_api.h.
    // To get Timeline timestamp of the start of initialization we simply subtract the delta
    // from the Timeline timestamp at the current moment (the assumption is that the overhead
    // of the JNI call is negligible).
    long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
    FlutterJNI.nativeRecordStartTimestamp(initTimeMillis);
  }

複製程式碼

可以看到方法註解跟FlutterMain一毛一樣。

	// Do not run startInitialization more than once.
    if (this.settings != null) {
      return;
    }
複製程式碼

判斷保證了FlutterMain.startInitialization無法多次呼叫。因為之前呼叫會設定settings。

 FlutterLoader.Settings newSettings = new FlutterLoader.Settings();
 newSettings.setLogTag(settings.getLogTag());
複製程式碼

下面一行的程式碼要求Flutter引擎初始化必選在主執行緒

 if (Looper.myLooper() != Looper.getMainLooper()) {
      throw new IllegalStateException("startInitialization must be called on the main thread");
    }
複製程式碼

初始化配置

從Manifest.xml中初始化一些引數

/**
   * Initialize our Flutter config values by obtaining them from the manifest XML file, falling back
   * to default values.
   */
  private void initConfig(@NonNull Context applicationContext) {
    Bundle metadata = getApplicationInfo(applicationContext).metaData;

    // There isn't a `<meta-data>` tag as a direct child of `<application>` in
    // `AndroidManifest.xml`.
    if (metadata == null) {
      return;
    }

    aotSharedLibraryName =
        metadata.getString(PUBLIC_AOT_SHARED_LIBRARY_NAME, DEFAULT_AOT_SHARED_LIBRARY_NAME);
    flutterAssetsDir =
        metadata.getString(PUBLIC_FLUTTER_ASSETS_DIR_KEY, DEFAULT_FLUTTER_ASSETS_DIR);

    vmSnapshotData = metadata.getString(PUBLIC_VM_SNAPSHOT_DATA_KEY, DEFAULT_VM_SNAPSHOT_DATA);
    isolateSnapshotData =
        metadata.getString(PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY, DEFAULT_ISOLATE_SNAPSHOT_DATA);
  }
複製程式碼

如果不設定的話都是預設引數

/** Finds Flutter resources in an application APK and also loads Flutter's native library. */
public class FlutterLoader {
  private static final String TAG = "FlutterLoader";

  // Must match values in flutter::switches
  private static final String AOT_SHARED_LIBRARY_NAME = "aot-shared-library-name";
  private static final String SNAPSHOT_ASSET_PATH_KEY = "snapshot-asset-path";
  private static final String VM_SNAPSHOT_DATA_KEY = "vm-snapshot-data";
  private static final String ISOLATE_SNAPSHOT_DATA_KEY = "isolate-snapshot-data";
  private static final String FLUTTER_ASSETS_DIR_KEY = "flutter-assets-dir";

  // XML Attribute keys supported in AndroidManifest.xml
  private static final String PUBLIC_AOT_SHARED_LIBRARY_NAME =
      FlutterLoader.class.getName() + '.' + AOT_SHARED_LIBRARY_NAME;
  private static final String PUBLIC_VM_SNAPSHOT_DATA_KEY =
      FlutterLoader.class.getName() + '.' + VM_SNAPSHOT_DATA_KEY;
  private static final String PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY =
      FlutterLoader.class.getName() + '.' + ISOLATE_SNAPSHOT_DATA_KEY;
  private static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY =
      FlutterLoader.class.getName() + '.' + FLUTTER_ASSETS_DIR_KEY;

  // 用於預編譯快照元件的資源名稱。
  private static final String DEFAULT_AOT_SHARED_LIBRARY_NAME = "libapp.so";
  private static final String DEFAULT_VM_SNAPSHOT_DATA = "vm_snapshot_data";
  private static final String DEFAULT_ISOLATE_SNAPSHOT_DATA = "isolate_snapshot_data";
  private static final String DEFAULT_LIBRARY = "libflutter.so";
  private static final String DEFAULT_KERNEL_BLOB = "kernel_blob.bin";
  private static final String DEFAULT_FLUTTER_ASSETS_DIR = "flutter_assets";

 // Mutable because default values can be overridden via config properties
  private String aotSharedLibraryName = DEFAULT_AOT_SHARED_LIBRARY_NAME;
  private String vmSnapshotData = DEFAULT_VM_SNAPSHOT_DATA;
  private String isolateSnapshotData = DEFAULT_ISOLATE_SNAPSHOT_DATA;
  private String flutterAssetsDir = DEFAULT_FLUTTER_ASSETS_DIR;
...

}
複製程式碼

其實initConfig()就是給aotSharedLibraryName,vmSnapshotData,isolateSnapshotData,flutterAssetsDir這四個變數賦值,通過命名我們能大體猜出這四個變數代表了什麼。 aotSharedLibraryName看意思就是通過aot打成的二級制包。預設名是libapp.so,其實裡面就是Flutter專案中通過Dart實現的業務程式碼,現在被打成so庫。 flutterAssetsDir應該是asset的路徑,通過該路徑可以找到asset中資原始檔。 其他兩個看名字就是和虛擬機器還有Isolate相關。

初始化資源

下面看資源初始化的方法:initResources()

 /** Extract assets out of the APK that need to be cached as uncompressed files on disk. */
  private void initResources(@NonNull Context applicationContext) {
    new ResourceCleaner(applicationContext).start();

    if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
      final String dataDirPath = PathUtils.getDataDirectory(applicationContext);
      final String packageName = applicationContext.getPackageName();
      final PackageManager packageManager = applicationContext.getPackageManager();
      final AssetManager assetManager = applicationContext.getResources().getAssets();
      resourceExtractor =
          new ResourceExtractor(dataDirPath, packageName, packageManager, assetManager);

      // In debug/JIT mode these assets will be written to disk and then
      // mapped into memory so they can be provided to the Dart VM.
      resourceExtractor
          .addResource(fullAssetPathFrom(vmSnapshotData))
          .addResource(fullAssetPathFrom(isolateSnapshotData))
          .addResource(fullAssetPathFrom(DEFAULT_KERNEL_BLOB));

      resourceExtractor.start();
    }
  }
複製程式碼

先清了下資源,然後載入了Asset中的資源到記憶體這樣Dart虛擬機器才能使用。Flutter中的圖片字型等資源在打包後都會被放置在asset目錄下。vmSnapshotData,isolateSnapshotData初始化的變數在這用到了。但是,這個是DEBUG或者JIT模式下的方法,所以說release包載入資源的方式應該是不一樣的

載入Flutter.so

    System.loadLibrary("flutter");
複製程式碼

這裡是載入二進位制的庫,預設全名應該是libFlutter.so,主要是執行時環境。

註冊VsyncWatcher

	VsyncWaiter.getInstance(
            (WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE))
        .init();
複製程式碼

這個還沒細看,應該是註冊後每次有脈衝就會接到通知,正常是每秒60次吧(這是猜測的哈)。

記錄初始化的耗時

long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
    FlutterJNI.nativeRecordStartTimestamp(initTimeMillis);
複製程式碼

可以看到FlutterApplica基本上就是初始化一些配置,載入了資源和Flutter.so。

FlutterActivity

public class FlutterActivity extends Activity
    implements FlutterActivityAndFragmentDelegate.Host, LifecycleOwner {
    
	 ...
	 
  // Delegate that runs all lifecycle and OS hook logic that is common between
  // FlutterActivity and FlutterFragment. See the FlutterActivityAndFragmentDelegate
  // implementation for details about why it exists.
  @VisibleForTesting protected FlutterActivityAndFragmentDelegate delegate;

  @NonNull private LifecycleRegistry lifecycle;

  public FlutterActivity() {
    lifecycle = new LifecycleRegistry(this);
  }

  
  @VisibleForTesting
  /* package */ void setDelegate(@NonNull FlutterActivityAndFragmentDelegate delegate) {
    this.delegate = delegate;
  }

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    switchLaunchThemeForNormalTheme();

    super.onCreate(savedInstanceState);

    lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);

    delegate = new FlutterActivityAndFragmentDelegate(this);
    delegate.onAttach(this);
    delegate.onActivityCreated(savedInstanceState);

    configureWindowForTransparency();
    setContentView(createFlutterView());
    configureStatusBarForFullscreenFlutterExperience();
  }
  
...

}
複製程式碼

看下FlutterActivity的onCreate()中其實就兩個比較重要的步驟。繫結DelegatecreateFlutterView

	setContentView(createFlutterView());
複製程式碼

FlutterActivityAndFragmentDelegate

FlutterActivity的onCreate()總共FlutterActivityAndFragmentDelegate總共做了三步:

  • 建立Delegate,
  • 繫結
  • onActivityCreated。

    delegate = new FlutterActivityAndFragmentDelegate(this);
    delegate.onAttach(this);
    delegate.onActivityCreated(savedInstanceState);

複製程式碼

構造Delegate

看下它的建構函式:

FlutterActivityAndFragmentDelegate(@NonNull Host host) {
    this.host = host;
  }
複製程式碼

傳入了一個Host,這個Host被FlutterActivity給實現:

public class FlutterActivity extends Activity
    implements FlutterActivityAndFragmentDelegate.Host, LifecycleOwner 
複製程式碼

從FlutterActivity的繼承和實現可以看出,FlutterActivity除了Activity和getLifecycle等方法,其他的都是對Host的實現。也可以理解為針對Flutter相關的操作都定義在Host中了。

/**
   * The {@link FlutterActivity} or {@link FlutterFragment} that owns this {@code
   * FlutterActivityAndFragmentDelegate}.
   */
  /* package */ interface Host
      extends SplashScreenProvider, FlutterEngineProvider, FlutterEngineConfigurator {
複製程式碼

大體有一下

FlutterApp啟動流程分析

onAttach

該方法原始碼如下:

/**
   * Invoke this method from {@code Activity#onCreate(Bundle)} or {@code
   * Fragment#onAttach(Context)}.
   *
   * <p>This method does the following:
   *
   * <p>
   *
   * <ol>
   *   <li>Initializes the Flutter system.
   *   <li>Obtains or creates a {@link FlutterEngine}.
   *   <li>Creates and configures a {@link PlatformPlugin}.
   *   <li>Attaches the {@link FlutterEngine} to the surrounding {@code Activity}, if desired.
   *   <li>Configures the {@link FlutterEngine} via {@link
   *       Host#configureFlutterEngine(FlutterEngine)}.
   * </ol>
   */
  void onAttach(@NonNull Context context) {
    ensureAlive();

    // When "retain instance" is true, the FlutterEngine will survive configuration
    // changes. Therefore, we create a new one only if one does not already exist.
    if (flutterEngine == null) {
      setupFlutterEngine();
    }

    // Regardless of whether or not a FlutterEngine already existed, the PlatformPlugin
    // is bound to a specific Activity. Therefore, it needs to be created and configured
    // every time this Fragment attaches to a new Activity.
    // TODO(mattcarroll): the PlatformPlugin needs to be reimagined because it implicitly takes
    //                    control of the entire window. This is unacceptable for non-fullscreen
    //                    use-cases.
    platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);

    if (host.shouldAttachEngineToActivity()) {
      // Notify any plugins that are currently attached to our FlutterEngine that they
      // are now attached to an Activity.
      //
      // Passing this Fragment's Lifecycle should be sufficient because as long as this Fragment
      // is attached to its Activity, the lifecycles should be in sync. Once this Fragment is
      // detached from its Activity, that Activity will be detached from the FlutterEngine, too,
      // which means there shouldn't be any possibility for the Fragment Lifecycle to get out of
      // sync with the Activity. We use the Fragment's Lifecycle because it is possible that the
      // attached Activity is not a LifecycleOwner.
      Log.v(TAG, "Attaching FlutterEngine to the Activity that owns this Fragment.");
      flutterEngine
          .getActivityControlSurface()
          .attachToActivity(host.getActivity(), host.getLifecycle());
    }

    host.configureFlutterEngine(flutterEngine);
  }

複製程式碼

該方法會在Activity的onCreate()和Fragment的onAttach()方法中呼叫。這個方法做了以下操作:

  • 初始化了Flutter系統
  • 獲取或者建立了Flutter引擎
  • 建立並配置了PlatformPlugin
  • 如果需要,將Flutter引擎附加到周圍的 Activity
  • 通過configureFlutterEngine(FlutterEngine)來配置Flutter引擎
獲取或者建立Flutter引擎

在確保當前delegate沒有被釋放並且當前delegate沒有引擎的情況下,我們開發設定一個引擎。

	 ensureAlive();

    // When "retain instance" is true, the FlutterEngine will survive configuration
    // changes. Therefore, we create a new one only if one does not already exist.
    if (flutterEngine == null) {
      setupFlutterEngine();
    }
複製程式碼

設定引擎主要是三步:

  • 如果有快取的引擎,使用快取的引擎
  • 使用FlutterActivity子類中的provideFlutterEngine()提供了引擎
  • 如果上面兩種都沒有,就自己例項一個引擎
 @VisibleForTesting
  /* package */ void setupFlutterEngine() {
    
    // First, check if the host wants to use a cached FlutterEngine.
    String cachedEngineId = host.getCachedEngineId();
    if (cachedEngineId != null) {
      flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId);
      isFlutterEngineFromHost = true;
      if (flutterEngine == null) {
        throw new IllegalStateException(
            "The requested cached FlutterEngine did not exist in the FlutterEngineCache: '"
                + cachedEngineId
                + "'");
      }
      return;
    }

    // Second, defer to subclasses for a custom FlutterEngine.
    flutterEngine = host.provideFlutterEngine(host.getContext());
    if (flutterEngine != null) {
      isFlutterEngineFromHost = true;
      return;
    }
 // Our host did not provide a custom FlutterEngine. Create a FlutterEngine to back our
    // FlutterView.
    Log.v(
        TAG,
        "No preferred FlutterEngine was provided. Creating a new FlutterEngine for"
            + " this FlutterFragment.");
    flutterEngine =
        new FlutterEngine(
            host.getContext(),
            host.getFlutterShellArgs().toArray(),
            /*automaticallyRegisterPlugins=*/ false);
    isFlutterEngineFromHost = false;
複製程式碼
註冊常用原生外掛到引擎

無論FlutterEngine是否已存在,PlatformPlugin都繫結到特定的Activity。因此,每次此Fragment附加到新的Activity時,都需要建立和配置它的一些平臺外掛。

platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);
複製程式碼

具體實現在FlutterActivity

 @Nullable
  @Override
  public PlatformPlugin providePlatformPlugin(
      @Nullable Activity activity, @NonNull FlutterEngine flutterEngine) {
    if (activity != null) {
      return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel());
    } else {
      return null;
    }
  }
複製程式碼
public PlatformPlugin(Activity activity, PlatformChannel platformChannel) {
    this.activity = activity;
    this.platformChannel = platformChannel;
    this.platformChannel.setPlatformMessageHandler(mPlatformMessageHandler);

    mEnabledOverlays = DEFAULT_SYSTEM_UI;
  }

複製程式碼

然後主要就做了一件事,就是對當前引擎的PlatformChannel設定MessageHandler。具體設定哪些外掛如下:


private final PlatformChannel.PlatformMessageHandler mPlatformMessageHandler =
      new PlatformChannel.PlatformMessageHandler() {
        @Override
        public void playSystemSound(@NonNull PlatformChannel.SoundType soundType) {
          PlatformPlugin.this.playSystemSound(soundType);
        }

        @Override
        public void vibrateHapticFeedback(
            @NonNull PlatformChannel.HapticFeedbackType feedbackType) {
          PlatformPlugin.this.vibrateHapticFeedback(feedbackType);
        }

        @Override
        public void setPreferredOrientations(int androidOrientation) {
          setSystemChromePreferredOrientations(androidOrientation);
        }

        @Override
        public void setApplicationSwitcherDescription(
            @NonNull PlatformChannel.AppSwitcherDescription description) {
          setSystemChromeApplicationSwitcherDescription(description);
        }

        @Override
        public void showSystemOverlays(@NonNull List<PlatformChannel.SystemUiOverlay> overlays) {
          setSystemChromeEnabledSystemUIOverlays(overlays);
        }

        @Override
        public void restoreSystemUiOverlays() {
          restoreSystemChromeSystemUIOverlays();
        }

        @Override
        public void setSystemUiOverlayStyle(
            @NonNull PlatformChannel.SystemChromeStyle systemUiOverlayStyle) {
          setSystemChromeSystemUIOverlayStyle(systemUiOverlayStyle);
        }

        @Override
        public void popSystemNavigator() {
          PlatformPlugin.this.popSystemNavigator();
        }

        @Override
        public CharSequence getClipboardData(
            @Nullable PlatformChannel.ClipboardContentFormat format) {
          return PlatformPlugin.this.getClipboardData(format);
        }

        @Override
        public void setClipboardData(@NonNull String text) {
          PlatformPlugin.this.setClipboardData(text);
        }

        @Override
        public List<Rect> getSystemGestureExclusionRects() {
          return PlatformPlugin.this.getSystemGestureExclusionRects();
        }

        @Override
        public void setSystemGestureExclusionRects(@NonNull ArrayList<Rect> rects) {
          PlatformPlugin.this.setSystemGestureExclusionRects(rects);
        }
      };
複製程式碼

就是音量震動等需要呼叫系統原生功能的東西。

將引擎上的外掛綁到Activity上
		flutterEngine
          .getActivityControlSurface()
          .attachToActivity(host.getActivity(), host.getLifecycle());
複製程式碼
對外暴露配置當前引擎的方法

 host.configureFlutterEngine(flutterEngine);

 /** Hook for the host to configure the {@link FlutterEngine} as desired. */
  void configureFlutterEngine(@NonNull FlutterEngine flutterEngine);


  @Override
  public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    registerPlugins(flutterEngine);
  }

private static void registerPlugins(@NonNull FlutterEngine flutterEngine) {
    try {
      Class<?> generatedPluginRegistrant =
          Class.forName("io.flutter.plugins.GeneratedPluginRegistrant");
      Method registrationMethod =
          generatedPluginRegistrant.getDeclaredMethod("registerWith", FlutterEngine.class);
      registrationMethod.invoke(null, flutterEngine);
    } catch (Exception e) {
      Log.w(
          TAG,
          "Tried to automatically register plugins with FlutterEngine ("
              + flutterEngine
              + ") but could not find and invoke the GeneratedPluginRegistrant.");
    }
  }
複製程式碼

其實最後這一步就是呼叫了FlutterActivity中的registerPlugins()方法。

作用是將Flutter專案中 pubspec.yaml檔案中依賴的三方外掛全部註冊上。 (note:其實在1.12之前這一步是需要開發者自己做的,但是1.1.2之後就自動幫你實現了)

onActivityCreated

給外掛恢復狀態的機會


  void onActivityCreated(@Nullable Bundle bundle) {
    Log.v(TAG, "onActivityCreated. Giving plugins an opportunity to restore state.");
    ensureAlive();

    if (host.shouldAttachEngineToActivity()) {
      flutterEngine.getActivityControlSurface().onRestoreInstanceState(bundle);
    }
  }

複製程式碼

createFlutterView

上面的內容都是些關於引擎和外掛的配置。但是具體如何顯示Flutter的檢視還沒涉及。

@Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
	 ...
    setContentView(createFlutterView());
    ...
  }

複製程式碼

從上面程式碼可以看到FlutterActivity具體顯示的檢視由createFlutterView()決定,而具體實現又在delegate中


  @NonNull
  private View createFlutterView() {
    return delegate.onCreateView(
        null /* inflater */, null /* container */, null /* savedInstanceState */);
  }


  @NonNull
  View onCreateView(
      LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
	...
    if (host.getRenderMode() == RenderMode.surface) {
      FlutterSurfaceView flutterSurfaceView =
          new FlutterSurfaceView(
              host.getActivity(), host.getTransparencyMode() == TransparencyMode.transparent);

      // Allow our host to customize FlutterSurfaceView, if desired.
      host.onFlutterSurfaceViewCreated(flutterSurfaceView);

      // Create the FlutterView that owns the FlutterSurfaceView.
      flutterView = new FlutterView(host.getActivity(), flutterSurfaceView);
    } else {
      FlutterTextureView flutterTextureView = new FlutterTextureView(host.getActivity());

      // Allow our host to customize FlutterSurfaceView, if desired.
      host.onFlutterTextureViewCreated(flutterTextureView);

      // Create the FlutterView that owns the FlutterTextureView.
      flutterView = new FlutterView(host.getActivity(), flutterTextureView);
    }

    // Add listener to be notified when Flutter renders its first frame.
    flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);

    flutterSplashView = new FlutterSplashView(host.getContext());
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
      flutterSplashView.setId(View.generateViewId());
    } else {
      flutterSplashView.setId(486947586);
    }
    flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());

    Log.v(TAG, "Attaching FlutterEngine to FlutterView.");
    flutterView.attachToFlutterEngine(flutterEngine);

    return flutterSplashView;
  }


複製程式碼

我們通過上面的程式碼可以發現最後返回的不是FlutterView,而是FlutterSplashView。還有FlutterView的建立需要使用到FlutterSurfaceView或者FlutterTextureView。這還是很有意思。下面具體看下這個方法主要做了什麼:

  • 建立FlutterView。
  • FlutterView新增第一幀繪製成功回撥。
  • 建立FlutterSplashView並掛載到View樹中。
  • 將FlutterView交給FlutterSplashView並掛載到引擎。

建立FlutterView

首先需要知道當前配置的RenderModel。如果RenderModel是surface則需要先建立FlutterSurfaceView,如果是texture則需要建立FlutterTexture。然後再通過FlutterSurfaceView和FlutterTextureView建立FlutterView。只是看名字我們可以猜測FlutterView的具體UI是通過SurfaceView或者TexutureView來顯示的。而FlutterSurfaceView和FlutterTextureView也分別整合原生的SurfaceView和TextureView。

系統推薦使用RenderModel為surface。理由是這樣效能更高。其實這個跟你再使用SurfaceView或者TextureView實現視訊播放器是一樣的。

FlutterSplashView

**
 * {@code View} that displays a {@link SplashScreen} until a given {@link FlutterView} renders its
 * first frame.
 */
/* package */ final class FlutterSplashView extends FrameLayout {
複製程式碼

看類註解理解為:一個在FlutterView繪製完第一幀前用來顯示SplashScreen的View。 如果在使用debug模式編寫Flutter會發現FlutterView的顯示是有點慢的,所以此時先顯示一個之前設定的splashScreen當Flutter第一幀繪製完,再將splashScreen移除。

除了上述內容外,此處主要還把FlutterSplashView掛載到了當前的View樹種。並將FlutterView新增到了FlutterSplashView中。最後還把當前的FlutterView關聯到了當前Activity的引擎上,因為FlutterView是依賴於Flutter引擎繪製。

這些都完成後,FlutterActivity的onCreate()就執行完了,此時等待FlutterView第一幀繪製完就能顯示Flutter的檢視了。

相關文章