一、跨平台技术的出现背景
1. 原生开发
Android基于Java或者Kotlin, iOS基于Objective-C或Swift,直接调用各自平台的SDK开发的应用程序;
- 优点:能快速访问各种硬件功能(GPS、摄像头)速度快、性能高、可以实现复杂动画及绘制,整体用户体验好
- 缺点: 开发成本高,一个应用要维护两套代码,一致性差
为了加快产品的开发周期,提高开发效率,保持多端的一致性,逐渐出现了多种跨平台技术,下面选取一些常用技术作为对比:
2. H5+原生混合开发
H5技术在各类app中使用的频率非常高,通过原生的网页加载控件WebView (Android)或WKWebView(iOS)来加载H5页面,而WebView实质上就是一个浏览器内核,其JavaScript依然运行在一个权限受限的沙箱中,所以对于大多数系统能力都没有访问权限,如无法访问文件系统、不能使用蓝牙等。所以我们通常要使用JsBridge来进行两端的通信,虽然web技术栈社区及资源丰富,但是性能不够好,对于复杂用户界面或动画,容易出现卡顿和掉帧。
3. React Native
React Native (简称RN)是Facebook于2015年4月开源的跨平台移动应用开发框架,是Facebook早先开源的JS框架 React 在原生移动应用平台的衍生产物,目前支持iOS和Android两个平台。RN使用Javascript语言,类似于HTML的JSX,以及CSS来开发移动应用。
4. Weex
Weex是阿里巴巴于2016年发布的跨平台移动端开发框架,思想及原理和React Native类似,最大的不同是语法层面,Weex支持Vue语法和Rax语法,Rax 的 DSL(Domain Specific Language) 语法是基于 React JSX 语法而创造。与 React 不同,在 Rax 中 JSX 是必选的,它不支持通过其它方式创建组件,所以学习 JSX 是使用 Rax 的必要基础。而React Native只支持JSX语法。
二 、Flutter的优势
Flutter和以上几种跨平台技术最大的不同是其自行实现一套渲染框架,可通过调用skia等方式完成自渲染,而不依赖于原生控件,也不用依赖于webview,使用的语言也从JavaScript变成了Dart。
1. 自绘引擎Skia
Flutter使用Skia作为其2D渲染引擎,Skia是Google的一个2D图形处理函数库,包含字型、坐标转换,以及点阵图都有高效能且简洁的表现,Skia是跨平台的,并提供了非常友好的API,目前Google Chrome浏览器和Android均采用Skia作为其绘图引擎。
2. Dart语言
Flutter使用Dart而不是JavaScript作为开发语言,主要有如下考虑:
- 基于JIT的快速开发周期:Flutter在开发阶段采用,采用JIT模式,这样就避免了每次改动都要进行编译,极大的节省了开发时间;
- 基于AOT的发布包: Flutter在发布时可以通过AOT生成高效的ARM代码以保证应用性能。而JavaScript则不具有这个能力。
3. 总结:
技术类型 | UI渲染方式 | 性能 | 开发效率 | 动态化 | 框架代表 |
---|---|---|---|---|---|
H5 | WebView 渲染 | 一般 | 高 | 支持 | Cordova、Ionic |
JavaScript+原生渲染 | 好 | 中 | 中 | 支持 | RN、Weex |
Flutter | 好 | 高 | 高 | 不支持 | Flutter |
三、Flutter框架结构

