Flutter学习系列(5)— 页面初始化

前面一篇介绍了Flutter Android App在进程启动时做的初始化,这一篇主要看一下Flutter的页面显示需要做的初始化。 在我们的Demo中只有一个MainActivity页面。

 

一 MainActivity

 

先看一下这个主页的声明

   <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- This keeps the window background of the activity showing
                 until Flutter renders its first frame. It can be removed if
                 there is no splash screen (such as the default splash screen
                 defined in @style/LaunchTheme). -->
            <meta-data
                android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
                android:value="true" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

没有什么特别,只是声明了一个meta-data,指定了显示一个splash画面,看一下这个页面的具体实现

public class MainActivity extends FlutterActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);
  }
}

我们的主页继承与FlutterActivity,只在onCreate中调用了下面的方法

/**
 * Generated file. Do not edit.
 */
public final class GeneratedPluginRegistrant {
  public static void registerWith(PluginRegistry registry) {
    if (alreadyRegisteredWith(registry)) {
      return;
    }
  }

  private static boolean alreadyRegisteredWith(PluginRegistry registry) {
    final String key = GeneratedPluginRegistrant.class.getCanonicalName();
    if (registry.hasPlugin(key)) {
      return true;
    }
    registry.registrarFor(key);
    return false;
  }
}

GeneratedPluginRegistrant是自动生成的一个类,  调用了PluginRegistry的registrarFor方法进行插件的注册。所谓Flutter Plugin是一个package,plugin可以提供访问当前平台原生API的能力。我们创建工程时可以创建一个Flutter Plugin项目,并在Flutter APP中引用。这里的PluginRegistry 就是我们的MainActivity,而MainActivity继承自FlutterActivity。

 

 

 

二 FlutterActivity

 

/**
 * Base class for activities that use Flutter.
 */
public class FlutterActivity extends Activity implements FlutterView.Provider, PluginRegistry, ViewFactory {
   
    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
    public FlutterView getFlutterView() {
        return viewProvider.getFlutterView();
    }

    @Override
    public FlutterView createFlutterView(Context context) {
        return null;
    }

    @Override
    public FlutterNativeView createFlutterNativeView() {
        return null;
    }

    @Override
    public boolean retainFlutterNativeView() {
        return false;
    }

    @Override
    public final boolean hasPlugin(String key) {
        return pluginRegistry.hasPlugin(key);
    }

    @Override
    public final Registrar registrarFor(String pluginKey) {
        return pluginRegistry.registrarFor(pluginKey);
    }

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

    @Override
    protected void onDestroy() {
        eventDelegate.onDestroy();
        super.onDestroy();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (!eventDelegate.onActivityResult(requestCode, resultCode, data)) {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }
}

上面是FlutterActivity的主要代码, 看起来这个Activity并没有实现什么具体的功能, 但是它关联的类却非常多,总结下来有几点

  1. 它继承自Activity,并且实现了FlutterView.Provider、PluginRegistry、ViewFactory三个接口
  2. 内部有一个FlutterActivityDelegate对象,其他的三个成员变量都被设置为了delegate对象
  3. Activity中实现的接口的方法最终都是调用了FlutterActivityDelegate相关方法

 

 

FlutterActivity的代理模式

因为FlutterActivity中涉及的类和接口和相互关系有点复杂,制作成类图看一下就相对清楚一些:

从代码和UML图中可以很容易看出,FlutterActivity和FlutterActivityDelegate之间采用了代理模式,他们实现共通的接口和方法(声明周期相关方法),FlutterActivity中的成员变量实际都指向了FlutterActivityDelegate,所以实际Activity相关的功能都在FlutterActivityDelegate中实现了。 这样做是因为,我们的项目一般都会有自己的基类Activity,而无法直接继承FlutterActivity或者是FlutterFragmentActivity。 所以提供了FlutterActivityDelegate, 这样可以很方便的接入到我们自己的Activity基类或者Flutter相关的Activity中。下面具体看一下这些接口和实现。

 

PluginRegistry

 

这个类是给Flutter Plugin用来建立和原生API直接调用的。UML图入下:

内部还有7个接口,目前我们只关注FlutterActivity中实现的3个方法,


    @Override
    public final boolean hasPlugin(String key) {
        return pluginRegistry.hasPlugin(key);
    }

    @Override
    public final <T> T valuePublishedByPlugin(String pluginKey) {
        return pluginRegistry.valuePublishedByPlugin(pluginKey);
    }

