Flutter - 特性

在我們深入學習 Flutter 之前,對 Flutter 特性有個清晰的認識我認為是非常有必要的,這會讓我們后面的學習更加透徹

尤其是這幾天我被拉去了一個賣 Flutter 學習課程的公開課里,前面的內容很不錯,在開始 Flutter 課程之前,先把 Flutter 的特性說上這么一說,我覺得這樣學習的話效果會很不錯。不能一上來就直接學,得先對要學習的內容有個深刻的認識,這樣才好在學習的時候有更深入,更正確的認識

Flutter 的原理很簡單,?創(chuàng)建一張畫布,并在這張畫布上渲染界面。同時,監(jiān)聽原生事件,在 Dart 層響應所有觸摸事件。這和跨平臺游戲引擎的原理是一致的。抽象出統(tǒng)一的界面、觸摸、交互語言,然后使用一致的渲染引擎呈現(xiàn)最終產(chǎn)物


Flutter 最顯著的特性就 2 個:

跨平臺 - 是指我們可以通過同樣的 Flutter 代碼在 android,ios 2 端實現(xiàn)同樣 UI 效果的 app ,這里就要說明了 Flutter 實質上實現(xiàn)的 UI 跨平臺,涉及到不同平臺的巨大差異,是不可能使用同一套代碼在多端實現(xiàn)相同的效果的,未來隨著 Fuchsia 系統(tǒng)的上線,F(xiàn)lutter 的運行環(huán)境還會加上 pc,物聯(lián)網(wǎng),web 端,多端之間的差異更是會拉大,想著一套代碼運行那是沒戲的,只能是通過 Flutter 代碼實現(xiàn) UI 層級的多端運行, Flutter 代碼取調用各端本地實現(xiàn)的功能 lib

Fuchsia 已經(jīng)可以在 PC 運行了

我們知道之前 FaceBook 的 RN 也是號稱:"一處編寫,處處使用",后來不像宣城的這么好事,然后改成了 "一處學習,處處使用",再然后就沒然后啦,18年 FaceBook 停更 RN 了,也就是放棄了。RN 的是使用 JS 語言編寫代碼,然后由 RN 解釋器在本地映射生成 native 的 view 已實現(xiàn)顯示的,android 吧本身 UI 性能就不怎么樣,要不網(wǎng)上怎么會有一大票鋪天蓋地的 UI 性能優(yōu)化文章呢,RN 在 android 之上再次加上了 view 解釋器和生成器,無形這又是增加了不少任務,所以 RN 的性能優(yōu)化了好久,最后還是不理想,無奈放棄,這也是有原因的,其實最大的原因就是 Flutter 的出現(xiàn),F(xiàn)lutter 的性能相比 RN 有太大的優(yōu)勢,F(xiàn)aceBook 也就不在 RN 身上在費事了,注定是打不過 Flutter 的,再說 RN 的坑太多,大伙都不用了,沒見阿里爸爸都寫了一篇 RN 從入門到放棄嗎

哈哈這不正巧 FaceBook 正式回應了 Flutter 了嗎,在專訪中面對直白的問題,F(xiàn)aceBook 沒有做正面回答就是最好的答案,呵呵,F(xiàn)aceBook 自己都信心不足,說什么 RN 和 Flutter 不是一個類型的,沒有可比性,這不是純扯淡嘛,詳見:

最好最全的 Flutter vs React Native 對比

都說 Flutter 性能好,為啥呢,這還得從 Flutter 的架構看,其實很簡單

Flutter 的架構圖

Engine C++ 內嵌層包括: Skia | Dart | Text

  • Skia - 是開源的二維圖形庫,提供了適用于多種軟硬件平臺的通用API。其已作為 Google Chrome,Chrome OS,Android, Mozilla Firefox, Firefox OS 等其他眾多產(chǎn)品的圖形引擎,支持平臺還包括 Windows7 / macOS / iOS8 / Android4.1 / Ubuntu14.04
  • Dart - 部分主要包括 : Dart Runtime,Garbage Collection(GC)
  • Text - 文本渲染,其渲染層次如下:衍生自minikin的libtxt庫(用于字體選擇,分隔行)。HartBuzz用于字形選擇和成型。Skia作為渲染/GPU后端,在Android和Fuchsia上使用FreeType渲染,在iOS上使用CoreGraphics來渲染字體。

