FlutterBoost3

Native层设计

image-20210513155202062

FlutterBoostPlugin类设计

image-20210513155343058

FlutterBoost.setup

1. initialize default engine

public void setup(Application application, FlutterBoostDelegate delegate, Callback callback, FlutterBoostOptions options) {
    // 1. initialize default engine
    FlutterEngine engine = FlutterEngineCache.getInstance().get(ENGINE_ID);
    if (engine == null) {
        if (options == null) options = FlutterBoostOptions.createDefault();
        engine = new FlutterEngine(application, options.shellArgs());
        engine.getNavigationChannel().setInitialRoute(options.initialRoute());
        engine.getDartExecutor().executeDartEntrypoint(new DartExecutor.DartEntrypoint(
                FlutterMain.findAppBundlePath(), options.dartEntrypoint()));
        if(callback != null) callback.onStart(engine);
        FlutterEngineCache.getInstance().put(ENGINE_ID, engine);
    }

    // 2. set delegate
    getPlugin().setDelegate(delegate);

    //3. register ActivityLifecycleCallbacks
    setupActivityLifecycleCallback(application);
}

2. setDelegateToPlugin

public FlutterEngine(@NonNull Context context, @Nullable String[] dartVmArgs) {
  this(context, /* flutterLoader */ null, new FlutterJNI(), dartVmArgs, true);//最后的参数true表示automaticallyRegisterPlugins
}
  public FlutterEngine(
      @NonNull Context context,
      @Nullable FlutterLoader flutterLoader,
      @NonNull FlutterJNI flutterJNI,
      @Nullable String[] dartVmArgs,
      boolean automaticallyRegisterPlugins) {
    this(
        context,
        flutterLoader,
        flutterJNI,
        new PlatformViewsController(),
        dartVmArgs,
        automaticallyRegisterPlugins);
  }
//最终在FlutterEngine构造方法结尾进行如下操作
 if (automaticallyRegisterPlugins) {
      registerPlugins();
 }
//反射调用自动生成的注册类的注册方法
  private void registerPlugins() {
    try {
      Class<?> generatedPluginRegistrant =
          Class.forName("io.flutter.plugins.GeneratedPluginRegistrant");
      Method registrationMethod =
          generatedPluginRegistrant.getDeclaredMethod("registerWith", FlutterEngine.class);
      registrationMethod.invoke(null, this);
    } catch (Exception e) {
      Log.w(
          TAG,
          "Tried to automatically register plugins with FlutterEngine ("
              + this
              + ") but could not find and invoke the GeneratedPluginRegistrant.");
    }
  }
package io.flutter.plugins;
/**
 * Generated file. Do not edit.
 * This file is generated by the Flutter tool based on the
 * plugins that support the Android platform.
 */
@Keep
public final class GeneratedPluginRegistrant {
  public static void registerWith(@NonNull FlutterEngine flutterEngine) {
    flutterEngine.getPlugins().add(new com.idlefish.flutterboost.FlutterBoostPlugin());
    flutterEngine.getPlugins().add(new io.flutter.plugins.flutter_plugin_android_lifecycle.FlutterAndroidLifecyclePlugin());
    flutterEngine.getPlugins().add(new io.flutter.plugins.imagepicker.ImagePickerPlugin());
    flutterEngine.getPlugins().add(new io.flutter.plugins.videoplayer.VideoPlayerPlugin());
  }
}
public FlutterBoostPlugin getPlugin() {
    if (plugin == null) {
        FlutterEngine engine = FlutterEngineCache.getInstance().get(ENGINE_ID);
        if (engine == null) {
            throw new RuntimeException("FlutterBoost might *not* have been initialized yet!!!");
        }
        plugin = getFlutterBoostPlugin(engine);
    }
    return plugin;
}
public class FlutterBoostPlugin implements FlutterPlugin, Messages.NativeRouterApi {
  
    @Override
    public void onAttachedToEngine(FlutterPluginBinding binding) {
        Messages.NativeRouterApi.setup(binding.getBinaryMessenger(), this);
        channel = new Messages.FlutterRouterApi(binding.getBinaryMessenger());
    }

    @Override
    public void onDetachedFromEngine(FlutterPluginBinding binding) {
        channel = null;
    }
}

3. register ActivityLifecycleCallbacks

