Flutter外掛釋出及Flutter APP啟動原始碼探索

CodeOver發表於2020-06-20

前言

本文主要分析並實踐外掛釋出示例,然後再由外掛何時載入探索到Flutter App啟動原始碼。

主要解決三個問題:外掛編寫和釋出、外掛載入時機、黑屏/白屏原因

ps:篇幅過長,需要耐心

環境:

Dart 2.8.4

Flutter 1.17.3

參考資料

Flutter中文網開發Packages和外掛

目錄

Flutter外掛釋出及Flutter APP啟動原始碼探索

一、外掛開發步驟

  1. Android Studio -> New Flutter Project 選擇 Flutter Plugin

    建立後如下圖

    Flutter外掛釋出及Flutter APP啟動原始碼探索Flutter外掛釋出及Flutter APP啟動原始碼探索


    可以看出外掛和Flutter工程其實一樣,目錄中就多了一個example (示例測試工程可用於外掛的除錯)。我們寫外掛的話,一般 程式碼寫在 android或者ios下,Flutter程式碼寫道lib下,其實和Flutter與Native通訊一樣,相當於你封裝了功能,外部呼叫而已。

  2. 原生端開發

    • android模組下 或者是 ios模組下,和原生開發一樣,整合與Flutter通訊類程式碼,具體使用見上篇文章

  3. Flutter端開發

    • 見上篇文章講解

  4. 配置檔案 pubspec.yaml

    • 如果你的Flutter程式碼依賴於第三方庫,需要在這裡面配置,如果裡面有依賴A 、B,A裡面依賴了C的1.0版本,B裡面依賴了C的2.0版本,你可以直接在pubspec.yaml中指定依賴C的版本號。

    • 在該檔案內對外掛進行介紹

      Flutter外掛釋出及Flutter APP啟動原始碼探索Flutter外掛釋出及Flutter APP啟動原始碼探索


  5. 其它配置

    • 在CHANGELOG.md中新增Change記錄 可以檢視其它外掛是如何編寫的 Dart Packages 隨便找個外掛,依葫蘆畫瓢

    • 在README.md中新增使用說明

    • LICENSE 包含軟體包許可條款的檔案

  6. 檢查我們專案的目錄結構以及語法,以確保其內容的完整性和正確性

    flutter packages pub pusblish --dry-run複製程式碼
  7. 釋出外掛

    想要釋出外掛,第一步需要有一個賬號(谷歌賬號)

    接下來執行,釋出到Pub平臺

    flutter packages pub publish複製程式碼

    在第一次執行過程中,會提示讓你輸入賬戶驗證資訊。

    如果想釋出到私服,可以使用

    flutter packages pub publish --server==私服地址複製程式碼
  8. 接下來就可以將專案內的埋點功能作為外掛進行封裝,下面舉個例子,來實現Flutter調原生方法,原生方法內就需要我們自己實現一些埋點功能。大佬們可以直接忽略本小點,筆者是渣渣,要多努力實現一下。

    • 用AS開啟android模組,我們可以看到目錄下建立了UmpluginPlugin.kt檔案,自行查閱外掛的main.dart程式碼和該部分程式碼就可以發現,Flutter與Native利用MethodChannel進行通訊,獲取Android的Build.VERSION。

      Flutter外掛釋出及Flutter APP啟動原始碼探索Flutter外掛釋出及Flutter APP啟動原始碼探索


    • 首先是Native端

      PluginProxy類,業務邏輯都交給它處理,因為想著有些日誌需要存到本地,到一定時候才上傳的,所以實現了LifecycleCallbacks和許可權回撥,dart可以呼叫來觸發,這裡只關於uploadLog方法

      public class PluginProxy implements Application.ActivityLifecycleCallbacks,PluginRegistry.RequestPermissionsResultListener {
          private final Context context;
          private final Application application;
      ​
          public PluginProxy(PluginRegistry.Registrar registrar) {
              this.context = registrar.context();
              this.application = (Application) context;
          }
          public void uploadLog(MethodCall call,MethodChannel.Result result){
              Object message = call.arguments();
              if(message instanceof String) {
                  Toast.makeText(application, (String) message, Toast.LENGTH_SHORT).show();
                  result.success("Native uploadLog Ok !");
              }
          }
          @Override
          public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
          }
          @Override
          public void onActivityStarted(Activity activity) {
          }
          @Override
          public void onActivityResumed(Activity activity) {
          }
          @Override
          public void onActivityPaused(Activity activity) {
          }
          @Override
          public void onActivityStopped(Activity activity) {
          }
      ​
          @Override
          public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
          }
          @Override
          public void onActivityDestroyed(Activity activity) {
              application.unregisterActivityLifecycleCallbacks(this);
          }
          @Override
          public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
              return false;
          }
      }複製程式碼

      FlutterUmDemoPlugin類,你可以按照exmaple中的例子寫外掛,寫完執行example就行了,也可以按照我這種方式寫

      public class FlutterUmDemoPlugin implements MethodChannel.MethodCallHandler,FlutterPlugin {
      ​
          private  PluginProxy proxy;
      ​
          public FlutterUmDemoPlugin(){
      ​
          }
      ​
          private FlutterUmDemoPlugin( PluginProxy proxy) {
              this.proxy = proxy;
          }
      ​
          public static void registerWith(PluginRegistry.Registrar registrar) {
              MethodChannel channel = new MethodChannel(registrar.messenger(), "umplugin");
              PluginProxy proxy = new PluginProxy(registrar.context());
              channel.setMethodCallHandler(new FlutterUmDemoPlugin( proxy));
          }
      ​
          @Override
          public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
              if (call.method.equals("uploadLog")) {
                  proxy.uploadLog(call, result);
              } else {
                  result.notImplemented();
              }
          }
      ​
      ​
          @Override
          public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
              MethodChannel channel = new MethodChannel(binding.getBinaryMessenger(), "umplugin");
              PluginProxy proxy = new PluginProxy(binding.getApplicationContext());
              channel.setMethodCallHandler(new FlutterUmDemoPlugin(proxy));
          }
      ​
          @Override
          public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
      ​
          }
      }
      ​
      ​複製程式碼
    • 在建立外掛工程時,app裡自動生成的 UmpluginPlugin 類 中以下兩個方法加入如下程式碼

          companion object {
              @JvmStatic
              fun registerWith(registrar: Registrar) {
      //            val channel = MethodChannel(registrar.messenger(), "umplugin")
      //            channel.setMethodCallHandler(UmpluginPlugin())
      //以下為加入
                  FlutterUmDemoPlugin.registerWith(registrar)
              }
          }複製程式碼
          override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
      //        channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "umplugin")
      //        channel.setMethodCallHandler(this);
      //加入
              var plugin = FlutterUmDemoPlugin();
              plugin.onAttachedToEngine(flutterPluginBinding);
      ​
      ​
          }複製程式碼
    • Dar端

      class UmDemoPlugin {
        static const MethodChannel _channel = const MethodChannel("umplugin");
      ​
        static Future<String> uploadLog(String message) async {
          return await _channel.invokeMethod("uploadLog", message);
        }
      }
      ​複製程式碼
    • 接下來就是在專案測試一下

      匯入本地依賴,下面的寫法如果不行,那麼你就換成絕對路徑,例如 E:\xx\plugin\

       dependencies:
       # ....
       umplugin:
          path: ../um_plugin/複製程式碼

      專案裡接入

          floatingActionButton: FloatingActionButton(
                  onPressed: _upload(),
                  child: Icon(Icons.add),
                ),
                
      Future<void>_upload() async {
          String message= await UmDemoPlugin.uploadLog("Flutter發起上傳日誌") ;
          setState(() {
            _counter = message;
          });
        }複製程式碼

      效果如下

      Flutter外掛釋出及Flutter APP啟動原始碼探索Flutter外掛釋出及Flutter APP啟動原始碼探索


