irpas技术客

Flutter的原理及美团的实践(中),移动开发工程师核心竞争力_flutter 美团_wq221aas

未知 766

loadData: (callback) async {

Map<String, dynamic> data = await post(“home/groups”);

if (data == null) {

callback(false);

return;

}

_data = AllCategoryResponse.fromJson(data);

if (_data == null || _data.code != 0) {

callback(false);

return;

}

callback(true);

}),

SO库兼容性

Flutter官方只提供了四种CPU架构的SO库:armeabi-v7a、arm64-v8a、x86和x86-64,其中x86系列只支持Debug模式,但是外卖使用的大量SDK都只提供了armeabi架构的库。

虽然我们可以通过修改引擎src根目录和third_party/dart目录下build/config/arm.gni,third_party/skia目录下的BUILD.gn等配置文件来编译出armeabi版本的Flutter引擎,但是实际上市面上绝大部分设备都已经支持armeabi-v7a,其提供的硬件加速浮点运算指令可以大大提高Flutter的运行速度,在灰度阶段我们可以主动屏蔽掉不支持armeabi-v7a的设备,直接使用armeabi-v7a版本的引擎。

做到这点我们首先需要修改Flutter提供的引擎,在Flutter安装目录下的bin/cache/artifacts/engine下有Flutter下载的所有平台的引擎:

我们只需要修改android-arm、android-arm-profile和android-arm-release下的flutter.jar,将其中的lib/armeabi-v7a/libflutter.so移动到lib/armeabi/libflutter.so即可:

cd $FLUTTER_ROOT/bin/cache/artifacts/engine

for arch in android-arm android-arm-profile android-arm-release; do

pushd $arch

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

popd

done

这样在打包后Flutter的SO库就会打到APK的lib/armeabi目录中。在运行时如果设备不支持armeabi-v7a可能会崩溃,所以我们需要主动识别并屏蔽掉这类设备,在Android上判断设备是否支持armeabi-v7a也很简单:

public static boolean isARMv7Compatible() {

try {

if (SDK_INT >= LOLLIPOP) {

for (String abi : Build.SUPPORTED_32_BIT_ABIS) {

if (abi.equals(“armeabi-v7a”)) {

return true;

}

}

} else {

if (CPU_ABI.equals(“armeabi-v7a”) || CPU_ABI.equals(“arm64-v8a”)) {

return true;

}

}

} catch (Throwable e) {

L.wtf(e);

}

return false;

}

灰度和自动降级策略

Horn是一个美团内部的跨平台配置下发SDK,使用Horn可以很方便地指定灰度开关:

在条件配置页面定义一系列条件,然后在参数配置页面添加新的字段flutter即可:

因为在客户端做了ABI兜底策略,所以这里定义的ABI规则并没有启用。

Flutter目前仍然处于Beta阶段,灰度过程中难免发生崩溃现象,观察到崩溃后再针对机型或者设备ID来做降级虽然可以尽量降低影响,但是我们可以做到更迅速。外卖的Crash采集SDK同时也支持JNI Crash的收集,我们专门为Flutter注册了崩溃监听器,一旦采集到Flutter相关的JNI Crash就立即停止该设备的Flutter功能,启动Flutter之前会先判断FLUTTER_NATIVE_CRASH_FLAG文件是否存在,如果存在则表示该设备发生过Flutter相关的崩溃,很有可能是不兼容导致的问题,当前版本周期内在该设备上就不再使用Flutter功能。

除了崩溃以外,Flutter页面中的Dart代码也可能发生异常,例如服务器下发数据格式错误导致解析失败等等,Dart也提供了全局的异常捕获功能:

import ‘package:wm_app/plugins/wm_metrics.dart’;

void main() {

runZoned(() => runApp(WaimaiApp()), onError: (Object obj, StackTrace stack) {

uploadException(“KaTeX parse error: Undefined control sequence: \n at position 4: obj\?n?stack”);

});

}

这样我们就可以实现全方位的异常监控和完善的降级策略,最大程度减少灰度时可能对用户带来的影响。

分析崩溃堆栈和异常数据

Flutter的引擎部分全部使用C/C++实现,为了减少包大小,所有的SO库在发布时都会去除符号表信息。和其他的JNI崩溃堆栈一样,我们上报的堆栈信息中只能看到内存地址偏移量等信息:


