FlutterActivity等是如何把flutter UI集成到原生页面中去的?带着这个疑问,通过源码来分析一下它们的原理。
-
EntryPoint解析
我们知道可以通过FlutterEngine的dartExecutor执行executeDartEntrypoint来设置切入点,这个切入点就是dart代码的入口。先来看一下默认的切入点:
@NonNull public static DartEntrypoint createDefault() { FlutterLoader flutterLoader = FlutterInjector.instance().flutterLoader(); if (!flutterLoader.initialized()) { throw new AssertionError( "DartEntrypoints can only be created once a FlutterEngine is created."); } return new DartEntrypoint(flutterLoader.findAppBundlePath(), "main"); }
DartEntrypoint构造方法的第二个参数是切入点函数名,注意是不带参数的函数,所以默认的最开始执行的dart函数是main()。
如果需要更改函数名,就要自定义构造对象:
engine.dartExecutor.executeDartEntrypoint( DartExecutor.DartEntrypoint( FlutterInjector.instance().flutterLoader().findAppBundlePath(), "showCell"))
那么dart文件有那么多,该选择哪个文件中的切入点函数呢?Flutter要求程序第一个执行的文件必须是lib/main.dart,如果没有此文件的话会抛出异常:
$ flutter build apk $ Target file "lib/main.dart" not found. $ Process finished with exit code 1
根据官方的介绍,executeDartEntrypoint执行后就在flutterEngine中持有了一个0尺寸的View,等到attachToActivity后就显示在界面上。
public void executeDartEntrypoint(@NonNull DartEntrypoint dartEntrypoint) { if (isApplicationRunning) { Log.w(TAG, "Attempted to run a DartExecutor that is already running."); return; } Log.v(TAG, "Executing Dart entrypoint: " + dartEntrypoint); flutterJNI.runBundleAndSnapshotFromLibrary( dartEntrypoint.pathToBundle, dartEntrypoint.dartEntrypointFunctionName, dartEntrypoint.dartEntrypointLibrary, assetManager); isApplicationRunning = true; }
最终会调用JNI方法:
private native void nativeRunBundleAndSnapshotFromLibrary( long nativeShellHolderId, @NonNull String bundlePath, @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction, @NonNull AssetManager manager);
-
FlutterEngine
首先看一下FlutterEngine的构造方法:
public FlutterEngine( @NonNull Context context, @Nullable FlutterLoader flutterLoader, @NonNull FlutterJNI flutterJNI, @NonNull PlatformViewsController platformViewsController, @Nullable String[] dartVmArgs, boolean automaticallyRegisterPlugins, boolean waitForRestorationData) { AssetManager assetManager; try { assetManager = context.createPackageContext(context.getPackageName(), 0).getAssets(); } catch (NameNotFoundException e) { assetManager = context.getAssets(); } this.dartExecutor = new DartExecutor(flutterJNI, assetManager); this.dartExecutor.onAttachedToJNI(); DeferredComponentManager deferredComponentManager = FlutterInjector.instance().deferredComponentManager(); accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI); deferredComponentChannel = new DeferredComponentChannel(dartExecutor); keyEventChannel = new KeyEventChannel(dartExecutor); lifecycleChannel = new LifecycleChannel(dartExecutor); localizationChannel = new LocalizationChannel(dartExecutor); mouseCursorChannel = new MouseCursorChannel(dartExecutor); navigationChannel = new NavigationChannel(dartExecutor); platformChannel = new PlatformChannel(dartExecutor); restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData); settingsChannel = new SettingsChannel(dartExecutor); systemChannel = new SystemChannel(dartExecutor); textInputChannel = new TextInputChannel(dartExecutor); if (deferredComponentManager != null) { deferredComponentManager.setDeferredComponentChannel(deferredComponentChannel); } this.localizationPlugin = new LocalizationPlugin(context, localizationChannel); this.flutterJNI = flutterJNI; if (flutterLoader == null) { flutterLoader = FlutterInjector.instance().flutterLoader(); } if (!flutterJNI.isAttached()) { flutterLoader.startInitialization(context.getApplicationContext()); flutterLoader.ensureInitializationComplete(context, dartVmArgs); } flutterJNI.addEngineLifecycleListener(engineLifecycleListener); flutterJNI.setPlatformViewsController(platformViewsController); flutterJNI.setLocalizationPlugin(localizationPlugin); flutterJNI.setDeferredComponentManager(FlutterInjector.instance().deferredComponentManager()); // It should typically be a fresh, unattached JNI. But on a spawned engine, the JNI instance // is already attached to a native shell. In that case, the Java FlutterEngine is created around // an existing shell. if (!flutterJNI.isAttached()) { attachToJni(); } // TODO(mattcarroll): FlutterRenderer is temporally coupled to attach(). Remove that coupling if // possible. this.renderer = new FlutterRenderer(flutterJNI); this.platformViewsController = platformViewsController; this.platformViewsController.onAttachedToJNI(); this.pluginRegistry = new FlutterEngineConnectionRegistry(context.getApplicationContext(), this, flutterLoader); // Only automatically register plugins if both constructor parameter and // loaded AndroidManifest config turn this feature on. if (automaticallyRegisterPlugins && flutterLoader.automaticallyRegisterPlugins()) { registerPlugins(); } }
梳理一下:
- dartExecutor是在这里执行onAttachedToJNI方法的,这是执行executeDartEntrypoint的前提条件。
- 一系列的Channel创建。
- flutterLoader创建方法内部会返回一个new FlutterLoader()。
- platformViewsController是传递参数new PlatformViewsController()。
- pluginRegistry是FlutterEngineConnectionRegistry。
- 最后一段代码是否自动注册Plugin,默认是自动注册。
-
FlutterActivity初始化
以startActivity为例,此时我们来看一下FlutterActivity是如何使用FlutterEngine的。
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { switchLaunchThemeForNormalTheme(); super.onCreate(savedInstanceState); delegate = new FlutterActivityAndFragmentDelegate(this); delegate.onAttach(this); delegate.onRestoreInstanceState(savedInstanceState); lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); configureWindowForTransparency(); setContentView(createFlutterView()); configureStatusBarForFullscreenFlutterExperience(); }
delegate.onAttach()中:
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(); } if (host.shouldAttachEngineToActivity()) { Log.v(TAG, "Attaching FlutterEngine to the Activity that owns this delegate."); flutterEngine.getActivityControlSurface().attachToActivity(this, host.getLifecycle()); } platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine); host.configureFlutterEngine(flutterEngine); }
setupFlutterEngine如下:
void setupFlutterEngine() { Log.v(TAG, "Setting up FlutterEngine."); // 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, /*willProvideRestorationData=*/ host.shouldRestoreAndSaveState()); isFlutterEngineFromHost = false; }
可见这里创建了flutterEngine,首先会从cache中取,如果没有缓存则从provideFlutterEngine方法中返回,如果该方法没有重写创建则new一个,注意这里new生成的FlutterEngine的automaticallyRegisterPlugins属性设置为false,即不会自动注册plugin。
创建完FlutterEngine之后如果需要则把它关联到Activity,FlutterActivity是一定会关联的,如果是FlutterFragment则根据setArguments传参决定。
getActivityControlSurface()方法返回的是pluginRegistry,是FlutterEngineConnectionRegistry:
@Override public void attachToActivity(@NonNull Activity activity, @NonNull Lifecycle lifecycle) { Log.v( TAG, "Attaching to an Activity: " + activity + "." + (isWaitingForActivityReattachment ? " This is after a config change." : "")); if (this.exclusiveActivity != null) { this.exclusiveActivity.detachFromFlutterEngine(); } // If we were already attached to an app component, detach from it. detachFromAppComponent(); if (this.exclusiveActivity != null) { throw new AssertionError("Only activity or exclusiveActivity should be set"); } this.activity = activity; attachToActivityInternal(activity, lifecycle); }
首先会和之前的FlutterEngine接触绑定,然后attach新的:
private void attachToActivityInternal(@NonNull Activity activity, @NonNull Lifecycle lifecycle) { this.activityPluginBinding = new FlutterEngineActivityPluginBinding(activity, lifecycle); // Activate the PlatformViewsController. This must happen before any plugins attempt // to use it, otherwise an error stack trace will appear that says there is no // flutter/platform_views channel. flutterEngine .getPlatformViewsController() .attach(activity, flutterEngine.getRenderer(), flutterEngine.getDartExecutor()); // Notify all ActivityAware plugins that they are now attached to a new Activity. for (ActivityAware activityAware : activityAwarePlugins.values()) { if (isWaitingForActivityReattachment) { activityAware.onReattachedToActivityForConfigChanges(activityPluginBinding); } else { activityAware.onAttachedToActivity(activityPluginBinding); } } isWaitingForActivityReattachment = false; }
然后走到PlatformViewsController的attach中:
public void attach( Context context, TextureRegistry textureRegistry, @NonNull DartExecutor dartExecutor) { if (this.context != null) { throw new AssertionError( "A PlatformViewsController can only be attached to a single output target.\n" + "attach was called while the PlatformViewsController was already attached."); } this.context = context; this.textureRegistry = textureRegistry; platformViewsChannel = new PlatformViewsChannel(dartExecutor); platformViewsChannel.setPlatformViewsHandler(channelHandler); }
回到delegate.onAttach()中,最后执行host.configureFlutterEngine方法:
GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
@Keep public final class GeneratedPluginRegistrant { public static void registerWith(@NonNull FlutterEngine flutterEngine) { } }
这个类是Flutter tool自动生成代码的,用来注册自定义的Android相关的plugin类,即只有定义了继承自FlutterPlugin的类时registerWith内才会有代码,比如:
public static void registerWith(@NonNull FlutterEngine flutterEngine) { flutterEngine.getPlugins().add(new io.flutter.plugins.sensors.SensorsPlugin()); flutterEngine.getPlugins().add(new io.flutter.plugins.urllauncher.UrlLauncherPlugin()); }
回到onCreate中,configureWindowForTransparency这里就用到了前面intent设置的background属性:
private void configureWindowForTransparency() { BackgroundMode backgroundMode = getBackgroundMode(); if (backgroundMode == BackgroundMode.transparent) { getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); } }
setContentView中传递的View是由createFlutterView生成的,接下来看重点的createFlutterView。
-
createFlutterView
@NonNull private View createFlutterView() { return delegate.onCreateView( null /* inflater */, null /* container */, null /* savedInstanceState */); }
@NonNull View onCreateView( LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Log.v(TAG, "Creating FlutterView."); ensureAlive(); 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 { // TODO(mattcarroll): Find a better solution to this ID. This is a random, static ID. // It might conflict with other Views, and it means that only a single FlutterSplashView // can exist in a View hierarchy at one time. flutterSplashView.setId(486947586); } flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen()); Log.v(TAG, "Attaching FlutterEngine to FlutterView."); flutterView.attachToFlutterEngine(flutterEngine); return flutterSplashView; }
可以看到,会根据渲染模式创建两种不同的FlutterView:FlutterSurfaceView、FlutterTextureView,渲染模式根据:
@Override public RenderMode getRenderMode() { return getBackgroundMode() == BackgroundMode.opaque ? RenderMode.surface : RenderMode.texture; }
不透明的就是RenderMode.surface。
针对两种不同的View会分别执行onFlutterTextureViewCreated和onFlutterTextureViewCreated方法,这两个方法没有默认实现交由子类按需使用。
不同参数的FlutterView构造方法都会执行一个init方法:
private void init() { Log.v(TAG, "Initializing FlutterView"); if (flutterSurfaceView != null) { Log.v(TAG, "Internally using a FlutterSurfaceView."); addView(flutterSurfaceView); } else if (flutterTextureView != null) { Log.v(TAG, "Internally using a FlutterTextureView."); addView(flutterTextureView); } else { Log.v(TAG, "Internally using a FlutterImageView."); addView(flutterImageView); } // FlutterView needs to be focusable so that the InputMethodManager can interact with it. setFocusable(true); setFocusableInTouchMode(true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS); } }
FlutterView继承自FrameLayout,可以看到这里会把前面传递来的的FlutterSurfaceView或FlutterTextureView给添加进去。FlutterSplashView也是个FrameLayout,它的displayFlutterViewWithSplash方法如下:
public void displayFlutterViewWithSplash( @NonNull FlutterView flutterView, @Nullable SplashScreen splashScreen) { // If we were displaying a previous FlutterView, remove it. if (this.flutterView != null) { this.flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener); removeView(this.flutterView); } // If we were displaying a previous splash screen View, remove it. if (splashScreenView != null) { removeView(splashScreenView); } // Display the new FlutterView. this.flutterView = flutterView; addView(flutterView); this.splashScreen = splashScreen; // Display the new splash screen, if needed. if (splashScreen != null) { if (isSplashScreenNeededNow()) { Log.v(TAG, "Showing splash screen UI."); // This is the typical case. A FlutterEngine is attached to the FlutterView and we're // waiting for the first frame to render. Show a splash UI until that happens. splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState); addView(this.splashScreenView); flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener); } else if (isSplashScreenTransitionNeededNow()) { Log.v( TAG, "Showing an immediate splash transition to Flutter due to previously interrupted transition."); splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState); addView(splashScreenView); transitionToFlutter(); } else if (!flutterView.isAttachedToFlutterEngine()) { Log.v( TAG, "FlutterView is not yet attached to a FlutterEngine. Showing nothing until a FlutterEngine is attached."); flutterView.addFlutterEngineAttachmentListener(flutterEngineAttachmentListener); } } }
可见,FlutterSplashView是当Manifest内配置了启动View的metadata时在flutterView上方再加一个splashView。
所以到此,我们知道了,其实Activity显示的实质性的flutter内容就是前面的FlutterSurfaceView或FlutterTextureView。
接下来,flutterView.attachToFlutterEngine(flutterEngine)把flutterView和flutterEngine绑定在一起,这也是原生和flutter的绑定,attachToFlutterEngine方法中的renderSurface.attachToRenderer(flutterRenderer)把FlutterSurfaceView或FlutterTextureView和FlutterRender绑定在一起,以FlutterSurfaceView为例:
public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) { Log.v(TAG, "Attaching to FlutterRenderer."); if (this.flutterRenderer != null) { Log.v( TAG, "Already connected to a FlutterRenderer. Detaching from old one and attaching to new one."); this.flutterRenderer.stopRenderingToSurface(); this.flutterRenderer.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener); } this.flutterRenderer = flutterRenderer; isAttachedToFlutterRenderer = true; this.flutterRenderer.addIsDisplayingFlutterUiListener(flutterUiDisplayListener); // If we're already attached to an Android window then we're now attached to both a renderer // and the Android window. We can begin rendering now. if (isSurfaceAvailableForRendering) { Log.v( TAG, "Surface is available for rendering. Connecting FlutterRenderer to Android surface."); connectSurfaceToRenderer(); } }
private void connectSurfaceToRenderer() { if (flutterRenderer == null || getHolder() == null) { throw new IllegalStateException( "connectSurfaceToRenderer() should only be called when flutterRenderer and getHolder() are non-null."); } flutterRenderer.startRenderingToSurface(getHolder().getSurface()); }
public void startRenderingToSurface(@NonNull Surface surface) { if (this.surface != null) { stopRenderingToSurface(); } this.surface = surface; flutterJNI.onSurfaceCreated(surface); }
可见,FlutterRender负责调用flutterJNI来将flutter代码成对应的Android原生View(这个过程是通过JNI实现的),然后flutterView持有这个surface,调用setContentView把flutterView放入Window中。
-
总结
总之,Flutter的显示原理就是把Flutter的代码通过JNI转换成对应效果的原生View。
FlutterActivity里面有一个FlutterActivityAndFragmentDelegate来代理各种行为,创建一个FlutterView,用来作为转换后的View的父容器,然后它会和FlutterEngine绑定,FlutterEngine持有FlutterRenderer,FlutterRenderer又是和FlutterJNI绑定在一起的,最后通过FlutterJNI来调用JNI代码转换dart代码,FlutterJNI在FlutterEngine构造的时候又通过dartExecutor.onAttachedToJNI()方法把设置的切入点和FlutterJNI绑定在一起,FlutterJNI在查找dart代码执行入口的时候就会用到这个切入点。
另外,FlutterEngine里面还持有了各种和Flutter打交道的channel,除了界面显示,其他的一些比如生命周期、各种事件等的交互都是通过FlutterEngine来处理的。