kernel_blob.bin和platform.dill都是由flutter_tools中的bundle.dart中调用KernelCompiler生成。

在Release模式(flutter run --release)下,Flutter会使用Dart的AOT运行模式,编译时将Dart代码转换成ARM指令:

kernel_blob.bin和platform.dill都不在打包后的APK中,取代其功能的是(isolate/vm)snapshot(data/instr)四个文件。snapshot文件由Flutter SDK中的flutter/bin/cache/artifacts/engine/android-arm-release/darwin-x64/gen_snapshot命令生成,vm_snapshot_*是Dart虚拟机运行所需要的数据和代码指令,isolate_snapshot_*则是每个isolate运行所需要的数据和代码指令。

Flutter App运行机制

Flutter构建出的APK在运行时会将所有assets目录下的资源文件解压到App私有文件目录中的flutter目录下,主要包括处理字符编码的icudtl.dat,还有Debug模式的kernel_blob.bin、platform.dill和Release模式下的4个snapshot文件。默认情况下Flutter在Application#onCreate时调用FlutterMain#startInitialization来启动解压任务,然后在FlutterActivityDelegate#onCreate中调用FlutterMain#ensureInitializationComplete来等待解压任务结束。

Flutter在Debug模式下使用JIT执行方式,主要是为了支持广受欢迎的热刷新功能:

触发热刷新时Flutter会检测发生改变的Dart文件,将其同步到App私有缓存目录下,DartVM加载并且修改对应的类或者方法,重建控件树后立即可以在设备上看到效果。

在Release模式下Flutter会直接将snapshot文件映射到内存中执行其中的指令:

在Release模式下,FlutterActivityDelegate#onCreate中调用FlutterMain#ensureInitializationComplete方法中会将AndroidManifest中设置的snapshot(没有设置则使用上面提到的默认值)文件名等运行参数设置到对应的C++同名类对象中,构造FlutterNativeView实例时调用nativeAttach来初始化DartVM,运行编译好的Dart代码。

打包Android Library

了解Flutter项目的构建和运行机制后,我们就可以按照其需求打包成AAR然后集成到现有原生App中了。首先在andorid/app/build.gradle中修改:

简单修改后我们就可以使用Android Studio或者Gradle命令行工具将Flutter代码打包到aar中了。Flutter运行时所需要的资源都会包含在aar中,将其发布到maven服务器或者本地maven仓库后,就可以在原生App项目中引用。

但这只是集成的第一步,为了让Flutter页面无缝衔接到外卖App中,我们需要做的还有很多。

图片资源复用

Flutter默认将所有的图片资源文件打包到assets目录下,但是我们并不是用Flutter开发全新的页面,图片资源原来都会按照Android的规范放在各个drawable目录,即使是全新的页面也会有很多图片资源复用的场景,所以在assets目录下新增图片资源并不合适。

Flutter官方并没有提供直接调用drawable目录下的图片资源的途径,毕竟drawable这类文件的处理会涉及大量的Android平台相关的逻辑(屏幕密度、系统版本、语言等等),assets目录文件的读取操作也在引擎内部使用C++实现,在Dart层面实现读取drawable文件的功能比较困难。Flutter在处理assets目录中的文件时也支持添加多倍率的图片资源,并能够在使用时自动选择,但是Flutter要求每个图片必须提供1x图,然后才会识别到对应的其他倍率目录下的图片:

flutter:

assets:

images/cat.png images/2x/cat.png images/3.5x/cat.png

new Image.asset(‘images/cat.png’);

这样配置后,才能正确地在不同分辨率的设备上使用对应密度的图片。但是为了减小APK包体积我们的位图资源一般只提供常用的2x分辨率,其他分辨率的设备会在运行时自动缩放到对应大小。针对这种特殊的情况,我们在不增加包体积的前提下,同样提供了和原生App一样的能力:

在调用Flutter页面之前将指定的图片资源按照设备屏幕密度缩放,并存储在App私有目录下。 Flutter中使用时通过自定义的WMImage控件来加载,实际是通过转换成FileImage并自动设置scale为devicePixelRatio来加载。

这样就可以同时解决APK包大小和图片资源缺失1x图的问题。

Flutter和原生代码的通信

我们只用Flutter实现了一个页面,现有的大量逻辑都是用Java实现,在运行时会有许多场景必须使用原生应用中的逻辑和功能,例如网络请求,我们统一的网络库会在每个网络请求中添加许多通用参数,也会负责成功率等指标的监控,还有异常上报,我们需要在捕获到关键异常时将其堆栈和环境信息上报到服务器。这些功能不太可能立即使用Dart实现一套出来,所以我们需要使用Dart提供的Platform Channel功能来实现Dart→Java之间的互相调用。

以网络请求为例,我们在Dart中定义一个MethodChannel对象:

import ‘dart:async’;

import ‘package:flutter/services.dart’;

const MethodChannel _channel = const MethodChannel(‘com.sankuai.waimai/network’);

Future> post(String path, [Map form]) async {

return _channel.invokeMethod(“post”, {‘path’: path, ‘body’: form}).then((result) {

return new Map.from(result);

}).catchError((_) => null);

}

然后在Java端实现相同名称的MethodChannel:

public class FlutterNetworkPlugin implements MethodChannel.MethodCallHandler {

private static final String CHANNEL_NAME = “com.sankuai.waimai/network”;

@Override

public void onMethodCall(MethodCall methodCall, final MethodChannel.Result result) {

switch (methodCall.method) {

case “post”:

RetrofitManager.performRequest(post((String) methodCall.argument(“path”), (Map) methodCall.argument(“body”)),

new DefaultSubscriber() {

@Override

public void onError(Throwable e) {

result.error(e.getClass().getCanonicalName(), e.getMessage(), null);

}

@Override

public void onNext(Map stringBaseResponse) {

result.success(stringBaseResponse);

}

}, tag);

break;

default:

result.notImplemented();

break;

}

}

}

在Flutter页面中注册后,调用post方法就可以调用对应的Java实现:

loadData: (callback) async {

Map 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功能。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)

面试复习笔记

这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。 节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960页Android开发笔记》

《1307页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长! 列出的知识点会大大增加通过前两轮技术面试的几率。

[外链图片转存中…(img-0QjT9Ymt-1712634422852)]

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

[外链图片转存中…(img-TeAVUhvJ-1712634422853)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长! [外链图片转存中…(img-yXUxAiD1-1712634422853)]

推荐阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: