前语:跟着Flutter在国内移动运用的成熟度,大部分企业都开始认可Flutter的可持续开展,逐渐引入Flutter技术栈。

由此关于开发人员的技术储备问题,会发生必定的疑问。今天笔者将从咱们在OS中运用Flutter的各种玩法,聊聊陈词滥调的论题:Flutter开发者到底需不需求懂原生渠道?

缘起

《Flutter开发者需求把握原生Android吗?》
这个论题跟Flutter与RN对比Flutter会不会凉同属一类,都是前两年社群最喜欢争论的论题。剧烈的讨论无非是张望者太多,加之Flutter不成熟,在运用过程中会遇到不少坑。

直到本年3.7.0、3.10.0相继发布,结构改善和社区的丰富,让更多人挑选拥抱Flutter,关于此类型的论题才开始沉寂下来。许多招聘网站也直接呈现了Flutter开发这个岗位,并且技术也不要求原生,甚至加分项前端的技术。好像Flutter开发者在开发过程中很少用到原生的技术,但是现实绝非如此。

我专攻Flutter有3年了,期间Android、iOS、Windows运用做过不少,Web、Linux也都略有研讨;这次我将直接从Android渠道动身,用切身经历来论说下:Flutter开发者,真的需求懂Android。

Flutter只是个UI结构

打开一个Flutter的项目,咱们能够看到整个运用其实是基于一个Activity运转的,归于单页运用。

package com.wxq.test
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

Activity承继自FlutterActivity,FlutterActivityonCreate内会创立FlutterActivityAndFragmentDelegate

// io/flutter/embedding/android/FlutterActivity.java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
  switchLaunchThemeForNormalTheme();
  super.onCreate(savedInstanceState);
  // 创立代理,ActivityAndFragment都支撑哦
  delegate = new FlutterActivityAndFragmentDelegate(this);
  delegate.onAttach(this); // 这个办法创立引擎,并且将context吸附上去
  delegate.onRestoreInstanceState(savedInstanceState);
  lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
  configureWindowForTransparency();
  // 设置Activity的View,createFlutterView内部也是调用代理的办法
  setContentView(createFlutterView()); 
  configureStatusBarForFullscreenFlutterExperience();
}

这个代理将会经过engineGroup办理FlutterEngine,经过onAttach创立FlutterEngine,并且运转createAndRunEngine办法

