NA嵌入Flutter页面

// 传递参数只需要在路由名称后面进行拼接。

FlutterFragment.NewEngineFragmentBuilder initialRoute = fragmentBuilder.initialRoute(“yc”);

FlutterFragment flutterFragment = initialRoute.build();

getSupportFragmentManager()

.beginTransaction()

.add(R.id.rl_flutter, flutterFragment)

.commit();

// 存在的问题

// 使用的withNewEngine()方法从名称上也能看出每次都是创建一个新的FlutterEngine对象来显示Flutter UI,

// 但是从官方文档中我们可以了解到每个FlutterEngine对象在显示出Flutter UI之前

// 是需要一个warm-up(不知道能不能翻译为预热)期的,这会导致屏幕呈现短暂的空白,

// 解决方式就是预先创建并启动FlutterEngine,完成warm-up过程,然后将这个FlutterEngine缓存起来,

// 之后使用这个FlutterEngine来显示出Flutter UI。

// 解决方案看:FlutterFragmentCachedActivity

// 如何获取到FlutterEngine对象呢?FlutterFragment中定义了一个getFlutterEngine()方法,

// 从方法名来看大概就是获取FlutterEngine对象。

// 尝试过创建MethodChannel时传入flutterFragment.getFlutterEngine().getDartExecutor(),

// 运行后会直接抛出空指针异常,异常产生的位置在FlutterFragment的getFlutterEngine()方法中

// 错误原因是这里的delegate为null,全局搜索一下,发现在FlutterFragment的onAttach()方法中会对delegate赋值,也就是说明此时没有执行onAttach()方法。

// 猜测这就是由于上面提到过的FlutterEngine的warm-up机制,这是一个耗时过程,

// 因此FlutterFragment并不会立刻执行onAttach()方法,导致我们在Activity的onCreate()方法中直接使用FlutterFragment的getFlutterEngine()方法会抛出异常。

// todo 调用下面这句话会空指针崩溃

// FlutterEngine flutterEngine = flutterFragment.getFlutterEngine();

}

  • Flutter添加页面

  • 这个同上

3.3 使用新版本FlutterActivity

  • 原生引入Flutter页面方式

  • 使用FlutterActivity,这里的FlutterActivity也是位于io.flutter.embedding.android包下的。

<activity

android:name=“io.flutter.embedding.android.FlutterActivity”

android:configChanges=“orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode”

android:hardwareAccelerated=“true”

android:theme=“@style/AppTheme”

android:windowSoftInputMode=“adjustResize” />

/**

  • 和介绍的创建FlutterFragment的三种方式是对应的

  • FlutterActivity显示的Flutter路由是在创建Intent对象时指定的,

  • 优点就是使用起来更简单,缺点就是不够灵活,

  • 无法像FlutterView/FlutterFragment那样只是作为原生页面中的一部分展示,

  • 因此这种方式更适合整个页面都是由Flutter编写的场景。

*/

private void test(){

// 方式一、FlutterActivity显示的路由名称为"/",不可设置

/*startActivity(

FlutterActivity.createDefaultIntent(this)

);*/

// 方式二、FlutterActivity显示的路由名称可设置,每次都创建一个新的FlutterEngine对象

startActivity(

FlutterActivity

.withNewEngine()

.initialRoute(“yc”)

.build(this)

);

// 方式三、FlutterActivity显示的路由名称可设置,使用缓存好的FlutterEngine对象

/*startActivity(

FlutterActivity

.withCachedEngine(“my_engine_id”)

.build(this)

);*/

}

  • 使用这种方式特点

  • 这种方式不需要我们自己创建一个Activity,FlutterActivity显示的Flutter路由是在创建Intent对象时指定的,优点就是使用起来更简单,缺点就是不够灵活,无法像FlutterView/FlutterFragment那样只是作为原生页面中的一部分展示,因此这种方式更适合整个页面都是由Flutter编写的场景。

3.4 补充说明问题

  • 将Flutter版本更新到了1.17,发现上述代码运行后FlutterView无法显示,这个是为什么呢?

  • 和官方提供的示例flutter_view进行了对比,才发现缺少了下面的代码:

@Override

protected void onResume() {

super.onResume();

// flutterEngine.getLifecycleChannel()获取到的是一个LifecycleChannel对象,类比于MethodChannel,

// 作用大概就是将Flutter和原生端的生命周期相互联系起来。

flutterEngine.getLifecycleChannel().appIsResumed();

}

