前语:跟着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社区并不支撑的才能,比如:getMetaData
、getMacAddress
、reboot/shutdown
、sendBroadcast
等,这些才能都需求咱们运用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、更让自己走的更远!