作技術,只有弄懂了原理,才能遇事不慌,手中無碼,心中有碼。這篇文章主要研究Flutter 在安卓平臺上的啓動流程源碼。前端
當咱們建立一個Flutter app工程時,打開android目錄下的源碼,會發現有一個MainActivity
繼承自FlutterActivity
,整個MainActivity
很是簡單,只在onCreate
下加了一行代碼GeneratedPluginRegistrant.registerWith(this)
,那麼FlutterActivity
又是何方神聖呢?FlutterActivity
的源碼在Flutter SDK的jar包中,想要研究Flutter源碼,第一件事就是須要下載一套SDK源碼,咱們能夠在GitHub上下載 engine源碼java
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}
複製代碼
engine\src\flutter\shell\platform\android\io\flutter\app\FlutterActivity.java
省略部分源碼,刪除註釋後代碼以下android
public class FlutterActivity extends Activity implements FlutterView.Provider, PluginRegistry, ViewFactory {
private static final String TAG = "FlutterActivity";
private final FlutterActivityDelegate delegate = new FlutterActivityDelegate(this, this);
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);
}
@Override
protected void onStart() {
super.onStart();
eventDelegate.onStart();
}
@Override
protected void onResume() {
super.onResume();
eventDelegate.onResume();
}
@Override
protected void onDestroy() {
eventDelegate.onDestroy();
super.onDestroy();
}
// ...省略部分源碼...
}
複製代碼
能夠看到FlutterActivity
繼承自Activity
,並實現了三個接口,FlutterActivity
的生命週期方法,均由一個代理類FlutterActivityDelegate
處理。c++
engine\src\flutter\shell\platform\android\io\flutter\app\FlutterActivityDelegate.java
git
@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);
}
// 獲取intent傳入的參數信息
String[] args = getArgsFromIntent(activity.getIntent());
// 初始化一些參數配置信息,包括打包的flutter代碼路徑、應用存儲目錄、引擎緩存目錄等
FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), args);
flutterView = viewFactory.createFlutterView(activity);
// 真正建立contentView的地方
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);
}
}
複製代碼
咱們天然是要找到onCreate
方法,以上代碼我增長了一點註釋,咱們要快速定位到關鍵代碼,什麼是關鍵代碼,看到了activity.setContentView(flutterView);
,這裏就是關鍵代碼,終於見到了咱們熟悉的setContentView
了。這裏咱們不由要問,這個flutterView
究竟是個什麼View?github
一開始咱們就知道ViewFactory
是FlutterActivity
實現的,這裏createFlutterView
方法實現也在FlutterActivity
裏,但這個方法始終返回空,再看createFlutterNativeView
方法,仍然是返回空shell
@Override
public FlutterNativeView createFlutterNativeView() {
return null;
}
@Override
public FlutterNativeView createFlutterNativeView() {
return null;
}
複製代碼
這裏真正的flutterView
其實是經過flutterView = new FlutterView(activity, null, nativeView)
這行代碼new
出來的,而後傳遞給setContentView
數據庫
接下來直接看到FlutterView
源碼(省略部分代碼)編程
public class FlutterView extends SurfaceView implements BinaryMessenger, TextureRegistry {
private static final String TAG = "FlutterView";
public FlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView) {
super(context, attrs);
Activity activity = getActivity(getContext());
if (activity == null) {
throw new IllegalArgumentException("Bad context");
}
if (nativeView == null) {
mNativeView = new FlutterNativeView(activity.getApplicationContext());
} else {
mNativeView = nativeView;
}
dartExecutor = mNativeView.getDartExecutor();
flutterRenderer = new FlutterRenderer(mNativeView.getFlutterJNI());
mIsSoftwareRenderingEnabled = FlutterJNI.nativeGetIsSoftwareRenderingEnabled();
mMetrics = new ViewportMetrics();
mMetrics.devicePixelRatio = context.getResources().getDisplayMetrics().density;
setFocusable(true);
setFocusableInTouchMode(true);
mNativeView.attachViewAndActivity(this, activity);
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();
}
};
getHolder().addCallback(mSurfaceCallback);
mActivityLifecycleListeners = new ArrayList<>();
mFirstFrameListeners = new ArrayList<>();
// Create all platform channels
navigationChannel = new NavigationChannel(dartExecutor);
keyEventChannel = new KeyEventChannel(dartExecutor);
lifecycleChannel = new LifecycleChannel(dartExecutor);
localizationChannel = new LocalizationChannel(dartExecutor);
platformChannel = new PlatformChannel(dartExecutor);
systemChannel = new SystemChannel(dartExecutor);
settingsChannel = new SettingsChannel(dartExecutor);
// Create and setup plugins
PlatformPlugin platformPlugin = new PlatformPlugin(activity, platformChannel);
addActivityLifecycleListener(platformPlugin);
mImm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
mTextInputPlugin = new TextInputPlugin(this, dartExecutor);
androidKeyProcessor = new AndroidKeyProcessor(keyEventChannel, mTextInputPlugin);
androidTouchProcessor = new AndroidTouchProcessor(flutterRenderer);
// Send initial platform information to Dart
sendLocalesToDart(getResources().getConfiguration());
sendUserPlatformSettingsToDart();
}
複製代碼
到這裏就明白了,FlutterView
實際上就是安卓中的SurfaceView
,由於SurfaceView
是雙緩衝的,能夠在子線程更新UI,性能高效,所以一般是用來作遊戲開發、視頻直播。通過簡單的源碼分析,咱們大體能明白Flutter在安卓上的實現方式,整個Flutter開發的app都是在一個Activity
中進行渲染的,這就有點像如今前端流行的所謂單頁應用。數組
還個方法中還建立了各類平臺插件和platform channel
,用於Flutter層和原生代碼之間的數據傳遞。
如今咱們再回過頭來看一下剛剛漏過的一些代碼,看看它們作了些什麼事 onCreate
函數中有調用FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), args)
/** * Blocks until initialization of the native system has completed. */
public static void ensureInitializationComplete(@NonNull Context applicationContext, @Nullable String[] args) {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("ensureInitializationComplete must be called on the main thread");
}
if (sSettings == null) {
throw new IllegalStateException("ensureInitializationComplete must be called after startInitialization");
}
if (sInitialized) {
return;
}
try {
sResourceExtractor.waitForCompletion();
List<String> shellArgs = new ArrayList<>();
shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
ApplicationInfo applicationInfo = getApplicationInfo(applicationContext);
shellArgs.add("--icu-native-lib-path=" + applicationInfo.nativeLibraryDir + File.separator + DEFAULT_LIBRARY);
// ...省略...
if (sSettings.getLogTag() != null) {
shellArgs.add("--log-tag=" + sSettings.getLogTag());
}
String appBundlePath = findAppBundlePath(applicationContext);
String appStoragePath = PathUtils.getFilesDir(applicationContext);
String engineCachesPath = PathUtils.getCacheDirectory(applicationContext);
nativeInit(applicationContext, shellArgs.toArray(new String[0]),
appBundlePath, appStoragePath, engineCachesPath);
sInitialized = true;
} catch (Exception e) {
Log.e(TAG, "Flutter initialization failed.", e);
throw new RuntimeException(e);
}
}
複製代碼
該方法註釋已經明確告訴咱們,這是一個阻塞的方法,直到底層初始化完成。這意味着該方法執行會影響app的啓動速度。總的來講,該方法作了幾件事,它保證初始化操做在主線程運行,調用sResourceExtractor.waitForCompletion()
完成資源文件的提取工做,拼接全部相關的shellArgs
參數,包括intent
中的參數,配置dart
代碼編譯產物appBundle路徑,應用存儲、引擎緩存目錄等信息, 最後經過執行nativeInit
函數在c++層初始化這些信息
接下來,還有一個地方值得一說,在建立FlutterView
以後,調用了一個createLaunchView
方法
private View createLaunchView() {
if (!showSplashScreenUntilFirstFrame()) {
return null;
}
final Drawable launchScreenDrawable = getLaunchScreenDrawableFromActivityTheme();
if (launchScreenDrawable == null) {
return null;
}
final View view = new View(activity);
view.setLayoutParams(matchParent);
view.setBackground(launchScreenDrawable);
return view;
}
複製代碼
這個方法,其實就是在Flutter啓動後添加一個splash view
。你們知道,在Flutter應用啓動以後,會有一小段白屏的時間,之因此會白屏,就是這個方法致使的。目前的解決辦法,就是手動添加一個設計好的閃屏頁,平滑的過分一下。那麼如何修改默認的白屏頁,設置咱們本身的splash view
呢?
private Drawable getLaunchScreenDrawableFromActivityTheme() {
TypedValue typedValue = new TypedValue();
if (!activity.getTheme().resolveAttribute(
android.R.attr.windowBackground,
typedValue,
true)) {
return null;
}
if (typedValue.resourceId == 0) {
return null;
}
try {
return activity.getResources().getDrawable(typedValue.resourceId);
} catch (NotFoundException e) {
Log.e(TAG, "Referenced launch screen windowBackground resource does not exist");
return null;
}
}
複製代碼
能夠看到,這個Drawable
實際上是從主題的windowBackground
中取的,咱們打開app工程中的styles.xml
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> <!-- Show a splash screen on the activity. Automatically removed when Flutter draws its first frame --> <item name="android:windowBackground">@drawable/launch_background</item> </style>
複製代碼
看到,windowBackground
實際上是指定爲launch_background
,在drawable
文件夾下找到它
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item> <bitmap android:gravity="center" android:src="@mipmap/launch_image" /> </item> -->
</layer-list>
複製代碼
這裏默認背景確實被設置成了白色,而且還友好的給出了一個設置圖片的示例,將註釋的代碼打開,就能夠設置本身的splash view
咱們知道安卓app啓動時,首先調用的是Application
的onCreate
,如今來看一看Flutter應用的Application作了些什麼 flutter\shell\platform\android\io\flutter\app\FlutterApplication.java
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;
}
}
複製代碼
整個FlutterApplication
比較簡單,主要調用了FlutterMain.startInitialization(this)
開啓初始化
public static void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("startInitialization must be called on the main thread");
}
// Do not run startInitialization more than once.
if (sSettings != null) {
return;
}
sSettings = settings;
long initStartTimestampMillis = SystemClock.uptimeMillis();
initConfig(applicationContext);
initAot(applicationContext);
initResources(applicationContext);
System.loadLibrary("flutter");
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
nativeRecordStartTimestamp(initTimeMillis);
}
複製代碼
該方法註釋代表,它是用來初始化底層的Flutter 引擎的,主要作了幾件事,
在FlutterApplication
中加載了引擎的so,咱們不由要問,那Flutter引擎又是在哪建立的,怎麼與原生Java代碼關聯起來的呢?
在FlutterView
構造方法中建立了FlutterNativeView
,看到FlutterNativeView
的構造方法
public FlutterNativeView(@NonNull Context context, boolean isBackgroundView) {
mContext = context;
mPluginRegistry = new FlutterPluginRegistry(this, context);
mFlutterJNI = new FlutterJNI();
mFlutterJNI.setRenderSurface(new RenderSurfaceImpl());
this.dartExecutor = new DartExecutor(mFlutterJNI);
mFlutterJNI.addEngineLifecycleListener(new EngineLifecycleListenerImpl());
attach(this, isBackgroundView);
assertAttached();
}
複製代碼
在這裏主要建立了FlutterJNI
對象,並調用了一個關鍵方法attach
,一路跟蹤該方法調用
private void attach(FlutterNativeView view, boolean isBackgroundView) {
mFlutterJNI.attachToNative(isBackgroundView);
dartExecutor.onAttachedToJNI();
}
複製代碼
flutter\shell\platform\android\io\flutter\embedding\engine\FlutterJNI.java
public void attachToNative(boolean isBackgroundView) {
ensureRunningOnMainThread();
ensureNotAttachedToNative();
nativePlatformViewId = nativeAttach(this, isBackgroundView);
}
private native long nativeAttach(FlutterJNI flutterJNI, boolean isBackgroundView);
複製代碼
最後發現調用了一個native
方法,並將FlutterJNI
實例對象自身傳入了C++
層。這裏,咱們怎麼才能找到native
方法所對應的C++源碼呢?
我這裏就介紹一種最簡單的傻瓜式方法,不須要太多技巧,你們能夠下載一個FileLocatorPro工具,它是Windows平臺上的文本搜索神器,建議最好安裝一個。
flutter\shell\platform\android
設置爲搜索路徑,另外還能夠在文件名稱一欄填入
*.cc
,表示僅搜索以
.cc
做爲後綴的文件,這裏
.cc
是C++源文件後綴名。最後咱們秒搜到了匹配的內容
flutter\shell\platform\android\platform_view_android_jni.cc
static const JNINativeMethod flutter_jni_methods[] = {
// Start of methods from FlutterNativeView
{
.name = "nativeAttach",
.signature = "(Lio/flutter/embedding/engine/FlutterJNI;Z)J",
.fnPtr = reinterpret_cast<void*>(&AttachJNI),
}
複製代碼
這是一個結構體數組,能夠看到nativeAttach
對應的函數指針是AttachJNI
,咱們繼續在當前文件中找到AttachJNI
函數,它就是Java層nativeAttach
方法的具體實現
// Called By Java
static jlong AttachJNI(JNIEnv* env, jclass clazz, jobject flutterJNI, jboolean is_background_view) {
fml::jni::JavaObjectWeakGlobalRef java_object(env, flutterJNI);
auto shell_holder = std::make_unique<AndroidShellHolder>(
FlutterMain::Get().GetSettings(), java_object, is_background_view);
if (shell_holder->IsValid()) {
return reinterpret_cast<jlong>(shell_holder.release());
} else {
return 0;
}
}
複製代碼
這裏的C++代碼主要乾了一件事,就是經過make_unique
來建立了一個AndroidShellHolder
對象,所以咱們須要找到AndroidShellHolder
類的構造方法
flutter/shell/platform/android/android_shell_holder.cc
AndroidShellHolder::AndroidShellHolder(
flutter::Settings settings,
fml::jni::JavaObjectWeakGlobalRef java_object,
bool is_background_view)
: settings_(std::move(settings)), java_object_(java_object) {
static size_t shell_count = 1;
auto thread_label = std::to_string(shell_count++);
FML_CHECK(pthread_key_create(&thread_destruct_key_, ThreadDestructCallback) ==
0);
if (is_background_view) {
thread_host_ = {thread_label, ThreadHost::Type::UI};
} else {
thread_host_ = {thread_label, ThreadHost::Type::UI | ThreadHost::Type::GPU |
ThreadHost::Type::IO};
}
// ...省略...
// The current thread will be used as the platform thread. Ensure that the
// message loop is initialized.
fml::MessageLoop::EnsureInitializedForCurrentThread();
fml::RefPtr<fml::TaskRunner> gpu_runner;
fml::RefPtr<fml::TaskRunner> ui_runner;
fml::RefPtr<fml::TaskRunner> io_runner;
fml::RefPtr<fml::TaskRunner> platform_runner =
fml::MessageLoop::GetCurrent().GetTaskRunner();
if (is_background_view) {
auto single_task_runner = thread_host_.ui_thread->GetTaskRunner();
gpu_runner = single_task_runner;
ui_runner = single_task_runner;
io_runner = single_task_runner;
} else {
gpu_runner = thread_host_.gpu_thread->GetTaskRunner();
ui_runner = thread_host_.ui_thread->GetTaskRunner();
io_runner = thread_host_.io_thread->GetTaskRunner();
}
flutter::TaskRunners task_runners(thread_label, // label platform_runner, // platform gpu_runner, // gpu ui_runner, // ui io_runner // io );
shell_ =
Shell::Create(task_runners, // task runners
settings_, // settings
on_create_platform_view, // platform view create callback
on_create_rasterizer // rasterizer create callback
);
platform_view_ = weak_platform_view;
FML_DCHECK(platform_view_);
is_valid_ = shell_ != nullptr;
// ...省略...
}
複製代碼
這個方法裏面代碼比較多,省略部分代碼,咱們看到最重要的幾個地方,首先這裏有四個線程,除了當前線程做爲platform
線程,還建立了三個新的線程
gpu
線程ui
線程io
線程此處當配圖,來自閒魚技術博客
四個線程分別持有一個TaskRunner
對象,後續會經過這些TaskRunner
來將一些操做放到對應的線程中去執行。
Platform Task Runner
的線程能夠理解爲是主線程,它不只僅處理與Engine交互,它還處理來自平臺的消息。
UI Task Runner
被用於執行Dart root isolate代碼,簡單說也就是Dart語言的主線程。咱們所編寫的Dart代碼基本就是在這個線程運行。所以該線程繁忙會致使UI卡頓,若是有繁重的計算任務,如加密、解壓縮等,應當在Dart中另起一個isolate
來執行代碼。須要注意,另啓的isolate
是不能與Flutter引擎交互的,表如今開發中,也就是不能在新建立的isolate
中調用插件,例如在新的isolate
操做SQlite數據庫,若有這種需求,可使用FlutterIsolate 庫
GPU Task Runner
用於執行設備GPU的相關調用。主要是配置管理每一幀繪製所須要的GPU資源。若是該線程卡頓,直接致使程序卡頓。一般來講用平臺代碼和Dart代碼都沒法直接操做到該線程
IO Runner
的主要是從圖片存儲(如磁盤)中讀取壓縮的圖片格式,將圖片數據進行處理,爲GPU Runner的渲染作好準備。也就是處理與磁盤IO相關的事務
最後,看到如下函數的調用,此處便是建立引擎的地方
shell_ =
Shell::Create(task_runners, // task runners
settings_, // settings
on_create_platform_view, // platform view create callback
on_create_rasterizer // rasterizer create callback
);
複製代碼
你們有興趣能夠找相關源碼,繼續跟蹤一下源碼,看看引擎是如何建立的
\flutter\shell\common\shell.cc
複製代碼
經過反編譯Flutter生成的apk,咱們很清楚的知道,Dart代碼編譯後的產物,包括相關的資源文件,實際上都是打包到安卓的assets
目錄下的,那麼Dart代碼是在哪加載執行的呢?
在FlutterActivityDelegate
的onCreate
方法的最後有以下代碼
String appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
if (appBundlePath != null) {
runBundle(appBundlePath);
}
複製代碼
這裏首先獲取資源文件提取後在應用所屬目錄下的路徑,而後調用runBundle
進行加載執行。runBundle
方法最終調用FlutterJNI
中的一個native方法
private native void nativeRunBundleAndSnapshotFromLibrary( long nativePlatformViewId, @NonNull String[] prioritizedBundlePaths, @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction, @NonNull AssetManager manager );
複製代碼
調用過程以下,點擊大圖
FlutterActivityDelegate
中onCreate
流程圖
FlutterMain.ensureInitializationComplete
方法調用