likes
comments
collection
share

Flutter开发者,需要会原生吗?-- Android 篇

作者站长头像
站长
· 阅读数 46

前言:随着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、更让自己走的更远!

转载自:https://juejin.cn/post/7295571705689423907
评论
请登录