二、Flutter啟動原始碼分析

這節主要是為了瞭解外掛是什麼時候註冊的,帶著這個問題順帶了解了另一個問題

  • 建立Flutter後,在Android中生成的GeneratePluginRegistrant,裡面註冊外掛registerWith方法是什麼時候呼叫註冊的

  • Flutter App啟動後,黑屏是如何造成的

1、APP啟動回顧

首先回顧一下App啟動時,Application建立和Activity建立過程的主要呼叫的生命週期方法,具體原始碼分析看 AOSP Android8.0冷啟動流程分析

Flutter外掛釋出及Flutter APP啟動原始碼探索Flutter外掛釋出及Flutter APP啟動原始碼探索

這裡再簡單說一下,Application會去讀取AndroidManifest.xml配置的Application,除非沒有,否則執行的是你設定的Application

2、FlutterApplication分析

我們從建立的Flutter工程Android模組,可以看到,AndroidManifest.xml的application節點如下

Flutter外掛釋出及Flutter APP啟動原始碼探索Flutter外掛釋出及Flutter APP啟動原始碼探索

所以我們這裡按照原生App啟動流程分析一下,主要就是看FlutterApplication的onCreate到底做了些什麼

public class FlutterApplication extends Application {
  @Override
  @CallSuper
  public void onCreate() {
    super.onCreate();
    FlutterMain.startInitialization(this);
  }
  private Activity mCurrentActivity = null;
​
  public Activity getCurrentActivity() {
    return mCurrentActivity;
  }
  public void setCurrentActivity(Activity mCurrentActivity) {
    this.mCurrentActivity = mCurrentActivity;
  }
}複製程式碼

