与Flutter第一次亲密接触-Android 视角

Posted Android技术之家

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了与Flutter第一次亲密接触-Android 视角相关的知识,希望对你有一定的参考价值。

作者简介

万坤,5年安卓开发经验,16年加入饿了么,现任职饿了么资深安卓开发工程师,负责饿了么物流安卓相关APP线上的高稳定运行。

前言

Flutter在今年6月份发布第一个Release预览版以来,开发热度呈现了井喷式的爆发。Github上Flutter项目的小星星也已经涨到了3.6万了,同时国内闲鱼团队已经将Flutter用到了业务中并上线运行。可以说Flutter已经有了非常成熟的使用环境,在我们团队内部大家也是跃跃欲试。这里我选择了我们团队页面中一个比较轻量级的页面-设置页面,完成了 Flutter的开发和上线准备工作,下面主要是分享一下这一次亲密接触的经验和心得。

混合开发

实际上我们如果想把Flutter引入到现有的业务中去,就必然会涉及到Flutter和Native混合开发的问题,尤其是Flutter的代码怎么引入到我们原有的工程(实际上官方的Demo是一个纯Flutter的工程)。我这边参考闲鱼的做法,在android端实现的主要步骤如下:

  • 1.新建一个Android的module工程。将此工程作为Flutter相关业务打包的工程,最终输出aar供主工程直接依赖;

  • 2.将Flutter的jar包直接引入到lib目录下。flutter.jar位于 [Flutter SDK目录]/bin/cache/artifacts/engine,Flutter官方只提供了四种CPU架构的SO库:armeabi-v7a、arm64-v8a、x86和x86-64, 但是目前我们使用的SDK大部分只使用了armeabi架构,这里需要将arm目录下面的jar稍作改造,主要是解压后将armeabi-v7a目录更名为armeabi后再打包,可以通过以下的脚本实现:

    cp flutter.jar flutter-armeabi-v7a.jar
    unzip flutter.jar lib/armeabi-v7a/libflutter.so
    mv lib/armeabi-v7a lib/armeabi
    zip -d flutter.jar lib/armeabi-v7a/libflutter.so
    zip flutter.jar lib/armeabi/libflutter.so复制代码
  • 3.新建一个FlutterActivity。这个Activity供Native页面跳转。同时也承载了和原生通信以及页面route的功能,主要代码如下:

 1public class MyFlutterActivity extends FlutterActivity {    @Override
2    protected void onCreate(Bundle savedInstanceState) {
3        FlutterMain.startInitialization(this);        super.onCreate(savedInstanceState);
4        GeneratedPluginRegistrant.registerWith(this);        //flutter和原生通信的channel实现
5        CustomChannel.registerSettingsMethodCall(this, getFlutterView());
6    }    //根据pageId跳到到相应的flutter page
7    public static void start(Context context, String page) {
8        Intent intent = new Intent(context, MyFlutterActivity.class);
9        intent.setAction(Intent.ACTION_RUN);
10        intent.putExtra("route", page);
11        context.startActivity(intent);
12    }
13}
  • 4.新建Flutter工程,这里推荐把Flutter工程作为Android工程的一个submodule。

  • 5.拷贝Flutter工程build产出物。flutter build之后会生成一些字节码和资源文件,在打包时拷贝到assets目录下供运行时使用。我们可以在Flutter工程开发完成之后通过以下的脚本输出产出物到Android工程:


  • 这里也实现了一个小的脚本,在Flutter代码修改后直接接入到工程中运行:


与Flutter第一次亲密接触-Android 视角

  • 不过还是建议直接先在Flutter工程中调试完成后加入到主工程,毕竟Flutter的hot reload还是挺方便的。

Route

混合开发中遇到的另外一个问题就是页面的跳转管理问题,尤其是原生和Flutter之间的相互跳转,涉及到route问题,这里Flutter也做了很好的支持:


App可以添加一个routes列表,通过

Navigator.pushNamed(context, routeName);

Navigator.pop(context);

进行页面的跳转,在Flutter内部进行页面的跳转没有任何问题,但原生与Flutter之间的页面跳转其实遇到了这样的问题:

我们在一个Flutter工程中实现了多个页面,他们不总是一个入口,但是这里却只有一个入口,home的参数怎么从Native端传进来呢?

查看MaterialApp的源码,这里有一个initialRoute的参数, 他是APP中Navigator默认展示的页面,而且这个参数接受从Native端传入。

initialRoute: widget.initialRoute ??ui.window.defaultRouteName,