@Override

protected void onPause() {

super.onPause();

flutterEngine.getLifecycleChannel().appIsInactive();

}

@Override

protected void onStop() {

super.onStop();

flutterEngine.getLifecycleChannel().appIsPaused();

}

  • 可能和生命周期有关系

  • flutterEngine.getLifecycleChannel()获取到的是一个LifecycleChannel对象,类比于MethodChannel,作用大概就是将Flutter和原生端的生命周期相互联系起来。

  • 这里分别在onResume()、onPause()和onStop()方法中调用了LifecycleChannel的appIsResumed()、appIsInactive()和appIsPaused()方法,作用就是同步Flutter端与原生端的生命周期。添加上述代码后,FlutterView就可以正常显示了。

  • 为何在之后版本要添加

  • 可能是FlutterVIew的渲染机制有了一些变化,在接收到原生端对应生命周期方法中发送的通知才会显示,具体原理还是要对比一下现在和以前的源码。

04.如何处理NA跳转flutter传参

4.1 NA如何传递参数给Flutter?

  • 如果需要在页面跳转时传递参数呢,如何在Flutter代码中获取到原生代码中的参数呢?其实很简单,只需要在route后面拼接上参数就可以了。

NavigationChannel navigationChannel = flutterEngine.getNavigationChannel();

String route = “yc?{“name”:“杨充”}”;

navigationChannel.setInitialRoute(route);

FlutterFragment.NewEngineFragmentBuilder fragmentBuilder = FlutterFragment.withNewEngine();

// 使用建造者模式构造出FlutterFragment对象,可以通过initialRoute()方法指定初始路由名称。

// 传递参数只需要在路由名称后面进行拼接。

String route = “yc?{“author”:“杨充”}”;

FlutterFragment.NewEngineFragmentBuilder initialRoute = fragmentBuilder.initialRoute(route);

FlutterFragment flutterFragment = initialRoute.build();

4.2 传递参数注意事项

  • 将路由名称和参数间用“?”隔开,就像浏览器中的url一样,参数使用了Json格式传递,原因就是方便Flutter端解析,而且对于一些复杂的数据,比如自定义对象,使用Json序列化也很好实现。

4.3 Flutter接收传递参数

Widget _widgetForRoute() {

//var route = window.defaultRouteName;

Map<String, dynamic> router = parseRouter();

var route = router[“route”];

switch (route) {

case ‘yc’:

return AboutMePage(title: ‘匹配到了,这个是flutter页面’,params : router);

}

}

Map<String, dynamic> parseRouter(){

String url = window.defaultRouteName;

// route名称,路由path路径名称

String route = url.indexOf(‘?’) == -1 ? url : url.substring(0, url.indexOf(‘?’));

// 参数Json字符串

String paramsJson = url.indexOf(‘?’) == -1 ? ‘{}’ : url.substring(url.indexOf(‘?’) + 1);

// 解析参数

Map<String, dynamic> params = json.decode(paramsJson);

params[“route”] = route;

return params;

}

  • 通过"?"将路由名称和参数分开,将参数对应的Json字符串解析为Map对象,需要导入dart:convert包。

05.思考遇到的几个问题分析

5.1 setInitialRoute生效问题

//第一种是生效的

private void addFlutterView() {

flutterEngine = new FlutterEngine(this);

binaryMessenger = flutterEngine.getDartExecutor().getBinaryMessenger();

flutterEngine.getNavigationChannel().setInitialRoute(“yc”);

flutterEngine.getDartExecutor().executeDartEntrypoint(

DartExecutor.DartEntrypoint.createDefault()

);

flutterView = new FlutterView(this);

FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(

ViewGroup.LayoutParams.MATCH_PARENT,

ViewGroup.LayoutParams.MATCH_PARENT);

rlFlutter.addView(flutterView, lp);

flutterView.attachToFlutterEngine(flutterEngine);

}

//第二种是不生效的