可以看到onCreate中執行了 FlutterMain 中的靜態發方法 startInitialization(this)

public static void startInitialization(@NonNull Context applicationContext) {
    if (isRunningInRobolectricTest) {
      return;
    }
    FlutterLoader.getInstance().startInitialization(applicationContext);
  }複製程式碼

接下來它會執行 FlutterLoader 的 startInitialization(applicationContext)

  public void startInitialization(@NonNull Context applicationContext) {
    startInitialization(applicationContext, new Settings());
  }
    public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
    // Do not run startInitialization more than once.
    if (this.settings != null) {
      return;
    }
    if (Looper.myLooper() != Looper.getMainLooper()) {
      throw new IllegalStateException("startInitialization must be called on the main thread");
    }
​
    // Ensure that the context is actually the application context.
    applicationContext = applicationContext.getApplicationContext();
​
    this.settings = settings;
​
    long initStartTimestampMillis = SystemClock.uptimeMillis();
    initConfig(applicationContext);
    initResources(applicationContext);
​
    System.loadLibrary("flutter");
​
    VsyncWaiter.getInstance(
            (WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE))
        .init();
​
    // We record the initialization time using SystemClock because at the start of the
    // initialization we have not yet loaded the native library to call into dart_tools_api.h.
    // To get Timeline timestamp of the start of initialization we simply subtract the delta
    // from the Timeline timestamp at the current moment (the assumption is that the overhead
    // of the JNI call is negligible).
    long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
    FlutterJNI.nativeRecordStartTimestamp(initTimeMillis);
  }  複製程式碼

startInitialization 方法中,我們可以看到首先通過判斷 settings是否為空 來保證方法執行一次

然後接下來就是檢查是否主執行緒

再然後就是呼叫 initConfig 方法,讀取manifest中meteData配置,初始化配置資訊

然後呼叫initResources 來初始化在 除錯 或者 JIT模式 下的一些變數,包括資料儲存路徑和packageName等,然後執行ResourceExtractor的start方法,拷貝asset目錄下的相關資源到私有目錄下 (路徑地址 :applicationContext.getDir("flutter", Context.MODE_PRIVATE).getPath() )

再接下來就是通過Sytem.loadLibrary("flutter")載入so庫

再然後就是通過VsyncWaiter的 init 方法呼叫 FlutterJNI.setAsyncWaitForVsyncDelegate(asyncWaitForVsyncDelegate) 主要是用來收到系統VSYNC訊號後,呼叫doFrame來更新UI

最後就是呼叫 FlutterJNI.nativeRecordStartTimestamp(initTimeMillis) 來通知初始化耗時時間了

最後來個時序圖

Flutter外掛釋出及Flutter APP啟動原始碼探索Flutter外掛釋出及Flutter APP啟動原始碼探索


3、FlutterActivity 分析

按照步驟,分析完FlutterApplication,下一步就應該是配置的啟動Activity分析,同樣先看一下AndroidManifest.xml

Flutter外掛釋出及Flutter APP啟動原始碼探索Flutter外掛釋出及Flutter APP啟動原始碼探索

點選MainActivity可以看出,它是繼承的 FlutterActivity

class MainActivity: FlutterActivity() {
}複製程式碼

是不是看完會想,怎麼都沒實現方法呢,那肯定都是FlutterActivity實現了,包括佈局建立

3.1、檢視 FlutterActivity 的 onCreate 方法

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    switchLaunchThemeForNormalTheme(); //這個就是獲取清單檔案裡面配置的NormalTheme,設定一下
​
    super.onCreate(savedInstanceState);
​
    lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
​
    delegate = new FlutterActivityAndFragmentDelegate(this);
    delegate.onAttach(this);
    delegate.onActivityCreated(savedInstanceState); 