1. Flutter Framework
- 底下两层(Foundation和Animation、Painting、Gestures)在Google的一些视频中被合并为一个dart UI层,对应的是Flutter中的dart:ui包,它是Flutter引擎暴露的底层UI库,提供动画、手势及绘制能力。
- Rendering层,这一层是一个抽象的布局层,它依赖于dart UI层,Rendering层会构建一个UI树,当UI树有变化时,会计算出有变化的部分,然后更新UI树,最终将UI树绘制到屏幕上,这个过程类似于React中的虚拟DOM。Rendering层可以说是Flutter UI框架最核心的部分,它除了确定每个UI元素的位置、大小之外还要进行坐标变换、绘制(调用底层dart:ui)。
- Widgets层是Flutter提供的的一套基础组件库,在基础组件库之上,Flutter还提供了 Material 和Cupertino两种视觉风格的组件库。而我们Flutter开发的大多数场景,只是和这两层打交道。
2. Flutter Engine
- 这是一个纯 C++实现的 SDK,其中包括了 Skia引擎、Dart运行时、文字排版引擎等。在代码调用 dart:ui库时,调用最终会走到Engine层,然后实现真正的绘制逻辑。
四、Flutter 基础
五、 混合环境配置
在实际开发中,对于这种新型技术,为了降低引入的风险,一般都会逐步地迁移和体验,这样就涉及到混合项目的搭建,native和flutter两端的通信交互问题,下面以主项目MyApp为例,记录一下混合接入步骤:
开发环境:
[✓] Flutter (Channel unknown, v1.12.13+hotfix.6, on Mac OS X 10.15.3 19D76,
locale zh-Hans-CN)
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
[✓] Android Studio (version 3.5)
1. 国内镜像配置(强烈推荐,可以极大提高分支切换和升级速度。以下两者任选其一,有时候一个不行可以切换另外一个)
镜像1:
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
镜像2:
export FLUTTER_STORAGE_BASE_URL=https://mirrors.sjtug.sjtu.edu.cn
export PUB_HOSTED_URL=https://dart-pub.mirrors.sjtug.sjtu.edu.cn
2. 在MyApp/app/build.gradle
添加so库相关过滤
android {
//...
defaultConfig {
ndk {
// Filter for architectures supported by Flutter.
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
}
3. 进入到MyApp的父级文件夹
下,创建my_flutter module
cd some/path/
flutter create -t module my_flutter
4. 在MyApp/app/build.gradle
文件中增加java8支持
android {
//...
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}
5. 在MyApp/settings.gradle
中,添加以下依赖
include ':app' // assumed existing content
setBinding(new Binding([gradle: this])) // new
evaluate(new File( // new
settingsDir.parentFile, // new
'my_flutter/.android/include_flutter.groovy' // new
)) // new
6. 在MyApp/app/build.gradle
中,依赖flutter 模块
implementation project(':flutter')
7. 在AndroidManifest.xml中注册FlutterActivity
<activity
android:name="io.flutter.embedding.android.FlutterActivity"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
/>
8. 任意页面添加跳转代码,以在TestActivity中为例
myButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startActivity(
FlutterActivity.createDefaultIntent(TestActivity.this)
);
}
});
六、 混合栈管理,FlutterBoost的使用