重點看紅圈部分,F(xiàn)lutter 是直接使用 Skia 2D 圖形引擎來繪制的,不再是 RN 的那套映射 native 本地組件了,F(xiàn)lutter 做的比 native 更徹底,理論性能上甚至比 native 上還好

Flutter 怎么干的,F(xiàn)lutter 沒有 xml 了,UI 組件直接寫在代碼里面,F(xiàn)lutter 生成的 android 項目只有 MainActivity 這一個頁面, MainActivity 頁面里面只有 FlutterView 這一個 view ,所有的 UI 組件全部由 FlutterView 直接在 canvas 上繪制,比 android 原生還利落

android 原生得經(jīng)過 xml(Dom 解析) -> layoutInflater 反射出 view 視圖樹來 -> rootView 的 measu,layout,draw -> 系統(tǒng)驅動 Skia 2D 繪圖引擎來繪制圖像

Flutter 沒有了 xml ,也就不用 Dom 解析了,直接通過 canvas 驅動 Skia 2D 繪圖引擎來繪制,F(xiàn)lutterView 渲染 UI 也比 android 強很多,不再是 android 那樣從 rootView 一層一層的反復計算,定位再繪制。Flutter 使用統(tǒng)一的 UI 模型:widget,在更新 widget 的時候,采用類似于 recycleview DiffUtils 那樣的 view 復用機制復用 widget 對象,無用的 widget 能復用就復用,更更新數(shù)據(jù)就更新數(shù)據(jù),這樣比實現(xiàn)更高效率的 UI 渲染。當然一切看優(yōu)化,F(xiàn)lutter 要是優(yōu)化到位,性能肯定比原生要好這是一定的了,可能還好很多

圖不是很清晰,大家看個意思


Flutter 對比 android 繪制流程

通過對比,我想說 Flutter 真是太棒了,Google 通過 android 的鍛煉已經(jīng)對移動端足夠了解,坑都趟的差不多了,所以作為下一代產(chǎn)品預熱的 Flutter 天然的在技術上對 android NB 多了,以 android 現(xiàn)在的市場占有率,Google 的能力,F(xiàn)uchsia 一出來包括硬件和軟件在內的生態(tài)很快都能做起來,取代 android 也就是 2-3年的事

為了做在到多端的 UI 統(tǒng)一,F(xiàn)lutter 提供的 UI 組件在 android 和 ios 上分別了做了自行繪制,以保證式樣相同


Flutter 在 android / ios 上 UI 對比_1
Flutter 在 android / ios 上 UI 對比_2

Flutter 在 Android 系統(tǒng)上使用 Skia 繪圖引擎繪制圖形,在 ios 上使用 JPEG 繪圖引擎繪制圖形,這些都是各端原生的,將來還會有 pc 端的,web 端的實現(xiàn)??梢钥吹?Flutter 思路的巧妙,簡便,當然也是任務量巨大,所以 Flutter 才能在眾多跨平臺方案中脫穎而出,贏得寶座,這里我得再次膜拜 Google 大神

熱重載 - 這個不要理解為熱修復啊,不是這個,熱重載指的是在開發(fā)階段我們只要 Control + s 保存就能直接在設備上更新代碼了

Flutter 在 deBug 階段不需要編譯,直接發(fā)送 Dart 文件差異包給鏈接的設備更新代碼,因為沒有 編譯,所以也不用 打包,安裝,這2個過程,可以做到秒級更新的水平,這比傳統(tǒng)的 android 編譯,打包,安裝的過程快了不知道多少,真是一個天上一個地下,這個時候我想大家都會深深體驗到:科技改變生活這句話含義的,有了這個優(yōu)勢,我們一天至少多了 20-30 分鐘的工作時間


Flutter 渲染機制

Flutter 界面渲染過程分為三個階段:布局、繪制、合成