// io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
void onAttach(@NonNull Context context) {
  ensureAlive();
  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);
  isAttached = true;
}
@VisibleForTesting
/* package */ void setupFlutterEngine() {
  Log.v(TAG, "Setting up FlutterEngine.");
  // 省略处理引擎缓存的代码
  String cachedEngineGroupId = host.getCachedEngineGroupId();
  if (cachedEngineGroupId != null) {
    FlutterEngineGroup flutterEngineGroup =
        FlutterEngineGroupCache.getInstance().get(cachedEngineGroupId);
    if (flutterEngineGroup == null) {
      throw new IllegalStateException(
          "The requested cached FlutterEngineGroup did not exist in the FlutterEngineGroupCache: '"
              + cachedEngineGroupId
              + "'");
    }
    // *** 要点 ***
    flutterEngine =
        flutterEngineGroup.createAndRunEngine(
            addEntrypointOptions(new FlutterEngineGroup.Options(host.getContext())));
    isFlutterEngineFromHost = false;
    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.");
  FlutterEngineGroup group =
      engineGroup == null
          ? new FlutterEngineGroup(host.getContext(), host.getFlutterShellArgs().toArray())
          : engineGroup;
  flutterEngine =
      group.createAndRunEngine(
          addEntrypointOptions(
              new FlutterEngineGroup.Options(host.getContext())
                  .setAutomaticallyRegisterPlugins(false)
                  .setWaitForRestorationData(host.shouldRestoreAndSaveState())));
  isFlutterEngineFromHost = false;
}

再调用onCreateView创立SurfaceView或者外接纹路TextureView,这个View就是Flutter的赖以制作的画布。

// io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
@NonNull
View onCreateView(
    LayoutInflater inflater,
    @Nullable ViewGroup container,
    @Nullable Bundle savedInstanceState,
    int flutterViewId,
    boolean shouldDelayFirstAndroidViewDraw) {
  Log.v(TAG, "Creating FlutterView.");
  ensureAlive();
  if (host.getRenderMode() == RenderMode.surface) {
    FlutterSurfaceView flutterSurfaceView =
        new FlutterSurfaceView(
            host.getContext(), 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.getContext(), flutterSurfaceView);
  } else {
    FlutterTextureView flutterTextureView = new FlutterTextureView(host.getContext());
    flutterTextureView.setOpaque(host.getTransparencyMode() == TransparencyMode.opaque);
    // Allow our host to customize FlutterSurfaceView, if desired.
    host.onFlutterTextureViewCreated(flutterTextureView);
    // Create the FlutterView that owns the FlutterTextureView.
    flutterView = new FlutterView(host.getContext(), flutterTextureView);
  }
  flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
  // 疏忽一些代码...
  return flutterView;
}

由此可见,Flutter的引擎实际上是运转在Android供给的View上,这个View必定是设置在Android的组件上,能够是Activity、Framgent,也能够是WindowManager。
这就给咱们带来了很大的可塑性,只需你能把握这套原理,混合开发就随便玩了。

Android,是有必要的才能

经过对Flutter运转机制的分析,咱们很明确它就是个单纯的UI结构,惊艳的跨端UI都离不开Android的才能,这也说明Flutter开发者不需求会原生注定走不远
下面几个比如,也能够充分论证这个观念。

一、Flutter插件从哪里来

上面叙述到的原理,Flutter项目脚手架现已帮咱们做好,但这只是UI制作层面的;实际上许多Flutter运用,事务才能都是由Pub.dev供给的,跟着社区结构的增多,开发者大多时分是感知不到需求Android才能的。
但是事务的开展是迅速的,咱们开始需求许多pub社区并不支撑的才能,比如:getMetaDatagetMacAddressreboot/shutdownsendBroadcast等,这些才能都需求咱们运用Android常识,以编写插件的方式,供给给Flutter调用。
Flutter Plugin在Dart层和Android层都完成了MethodChannel目标,同一个Engine下,只需传入一致的channelId字符,就能树立双向的通道相互传输基本类型数据。

class FlutterNativeAbilityPlugin : FlutterPlugin, MethodCallHandler {
    private var applicationContext: Context? = null
    private lateinit var channel: MethodChannel
    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        applicationContext = flutterPluginBinding.applicationContext
        channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_native_ability")
        channel.setMethodCallHandler(this)
    }
class MethodChannelFlutterNativeAbility extends FlutterNativeAbilityPlatform {
  /// The method channel used to interact with the native platform.
  @visibleForTesting
  final methodChannel = const MethodChannel('flutter_native_ability');
}

发送端经过invokeMethod调用对应的methodName,传入arguments;接纳端经过完成onMethodCall办法,接纳发送端的invokeMethod操作,执行需求的操作后,经过Result目标回来结果。

@override
Future<String> getMacAddress() async {
  final res = await methodChannel.invokeMethod<String>('getMacAddress');
  return res ?? '';
}
@override
Future<void> reboot() async {
  await methodChannel.invokeMethod<String>('reboot');
}
"getMacAddress" -> {
    Log.i(TAG, "onMethodCall: getMacAddress")
    val macAddress = CommonUtils().getDeviceMac(applicationContext)
    result.success(macAddress)
}
"reboot" -> {
    Log.i(TAG, "onMethodCall: reboot")
    beginToReboot(applicationContext)
    result.success(null)
}

ps:invokeMethod和onMethodCall双端都能完成,都能作为发送端和接纳端。

二、Flutter依赖于Android机制,得以“横行霸道”

目前咱们将Flutter运用于OS的开发,这需求咱们不单是从某个独立运用去考虑。许多运用、服务都需求从整个体系事务去规划,在以下这些需求中,咱们深切感受到:Flutter跟Android合作后,能发挥更大的事务价值。

  • Android服务运转dart代码,播送接纳器与Flutter通信

咱们许多服务需求开机自启,这有必要遵从Android的机制。一般做法是:接纳开机播送,在播送接纳器中启动Service,然后再去运转DartEngie,执行跨渠道的代码;

class MyTestService : Service() {
    private lateinit var engineGroup: FlutterEngineGroup
    override fun onCreate() {
        super.onCreate()
        startForeground()
        engineGroup = FlutterEngineGroup(this)
        // initService是Flutter层的办法进口点
        val dartEntrypoint = DartExecutor.DartEntrypoint(
            FlutterInjector.instance().flutterLoader().findAppBundlePath(),
            "initService"
        )
        val flutterEngine = engineGroup.createAndRunEngine(this, dartEntrypoint)
        // Flutter调用Native办法的 MethodChannel 也初始化一下,调用安装接口需求
        FlutterToNativeChannel(flutterEngine, this)
    }
}

一起各运用之间需求通信,这时咱们也会经过Broadcat播送机制,在Android的播送接纳器中,经过MechodChannel发送给Flutter端。

总而言之,咱们有必要 遵从体系的组件规则,基于Flutter供给的通信方式,将Android的音讯、事情等发回给Flutter, 带来的跨端效益是实实在在的!

  • 悬浮窗需求

悬浮窗口在视频/直播场景下用的最多,当你的运用需求开启悬浮窗的时分,Flutter将完全无法支撑这个需求。
实际上咱们只需求在Android中创立一个WindowManager,基于EngineGround创立一个DartEngine;然后创立flutterView,把DartEngine吸附到flutterView上,最终把flutterView Add to WindowManager即可。

private lateinit var flutterView: FlutterView
private var windowManager = context.getSystemService(Service.WINDOW_SERVICE) as WindowManager
private val inflater =
    context.getSystemService(Service.LAYOUT_INFLATER_SERVICE) as LayoutInflater
private val metrics = DisplayMetrics()
@SuppressLint("InflateParams")
private var rootView = inflater.inflate(R.layout.floating, null, false) as ViewGroup
windowManager.defaultDisplay.getMetrics(metrics)
layoutParams.gravity = Gravity.START or Gravity.TOP
windowManager.addView(rootView, layoutParams)
flutterView = FlutterView(inflater.context, FlutterSurfaceView(inflater.context, true))
flutterView.attachToFlutterEngine(engine)
engine.lifecycleChannel.appIsResumed()
rootView.findViewById<FrameLayout>(R.id.floating_window)
    .addView(
        flutterView,
        ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
        )
    )