​
    configureWindowForTransparency();
    setContentView(createFlutterView());
    configureStatusBarForFullscreenFlutterExperience(); //據當前系統版本來設定沉浸式狀態列
  }複製程式碼

可以看到佈局建立和配置相關操作在這裡,接下來分析下主要方法,次要方法都在程式碼中進行說明

3.2、 FlutterActivityAndFragmentDelegate 的 onAttach 方法

從之前的程式碼可以看到,在onCreate中先建立了 FlutterActivityAndFragmentDelegate,並把 this 傳給了該類的持有的Host型別的host變數,接下來才是呼叫onAttach方法,至於它的onActivityCreated方法就是恢復一些state狀態,和Activity的作用一樣,只是作用物件不一樣而已。

  void onAttach(@NonNull Context context) {
    ensureAlive();
    if (flutterEngine == null) {
      setupFlutterEngine();
    }
​
    platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);
​
    if (host.shouldAttachEngineToActivity()) { // 這個預設是true的
      Log.v(TAG, "Attaching FlutterEngine to the Activity that owns this Fragment.");
      flutterEngine
          .getActivityControlSurface()
          .attachToActivity(host.getActivity(), host.getLifecycle()); // 繫結生命週期
    }
​
    host.configureFlutterEngine(flutterEngine);
  }複製程式碼

a、先看ensureAlive方法,主要是通過 host 變數是否是為空來判斷 FlutterActivityAndFragmentDelegate 沒有被釋放,那什麼時候釋放呢,onDetch 的時候,這裡目前不是重點。如果該類釋放了,就會拋異常。

b、接下來是setupFlutterEngine方法,第一次進來肯定是需要執行的,這裡主要是獲得FlutterEngine,這裡會先通過從快取里根據cacheEngineId獲取FlutterEngine,如果沒有的話,就會呼叫FlutterActivity的provideFlutterEngine 看看開發者實現了獲取FlutterEngine,再沒有就是直接new FlutterEngine,詳細檢視下面程式碼

  @VisibleForTesting
  /* package */ void setupFlutterEngine() {
​
    // First, check if the host wants to use a cached FlutterEngine.
    String cachedEngineId = host.getCachedEngineId();
    if (cachedEngineId != null) {
      flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId);
      isFlutterEngineFromHost = true;
      if (flutterEngine == null) {
        throw new IllegalStateException(
            "The requested cached FlutterEngine did not exist in the FlutterEngineCache: '"
                + cachedEngineId
                + "'");
      }
      return;
    }
​
    // Second, defer to subclasses for a custom FlutterEngine.
    flutterEngine = host.provideFlutterEngine(host.getContext());
    if (flutterEngine != null) {
      isFlutterEngineFromHost = true;
      return;
    }
​
    flutterEngine =
        new FlutterEngine(
            host.getContext(),
            host.getFlutterShellArgs().toArray(),
            /*automaticallyRegisterPlugins=*/ false);
    isFlutterEngineFromHost = false;
  }複製程式碼

c、再接下來會呼叫 FlutterActivity 的 configureFlutterEngine 方法,猜猜這個方法主要做了些什麼

  @Override
  public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    registerPlugins(flutterEngine);
  }
    private static void registerPlugins(@NonNull FlutterEngine flutterEngine) {
    try {
      Class<?> generatedPluginRegistrant =
          Class.forName("io.flutter.plugins.GeneratedPluginRegistrant");
      Method registrationMethod =
          generatedPluginRegistrant.getDeclaredMethod("registerWith", FlutterEngine.class);
      registrationMethod.invoke(null, flutterEngine);
    } catch (Exception e) {
      Log.w(
          TAG,
          "Tried to automatically register plugins with FlutterEngine ("
              + flutterEngine
              + ") but could not find and invoke the GeneratedPluginRegistrant.");
    }
  }複製程式碼

反射呼叫了 GeneratedPluginRegistrant registerWith 方法 載入外掛。

3.3、configureWindowForTransparency 方法

給Window設定透明背景

  private void configureWindowForTransparency() {
    BackgroundMode backgroundMode = getBackgroundMode();
    if (backgroundMode == BackgroundMode.transparent) {
      getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
    }
  }複製程式碼

3.4、setContentView(createFlutterView()) 方法

這裡主要就是 createFlutterView 方法,接下來就是和Activity一樣的操作 setContentView 建立 View相關繪製物件,顯示介面

  @NonNull
  private View createFlutterView() {
    return delegate.onCreateView(
        null /* inflater */, null /* container */, null /* savedInstanceState */);
  }複製程式碼