布局和繪制在Flutter框架中完成,合成則交由引擎負責,至于采用合成圖層的原因:由于直接交付給 GPU 多圖層視圖數(shù)據(jù)是低效率的,可能會重復繪制,所以還需要進行一步圖層的合成,最后才交由引擎負責光柵化視圖

  • Widget 樹 - 就是我們的UI組件樹,但這個只是一種描述信息,渲染引擎是不認識的
  • RenderObject 樹 - 這才是渲染引擎真的認識的,我們需要將 Widget 轉化為能用來渲染視圖的 Render Object

這樣 Flutter 就節(jié)約了類似 android 這樣 layout xml DOM 解析操作的性能損耗與頻繁進行局部 DOM 操作的矛盾

以前 android DOM 會在每一次元素更新到來之時渲染一次DOM,F(xiàn)lutter 會先匯總各個元素的更新情況,通過diff算法計算出與原來 DOM 樹的差異,最后通過一次 DOM 更新解決

還有要注意一點,每一個 Flutter 實例會啟動三個線程,分別是 UI 線程、GPU 線程和 IO 線程,所以復用一個 Flutter 實例會減少資源消耗

剛詳細的 Flutter 渲染機制請看:紛爭再起:Flutter-UI繪制解析


從源碼看看 Flutter 怎么顯示的

目前我這只能運行 Flutter 編譯的 android app,這也是 android coder 關心的,其實從這里也能看出來 Flutter 如何跨平臺的

1. Flutter main 入口

Flutter 項目的入口是 main 函數(shù),也是整個 Flutter 項目執(zhí)行的開始,這里看出來 Dart 受 java 影響還是很深的


Flutter main 函數(shù)

Flutter 在 android 項目里會把 Flutter 的 main 入口編譯成 MainActivity


MainActivity 位置

而這個 MainActivity 則繼承自 FlutterActivity

class MainActivity(): FlutterActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    GeneratedPluginRegistrant.registerWith(this)
  }
}

看出來了吧,F(xiàn)lutter 就是這么在 android 中開始的,F(xiàn)lutter 編譯的 android 項目只有這么一個 Activity 的,想不想 Google 的風格,16 年可以興起一陣學習 Google Mail+ 單 Activity + 多 Fragment 的頁面組合方式,當年我還追來著,后來發(fā)現(xiàn) Fragment 棧管理真是難啊,這不單 Activity + 多輕量級 page 的思路在 Flutter 上又復活了,接著看,這個思路妥妥的

2. FlutterActivity

FlutterActivity 內部沒有啥邏輯,不看也沒事,是聲明了 Provider, PluginRegistry, ViewFactory 3個接口,然后自己實現(xiàn)

/// activity實現(xiàn)了三個接口
public class FlutterActivity extends Activity implements Provider, PluginRegistry, ViewFactory {

/// 視圖提供類,一個方法getFlutterView
public interface Provider {
    FlutterView getFlutterView();
}

/// 插件注冊
public interface PluginRegistry {
    /// 注冊一個插件
    PluginRegistry.Registrar registrarFor(String var1);
    /// 是否有這個插件
    boolean hasPlugin(String var1);
    /// 插件發(fā)布值
    <T> T valuePublishedByPlugin(String var1);
    /// 下面開始為插件生命周期回調
    public interface PluginRegistrantCallback {
        void registerWith(PluginRegistry var1);
    }

    /// 顯然是視圖銷毀的回調
    public interface ViewDestroyListener {
        boolean onViewDestroy(FlutterNativeView var1);
    }
        
    //// 不知道干啥,先留著
    public interface UserLeaveHintListener {
        void onUserLeaveHint();
    }
   
    //// activity生命周期回調之onNewIntent
    public interface NewIntentListener {
        boolean onNewIntent(Intent var1);
    }
    
  //// activity生命周期回調之onActivityResult
    public interface ActivityResultListener {
        boolean onActivityResult(int var1, int var2, Intent var3);
    }

//// activity生命周期回調之onRequestPermissionsResult
    public interface RequestPermissionsResultListener {
        boolean onRequestPermissionsResult(int var1, String[] var2, int[] var3);
    }
    /// 插件的注冊者,相當于插件的宿主。
    public interface Registrar {
        Activity activity();

        Context context();