windowManager.updateViewLayout(rootView, layoutParams)
  • 不再限制单页运用

最近咱们在晋级运用中,遇到一个比较为难的需求:在原有OTA功用下,新增一个U盘刺进本地晋级的功用,希望晋级才能和UI都能复用,且互不影响各自流程。

如果是Android项目很简单,把晋级的才能抽象,经过多个Activity办理自己的事务流程,互不干扰。但是Flutter项目归于单页运用,不或许一起展示两个路由页面各自处理,所以也有必要 走Android的机制,让Flutter运用一起运转多个Activity。

咱们在Android端监听了U盘的刺进事情,在需求本地晋级的时分直接弹出Activity。Activity是承继FlutterActivity的,经过<metadata>标签指定办法进口点。与MainActivity运转main区分开,然后经过重写getDartEntrypointArgs办法,把必要的参数传给Flutter进口函数,然后独立运转本地晋级的事务,并且UI和才能都能复用。

class LocalUpgradeActivity : FlutterActivity() {
}
<activity
    android:name=".LocalUpgradeActivity"
    android:exported="true"
    android:hardwareAccelerated="true"
    android:launchMode="singleTop"
    android:theme="@style/Theme.Transparent"
    android:windowSoftInputMode="adjustResize">
    <meta-data
        android:name="io.flutter.Entrypoint"
        android:value="runLocalUpgradeApp" /> <!-- 这里指定Dart层的进口点-->
</activity>
override fun getDartEntrypointArgs(): MutableList<String?> {
    val filePath: String? = intent?.getStringExtra("filePath")
    val tag: String? = intent?.getStringExtra("tag")
    return mutableListOf(filePath, tag)
}

至此,咱们的Flutter运用不再是单页运用,并且所有逻辑和UI都将在Flutter层完成!

总结

咱们遵从Android渠道的机制,把逻辑和UI都尽或许的交给Flutter层,让其在跨渠道上发挥更大的或许性,在落地过程的确切身体会到Android的常识是多么的重要!
当然咱们的运用场景或许相对杂乱,一般运用或许不会有这么多的运用组合;但不管Flutter如何完善,社区更加壮大,它都离不开底层渠道的支撑。
作为Flutter开发者,有精力的情况下,必定要多学各个渠道的结构和才能,让Flutter、更让自己走的更远!