可以看到,建立FlutterView的過程交給了 FlutterActivityAndFragmentDelegate ,方法如下

  @NonNull
  View onCreateView(
      LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    ensureAlive();
​
    if (host.getRenderMode() == RenderMode.surface) { // A 
      FlutterSurfaceView flutterSurfaceView =
          new FlutterSurfaceView(
              host.getActivity(), host.getTransparencyMode() == TransparencyMode.transparent);
      // Allow our host to customize FlutterSurfaceView, if desired.
      host.onFlutterSurfaceViewCreated(flutterSurfaceView);
      // Create the FlutterView that owns the FlutterSurfaceView.
      flutterView = new FlutterView(host.getActivity(), flutterSurfaceView);
    } else {
      FlutterTextureView flutterTextureView = new FlutterTextureView(host.getActivity());
      // Allow our host to customize FlutterSurfaceView, if desired.
      host.onFlutterTextureViewCreated(flutterTextureView);
      // Create the FlutterView that owns the FlutterTextureView.
      flutterView = new FlutterView(host.getActivity(), flutterTextureView);
    }
    // Add listener to be notified when Flutter renders its first frame.
    flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener); // B 
    flutterSplashView = new FlutterSplashView(host.getContext());
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
      flutterSplashView.setId(View.generateViewId());
    } else {
      flutterSplashView.setId(486947586);
    }
    // C
    flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());
​
    Log.v(TAG, "Attaching FlutterEngine to FlutterView.");
    flutterView.attachToFlutterEngine(flutterEngine);
​
    return flutterSplashView;
  }複製程式碼

a、我們按方法內程式碼從上往下分析,首先看一下 A 處的host.getRenderMode() == RenderMode.surface 這個判端預設是true

  @NonNull
  @Override
  public RenderMode getRenderMode() {
    return getBackgroundMode() == BackgroundMode.opaque ? RenderMode.surface : RenderMode.texture;
  }
    /** The mode of the background of a Flutter {@code Activity}, either opaque or transparent. */
  public enum BackgroundMode {
    /** Indicates a FlutterActivity with an opaque background. This is the default. */
    opaque,
    /** Indicates a FlutterActivity with a transparent background. */
    transparent
  }複製程式碼

FlutterTextureView和FlutterSurfaceView 的區別在於一個是在SurfaceTexture (從影像流中捕獲幀作為OpenGL ES紋理)上繪製UI,一個是在Surface (處理到由螢幕合成到緩衝區的資料)上繪製UI

b、接下來看 B 處的 flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener) 這裡主要是用來設定監聽事件,通知Android 我們已經繪製完畢

c、接下來看 C 處 程式碼,這裡是重點。 flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen())

  • 首先看這個方法內的 host.provideSplashScreen()

      public SplashScreen provideSplashScreen() {
        Drawable manifestSplashDrawable = getSplashScreenFromManifest();
        if (manifestSplashDrawable != null) {
          return new DrawableSplashScreen(manifestSplashDrawable);
        } else {
          return null;
        }
      }複製程式碼

    還記得之前的AndroidManifest.xml 中的 meta_data 配置嗎,getSplashScreenFromManifest 方法就是將 launch_background.xml (預設白的,這也就是出現白屏的問題) 轉換成Drawable,主要是用來做閃屏背景圖的,這裡僅僅是獲取到了閃屏Drawable,如果沒有呢?沒有那麼這個閃屏頁不就沒有了麼?那就是說啟動的時候連閃個白屏都會不給機會,直接給黑屏。那麼什麼時候會沒有,也就是meta_data啥時候會沒有配置,建立flutter_module的時候就沒有配置。

  • 再跟蹤 displayFlutterViewWithSplash 方法看看,下面貼出來的是主要程式碼

      public void displayFlutterViewWithSplash(
          @NonNull FlutterView flutterView, @Nullable SplashScreen splashScreen) {
       //....
        // Display the new FlutterView.
        this.flutterView = flutterView;
        addView(flutterView); // flutterView是一個FrameLayout,新增到FlutterSplashView中,onCreateView方法也是將splashView返回,然後setContetnView新增到DecorView中的
    ​
        this.splashScreen = splashScreen;
    ​
        // Display the new splash screen, if needed.
        if (splashScreen != null) {
          if (isSplashScreenNeededNow()) {  // A
         
            splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState);
            addView(this.splashScreenView);
            flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
          } else if (isSplashScreenTransitionNeededNow()) {  // B
    ​
            splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState);
            addView(splashScreenView);
            transitionToFlutter();
          } else if (!flutterView.isAttachedToFlutterEngine()) { //C
            
            flutterView.addFlutterEngineAttachmentListener(flutterEngineAttachmentListener);
          }
        }
      }複製程式碼

    上面 A 、B、C三處條件是哪個先執行呢?A 處為false,因為此時FlutterView還沒有和FlutterEngine繫結呢,B 處也為false,因為它內部也需要判斷FlutterView是否和FlutterEngine繫結了。所以最終會執行 C 處判斷條件,這裡主要是新增一個 flutterEngineAttachmentListener ,這個是重點

      private final FlutterView.FlutterEngineAttachmentListener flutterEngineAttachmentListener =
          new FlutterView.FlutterEngineAttachmentListener() {
            @Override
            public void onFlutterEngineAttachedToFlutterView(@NonNull FlutterEngine engine) {
              flutterView.removeFlutterEngineAttachmentListener(this);
              displayFlutterViewWithSplash(flutterView, splashScreen);
            }
    ​
            @Override
            public void onFlutterEngineDetachedFromFlutterView() {}
          };複製程式碼

    listener裡的 displayFlutterViewWithSplash 是幹嘛的呢?主要利用背景圖 DrawableSplashScreen 生成一個ImageView物件,並設定500毫秒透明度漸變的動畫,然後這樣第一幀繪製完畢後再將這個閃屏頁刪除。但是這個 listener的 onFlutterEngineAttachedToFlutterView 方法什麼時候會呼叫呢?