        Context activeContext();
        /// 這個BinaryMessager還不知道干啥
        BinaryMessenger messenger();
        /// TextureRegistry不清楚
        TextureRegistry textures();
        /// 獲取視圖
        FlutterView view();
        /// 尋找資源
        String lookupKeyForAsset(String var1);
        /// 尋找資源
        String lookupKeyForAsset(String var1, String var2);
        /// 發(fā)布值,與上面的valuePublishedByPlugin對應
        PluginRegistry.Registrar publish(Object var1);
        
        /// 增加生命周期回調
        PluginRegistry.Registrar addRequestPermissionsResultListener(PluginRegistry.RequestPermissionsResultListener var1);

        /// 增加生命周期回調
        PluginRegistry.Registrar addActivityResultListener(PluginRegistry.ActivityResultListener var1);

        PluginRegistry.Registrar addNewIntentListener(PluginRegistry.NewIntentListener var1);

        /// 增加生命周期回調
        PluginRegistry.Registrar addUserLeaveHintListener(PluginRegistry.UserLeaveHintListener var1);
        
        /// 增加生命周期回調
        PluginRegistry.Registrar addViewDestroyListener(PluginRegistry.ViewDestroyListener var1);
    }
}

/// 視圖工廠
  public interface ViewFactory {
        /// 創(chuàng)建flutterView
        FlutterView createFlutterView(Context var1);
        //// 創(chuàng)建nativeview
        FlutterNativeView createFlutterNativeView();
        ///  暫時搞不清楚干啥的,先留著
        boolean retainFlutterNativeView();
    }

    private final FlutterActivityEvents eventDelegate = delegate;
    private final FlutterView.Provider viewProvider = delegate;
    private final PluginRegistry pluginRegistry = delegate;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        eventDelegate.onCreate(savedInstanceState);
    }


3. FlutterActivityDelegate

在 2 中我們能看到 FlutterActivity 把包括 onCreate 在內的所有生命周期函數(shù)都交給 eventDelegate 這個代理了, eventDelegate 的類型是 FlutterActivityEvents ,那么重點是我們看看 FlutterActivityEvents 的 onCreate 干啥了

public final class FlutterActivityDelegate implements FlutterActivityEvents, FlutterView.Provider, PluginRegistry {

    ........

    @Override
    public void onCreate(Bundle savedInstanceState) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = activity.getWindow();
            window.addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setStatusBarColor(0x40000000);
            window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
        }

        String[] args = getArgsFromIntent(activity.getIntent());
        FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), args);

        flutterView = viewFactory.createFlutterView(activity);
        if (flutterView == null) {
            FlutterNativeView nativeView = viewFactory.createFlutterNativeView();
            flutterView = new FlutterView(activity, null, nativeView);
            flutterView.setLayoutParams(matchParent);
            activity.setContentView(flutterView);
            launchView = createLaunchView();
            if (launchView != null) {
                addLaunchView();
            }
        }

        if (loadIntent(activity.getIntent())) {
            return;
        }

        String appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
        if (appBundlePath != null) {
            runBundle(appBundlePath);
        }
    }
}

核心就是創(chuàng)建出 FlutterView 這個 view 對象,然后把 FlutterView setContentView 給 activity,這樣就完成了從 Flitter 到 android 平臺的顯示,MainActivity 這個AC 可是只有 FlutterView 這一個 view 啊,F(xiàn)lutter 所有的 UI 組件都是由 FlutterView 使用 canvas 直接繪制的。Flutter 就是這樣和 android 聯(lián)系的,脫離了 android 傳統(tǒng)的 xml 布局方式,節(jié)省了 xml dom 解析反射生成 viewTree 的操作

4. FlutterView

FlutterView 可以解答我們很多問題,而且是核心問題,從 FlutterView 我們可以看到 Flutter 的優(yōu)越性

public class FlutterView extends SurfaceView implements BinaryMessenger, TextureRegistry {

    public FlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView) {
        super(context, attrs);

        Activity activity = (Activity) getContext();
        if (nativeView == null) {
            mNativeView = new FlutterNativeView(activity.getApplicationContext());
        } else {
            mNativeView = nativeView;
        }

        mSurfaceCallback = new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                assertAttached();
                mNativeView.getFlutterJNI().onSurfaceCreated(holder.getSurface());
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                assertAttached();
                mNativeView.getFlutterJNI().onSurfaceChanged(width, height);
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                assertAttached();
                mNativeView.getFlutterJNI().onSurfaceDestroyed();
            }
        };
    }