private void setupActivityLifecycleCallback(Application application) {
    application.registerActivityLifecycleCallbacks(new BoostActivityLifecycle());
}
private class BoostActivityLifecycle implements Application.ActivityLifecycleCallbacks {
    private int activityReferences = 0;
    private boolean isActivityChangingConfigurations = false;

    private void dispatchForegroundEvent() {
        FlutterBoost.instance().setAppIsInBackground(false);
        FlutterBoost.instance().getPlugin().onForeground();
    }

    private void dispatchBackgroundEvent() {
        FlutterBoost.instance().setAppIsInBackground(true);
        FlutterBoost.instance().getPlugin().onBackground();
    }
}

NativeStartFlutterActivity

正常native方式启动FlutterBoostActivity

对应FlutterBoostDelegate中pushFlutterRoute方法的执行

Intent intent = new FlutterBoostActivity.CachedEngineIntentBuilder(FlutterBoostActivity.class, FlutterBoost.ENGINE_ID)
        .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.opaque)
        .destroyEngineWithActivity(false)
        .url("flutterPage")
        .urlParams(params)
        .build(this);
startActivityForResult(intent, REQUEST_CODE);

FlutterBoostActivity

public class FlutterBoostActivity extends FlutterActivity implements FlutterViewContainer {
    private static final String TAG = "FlutterBoostActivity";
    private FlutterView flutterView;
    private FlutterViewContainerObserver observer;

onCreate

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    observer = FlutterBoostPlugin.ContainerShadowNode.create(this, FlutterBoost.instance().getPlugin());
    observer.onCreateView();
}

onResume