为了实现高效、便捷的栈管理,推荐使用闲鱼开源的FlutterBoost,下面以Android support包和Flutter Version v1.12.13为例,记录使用过程(实际接入的时候,androidx或者support在不同的flutter version下会出现一些不兼容的情况,增加了试错成本)。
1. 在my_flutter/pubspec.yaml
目录下添加flutter端依赖
flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: 'task/task_v1.12.13_support_hotfixes'
2. 在Android端 Application的onCreate中添加初始化代码
public class MyApp extends Application{
@Override
public void onCreate() {
initFlutterBoost()
}
}
private void initFlutterBoost() {
INativeRouter router = new INativeRouter() {
@Override
public void openContainer(Context context, String url, Map<String, Object> urlParams, int requestCode, Map<String, Object> exts) {
String assembleUrl = Utils.assembleUrl(url, urlParams);
PageRouter.openPageByUrl(context, assembleUrl, urlParams);
}
};
FlutterBoost.BoostLifecycleListener boostLifecycleListener = new FlutterBoost.BoostLifecycleListener() {
@Override
public void beforeCreateEngine() {
}
@Override
public void onEngineCreated() {
FlutterBoost.instance().engineProvider().getPlugins().add(new FlutterLocalStoragePlugin());
FlutterBoost.instance().engineProvider().getPlugins().add(new UserManagerPlugin());
}
@Override
public void onPluginsRegistered() {
}
@Override
public void onEngineDestroy() {
}
};
Platform platform = new FlutterBoost
.ConfigBuilder(this, router)
.isDebug(true)
.whenEngineStart(FlutterBoost.ConfigBuilder.ANY_ACTIVITY_CREATED)
.renderMode(FlutterView.RenderMode.texture)
.lifecycleListener(boostLifecycleListener)
.build();
FlutterBoost.instance().init(platform);
}
3.在AndroidManifest.xml中注册BoostFlutterActivity
<activity
android:name="com.idlefish.flutterboost.containers.BoostFlutterActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:hardwareAccelerated="true"
android:theme="@style/Theme.AppCompat"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/brvah_sample_footer_loading" />
</activity>
4.Flutter端路由注册
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
FlutterBoost.singleton.registerPageBuilders({
'userInfoPage': (pageName, params, _) {
print("userInfo params:$params");
return UserInfoPage(params);
},
'flutter://mineFragment': (pageName, params, _) {
return MinePage(params);
},
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test-Flutter',
builder: FlutterBoost.init(postPush: _onRoutePushed),
home: Container());
}
}
5.Native端路由注册
public class PageRouter {
public final static Map<String, String> pageName = new HashMap<String, String>() {{
put("flutter://userInfoPage","userInfoPage");
}};
public static final String NATIVE_LOGIN_ACTIVITY_URL = "native://loginActivity";
public static boolean openPageByUrl(Context context, String url, Map params) {
return openPageByUrl(context, url, params, 0);
}
public static boolean openPageByUrl(Context context, String url, Map params, int requestCode) {
String path = url.split("\\?")[0];
Log.i("openPageByUrl", path);
try {
if (pageName.containsKey(path)) {
Intent intent = BoostFlutterActivity.withNewEngine().url(pageName.get(path)).params(params)
.backgroundMode(BoostFlutterActivity.BackgroundMode.opaque).build(context);
if (context instanceof Activity) {
Activity activity = (Activity) context;
activity.startActivityForResult(intent, requestCode);
} else {
context.startActivity(intent);
}
return true;
} else if (url.startsWith(NATIVE_LOGIN_ACTIVITY_URL)) {
Intent intent = new Intent(context, LoginActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
return true;
}
return false;
} catch (Throwable t) {
return false;
}
}
}
6.创建一个FlutterFragment
FlutterFragment mMineFragment = new FlutterFragment.NewEngineFragmentBuilder().url("flutter://mineFragment").params(params).build();
7.从Flutter页面启动一个NativeActivity
FlutterBoost.singleton.open("native://loginActivity");
8.从Flutter页面启动一个FlutterActivity
FlutterBoost.singleton.open("flutter://userInfoPage", urlParams: _userInfo);
可以看到FlutterBoost为Native和Flutter的跳转提供了统一标准,我们只需要在Native和Flutter两端维护一个路由表,就可以方便地实现混合栈的管理和参数传递
七、Android与Flutter的通信
在实际的开发过程中,我们时常要从flutter端访问native的数据或者硬件能力,这个时候需要使用到Platform Channel进行通信
1. Platform Channel 分类和简介
- BasicMessageChannel: 支持字符串和半结构化的数据传递
- MethodChannel: 支持方法调用,既可以从Flutter发平台发起方法调用,也可以从平台代码向Flutter发起调用
- EventChannel: 支持数据流通信
2. 插件的编写
使用频率最高的主要是MethodChannel,下面是其主要流程示意图

下面我们以MethodChannel的方式,实现一个退出登录的功能
2.1 Flutter端注册一个MethodeChannel并调用
class UserManager {
MethodChannel _methodChannel;
static UserManager _instance;
UserManager._internal() {
if (_methodChannel == null) {
_methodChannel = new MethodChannel(ChannelConstants.FLUTTER_USER_MANAGER);
}
}
static UserManager getInstance() {
if (_instance == null) {
_instance = new UserManager._internal();
}
return _instance;
}
Future<bool> logout() async {
bool logout = await _methodChannel.invokeMethod("logout");
return logout;
}
}
void _logout() async {
bool isLogout = await UserManager.getInstance().logout();
if(isLogout){
print("退出登录成功")
}else{
}
}
2.2 Android端插件的编写
public class UserManagerPlugin implements MethodChannel.MethodCallHandler, FlutterPlugin {
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
final MethodChannel methodChannel = new MethodChannel(FlutterBoost.instance().engineProvider().getDartExecutor().getBinaryMessenger(), ChannelConstants.FLUTTER_USER_MANAGER);
methodChannel.setMethodCallHandler(this);
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
if (call.method.equals("logout")) {
//这里的UserManager是Android端的实现,是真正的退出登录,清空token等用户数据
UserManager.getInstance().logout();
//这里给Flutter回调结果
result.success(true);
}
}
}
2.3 Android端的插件注册
在之前Application中执行的FlutterBoost的初始化代码的onEnginCreated回调中注册
FlutterBoost.BoostLifecycleListener boostLifecycleListener = new FlutterBoost.BoostLifecycleListener() {
@Override
public void beforeCreateEngine() {
}
@Override
public void onEngineCreated() {
FlutterBoost.instance().engineProvider().getPlugins().add(new UserManagerPlugin());
}
@Override
public void onPluginsRegistered() {
}
@Override
public void onEngineDestroy() {
}
};
这样我们在Flutter端调用UserManager.logout时,会通过注册的MethodChannel通道,调用到Native端的UserManager.logout方法,然后通过result.success(true)拿到调用结果
具体Platform Channel的底层实现原理,后面会用单独的篇幅介绍
网友评论