Build fingerprint: ‘Rock/odin/odin:7.1.1/NMF26F/1527007828:user/dev-keys’

Revision: ‘0’

Author: collect by ‘libunwind’

ABI: ‘arm64-v8a’

pid: 28937, tid: 29314, name: 1.ui >>> com.sankuai.meituan.takeoutnew <<<

signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0

backtrace:

r0 00000000 r1 ffffffff r2 c0e7cb2c r3 c15affcc

r4 c15aff88 r5 c0e7cb2c r6 c15aff90 r7 bf567800

r8 c0e7cc58 r9 00000000 sl c15aff0c fp 00000001

ip 80000000 sp c0e7cb28 lr c11a03f9 pc c1254088 cpsr 200c0030

#00 pc 002d7088 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so

#01 pc 002d5a23 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so

#02 pc 002d95b5 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so

#03 pc 002d9f33 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so

#04 pc 00068e6d /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so

#05 pc 00067da5 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so

#06 pc 00067d5f /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so

#07 pc 003b1877 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so

#08 pc 003b1db5 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so

#09 pc 0000241c /data/data/com.sankuai.meituan.takeoutnew/app_flutter/vm_snapshot_instr

单纯这些信息很难定位问题,所以我们需要使用NDK提供的ndk-stack来解析出具体的代码位置:

ndk-stack -sym PATH [-dump PATH]

Symbolizes the stack trace from an Android native crash.

-sym PATH sets the root directory for symbols

-dump PATH sets the file containing the crash dump (default stdin)

如果使用了定制过的引擎,必须使用engine/src/out/android-release下编译出的libflutter.so文件。一般情况下我们使用的是官方版本的引擎,可以在flutter_infra页面直接下载带有符号表的SO文件,根据打包时使用的Flutter工具版本下载对应的文件即可。比如0.4.4 beta版本:

$ flutter --version # version命令可以看到Engine对应的版本 06afdfe54e

Flutter 0.4.4 ? channel beta ? https://github.com/flutter/flutter.git

Framework ? revision f9bb4289e9 (5 weeks ago) ? 2018-05-11 21:44:54 -0700

Engine ? revision 06afdfe54e

Tools ? Dart 2.0.0-dev.54.0.flutter-46ab040e58

$ cat flutter/bin/internal/engine.version # flutter安装目录下的engine.version文件也可以看到完整的版本信息 06afdfe54ebef9168a90ca00a6721c2d36e6aafa

06afdfe54ebef9168a90ca00a6721c2d36e6aafa

拿到引擎版本号后在https://console.cloud.google.com/storage/browser/flutter_infra/flutter/06afdfe54ebef9168a90ca00a6721c2d36e6aafa/?看到该版本对应的所有构建产物,下载android-arm-release、android-arm64-release和android-x86目录下的symbols.zip,并存放到对应目录:

执行ndk-stack即可看到实际发生崩溃的代码和具体行数信息:

ndk-stack -sym flutter-production-syms/06afdfe54ebef9168a90ca00a6721c2d36e6aafa/armeabi-v7a -dump flutter_jni_crash.txt

********** Crash dump: **********

Build fingerprint: ‘Rock/odin/odin:7.1.1/NMF26F/1527007828:user/dev-keys’

pid: 28937, tid: 29314, name: 1.ui >>> com.sankuai.meituan.takeoutnew <<<

signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0

Stack frame #00 pc 002d7088 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine minikin::WordBreaker::setText(unsigned short const*, unsigned int) at /b/build/slave/Linux_Engine/build/src/out/android_release/…/…/flutter/third_party/txt/src/minikin/WordBreaker.cpp:55

Stack frame #01 pc 002d5a23 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine minikin::LineBreaker::setText() at /b/build/slave/Linux_Engine/build/src/out/android_release/…/…/flutter/third_party/txt/src/minikin/LineBreaker.cpp:74

Stack frame #02 pc 002d95b5 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine txt::Paragraph::ComputeLineBreaks() at /b/build/slave/Linux_Engine/build/src/out/android_release/…/…/flutter/third_party/txt/src/txt/paragraph.cc:273

Stack frame #03 pc 002d9f33 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine txt::Paragraph::Layout(double, bool) at /b/build/slave/Linux_Engine/build/src/out/android_release/…/…/flutter/third_party/txt/src/txt/paragraph.cc:428