@Override
public void onResume() {
    if (flutterView == null) {
        findFlutterView(getWindow().getDecorView());//递归遍历找到flutterView
    }

    super.onResume();
    if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
        if (FlutterBoost.instance().isAppInBackground() &&
                !FlutterBoost.instance().getPlugin().isTopContainer(getUniqueId())) {
            Log.w(TAG, "Unexpected activity lifecycle event on Android Q. " +
                    "See https://issuetracker.google.com/issues/185693011 for more details.");
            return;
        }
    }
    observer.onAppear();//main
    ActivityAndFragmentPatch.onResumeAttachToFlutterEngine(flutterView,
            getFlutterEngine(), this);//main
}
//class ContainerShadowNode
@Override
public void onAppear() {
    plugin.reorderContainer(getUniqueId(), this);
    plugin.pushRoute(getUniqueId(), getUrl(), getUrlParams(), null);//main

    plugin.onContainerShow(getUniqueId());
    Log.v(TAG, "#onAppear: " + getUniqueId() + ", " + plugin.getContainers());
}
//FlutterBoostPlugin
public void pushRoute(String uniqueId, String pageName, Map<String, Object> arguments,
                      final Reply<Void> callback) {
    if (channel != null) {
        Messages.CommonParams params = new Messages.CommonParams();
        params.setUniqueId(uniqueId);
        params.setPageName(pageName);
        params.setArguments((Map<Object, Object>)(Object) arguments);
        channel.pushRoute(params, reply -> {
            if (callback != null) {
                callback.reply(null);
            }
        });
    } else {
        throw new RuntimeException("FlutterBoostPlugin might *NOT* have attached to engine yet!");
    }
}
BoostFlutterRouterApi_pushRoute
/// The MessageChannel counterpart on the Dart side.
class BoostFlutterRouterApi extends FlutterRouterApi {
  final FlutterBoostAppState appState;
  static BoostFlutterRouterApi _instance;
@override
void pushRoute(CommonParams arg) {
  appState.push(
    arg.pageName,
    uniqueId: arg.uniqueId,
    arguments:
        Map<String, dynamic>.from(arg.arguments ?? <String, dynamic>{}),
    withContainer: true,
  );
}
void push(String pageName,
    {String uniqueId, Map<String, dynamic> arguments, bool withContainer}) {
  _cancelActivePointers();
  final existed = _findContainerByUniqueId(uniqueId);
  if (existed != null) {
    if (topContainer?.pageInfo?.uniqueId != uniqueId) {
      containers.remove(existed);
      containers.add(existed);

      //move the overlayEntry which matches this existing container to the top
      refreshOnMoveToTop(existed);
    }
  } else {
    final pageInfo = PageInfo(
        pageName: pageName,
        uniqueId: uniqueId ?? _createUniqueId(pageName),
        arguments: arguments,
        withContainer: withContainer);
    if (withContainer) {
      final container = _createContainer(pageInfo);
      final previousContainer = topContainer;
      containers.add(container);
      BoostLifecycleBinding.instance
          .containerDidPush(container, previousContainer);

      // Add a new overlay entry with this container
      refreshOnPush(container);//main
    } else {
      // In this case , we don't need to change the overlayEntries data,
      // so we don't call any refresh method
      topContainer.pages.add(BoostPage.create(pageInfo));
      topContainer.refresh();
    }
  }
  Logger.log('push page, uniqueId=$uniqueId, existed=$existed,'
      ' withContainer=$withContainer, arguments:$arguments, $containers');
}
void refreshOnPush(BoostContainer container) {
  refreshSpecificOverlayEntries(container, BoostSpecificEntryRefreshMode.add);
  assert(() {
    _saveStackForHotRestart();
    return true;
  }());
}
///Refresh an specific entry instead of all of entries to enhance the performace
///
///[container] : The container you want to operate, it is related with
///              internal [OverlayEntry]
///[mode] : The [BoostSpecificEntryRefreshMode] you want to choose
void refreshSpecificOverlayEntries(
    BoostContainer container, BoostSpecificEntryRefreshMode mode) {
  //Get OverlayState from global key
  final overlayState = overlayKey.currentState;
  if (overlayState == null) {
    return;
  }

  final hasScheduledFrame = SchedulerBinding.instance.hasScheduledFrame;
  final framesEnabled = SchedulerBinding.instance.framesEnabled;

  //deal with different situation
  switch (mode) {
    case BoostSpecificEntryRefreshMode.add:
      final entry = _ContainerOverlayEntry(container);
      _lastEntries.add(entry);
      overlayState.insert(entry);//main
      break;
/// Insert the given entry into the overlay.
///
/// If `below` is non-null, the entry is inserted just below `below`.
/// If `above` is non-null, the entry is inserted just above `above`.
/// Otherwise, the entry is inserted on top.
///
/// It is an error to specify both `above` and `below`.
void insert(OverlayEntry entry, { OverlayEntry? below, OverlayEntry? above }) {
  assert(_debugVerifyInsertPosition(above, below));
  assert(!_entries.contains(entry), 'The specified entry is already present in the Overlay.');
  assert(entry._overlay == null, 'The specified entry is already present in another Overlay.');
  entry._overlay = this;
  setState(() {
    _entries.insert(_insertionIndex(below, above), entry);
  });
}
Native侧onResumeAttachToFlutterEngine
public static void onResumeAttachToFlutterEngine(FlutterView flutterView, FlutterEngine flutterEngine, FlutterViewContainer container) {
    flutterView.attachToFlutterEngine(flutterEngine);//main
    flutterEngine.getLifecycleChannel().appIsResumed();
}

NativeStartNativeActivity

直接启动即可

context.startActivity(intent);

FlutterStartNativeActivity

BoostNavigator.instance.push("native"),

instance

/// A object that manages a set of pages with a hybrid stack.
///
class BoostNavigator {
  BoostNavigator._();

  static final BoostNavigator _instance = BoostNavigator._();
  
  static BoostNavigator get instance {
    _instance.appState ??= overlayKey.currentContext
        ?.findAncestorStateOfType<FlutterBoostAppState>();
    return _instance;
  }
}

push

/// Push the page with the given [name] onto the hybrid stack.
Future<T> push<T extends Object>(String name,
    {Map<String, dynamic> arguments, bool withContainer = false}) async {
  
    return future.then((dynamic _state) {
      final state = _state as InterceptorState<dynamic>;
      if (state.data is BoostInterceptorOption) {
        assert(state.type == InterceptorResultType.next);
        pushOption = state.data;
        if (isFlutterPage(pushOption.name)) {//根据main.dart中的routeFactory和配置的routerMap,判断是否是flutter page
          //flutter start flutter page
          return appState.pushWithResult(pushOption.name,
              arguments: pushOption.arguments, withContainer: withContainer);
        } else {
          //flutter start native page
          final params = CommonParams()
            ..pageName = pushOption.name
            ..arguments = pushOption.arguments;
          appState.nativeRouterApi.pushNativeRoute(params);
          return appState.pendNativeResult(pushOption.name);
        }
      } else {
        assert(state.type == InterceptorResultType.resolve);
        return Future<T>.value(state.data as T);
      }
    });

NativeRouterApi.pushNativeRoute

channel通信
// Autogenerated from Pigeon
class NativeRouterApi {
  Future<void> pushNativeRoute(CommonParams arg) async {
    final Object encoded = arg.encode();
    const BasicMessageChannel<Object> channel =
        BasicMessageChannel<Object>('dev.flutter.pigeon.NativeRouterApi.pushNativeRoute', StandardMessageCodec());
    final Map<Object, Object> replyMap = await channel.send(encoded) as Map<Object, Object>;
    if (replyMap == null) {
      throw PlatformException(
        code: 'channel-error',
        message: 'Unable to establish connection on channel.',
        details: null,
      );
    } else if (replyMap['error'] != null) {
      final Map<Object, Object> error = (replyMap['error'] as Map<Object, Object>);
      throw PlatformException(
        code: (error['code'] as String),
        message: error['message'] as String,
        details: error['details'],
      );
    } else {
      // noop
    }
  }
// Autogenerated from Pigeon (v0.1.23)

/** Sets up an instance of `NativeRouterApi` to handle messages through the `binaryMessenger`. */
static void setup(BinaryMessenger binaryMessenger, NativeRouterApi api) {
  {
    BasicMessageChannel<Object> channel =
        new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.NativeRouterApi.pushNativeRoute", new StandardMessageCodec());
    if (api != null) {
      channel.setMessageHandler((message, reply) -> {
        Map<String, Object> wrapped = new HashMap<>();
        try {
          @SuppressWarnings("ConstantConditions")
          CommonParams input = CommonParams.fromMap((Map<String, Object>)message);
          api.pushNativeRoute(input);//main
          wrapped.put("result", null);
        }
        catch (Error | RuntimeException exception) {
          wrapped.put("error", wrapError(exception));
        }
        reply.reply(wrapped);
      });
    } else {
      channel.setMessageHandler(null);
    }
  }
//FlutterBoostPlugin
@Override
public void pushNativeRoute(Messages.CommonParams params) {
    if (delegate != null) {
        delegate.pushNativeRoute(params.getPageName(), (Map<String, Object>) (Object)params.getArguments());
    } else {
        throw new RuntimeException("FlutterBoostPlugin might *NOT* set delegate!");
    }
}
调用FlutterBoostDelegate的实现类启动native页面
public class MyFlutterBoostDelegate implements FlutterBoostDelegate {
    private final int REQUEST_CODE = 999;

    @Override
    public void pushNativeRoute(String pageName, Map<String, Object> arguments) {
        Intent intent = new Intent(FlutterBoost.instance().currentActivity(), NativePageActivity.class);
        FlutterBoost.instance().currentActivity().startActivityForResult(intent, REQUEST_CODE);
    }

FlutterStartFlutterPage

继续上述的

return future.then((dynamic _state) {
  final state = _state as InterceptorState<dynamic>;
  if (state.data is BoostInterceptorOption) {
    assert(state.type == InterceptorResultType.next);
    pushOption = state.data;
    if (isFlutterPage(pushOption.name)) {//flutter start flutter page
      return appState.pushWithResult(pushOption.name,
          arguments: pushOption.arguments, withContainer: withContainer);
Future<T> pushWithResult<T extends Object>(String pageName,
    {String uniqueId, Map<String, dynamic> arguments, bool withContainer}) {
  final completer = Completer<T>();
  assert(uniqueId == null);
  uniqueId = _createUniqueId(pageName);
  if (withContainer) {//如果需要native层Activity容器包裹即将启动的页面
    final params = CommonParams()
      ..pageName = pageName
      ..uniqueId = uniqueId
      ..arguments = arguments ?? <String, dynamic>{};
    nativeRouterApi.pushFlutterRoute(params);//channel通知native启动flutter页面
  } else {//否则,push到overlay
    push(pageName,
        uniqueId: uniqueId, arguments: arguments, withContainer: false);
  }
  _pendingResult[uniqueId] = completer;
  return completer.future;
}

这里的push参考上面的overlay添加流程