FlutterView 繼承自 SurfaceView ,那么也就是說明 UI 繪制不是由 android UI 線程完成,而是完全完全由 Flutter 自己單干,這好啊,android UI 線程事太多了,把任務分解給別人干會讓系統(tǒng)快很多的,而且 SurfaceView 是雙緩存的,會緩存上一幀和當前幀,android 同步頁面信號時若是 UI 線程還沒把當前幀算出來,那么就會放棄當前幀的渲染,這就造成了丟幀,丟 3幀 用戶就會感覺到明顯的卡頓了,尤其是在動畫時有有味明顯。SurfaceView 不然,SurfaceView 有自己獨立的 surface 顯存區(qū)域,自己繪制自己, 雙緩存,頁面同步信號來是我只要把計算好的當前幀發(fā)給系統(tǒng)就行了,而不會管我現(xiàn)在是不是在計算下一幀

代碼里 FlutterView 的繪制實際交給了 FlutterNativeView.getFlutterJNI() - FlutterJNI,我們去看看 FlutterJNI

5. FlutterJNI

public class FlutterJNI {

  @UiThread
  public static native boolean nativeGetIsSoftwareRenderingEnabled();

  @UiThread
  public static native String nativeGetObservatoryUri();
  
  private Long nativePlatformViewId;
  private FlutterRenderer.RenderSurface renderSurface;
  private AccessibilityDelegate accessibilityDelegate;
  private PlatformMessageHandler platformMessageHandler;

  @UiThread
  public void onSurfaceCreated(@NonNull Surface surface) {
    ensureAttachedToNative();
    nativeSurfaceCreated(nativePlatformViewId, surface);
  }

  private native void nativeSurfaceCreated(long nativePlatformViewId, Surface surface);

  @UiThread
  public void onSurfaceChanged(int width, int height) {
    ensureAttachedToNative();
    nativeSurfaceChanged(nativePlatformViewId, width, height);
  }

  private native void nativeSurfaceChanged(long nativePlatformViewId, int width, int height);

  @UiThread
  public void onSurfaceDestroyed() {
    ensureAttachedToNative();
    nativeSurfaceDestroyed(nativePlatformViewId);
  }

  private native void nativeSurfaceDestroyed(long nativePlatformViewId);
}

FlutterJNI 里面有大量的 native 方法,都是和底層打交道的,期中 FlutterRenderer.RenderSurface renderSurface 就是負責顯示的,F(xiàn)lutterRenderer 是 Flutter 負責渲染的組件,剩下的大家自己取看吧,基本上這就解釋清楚了 Flutter 為啥可以跨平臺而 UI 樣式相同,因為純是自己畫的,脫離原生平臺組件


Flutter 內存/ GC 管理

以下內容來源于:閑魚技術團隊,參考文章: Flutter 內存機制梳理

Flutter 使用 Dart 語言開發(fā),所以和 java JVM 虛擬機一樣需要 Dart 的運行環(huán)境 VM。和Android ART 一樣,F(xiàn)lutter 也對Dart 源碼做了 AOT 編譯,直接將 Dart 源碼編譯成了本地字節(jié)碼,沒有了解釋執(zhí)行的過程,提升執(zhí)行性能

這里我們關注的是 Dart VM 內存分配和 GC 的相關部分。和 Java 不同,Dart 的線程 Isolate 是不共享內存的,擁有各自獨立的堆(Heap) 和棧 (Stack) ,并且是各自獨立 GC 的,彼此之間通過消息通道來通信。Dart天然不存在數(shù)據(jù)競爭和變量狀態(tài)同步的問題,整個 Flutter Framework Widget 的渲染過程都運行在一個 isolate 中

Dart 內存管理

GC 部分,Dart 和 java 是相同的,同樣在堆內存中分為:新生代 / 老年代,老年代里分 from / to 2快區(qū)域,進行整塊內存地址的來回復制,刪除