private void addFlutterView() {

flutterEngine = new FlutterEngine(this);

binaryMessenger = flutterEngine.getDartExecutor().getBinaryMessenger();

flutterEngine.getDartExecutor().executeDartEntrypoint(

DartExecutor.DartEntrypoint.createDefault()

);

flutterView = new FlutterView(this);

FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(

ViewGroup.LayoutParams.MATCH_PARENT,

ViewGroup.LayoutParams.MATCH_PARENT);

rlFlutter.addView(flutterView, lp);

// todo 放在这里不生效,思考为什么

flutterEngine.getNavigationChannel().setInitialRoute(“yc”);

flutterView.attachToFlutterEngine(flutterEngine);

// todo 放在这里不生效,思考为什么

// flutterEngine.getNavigationChannel().setInitialRoute(“yc”);

}

5.2 flutterFragment.getFlutterEngine()空指针

private void createChannel() {

// todo 调用下面这句话会空指针崩溃

FlutterEngine flutterEngine = flutterFragment.getFlutterEngine();

BinaryMessenger binaryMessenger = flutterEngine.getDartExecutor().getBinaryMessenger();

nativeChannel = new MethodChannel(binaryMessenger, METHOD_CHANNEL, StandardMethodCodec.INSTANCE);

}

//源码

@Nullable

public FlutterEngine getFlutterEngine() {

return delegate.getFlutterEngine();

}

  • 错误原因是这里的delegate为null

  • 翻看了一下源码,发现在FlutterFragment的onAttach()方法中会对delegate赋值,也就是说明此时没有执行onAttach()方法。

  • 问题分析

  • FlutterEngine的warm-up机制,这是一个耗时过程,因此FlutterFragment并不会立刻执行onAttach()方法,导致我们在Activity的onCreate()方法中直接使用FlutterFragment的getFlutterEngine()方法会抛出异常。

  • 如何解决问题

  • 想要解决问题,那就要等到FlutterFragment执行完onAttach()方法在调用getFlutterEngine。那么怎么去监听这个方法执行完呢?

06.Flutter页面关闭时Crash

Caused by: java.lang.RuntimeException: Cannot execute operation because FlutterJNI is not attached to native.

at io.flutter.embedding.engine.FlutterJNI.ensureAttachedToNative(FlutterJNI.java:259)

at io.flutter.embedding.engine.FlutterJNI.onSurfaceDestroyed(FlutterJNI.java:369)

at io.flutter.embedding.engine.renderer.FlutterRenderer.stopRenderingToSurface(FlutterRenderer.java:219)

at io.flutter.embedding.android.FlutterTextureView.disconnectSurfaceFromRenderer(FlutterTextureView.java:223)

at io.flutter.embedding.android.FlutterTextureView.access$400(FlutterTextureView.java:33)

at io.flutter.embedding.android.FlutterTextureView$1.onSurfaceTextureDestroyed(FlutterTextureView.java:84)

at android.view.TextureView.releaseSurfaceTexture(TextureView.java:261)

at android.view.TextureView.onDetachedFromWindowInternal(TextureView.java:232)

at android.view.View.dispatchDetachedFromWindow(View.java:22072)

at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:4747)

at android.view.ViewGroup.removeAllViewsInLayout(ViewGroup.java:6606)

at android.view.ViewGroup.removeAllViews(ViewGroup.java:6552)

at com.yc.fluttercontainer.FlutterEngineActivity.onDestroy(FlutterEngineActivity.java:292)

@Override

protected void onDestroy() {

super.onDestroy();

if (flutterEngine != null) {

flutterEngine.destroy();

}

mFlutterContainer.removeAllViews();

mFlutterView.removeAllViews();

if (mRenderSurface != null) {

// 打断内存泄漏

((FixFlutterTextureView) mRenderSurface).setSurfaceTextureListener(null);

}

}

07.Android引入flutter本质

  • 如何理解Android引入flutter页面

  • Android项目引入Flutter本质上是将Flutter编写的Widget嵌入到Activity中,类似于WebView,容器Activity相当于WebView,route相当于url,有两种方式FlutterView和FlutterFragment。页面间的跳转和传参可以借助MethodChannel来实现。

08.Flutter启动加载优化

8.1 分析flutter的启动页面流程