    @Override
    public final Registrar registrarFor(String pluginKey) {
        return pluginRegistry.registrarFor(pluginKey);
    }

pluginRegistry成员变量实际上是指向了delegate

private final PluginRegistry pluginRegistry = delegate;

 

FlutterViwe.Provider

 

这个接口是FlutterView类的一个内部接口,它只定义了一个方法返回一个FlutterView。定义这个接口主要是为了不使用FlutterrActivity作为基类的情况。FlutterView是一个容器,所有的Flutter App都运行在FlutterView中

/**
 * Interface for those objects that maintain and expose a reference to a
 * {@code FlutterView} (such as a full-screen Flutter activity).
 *
 * <p>
 * This indirection is provided to support applications that use an activity
 * other than {@link io.flutter.app.FlutterActivity} (e.g. Android v4 support
 * library's {@code FragmentActivity}). It allows Flutter plugins to deal in
 * this interface and not require that the activity be a subclass of
 * {@code FlutterActivity}.
 * </p>
 */
public interface Provider {
    /**
     * Returns a reference to the Flutter view maintained by this object. This may
     * be {@code null}.
     */
    FlutterView getFlutterView();
}

 

 

FlutterActivityDelegate.ViewFactory

 

/**
 * Specifies the mechanism by which Flutter views are created during the
 * operation of a {@code FlutterActivityDelegate}.
 *
 * <p>A delegate's view factory will be consulted during
 * {@link #onCreate(Bundle)}. If it returns {@code null}, then the delegate
 * will fall back to instantiating a new full-screen {@code FlutterView}.</p>
 *
 * <p>A delegate's native view factory will be consulted during
 * {@link #onCreate(Bundle)}. If it returns {@code null}, then the delegate
 * will fall back to instantiating a new {@code FlutterNativeView}. This is
 * useful for applications to override to reuse the FlutterNativeView held
 * e.g. by a pre-existing background service.</p>
 */
public interface ViewFactory {
    FlutterView createFlutterView(Context context);
    FlutterNativeView createFlutterNativeView();

    /**
     * Hook for subclasses to indicate that the {@code FlutterNativeView}
     * returned by {@link #createFlutterNativeView()} should not be destroyed
     * when this activity is destroyed.
     */
    boolean retainFlutterNativeView();
}

上面是这个接口的定义,定义的方法是用来创建FlutterView的,这里包含了FlutterViewFlutterNativeView,另一个方法是指定FlutterNativeView的生命周期。从源码中可以看到,FlutterActivity并没有创建View,都返回了null。 所以getFlutterView 返回的也是null。

  @Override
    public FlutterView getFlutterView() {
        return viewProvider.getFlutterView();
    }

    @Override
    public FlutterView createFlutterView(Context context) {
        return null;
    }

    @Override
    public FlutterNativeView createFlutterNativeView() {
        return null;
    }

 

 

三 FlutterActivityDelegate

 

对于FlutterActivity或者我们自己承载Flutter的Activity来说,相关的工作其实都是交给了FlutterActivityDelegate。

/**
 * Class that performs the actual work of tying Android {@link Activity}
 * instances to Flutter.
 *
 * <p>This exists as a dedicated class (as opposed to being integrated directly
 * into {@link FlutterActivity}) to facilitate applications that don't wish
 * to subclass {@code FlutterActivity}. The most obvious example of when this
 * may come in handy is if an application wishes to subclass the Android v4
 * support library's {@code FragmentActivity}.</p>
 *
 * <h3>Usage:</h3>
 * <p>To wire this class up to your activity, simply forward the events defined
 * in {@link FlutterActivityEvents} from your activity to an instance of this
 * class. Optionally, you can make your activity implement
 * {@link PluginRegistry} and/or {@link io.flutter.view.FlutterView.Provider}
 * and forward those methods to this class as well.</p>
 */
public final class FlutterActivityDelegate
        implements FlutterActivityEvents,
                   FlutterView.Provider,
                   PluginRegistry {
    private static final String SPLASH_SCREEN_META_DATA_KEY = "io.flutter.app.android.SplashScreenUntilFirstFrame";
    private static final String TAG = "FlutterActivityDelegate";
    private static final LayoutParams matchParent =
        new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);

    private final Activity activity;
    private final ViewFactory viewFactory;
    private FlutterView flutterView;
    private View launchView;