可以看到,Dart VM 借鑒了很多 JVM 的思路,Dart 中產(chǎn)生內存泄露的方式也和 Java 類似,Java 中很多排查內存泄露的思路和防止內存泄露的編程方法應該也可以借鑒過來

Android 將內存分為 JVM 虛擬機內存 和 Native 內存,系統(tǒng)對 JVM 虛擬機內存有一個上限,到達上限就會觸發(fā) OOM 異常,而對 Native 內存的使用沒有太嚴格的限制,現(xiàn)在的手機內存都很大,一般有較大的Native內存富余,這里我們來看下 Android 和 Flutter 對 bitmap 存儲的處理

  • android - 6.0 / 7.0 把 bitmap 儲存在 JVM 虛擬機內存中,8.0 開始把 bitmap 儲存在 Native 內存中
    android 6.0

    android 8.0
  • Flutter - 既沒用 JVM 虛擬機內存,也沒用 Native 內存,而是 Graphics 內存,我們可以叫顯存,是 GPU 使用的內存部分,這樣效率上要塊不少,因為不用省了把 bitmap 傳遞給 GPU 的這一部
    Flutter

    官方對 Graphics 內存的解釋

    不虧是 Google 大神,就是厲害啊

Flutter 編譯模式

Flutter 有 2種編譯模式:JIT | AOT

  • JIT - Just-in-time,動態(tài)(即時)編譯,邊運行邊編譯。開發(fā)階段使用,采用 JIT 模式,這樣就避免了每次改動都要進行編譯,實現(xiàn)極大的節(jié)省了開發(fā)時間
  • AOT - Ahead Of Time,指運行前編譯。發(fā)布階段使用,通過 AOT 生成高效的 ARM 代碼以保證應用性能,而JavaScript則不具有這個能力

Flutter 有 3種開發(fā)模式:debug | release | profile ,他們之間的差異比 android 中不同編譯模式的差距大

  • Debug 模式 - 使用 JIT 模式編譯 Dart 文件,也是我們上面說的熱重載,此模式支持所有的調試手段,但是在啟動時會變慢,對性能有影響
  • Release 模式 - 使用 AOT 模式編譯 Dart 文件,只支持真機,不支持模擬器,不支持各種調試手段,對包大小做了優(yōu)化,此模式性能最優(yōu)
  • Profile 模式 - 不支持模擬器,因為模擬器并不代表真實的性能,Profile 和 Release 相同,區(qū)別是 Profile 模式支持調試工具,

事實上 flutter 下的 iOS / Android 工程本質上依然是一個標準的 iOS / Android 工程,flutter只是通過在 BuildPhase 中添加 shell 來生成和嵌入 App.framework 和 Flutter.framework(iOS) , 通過 gradle 來添加 flutter.jar 和vm / isolate_snapshot_data / instr(Android) 來將 Flutter 相關代碼編譯和嵌入原生App而已

更具體的內容詳見:深入理解flutter的編譯原理與優(yōu)化


Flutter 項目結構

  • android - android 平臺相關代碼
  • ios - ios 平臺相關代碼
  • lib - flutter相關代碼,我們主要編寫的代碼就在這個文件夾
  • test - 用于存放測試代碼
  • pubspec.yaml - 配置文件,一般存放一些第三方的依賴。主要是用Dart的pub包管理工具

最后

最后了,我們來梳理下 Flutter 的優(yōu)點,摘自:淺談flutter的優(yōu)點與缺點

