ticktakclockの日記

技術ポエムを綴ったりします。GitHub idも同じです (@ticktakclock)

Flutter Androidのソースコードを読む(FlutterActivity編)

こんにちは、tkyです。

前回FlutterApplication.javaのコードを読んで、Dartを動作させるための初期設定を査読しました。

今回はFlutterActivity.javaのコードを読みながら、アプリ起動するまでを追ってみようと思います。 調べた軌跡を残しているので今回もそれなりに長い記事となっております。

前回も書きましたが・・・今回読んでいくのはこれ

flutter.jar

Android Studioにはjarのclassファイルに定義ジャンプすることができるので⌘ + クリック(winの場合は ctrl + クリック)で読めますが、 ここで読めるのはあくまでjavaで書かれた領域だけなのでネイティブコード(C/C++の領域のことを指します)も見ることを考慮して flutter/engineもcloneしておきます。

github.com

目次

  • FlutterActivity.javaを探す

FlutterActivity

以下にそれぞれあります。engine側のコードがビルドされてflutter.jarになります。

  • engine側
    • engine/shell/platform/android/io/flutter/app/FlutterActivity.java
  • flutter.jar側
    • io/flutter/app/FlutterActivity.java

コンストラクタを見てみましょう。FlutterActivityDelegateにほぼ集約されてそうな感じです。

public class FlutterActivity extends Activity implements Provider, PluginRegistry, ViewFactory {
    private final FlutterActivityDelegate delegate = new FlutterActivityDelegate(this, this);
    private final FlutterActivityEvents eventDelegate;
    private final Provider viewProvider;
    private final PluginRegistry pluginRegistry;

    public FlutterActivity() {
        this.eventDelegate = this.delegate;
        this.viewProvider = this.delegate;
        this.pluginRegistry = this.delegate;
    }

FlutterActivityDelegateとは

名前が名前だけあって、Activityがやる処理の委譲クラスです。 ここでは大体以下のことをしているようでした。