String get defaultRouteName => _defaultRouteName();

String _defaultRouteName() native 'Window_defaultRouteName';

从这段代码里面可以看到如果在flutter中APP没有设置initialRoute,就会从Native中获取。这样我们就可以在Native端传入不同的初始页面,在Android端代码可以这样实现:

与Flutter第一次亲密接触-Android 视角

ios中也有同样的设置initialRoute的部分。

布局

Flutter中的布局是基于Widget的,可以说一切皆Widget。系统给我们提供了大量已经实现好的Widget,基本上我们是在这些Widget的基础上做一些组合完成布局的。不过这样的结果也导致了Widget的结构非常扁平,Widget的种类异常繁多,给上手带来一些难度。在Flutter IO的目录中,系统帮我们罗列了大概有146个之多的Widget的类型,这里我简单的就我这段时间使用比较高频的一些Widget谈一些自己的体会。

StatelessWidget和StatefulWidget

我们的布局组合大部分需要继承这两个Widget。从字面意义来说很容易区分,一个是有状态的,一个是无状态的,但实际使用中却经常容易混淆。可以说除非是一些写死的icon,基本上所有的页面节点都是有状态的,都会涉及到样式文案等的更新,主要是看这个state维护在哪里,如果维护在父控件,那么这个相关的子控件就是个无状态的。下面以CupertinoSwitch 为例简单的对两种Widget做一个说明,也是我在实际使用过程中踩过的坑。 CupertinoSwitch是系统提供的一个iOS风格的Switch控件,定义非常简单:

 1class CupertinoSwitch extends StatefulWidget {
2  const CupertinoSwitch({
3    Key key,
4    @required this.value,
5    @required this.onChanged,
6    this.activeColor,
7  }) : super(key: key);
8  final bool value;
9  final ValueChanged<bool> onChanged;
10  final Color activeColor;
11  @override
12  _CupertinoSwitchState createState() => new _CupertinoSwitchState();
13  @override
14  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
15    super.debugFillProperties(properties);
16    properties.add(new FlagProperty('value', value: value, ifTrue: 'on', ifFalse: 'off', showName: true));
17    properties.add(new ObjectFlagProperty<ValueChanged<bool>>('onChanged', onChanged, ifNull: 'disabled'));
18  }
19}
20class _CupertinoSwitchState extends State<CupertinoSwitchwith TickerProviderStateMixin {
21  @override
22  Widget build(BuildContext context) {
23    return new _CupertinoSwitchRenderObjectWidget(
24      value: widget.value,
25      activeColor: widget.activeColor ?? CupertinoColors.activeGreen,
26      onChanged: widget.onChanged,
27      vsync: this,
28    );
29  }
30}
31
32链接:https://juejin.im/post/5b8d46c3e51d4538e710bc78
33来源:掘金
34著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

它是一个StatefulWidget,实际上我们看到这个_CupertinoSwitchState是没有维护任何信息的,使用的参数都是Widget的,所以说他也可以是一个StatelessWidget。这里我曾经也犯过一个错误,我在CupertinoSwitch基础上封装了一个CheckBox,维护了一个checked的state,我在父控件中需要更新异步返回的数据对CheckBox进行刷新,发现刷新无效。原来是因为我只能刷新Widget的checked,而无法更新他的state,导致他的页面没有做更新。实际上在开始写flutter的布局时经常会带着Android的开发思维陷入死胡同,在Android经常我们通常是先完成控件的布局,然后再找到这些控件对这些控件做刷新操作。而在Flutter中,数据都是维护在state中,页面需要从state中取数据刷新,Widget可以说都是临时的,所以不要想着find到这个widget再调他的updateState这种逻辑了。

View和ViewGroup

