阿裡P7移動網際網路架構師進階視訊(每日更新中)免費學習請點選: https://space.bilibili.com/474380680
Flutter和原生性能對比
雖然使用原生實作(左)和Flutter實作(右)的全品類頁面在實際使用過程中幾乎分辨不出來:
但是我們還需要在性能方面有一個比較明确的資料對比。
我們最關心的兩個頁面性能名額就是頁面加載時間和頁面渲染速度。測試頁面加載速度可以直接使用美團内部的Metrics性能測試工具,我們将頁面Activity對象建立作為頁面加載的開始時間,頁面API資料傳回作為頁面加載結束時間。
從兩個實作的頁面分别啟動400多次的資料中可以看到,原生實作(AllCategoryActivity)的加載時間中位數為210ms,Flutter實作(FlutterCategoryActivity)的加載時間中位數為231ms。考慮到目前我們還沒有針對FlutterView做緩存和重用,FlutterView每次建立都需要初始化整個Flutter環境并加載相關代碼,多出的20ms還在預期範圍内:
因為Flutter的UI邏輯和繪制代碼都不在主線程執行,Metrics原有的FPS功能無法統計到Flutter頁面的真實情況,我們需要用特殊方法來對比兩種實作的渲染效率。Android原生實作的界面渲染耗時使用系統提供的FrameMetrics接口進行監控:
public class AllCategoryActivity extends WmBaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
getWindow().addOnFrameMetricsAvailableListener(new Window.OnFrameMetricsAvailableListener() {
List<Integer> frameDurations = new ArrayList<>(100);
@Override
public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
frameDurations.add((int) (frameMetrics.getMetric(TOTAL_DURATION) / 1000000));
if (frameDurations.size() == 100) {
getWindow().removeOnFrameMetricsAvailableListener(this);
L.w("AllCategory", Arrays.toString(frameDurations.toArray()));
}
}
}, new Handler(Looper.getMainLooper()));
}
super.onCreate(savedInstanceState);
// ...
}
}
Flutter在Framework層隻能取到每幀中UI操作的CPU耗時,GPU操作在Flutter引擎内部實作,是以要修改引擎來監控完整的渲染耗時,在Flutter引擎目錄下src/flutter/shell/common/
http://rasterizer.cc檔案中添加:
void Rasterizer::DoDraw(std::unique_ptr<flow::LayerTree> layer_tree) {
if (!layer_tree || !surface_) {
return;
}
if (DrawToSurface(*layer_tree)) {
last_layer_tree_ = std::move(layer_tree);
#if defined(OS_ANDROID)
if (compositor_context_->frame_count().count() == 101) {
std::ostringstream os;
os << "[";
const std::vector<TimeDelta> &engine_laps = compositor_context_->engine_time().Laps();
const std::vector<TimeDelta> &frame_laps = compositor_context_->frame_time().Laps();
size_t i = 1;
for (auto engine_iter = engine_laps.begin() + 1, frame_iter = frame_laps.begin() + 1;
i < 101 && engine_iter != engine_laps.end(); i++, engine_iter++, frame_iter++) {
os << (*engine_iter + *frame_iter).ToMilliseconds() << ",";
}
os << "]";
__android_log_write(ANDROID_LOG_WARN, "AllCategory", os.str().c_str());
}
#endif
}
}
即可得到每幀繪制時真正消耗的時間。測試時我們将兩種實作的頁面分别打開100次,每次打開後執行兩次滾動操作,使其繪制100幀,将這100幀的每幀耗時記錄下來:
for (( i = 0; i < 100; i++ )); do
openWMPage allcategory
sleep 1
adb shell input swipe 500 1000 500 300 900
adb shell input swipe 500 1000 500 300 900
adb shell input keyevent 4
done
将測試結果的100次啟動中每幀耗時取平均値,得到每幀平均耗時情況(橫坐标軸為幀序列,縱坐标軸為每幀耗時,機關為毫秒):
Android原生實作和Flutter版本都會在頁面打開的前5幀超過16ms,剛打開頁面時原生實作需要建立大量View,Flutter也需要建立大量Widget,後續幀中可以重用大部分控件和渲染節點(原生的RenderNode和Flutter的RenderObject),是以啟動時的布局和渲染操作都是最耗時的。
10000幀(100次×100幀每次)中Android原生總平均値為10.21ms,Flutter總平均値為12.28ms,Android原生實作總丢幀數851幀8.51%,Flutter總丢幀987幀9.87%。在原生實作的觸摸事件處理和過度繪制充分優化的前提下,Flutter完全可以媲美原生的性能。
總結
Flutter目前仍處于早期階段,也還沒有釋出正式的Release版本,不過我們看到Flutter團隊一直在為這一目标而努力。雖然Flutter的開發生态不如Android和iOS原生應用那麼成熟,許多常用的複雜控件還需要自己實作,有的甚至會比較困難(比如官方尚未提供的
ListView.scrollTo(index)功能),但是在高性能和跨平台方面Flutter在衆多UI架構中還是有很大優勢的。
開發Flutter應用隻能使用Dart語言,Dart本身既有靜态語言的特性,也支援動态語言的部分特性,對于Java和JavaScript開發者來說門檻都不高,3-5天可以快速上手,大約1-2周可以熟練掌握。
在開發全品類頁面的Flutter版本時我們也深刻體會到了Dart語言的魅力,Dart的語言特性使得Flutter的界面建構過程也比Android原生的XML+JAVA更直覺,代碼量也從原來的900多行減少到500多行(排除掉引用的公共元件)。Flutter頁面內建到App後APK體積至少會增加5.5MB,其中包括3.3MB的SO庫檔案和2.2MB的ICU資料檔案,此外業務代碼1300行編譯産物的大小有2MB左右。
Flutter本身的特性适合追求iOS和Android跨平台的一緻體驗,追求高性能的UI互動效果的場景,不适合追求動态化部署的場景。Flutter在Android上已經可以實作動态化部署,但是由于Apple的限制,在iOS上實作動态化部署非常困難,Flutter團隊也正在和Apple積極溝通。
原文作者:美團技術團隊
原文連結:
https://zhuanlan.zhihu.com/p/41732803