d、我們繼續看 flutterView.attachToFlutterEngine(flutterEngine) 方法,這個方法主要是將FlutterView和FlutterEngine繫結,FlutterView將會將收到使用者觸控事件、鍵盤事件等轉化給FlutterEngine,我們只關注這個方法內的三行程式碼,如下

   for (FlutterEngineAttachmentListener listener : flutterEngineAttachmentListeners) {
      listener.onFlutterEngineAttachedToFlutterView(flutterEngine);
    }複製程式碼

flutterEngineAttachmentListeners 這裡面存放的就是之前說的 listener物件,只要FlutterView和FlutterEngine繫結後,就會回撥來設定背景圖。

也來一個 時序圖

Flutter外掛釋出及Flutter APP啟動原始碼探索Flutter外掛釋出及Flutter APP啟動原始碼探索


4、總結

Flutter App 啟動流程,會先執行FlutterApplication的onCreate方法,初始化meta_data的配置,在除錯或者JIT模式下,拷貝asset目錄下的相關資源到flutter私有目錄下,載入flutter so庫,設定VSYNC訊號回撥給Native觸發,初始化完成後通知Native耗時時間。

然後就到了FlutterActivity的onCreate方法,主要是呼叫registerWith載入外掛,通過建立FlutterSplashView,傳遞給setContentView顯示的,其中FlutterSplashView會先add FlutterView,然後再add 背景圖 DrawableSplashScreen 生成的ImageView,在FlutterView和FlutterEngine繫結後,也就是第一幀繪製完後,會把背景圖生成的ImageView刪除。由於背景圖預設是根據 launch_background.xml生成的,預設是白色的,所以會出現白屏現象,又因為在建立Flutter Module時,在AndroidManifest.xml中不存在獲取背景圖的Meta_Data配置,所以出現黑屏。


三、塗色問題

你要在一個n * m的格子圖上塗色,你每次可以選擇一個未塗色的格子塗上你開始選定的那種顏色。同時為了美觀,我們要求你塗色的格子不能相鄰,也就是說,不能有公共邊,現在問你,在採取最優策略的情況下,你最多能塗多少個格子?給定格子圖的長 n 和寬m。請返回最多能塗的格子數目。測試樣例:1,2 返回 :1

PS:主要是為了偷懶,太晚了,寫不動了。

思路:左上角塗上選定的顏色,例如紅色,那麼可以理解為相鄰的顏色填為白色,所以剩下的顏色基本就定了,如果是偶數的話,那就是 (n * *m)/2,奇數的話,那就是(n*m + 1)/2。畫個矩陣品品就出來了。

public class DemoOne {

	public static int getMost(int n,int m) {
		return (n*m + 1)/2;
	}
}
複製程式碼


筆記六


相關文章