Flutter 優(yōu)點:
  • 性能強大,流暢
    Flutter對比weex和react native相比,性能的強大是有目共睹的。基于dom樹渲染原生組件,很難與直接在原生視圖上繪圖比肩性能,Google作為一個輪子大廠,直接在兩個平臺上重寫了各自的UIKit,對接到平臺底層,減少UI層的多層轉換,UI性能可以比肩原生,這個優(yōu)勢在滑動和播放動畫時尤為明顯。
  • UI跨平臺穩(wěn)定
    Google直接在兩個平臺上在底層重寫了UIKit,不依賴于Css等外部解釋器,幾乎不存在UI表達不理想,渲染不正常的情況,可以獲得非常穩(wěn)定的UI表達效果。Css換個瀏覽器就有不同的表現(xiàn),基于Css的跨平臺框架很難獲得穩(wěn)定的UI表現(xiàn)。
    可選靜態(tài)的語言,語言特性優(yōu)秀
  • 路由設計優(yōu)秀
    Flutter的路由傳值非常方便,push一個路由,會返回一個Future對象(也就是Promise對象),使用await或者.then就可以在目標路由pop,回到當前頁面時收到返回值。這個反向傳值的設計基本是甩了微信小程序一條街了。彈出dialog等一些操作也是使用的路由方法,幾乎不用擔心出現(xiàn)傳值困難
  • 單例模式
    Flutter支持單例模式,單例模式的實現(xiàn)也非常簡單。單例模式很好的解決了一些問題。相比之下,js的單例則并不是一個真正的單例,或者說不是一個簡單的單例,這也是受限于js所運行的環(huán)境。單例模式并不總是合理的,容易被濫用。但是在App的初期開發(fā)中,往往一個容易實現(xiàn)的單例可以幫助我們快速完成一些邏輯的搭建。
  • 優(yōu)秀的動畫設計
    Flutter的動畫簡單到不可思議,動畫對象會根據(jù)屏幕刷新率每秒產(chǎn)生很多個(一般是60個)浮點數(shù),只需要將一個組件屬性通過補間(Tween)關聯(lián)到動畫對象上,F(xiàn)lutter會確保在每一幀渲染正確的組件,從而形成連貫的動畫。這種十分暴力的操作在Flutter上卻看不到明顯的卡頓,這也是Flutter的一個魔力所在。相比之下其他跨平臺框架幾乎不能設計動畫……往往會遭遇非常嚴重的性能問題。
Flutter 缺點:
  • 假裝跨平臺,躲不開原生代碼
    這是最大的問題,跨平臺框架說白了就是UI跨平臺,最后還是在原生平臺運行,本來兩個平臺就有天壤之別,一套代碼就想吃掉iOS和Android在實際應用之中其實根本就不現(xiàn)實。Flutter具有與原生代碼互相調用的能力固然非常科學,但是問題反而顯得更加明顯——我一個前端工程師上哪里去知道什么是UIViewController,什么是Activity呢?我要是雙端都熟悉,學習Flutter就顯得很沒有必要。這是一個很矛盾的點,特別是在團隊里,只有幾個前端突然想學Flutter,是絕對做不來大項目的,如果有原生開發(fā)者,那就沒必要搞Flutter了。
  • 組合而不是繼承的思路
    Flutter提倡“組合”,而不是“繼承”。在iOS開發(fā)中,我們經(jīng)常會繼承UIView,重寫UIView的某個生命周期函數(shù),再添加一些方法和屬性,來完成一個自定義的View。但是在Flutter中這些都是不可能的——屬性都是final的,例如你繼承了了一個Container,你是不能在它的生命周期中修改他的屬性的。你始終需要嵌套組合幾種Widget,例如Row,Container,ListView等Widget。這種方法非常不符合直覺,初學時很難想明白如何構建一個完整的組件。
  • 糟糕的UI控件API
    雖然google盡可能的讓我們通過構造函數(shù)定制化Widget,但是也難免有遺漏的。例如,又一次我想修改一個Appbar的高度,居然沒有找到關于高度的屬性,通過閱讀源碼發(fā)現(xiàn),高度是寫死(const)的。上文已經(jīng)說過,無法通過生命周期來改變組件屬性,自己寫Appbar顯得非常沒必要,畢竟我還是想使用Appbar的各種方便的功能。最后我只能把他的源碼全部復制出來,直接修改高度來使用。初學框架,和一些初級開發(fā)者是不可能有迅速閱讀源碼的能力的(作為框架也不應該產(chǎn)生如此問題)。一些定制化的UI的Api設計經(jīng)常有缺失,好在我已經(jīng)基本習慣了。除了Appbar這種復雜的組件,自己寫一個小組件也并不費事。

補充

google IO 大會剛剛開完,會上 Google 宣布 Flutter 可以支持更多的平臺啦,這不 FLutter 1.5.4 版本就來了,可以支持 Web 端的開發(fā)了

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容