   public FlutterActivityDelegate(Activity activity, ViewFactory viewFactory) {
        this.activity = Preconditions.checkNotNull(activity);
        this.viewFactory = Preconditions.checkNotNull(viewFactory);
    }
}

可以看一下它的注释,Flutter的页面的初始化实际是由它执行的。这个类主要是为了在不想使用FlutterActivity作为父类的情况下使用,只需要简单的把activity声明周期相关的方法代理到FlutterActivityDelegate就行了。我们先简单看一下它的成员变量和构造函数:

  • activity和viewFactory: 指向了代理的Activity,也就是我们实际使用的MainActivity
  • flutterView: 这个表示当前activity中要显示的Flutter的View
  • launchView: 表示Flutter App启动时要显示的启动的View

 

Activity创建流程

 

public class MainActivity extends FlutterActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);
  }
}

当MainActivity创建时,进行了插件注册,这个前面提到了。因为继承与FlutterActivity, 所以最终会执行到FlutterActivityDelegate中的onCreate方法。

public void onCreate(Bundle savedInstanceState) {
    
        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);
        }
    }

 

一 Flutter Engine初始化

 

FlutterMain.ensureInitializationComplete是用来确保Flutter完成初始化,前面减少进程初始化知道有3个AsyncTask在进程启动后工作,因为是异步的,所以在主页启动时可能还没有完成。所以这里需要等待最后一个任务完成。

/**
 * Blocks until initialization of the native system has completed.
 * @param applicationContext The Android application context.
 * @param args Flags sent to the Flutter runtime.
 */
public static void ensureInitializationComplete(Context applicationContext, String[] args) {
 
    if (sInitialized) {
        return;
    }
    try {
        sResourceExtractor.waitForCompletion();
        List<String> shellArgs = new ArrayList<>();
        shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
        if (args != null) {
            Collections.addAll(shellArgs, args);
        }
        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);
    }
}

当任务完成后,会收集一些列的参数和环境变量给Engine使用(上面代码省略了)。最会调用一个Native方法来完成C++层的Flutter  Engine的初始化。

private static native void nativeInit(Context context, String[] args, String bundlePath, String appStoragePath, String engineCachesPath);

因为FlutterMain是static的,保证只能被初始化一次。

 

 

二 创建FlutterView

 

优先通过ViewFactory的createFlutterView来创建,从构造函数我们知道这里ViewFactory就是FlutterActivity,而我们的MainActivity也没有重写这个方法,所以默认返回的是null。这个时候会创建一个全屏的FlutterView。其中FlutterNativeView也是优先使用ViewFactory的实现。具体创建FlutterView的内容后面在单独研究。

如果没有自己创建FlutterView,delegate中会尝试创建一个LaunchView。

    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;
    }

如果需要显示的话要在Activity中增加一个mate-data。

<meta-data
     android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
     android:value="true" />

而这个LaunchView其实就是当前Activity的WindowBackground

 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;
        }
    }

 

 

三 运行Flutter Bundle

 

万事俱备,就差运行Flutter程序了。 这里发现可以指定当前页面要运行的FlutterBundle文件的路径。 通过ACTION_RUN和参数可以指定, 如果没有指定路径,默认使用本地的flutter_assets目录。 当然只有自己叫起Activity是才可以,我们DEMO中的MainActivity是系统叫起的,所以是ACTION_MAIN。

   private boolean loadIntent(Intent intent) {
        String action = intent.getAction();
        if (Intent.ACTION_RUN.equals(action)) {
            String route = intent.getStringExtra("route");
            String appBundlePath = intent.getDataString();
            if (appBundlePath == null) {
                // Fall back to the installation path if no bundle path was specified.
                appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
            }
            if (route != null) {
                flutterView.setInitialRoute(route);
            }

            runBundle(appBundlePath);
            return true;
        }

        return false;
    }

不管使用那个路径,最终执行到runBundle来启动flutter程序

   private void runBundle(String appBundlePath) {
        if (!flutterView.getFlutterNativeView().isApplicationRunning()) {
            FlutterRunArguments args = new FlutterRunArguments();
            ArrayList<String> bundlePaths = new ArrayList<>();
            ResourceUpdater resourceUpdater = FlutterMain.getResourceUpdater();
            if (resourceUpdater != null) {
                File patchFile = resourceUpdater.getInstalledPatch();
                JSONObject manifest = resourceUpdater.readManifest(patchFile);
                if (resourceUpdater.validateManifest(manifest)) {
                    bundlePaths.add(patchFile.getPath());
                }
            }
            bundlePaths.add(appBundlePath);
            args.bundlePaths = bundlePaths.toArray(new String[0]);
            args.entrypoint = "main";
            flutterView.runFromBundle(args);
        }
    }