这其实是Android中的概念了,那在Flutter中有对应的东西吗?对Widget根据child进行分类,大概可以分成这几类:

  • 1、无child。这类Widget对应我们在Android开发中的基础View,基本上是页面展示的最基础的元素了,像Text、Image、Icon、Checkbox、Switch等,使用比较简单,这里就不详细讲述了。

  • 2、单child。这类Widget对应我们在Android开发中的style,实际上是对Widget的一些样式的拓展,在Android中我们通常是把样式作为View的一个参数,Flutter中则是单独定义了很多Widget去支持这些样式。这样也造成了很多嵌套,实际上单child的这些Widget的多层嵌套并不会带来性能的损失。多child的Widget则尽量减少嵌套。

    • Container。这是使用比较广泛的一个Widget,它可以给child设置宽高、背景、Margin、Padding等。

    • Padding。可以使用EdgeInsets提供的两种设置padding的方式,all和only。

    • Center。子控件居中显示,默认子控件布局是尽量大的。

    • Align。设定子控件的对齐方式。

  • 3、多child。这类Widget对应我们在Android开发中的ViewGroup,涉及到页面的布局展示。

    • Row。水平的LinearLayout。可以通过不同的主轴和垂直轴的对齐方式,以及结合Expand控件,实现非常复杂的flex布局效果。

    • Column。垂直的LinearLayout。

    • Stack。FrameLayout。最普通的从左上角开始的布局,子控件相互层叠。

    • Table。表格。可以实现丰富的表格效果。

    • ListView。滚动的列表。

    • Card。Material Design风格的CardView。

问题

内存泄漏

在iOS端新开一个Flutter页面销毁后内存不会被回收,导致内存会不断上涨至应用被杀,应该是iOS端的一个bug,Android端没有出现,后续的Flutter版本应该会修复,当前需要做一些缓存的方式减少内存消耗。

黑屏

FlutterActivity在初始化FlutterView的时候比较耗时,会导致页面启动的时候黑屏,好在flutterView提供了一个addFirstFrameListener接口,看网上的方法是重写oncreate中的setContentView方法,在首帧绘制完成前后控制一个loading层的显示,查看Flutter源码也提供了官方的支持, FlutterActivityDelegate会在setContentView之后添加一个launchView,而launchView是否显示是根据两个参数决定的:

与Flutter第一次亲密接触-Android 视角

与Flutter第一次亲密接触-Android 视角

对应的配置是在manifest的activity中添加一个meta-data(注意是Activity的meta-data,而不是Application的):

这样就会在flutterView加载过程中显示windowBackground,如果想实现更复杂的lanchview,也可以参照FlutterActivityDelegate的实现方式。

卡顿

android端debug开发的时候页面显示非常卡顿。我这边开发的一个简单的设置页面,主要是一个ScrollView包裹着一个Column,debug模式下滑动卡顿,打开Flutter Inspector也是看到GPU和UI双曲线飘红。为了验证Release包的流畅性,我们在profile模式下打开Flutter Inspector,看到UI曲线一直显示绿色,fps也基本稳定在60,感观上也是操作比较流畅,但是GPU曲线一直飘红,看官方介绍 offscreen layers对GPU的计算有很大影响,因为涉及到频繁的调用savelayer。可以通过 checkerboardOffscreenLayers这个参数判断页面有没有在屏幕外绘制。

new MaterialApp(
	checkerboardOffscreenLayers: true,
);复制代码

我们这里有一个ScrollView,导致不可避免的产生了屏幕外的视图。由此可见Flutter对于ScrollView的支持并不高效,后续可以替换成listview提高重用性。

使用心得

开发Flutter将近两周的时间,使用起来感觉比较得心应手,生态可以说非常的健全了,Widget及Widget的自定义拓展基本上能满足各种复杂页面的开发。另外Dart语言可能是对Java开发来说最友好的Web语言了,而且AndroidStudio对它做了很好的支持,基本上我们还是可以做到点击自动跳转以及class一键import了。如果是一个新开的项目,用Flutter实现确实能带来很大的生产力的提高。

规划

目前对Flutter基本上只是一个大概的了解,后续将从以下几个方面深入理解整个Flutter框架。

  • 渲染流程 阅读Flutter Engine相关代码,深入了解底层渲染的原理。

  • 组件 网络、本地通信、存储、route框架、数据监控等基础模块的封装实现。

  • 性能工具 Flutter提供了大量的性能检测工具,借助这些工具可以定位和优化程序中的性能问题。

  • 命令解析 Flutter提供了很多命令行实现编译和打包,可以深入了解其中的实现原理。

  • 代码架构 可以将MVP、MVVM等架构方案引入到flutter中。

                        喜欢 就关注吧,欢迎投稿!



以上是关于与Flutter第一次亲密接触-Android 视角的主要内容,如果未能解决你的问题,请参考以下文章

第一次亲密接触Kotlin

与JMeter的第一次亲密接触

与postman的第一次亲密接触

秒杀多线程第二篇 多线程第一次亲密接触 CreateThread与_beginthreadex本质区别

秒杀多线程第二篇 多线程第一次亲密接触 CreateThread与_beginthreadex本质区别

tms web core 与 kbmmw 第一次亲密接触