protected void onCreate(@Nullable Bundle savedInstanceState) {

this.switchLaunchThemeForNormalTheme();

super.onCreate(savedInstanceState);

this.lifecycle.handleLifecycleEvent(Event.ON_CREATE);

this.delegate = new FlutterActivityAndFragmentDelegate(this);

//创建绑定引擎等

delegate.onAttach(this);

//用于插件、框架恢复状态

delegate.onActivityCreated(savedInstanceState);

//设置窗口背景透明,隐藏 status bar

configureWindowForTransparency();

//从这里分析,这里是咱们的入口

setContentView(createFlutterView());

this.configureStatusBarForFullscreenFlutterExperience();

}

  • 然后接着往下看,会调用到FlutterActivityAndFragmentDelegate类的onCreateView方法

  • FlutterActivityAndFragmentDelegate类,flutter的初始化、启动等操作都是委托给它的。

  • 大致了解到,创建了一个FlutterSurfaceView 它继承自surfaceView(我们的flutter页面也是渲染在这个surface上的)。之后我们用它初始化一个FlutterView,

@NonNull

View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

Log.v(“FlutterActivityAndFragmentDelegate”, “Creating FlutterView.”);

this.ensureAlive();

if (this.host.getRenderMode() == RenderMode.surface) {

//flutter 应用在surface上显示,所以会进入到这里

FlutterSurfaceView flutterSurfaceView = new FlutterSurfaceView(this.host.getActivity(), this.host.getTransparencyMode() == TransparencyMode.transparent);

this.host.onFlutterSurfaceViewCreated(flutterSurfaceView);

//用flutterSurfaceView 初始化了一个 FlutterView

this.flutterView = new FlutterView(this.host.getActivity(), flutterSurfaceView);

} else {

//否则,应用在TextureView上显示

FlutterTextureView flutterTextureView = new FlutterTextureView(this.host.getActivity());

this.host.onFlutterTextureViewCreated(flutterTextureView);

//用flutterTextureView 初始化了一个 FlutterView

this.flutterView = new FlutterView(this.host.getActivity(), flutterTextureView);

}

this.flutterView.addOnFirstFrameRenderedListener(this.flutterUiDisplayListener);

//创建一个闪屏view - FlutterSplashView

this.flutterSplashView = new FlutterSplashView(this.host.getContext());

if (VERSION.SDK_INT >= 17) {

this.flutterSplashView.setId(View.generateViewId());

} else {

this.flutterSplashView.setId(486947586);

}

//显示闪屏页

this.flutterSplashView.displayFlutterViewWithSplash(this.flutterView, this.host.provideSplashScreen());

Log.v(“FlutterActivityAndFragmentDelegate”, “Attaching FlutterEngine to FlutterView.”);

//所创建surface 绑定到engine上

this.flutterView.attachToFlutterEngine(this.flutterEngine);

return this.flutterSplashView;

}

  • 随后我们再创建一个FlutterSplashView (继承FrameLayout)。重要看调用displayFlutterViewWithSplash()方法。

  • 看到这里可知,通过splashScreen(是个接口),具体看接口实现类,然后创建一个splashScreenView,最后添加到flutter的布局中

public void displayFlutterViewWithSplash(@NonNull FlutterView flutterView, @Nullable SplashScreen splashScreen) {

if (this.splashScreenView != null) {

this.removeView(this.splashScreenView);

}

//省略大量代码

this.flutterView = flutterView;

this.addView(flutterView);

this.splashScreen = splashScreen;

if (splashScreen != null) {

if (this.isSplashScreenNeededNow()) {

Log.v(TAG, “Showing splash screen UI.”);

this.splashScreenView = splashScreen.createSplashView(this.getContext(), this.splashScreenState);

//添加 splashScreenView

this.addView(this.splashScreenView);

flutterView.addOnFirstFrameRenderedListener(this.flutterUiDisplayListener);

}

}

}

  • 那么什么时候移除这个启动Splash布局呢?在创建FlutterSplashView时,添加了一个完成事件的监听,当flutter加载成功后才将它移除。

结尾

我还总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料分享给大家。
(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

image

een = splashScreen;

if (splashScreen != null) {

if (this.isSplashScreenNeededNow()) {

Log.v(TAG, “Showing splash screen UI.”);

this.splashScreenView = splashScreen.createSplashView(this.getContext(), this.splashScreenState);

//添加 splashScreenView

this.addView(this.splashScreenView);

flutterView.addOnFirstFrameRenderedListener(this.flutterUiDisplayListener);

}

}

}

  • 那么什么时候移除这个启动Splash布局呢?在创建FlutterSplashView时,添加了一个完成事件的监听,当flutter加载成功后才将它移除。

结尾

我还总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料分享给大家。
(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

[外链图片转存中…(img-l1Xi3xpd-1720097806139)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值