首先会判断一下当前的FlutterView是有已经在运行程序,如果已经运行就不会重新运行。如果我们开启了动态更新,接下里就会检查当前是否有下载好的patch.zip文件,并且是否合法。如果存在会把patch和本机的bundle文件夹都加入到FlutterRunArguments中,然后调用FlutterView的runBundle来运行。

前面我们看过如果安装模式是立即安装,会等下载完成才执行ResourceExtractor,而FlutterActivity的初始化要等待ResourceExtractor执行完成。 所以如果有patch并且是立即安装,那么本地的budle应该已经是最新的的了,所以后面看FlutterView的时候要看看把2个路径都传进去的目的。另外可以指定运行其他路径的bundle,那么传patch是相当于默认值吗?

 

 

其他生命周期

 

   @Override
    public void onPause() {
        Application app = (Application) activity.getApplicationContext();
        if (app instanceof FlutterApplication) {
            FlutterApplication flutterApp = (FlutterApplication) app;
            if (activity.equals(flutterApp.getCurrentActivity())) {
                flutterApp.setCurrentActivity(null);
            }
        }
        if (flutterView != null) {
            flutterView.onPause();
        }
    }

    @Override
    public void onStart() {
        if (flutterView != null) {
            flutterView.onStart();
        }
    }

    @Override
    public void onResume() {
        Application app = (Application) activity.getApplicationContext();
        FlutterMain.onResume(app);
        if (app instanceof FlutterApplication) {
            FlutterApplication flutterApp = (FlutterApplication) app;
            flutterApp.setCurrentActivity(activity);
        }
    }

    @Override
    public void onStop() {
        flutterView.onStop();
    }

    @Override
    public void onPostResume() {
        if (flutterView != null) {
            flutterView.onPostResume();
        }
    }

    @Override
    public void onDestroy() {
        Application app = (Application) activity.getApplicationContext();
        if (app instanceof FlutterApplication) {
            FlutterApplication flutterApp = (FlutterApplication) app;
            if (activity.equals(flutterApp.getCurrentActivity())) {
                flutterApp.setCurrentActivity(null);
            }
        }
        if (flutterView != null) {
            final boolean detach =
                flutterView.getPluginRegistry().onViewDestroy(flutterView.getFlutterNativeView());
            if (detach || viewFactory.retainFlutterNativeView()) {
                // Detach, but do not destroy the FlutterView if a plugin
                // expressed interest in its FlutterNativeView.
                flutterView.detach();
            } else {
                flutterView.destroy();
            }
        }
    }

上面列举了Activity的生命周期,我们可以看到每个生命周期都通知到了FlutterView。其中onResume没有通知,而是使用onPostResume,因为onPostResume是当onResume完全执行完后触发,这个时候是可以获取到View的高和宽的,我想可能是这方面的原因。而在onResum中调用的了FlutterMain的方法

 public static void onResume(Context context) {
        if (sResourceUpdater != null) {
            if (sResourceUpdater.getDownloadMode() == ResourceUpdater.DownloadMode.ON_RESUME) {
                sResourceUpdater.startUpdateDownloadOnce();
            }
        }
    }

这里是前面说到动态个更新的模式,是启动更新还是onResume时更新。 但是感觉这有个问题,也就是所有使用了FlutterActivityDelegate的Activity都可能会在onResume时去下载更新,但是这个更新只针对APK中自带的bundle,并不是当前页面实际加载的bundle。

 

 

 

四 总结

 

这篇文章主要简单介绍了启动一个包含Flutter程序的Activity时做的事情。实际我们的Activity只是一个壳子, 真正运行Flutter程序的地方是FlutterView。 而FlutterActivityDelegate负责Flutter Engine的初始化、FlutterView的创建以及调用FlutterView执行flutter bundle文件。 并且Activity的声明周期也都通知给了FlutterView。 所以FlutterView才是整个Android和Flutter交互的核心。

0

如果本文对您有帮助,可以扫描下方二维码打赏!您的支持是我的动力!
微信打赏 支付宝打赏

发表评论

电子邮件地址不会被公开。 必填项已用*标注