本文主要分析並實踐插件發佈示例,而後再由插件什麼時候加載探索到Flutter App啓動源碼。android
主要解決三個問題:插件編寫和發佈、插件加載時機、黑屏/白屏緣由ios
ps:篇幅過長,須要耐心api
環境:緩存
Dart 2.8.4bash
Flutter 1.17.3markdown
Android Studio -> New Flutter Project 選擇 Flutter Pluginasync
建立後以下圖ide
能夠看出插件和Flutter工程其實同樣,目錄中就多了一個example (示例測試工程可用於插件的調試)。咱們寫插件的話,通常 代碼寫在 android或者ios下,Flutter代碼寫道lib下,其實和Flutter與Native通訊同樣,至關於你封裝了功能,外部調用而已。oop
原生端開發
android模塊下 或者是 ios模塊下,和原生開發同樣,集成與Flutter通訊類代碼,具體使用見上篇文章
Flutter端開發
見上篇文章講解
配置文件 pubspec.yaml
若是你的Flutter代碼依賴於第三方庫,須要在這裏面配置,若是裏面有依賴A 、B,A裏面依賴了C的1.0版本,B裏面依賴了C的2.0版本,你能夠直接在pubspec.yaml中指定依賴C的版本號。
在該文件內對插件進行介紹
其它配置
在CHANGELOG.md中添加Change記錄 能夠查看其它插件是如何編寫的 Dart Packages 隨便找個插件,依葫蘆畫瓢
在README.md中添加使用說明
LICENSE 包含軟件包許可條款的文件
檢查咱們項目的目錄結構以及語法,以確保其內容的完整性和正確性
flutter packages pub pusblish --dry-run複製代碼
發佈插件
想要發佈插件,第一步須要有一個帳號(谷歌帳號)
接下來執行,發佈到Pub平臺
flutter packages pub publish複製代碼
在第一次執行過程當中,會提示讓你輸入帳戶驗證信息。
若是想發佈到私服,可使用
flutter packages pub publish --server==私服地址複製代碼
接下來就能夠將項目內的埋點功能做爲插件進行封裝,下面舉個例子,來實現Flutter調原生方法,原生方法內就須要咱們本身實現一些埋點功能。大佬們能夠直接忽略本小點,筆者是渣渣,要多努力實現一下。
用AS打開android模塊,咱們能夠看到目錄下建立了UmpluginPlugin.kt文件,自行查閱插件的main.dart代碼和該部分代碼就能夠發現,Flutter與Native利用MethodChannel進行通訊,獲取Android的Build.VERSION。
首先是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後,在Android中生成的GeneratePluginRegistrant,裏面註冊插件registerWith方法是何時調用註冊的
Flutter App啓動後,黑屏是如何形成的
首先回顧一下App啓動時,Application建立和Activity建立過程的主要調用的生命週期方法,具體源碼分析看 AOSP Android8.0冷啓動流程分析
這裏再簡單說一下,Application會去讀取AndroidManifest.xml配置的Application,除非沒有,不然執行的是你設置的Application
咱們從建立的Flutter工程Android模塊,能夠看到,AndroidManifest.xml的application節點以下
因此咱們這裏按照原生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) 來通知初始化耗時時間了
最後來個時序圖
按照步驟,分析完FlutterApplication,下一步就應該是配置的啓動Activity分析,一樣先看一下AndroidManifest.xml
點擊MainActivity能夠看出,它是繼承的 FlutterActivity
class MainActivity: FlutterActivity() { }複製代碼
是否是看完會想,怎麼都沒實現方法呢,那確定都是FlutterActivity實現了,包括佈局建立
3.一、查看 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.二、 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.三、configureWindowForTransparency 方法
給Window設置透明背景
private void configureWindowForTransparency() { BackgroundMode backgroundMode = getBackgroundMode(); if (backgroundMode == BackgroundMode.transparent) { getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); } }複製代碼
3.四、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 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; } } 複製代碼
筆記六