  • flutterViewの保持
  • ライフサイクルメソッド(onCreate, onStart, onResume等)の処理
  • Intentの処理

ライフサイクルに従って適切にViewに状態を伝える役割をしているようですね。

onCreateで何をしているのか

FlutterMain.ensureInitializationComplete()、flutterViewの生成とIntentの処理をしていました。ここではflutterViewにフォーカスを当てて見てみます。

ensureInitializationComplete

初期化完了させるための確定処理、という意味で捉えます。

FlutterApplicationの処理にて初期化した、shared ibraryのpathやvm snapshot, isolateのpathを使い、vm shellの起動パラメータの生成処理を行います。 その後、nativeInit()を呼び出しています。

このインターフェースの定義はどこにあるのかというと以下にあります。

engine/shell/platform/android/flutter_main.cc
{
    .name = "nativeInit",
    .signature = "(Landroid/content/Context;[Ljava/lang/String;Ljava/"
           "lang/String;Ljava/lang/String;Ljava/lang/String;)V",
    .fnPtr = reinterpret_cast<void*>(&Init),
},

flutter_main.ccはJNIのインターフェース定義を行っています。 flutter_main.ccはSystem.loadLibrary("flutter");にてlibflutter.soがロードされたときに、 engine/shell/platform/android/library_loader.ccのonLoad内でflutter_main.ccが実行されます。 ※要するに、JNIの定義設定処理はFlutterApplication.javaのonCreate処理で行われているということになります。

nativeInitの実際の処理はdartのキャッシュに関する処理をしているようですが、今の私の理解を超えていたので一旦おいておきます。。

flutterViewの生成

ViewFactoryはFlutterActivityに実装されていますが、デフォルト実装でnullが返るようになっているのでonCreateで必ずFlutterViewを生成することになりそうです。

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

viewFactory.createFlutterNativeView

FlutterViewはSurfaceViewを継承しているViewであることはわかりますね。flutterで作られたアプリはこのsurfaceView上に描画されている、という理解です。

ではFlutterNativeViewは何なのか?実装を見るとJNI側に処理を渡すためのクラスになっているようです。

FlutterNativeViewにはnative修飾子のメソッドがいくつか定義されています。このメソッドの実装はC++側の実装となり、flutter.jarだと此処から先は確認することができません。

この先はengine側のソースコードを見ることになりますが、後ほど見ていくこととします。

あとのFlutterViewの処理は標準のプラグインの初期化処理らしき実装があるのがわかります。 この辺は自身でプラグインを作成したらわかりそうな気がする・・・!!ので機会を見てプラグイン作成にもチャレンジしてみたいと思います。

this.mFlutterLocalizationChannel = new MethodChannel(this, "flutter/localization", JSONMethodCodec.INSTANCE);
this.mFlutterNavigationChannel = new MethodChannel(this, "flutter/navigation", JSONMethodCodec.INSTANCE);
this.mFlutterKeyEventChannel = new BasicMessageChannel(this, "flutter/keyevent", JSONMessageCodec.INSTANCE);
this.mFlutterLifecycleChannel = new BasicMessageChannel(this, "flutter/lifecycle", StringCodec.INSTANCE);
this.mFlutterSystemChannel = new BasicMessageChannel(this, "flutter/system", JSONMessageCodec.INSTANCE);
this.mFlutterSettingsChannel = new BasicMessageChannel(this, "flutter/settings", JSONMessageCodec.INSTANCE);
PlatformPlugin platformPlugin = new PlatformPlugin(activity);
MethodChannel flutterPlatformChannel = new MethodChannel(this, "flutter/platform", JSONMethodCodec.INSTANCE);

そしてこのflutterViewをactivityでsetContentViewしてsurfaceViewが全面に表示されるようになります。

surfaceCallback

ではここからsurfaceCallbackの処理を追っかけてみたいと思います。いきなりネイティブ インターフェースがが出てきましたね。

this.mSurfaceCallback = new Callback() {
    public void surfaceCreated(SurfaceHolder holder) {
        FlutterView.this.assertAttached();
        FlutterView.nativeSurfaceCreated(FlutterView.this.mNativeView.get(), holder.getSurface());
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        FlutterView.this.assertAttached();
        FlutterView.nativeSurfaceChanged(FlutterView.this.mNativeView.get(), width, height);
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        FlutterView.this.assertAttached();
        FlutterView.nativeSurfaceDestroyed(FlutterView.this.mNativeView.get());
    }
};

nativeSurfaceCreatedの実装はどこにあるのか

FlutterViewに直接定義があったのですが、engine側では以下にあることがわかりました。

engine/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java

これはengineのコードとflutter.jarのコードは若干異なることをしめしていますが、もともとflutter.jarは逆コンパイルしているため、最適化されているのだろうと想定しています。

// engine/shell/platform/android/platform_view_android_jni.cc
{
  .name = "nativeSurfaceCreated",
  .signature = "(JLandroid/view/Surface;)V",
  .fnPtr = reinterpret_cast<void*>(&shell::SurfaceCreated),
},

PlatformView.SurfaceCreated

Androidにはplatform_view_androidiosにはplatform_view_iosがそれぞれplatform_viewを継承している構成になっています。

// engine/shell/platform/android/platform_view_android.cc
void PlatformViewAndroid::NotifyCreated(
    fml::RefPtr<AndroidNativeWindow> native_window) {
  if (android_surface_) {
    InstallFirstFrameCallback();
    android_surface_->SetNativeWindow(native_window);
  }
  PlatformView::NotifyCreated();
}
// engine/shell/common/platform_view.cc
void PlatformView::NotifyCreated() {
  delegate_.OnPlatformViewCreated(CreateRenderingSurface());
}
// engine/shell/common/shell.cc   
void Shell::OnPlatformViewCreated(std::unique_ptr<Surface> surface) {
・・・省略・・・
    if (rasterizer) {
      rasterizer->Setup(std::move(surface));
    }
・・・省略・・・
}

このRasterrizerというクラスでなにかしていそうです。これのincludeを見てみるとskiaで描画している雰囲気がバンバン出ています。

#include "third_party/skia/include/core/SkEncodedImageFormat.h"
#include "third_party/skia/include/core/SkImageEncoder.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
#include "third_party/skia/include/core/SkSerialProcs.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/core/SkSurfaceCharacterization.h"
#include "third_party/skia/include/utils/SkBase64.h"
・・・省略・・・
// engine/shell/common/rasterizer.cc
void Rasterizer::Setup(std::unique_ptr<Surface> surface) {
  surface_ = std::move(surface);
  compositor_context_->OnGrContextCreated();
}
・・・省略・・・
void Rasterizer::DoDraw(std::unique_ptr<flow::LayerTree> layer_tree) {
・・・省略・・・
bool Rasterizer::DrawToSurface(flow::LayerTree& layer_tree) {
・・・省略・・・

nativeSurfaceChangedを見てみる

nativeSurfaceChangedも同様に処理を追っていきます。先程のようにJNIまで探り当てて、platform_view_android側の処理を見つけ出します。

// engine/shell/platform/android/platform_view_android_jni.cc
static void SurfaceChanged(JNIEnv* env,
    jobject jcaller,
    jlong shell_holder,
    jint width,
    jint height) {
  ANDROID_SHELL_HOLDER->GetPlatformView()->NotifyChanged(
      SkISize::Make(width, height));
}
// engine/shell/platform/android/platform_view_android.cc
void PlatformViewAndroid::NotifyChanged(const SkISize& size) {
  if (!android_surface_) {
    return;
  }
  fml::AutoResetWaitableEvent latch;
  fml::TaskRunner::RunNowOrPostTask(
      task_runners_.GetGPUTaskRunner(),  //
      [&latch, surface = android_surface_.get(), size]() {
        surface->OnScreenSurfaceResize(size);
        latch.Signal();
      });
  latch.Wait();
}

調べていくとsurface_glにたどり着いてしまったのですが、ライフサイクル的にはsurfaceが変更されたときのイベントなのでネイティブ的にはsurfaceの初期化などをしていそうですね。

// engine/shell/platform/android/android_surface_gl.cc
bool AndroidSurfaceGL::OnScreenSurfaceResize(const SkISize& size) const {
  FML_DCHECK(onscreen_context_ && onscreen_context_->IsValid());
  return onscreen_context_->Resize(size);
}

nativeSurfaceDestroyed

こちらも調べていきます。おそらくラスタライザのteardownにつながってるんじゃないかなぁ、という推測をしながら調べてみます。

// engine/shell/platform/android/platform_view_android_jni.cc
static void SurfaceDestroyed(JNIEnv* env, jobject jcaller, jlong shell_holder) {
  ANDROID_SHELL_HOLDER->GetPlatformView()->NotifyDestroyed();
}
// engine/shell/common/platform_view.cc
void PlatformView::NotifyDestroyed() {
  delegate_.OnPlatformViewDestroyed();
}
// engine/shell/common/rasterizer.cc

void Shell::OnPlatformViewDestroyed() {
・・・省略・・・
    if (rasterizer) {
      rasterizer->Teardown();
    }
・・・省略・・・
}

ビンゴですね〜!

おわりに

少しflutter androidがどんな感じで描画しようとしているのか雰囲気がわかったような、わからないような・・・ もう少しsurfacecreatedから実際にdrawが実行されるまでの処理を追いかけて順次記事更新していこうと思います。