Stack frame #04 pc 00068e6d /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine blink::ParagraphImplTxt::layout(double) at /b/build/slave/Linux_Engine/build/src/out/android_release/…/…/flutter/lib/ui/text/paragraph_impl_txt.cc:54

Stack frame #05 pc 00067da5 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine tonic::DartDispatcher<tonic::IndicesHolder<0u>, void (blink::Paragraph::)(double)>::Dispatch(void (blink::Paragraph::)(double)) at /b/build/slave/Linux_Engine/build/src/out/android_release/…/…/topaz/lib/tonic/dart_args.h:150

Stack frame #06 pc 00067d5f /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine void tonic::DartCall<void (blink::Paragraph::)(double)>(void (blink::Paragraph::)(double), _Dart_NativeArguments*) at /b/build/slave/Linux_Engine/build/src/out/android_release/…/…/topaz/lib/tonic/dart_args.h:198

Stack frame #07 pc 003b1877 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine dart::NativeEntry::AutoScopeNativeCallWrapperNoStackCheck(_Dart_NativeArguments*, void ()(_Dart_NativeArguments)) at /b/build/slave/Linux_Engine/build/src/out/android_release/…/…/third_party/dart/runtime/vm/native_entry.cc:198

Stack frame #08 pc 003b1db5 /data/app/com.sankuai.meituan.takeoutnew-1/lib/arm/libflutter.so: Routine dart::NativeEntry::LinkNativeCall(_Dart_NativeArguments*) at /b/build/slave/Linux_Engine/build/src/out/android_release/…/…/third_party/dart/runtime/vm/native_entry.cc:348

Stack frame #09 pc 0000241c /data/data/com.sankuai.meituan.takeoutnew/app_flutter/vm_snapshot_instr

Dart异常则比较简单,默认情况下Dart代码在编译成机器码时并没有去除符号表信息,所以Dart的异常堆栈本身就可以标识真实发生异常的代码文件和行数信息:

FlutterException: type ‘_InternalLinkedHashMap<dynamic, dynamic>’ is not a subtype of type ‘num’ in type cast

#0 _$CategoryGroupFromJson (package:wm_app/lib/all_category/model/category_model.g.dart:29)

#1 new CategoryGroup.fromJson (package:wm_app/all_category/model/category_model.dart:51)

#2 _$CategoryListDataFromJson. (package:wm_app/lib/all_category/model/category_model.g.dart:5)

#3 MappedListIterable.elementAt (dart:_internal/iterable.dart:414)

#4 ListIterable.toList (dart:_internal/iterable.dart:219)

#5 _$CategoryListDataFromJson (package:wm_app/lib/all_category/model/category_model.g.dart:6)

#6 new CategoryListData.fromJson (package:wm_app/all_category/model/category_model.dart:19)

#7 _$AllCategoryResponseFromJson (package:wm_app/lib/all_category/model/category_model.g.dart:19)

#8 new AllCategoryResponse.fromJson (package:wm_app/all_category/model/category_model.dart:29)

#9 AllCategoryPage.build. (package:wm_app/all_category/category_page.dart:46)

#10 _WaimaiLoadingState.build (package:wm_app/all_category/widgets/progressive_loading_page.dart:51)

#11 StatefulElement.build (package:flutter/src/widgets/framework.dart:3730)

#12 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3642)

#13 Element.rebuild (package:flutter/src/widgets/framework.dart:3495)

#14 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2242)

总结

作为一名从事Android的开发者,很多人最近都在和我吐槽Android是不是快要凉了?而在我看来这正是市场成熟的表现,所有的市场都是温水煮青蛙,永远会淘汰掉不愿意学习改变,安于现状的那批人,希望所有的人能在大浪淘沙中留下来,因为对于市场的逐渐成熟,平凡并不是我们唯一的答案! 在最后我整理了一份资料,而且我们为了感谢很多支持的学者,资料是无偿分享的,需要的同学可以来学习学习 领取方式:GitHub地址

场的逐渐成熟,平凡并不是我们唯一的答案! 在最后我整理了一份资料,而且我们为了感谢很多支持的学者,资料是无偿分享的,需要的同学可以来学习学习 领取方式:GitHub地址 [外链图片转存中…(img-67VPlXNS-1643956941262)] [外链图片转存中…(img-tMeySZXF-1643956941262)]


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #Flutter #美团 #loaddata #callback #Async #MapampltString #dynamicampgt