2018-12-21修复,FlutterActivity的页面选择错误修改
自谷歌发布Flutter release版本几天后才开始学习Flutter,实在惭愧。在了解完一些基础知识之后开始尝试将编写的简单Flutter module打包进Android项目中。本文章将尝试过程中遇到的一些问题和笔记记录下来。
本篇文章只是闭门造车的结果,如有任何错误很抱歉!请帮忙指出,多谢了
Android项目依赖Flutter项目
对于已有的Android项目来说,将所有页面都换成flutter页面不太现实,只能从一些简单的页面入手逐个替换。
Flutter项目跟Android工程根文件夹是同级的,它不同于普通的Android module存在于Android工程根目录下。在AndroidStudio中创建Flutter module,也并不会将该项目放到Android项目目录中,而是默认选择Android项目根目录的同级目录下。
在依赖Flutter module的时候,首先需要在项目的setting.gradle
加入如下依赖
include ':app'
//加入下面配置
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'flutter_module/.android/include_flutter.groovy'
))
以上配置的Flutter module的位置是出于Android根目录同级目录下,如果Flutter module的路径不同需要另外设置File函数的参数。编译项目,会在Android项目下生成名为flutter的module,正常来说该module不需要去修改代码,只需要在app的build.gradle中依赖该fluttermodule即可。
dependencies {
...
// 加入下面配置
implementation project(':flutter')
}
自此,完成Android项目对Flutter项目的依赖
FlutterActivity
除了FlutterActivity页面,也有FlutterFragmentActivity页面,除了基类不同,其他实现均一致。
在创建的Flutter项目的.andorid module中,只有一个类,那就是MainActivity类。
其继承自FlutterActivity,运行该Flutter工程时,Android项目的入口就是该MainActivity类。
该FlutterActivity类是Flutter项目的页面入口,Flutter为Android项目提供了FlutterView和FlutterFragment作为展示页面,附着在Activity上面。而FlutterActivity使用的便是FlutterView。
那么,从开发的角度,接下来引出几个问题?
- 继承FlutterActivity只能默认进入Flutter设定的首页?
- Flutter页面的生命周期如何管理?
- Flutter页面与Android原生页面之间如何通讯?
- Flutter页面是如何绘制的?
查看源码
查看FlutterActivity的类声明,该类实现了三个接口
public class FlutterActivity extends Activity implements
Provider, PluginRegistry, ViewFactory {
...
}
这三个接口作用如下
- Provider:只有一个简单的方法,那就是getFlutterView()返回当前Activity中的Flutter页面
- PluginRegistry:插件注册相关的类,以后的文章再详细讲述
- ViewFactory:该接口有三个方法,分别是
public interface ViewFactory { FlutterView createFlutterView(Context var1); FlutterNativeView createFlutterNativeView(); boolean retainFlutterNativeView(); }
- FlutterView createFlutterView(Context context):该方法比较直观,就是生成一个Flutter的页面,供Activity展示。但是并没有在源码中找到引用它的地方。FlutterActivity的实现返回值是null。
- FlutterNativeView createFlutterNativeView():从字面意思是生成一个Flutter的原生View,但是并没有在源码中找到引用它的地方。FlutterActivity的实现返回值也是null。
- boolean retainFlutterNativeView():字面意思,保留Flutter原生页面。是一个boolean类型的值,但是并没有在源码中找到引用它的地方。FlutterActivity的实现返回值是false。
通过查看FlutterActivity所继承的三个接口,我们并没有找到FlutterActivity中直接生成FlutterView的线索,只能从实例变量中查找。
FlutterActivityDelegate
在进行一番阅读之后,发现该委派类。在Android源码中有很多使用委派模式的地方,该处也算是一个。并且,在FlutterActivity中,FlutterActivityDelegate对象会跟随Activity的生命周期方法被调用同名方法。查看FlutterActivityDelegate的源码
- 构造方法
其构造方法需要传入一个Activity对象,还有FlutterActivityDelegate.ViewFactory对象。但在上文已经发现FlutterActivityDelegate.ViewFactory的方法并无引用的地方,这里只需要着重关注Activity对象就好了。public FlutterActivityDelegate(Activity activity, FlutterActivityDelegate.ViewFactory viewFactory) { this.activity = (Activity)Preconditions.checkNotNull(activity); this.viewFactory = (FlutterActivityDelegate.ViewFactory)Preconditions.checkNotNull(viewFactory); }
- 同名生命周期方法:查看FlutterActivityDelegate类源码,该类定义了一些列对象Activity生命周期函数的同名函数。并分别运行在FlutterActivity类的对应生命周期中,由此可见Flutter页面的生命周期是由该委托类处理的
- onCreate:该方法中实现了flutterView的生成。查看代码
从FlutterActivity实现的ViewFactory方法我们已经得知,传递给委托类FlutterActivityDelegate实例的ViewFactory并没有生成FlutterView可供FlutterActivityDelegate使用。所以只能继续查看public void onCreate(Bundle savedInstanceState) { ... 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(); } } if (!this.loadIntent(this.activity.getIntent())) { if (!this.flutterView.getFlutterNativeView().isApplicationRunning()) { String appBundlePath = FlutterMain.findAppBundlePath(this.activity.getApplicationContext()); if (appBundlePath != null) { FlutterRunArguments arguments = new FlutterRunArguments(); arguments.bundlePath = appBundlePath; arguments.entrypoint = "main"; this.flutterView.runFromBundle(arguments); } } } }
this.flutterView = new FlutterView(this.activity, (AttributeSet)null, nativeView)
之后的代码。
这边需要注意的是,即使getFlutterView返回具体的FlutterView对象,Activity也不会去将返回的view设置到页面内容中的。而是会通过loadIntent
方法去读取intent中传递过来的route的值,去跳转到flutter项目中设定的route对应页面
FlutterView
public class FlutterView extends SurfaceView implements BinaryMessenger, TextureRegistry, AccessibilityStateChangeListener {
public FlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView) {
super(context, attrs);
...
Activity activity = (Activity)this.getContext();
if (nativeView == null) {
this.mNativeView = new FlutterNativeView(activity.getApplicationContext());
} else {
this.mNativeView = nativeView;
}
this.mNativeView.attachViewAndActivity(this, activity);
...
}
}
我们可以看到FlutterView继承自SurfaceView,在其构造方法中。如果传递的FlutterNativeView如果为空,那将会重新创建一个默认的FlutterNativeView。接着看
public class FlutterNativeView implements BinaryMessenger {
public FlutterNativeView(Context context) {
this(context, false);
}
public FlutterNativeView(Context context, boolean isBackgroundView) {
this.mNextReplyId = 1;
this.mPendingReplies = new HashMap();
this.mContext = context;
this.mPluginRegistry = new FlutterPluginRegistry(this, context);
this.attach(this, isBackgroundView);
this.assertAttached();
this.mMessageHandlers = new HashMap();
}
}
在这里我们可以看到FlutterNativeView实现了BinaryMessenger接口,而BinaryMessenger是一个数据信息交流对象,其接口声明如下
public interface BinaryMessenger {
/**
*Sends a binary message to the Flutter application.
*Parameters:
*channel - the name String of the logical channel used for the message.
*message - the message payload, a direct-allocated ByteBuffer with the message bytes between position zero and current position, or null.
*/
void send(String var1, ByteBuffer var2);
/**
* Sends a binary message to the Flutter application, optionally expecting a reply.
* Any uncaught exception thrown by the reply callback will be caught and logged.
* <p>
* Parameters:
* channel - the name String of the logical channel used for the message.
* message - the message payload, a direct-allocated ByteBuffer with the message bytes between position zero and current position, or null.
* callback - a BinaryMessenger.BinaryReply callback invoked when the Flutter application responds to the message, possibly null.
*/
void send(String var1, ByteBuffer var2, BinaryMessenger.BinaryReply var3);
/**
* Registers a handler to be invoked when the Flutter application sends a message to its host platform.
* Registration overwrites any previous registration for the same channel name. Use a null handler to deregister.
* <p>
* If no handler has been registered for a particular channel, any incoming message on that channel will be handled silently by sending a null reply.
* <p>
* Parameters:
* channel - the name String of the channel.
* handler - a BinaryMessenger.BinaryMessageHandler to be invoked on incoming messages, or null.
*/
void setMessageHandler(String var1, BinaryMessenger.BinaryMessageHandler var2);
/**
* Binary message reply callback. Used to submit a reply to an incoming message from Flutter.
* Also used in the dual capacity to handle a reply received from Flutter after sending a message.
*/
public interface BinaryReply {
/**
* Handles the specified reply.
* Parameters:
* reply - the reply payload, a direct-allocated ByteBuffer or null.
* Senders of outgoing replies must place the reply bytes between position zero and current position.
* Reply receivers can read from the buffer directly.
*/
void reply(ByteBuffer var1);
}
/**
* Handler for incoming binary messages from Flutter.
*/
public interface BinaryMessageHandler {
/**
* Handles the specified message.
* Handler implementations must reply to all incoming messages,
* by submitting a single reply message to the given BinaryMessenger.BinaryReply.
* Failure to do so will result in lingering Flutter reply handlers. The reply may be submitted asynchronously.
* <p>
* Any uncaught exception thrown by this method will be caught by the messenger implementation and logged,
* and a null reply message will be sent back to Flutter.
* <p>
* Parameters:
* message - the message ByteBuffer payload, possibly null.
* reply - A BinaryMessenger.BinaryReply used for submitting a reply back to Flutter.
*/
void onMessage(ByteBuffer var1, BinaryMessenger.BinaryReply var2);
}
}
要命的是Flutter框架在Android中还没有注释可以看,只能从官网查看文档。
这是一个用于在Flutter和Native之间交换数据的接口类,已知FlutterView已经实现了SurfaceView,flutterNativeView负责FlutterView和Flutter之间的通讯,再使用Skia绘制页面。
loadIntent
即使我们实现了getFlutterView方法
,FlutterActivityDelegate类也不会将该flutterView 添加到Activity的content中的,而是通过loadIntent
方法去打开对应的页面。loadIntent
的代码如下
private boolean loadIntent(Intent intent) {
String action = intent.getAction();
if ("android.intent.action.RUN".equals(action)) {
String route = intent.getStringExtra("route");
String appBundlePath = intent.getDataString();
if (appBundlePath == null) {
appBundlePath = FlutterMain.findAppBundlePath(this.activity.getApplicationContext());
}
if (route != null) {
this.flutterView.setInitialRoute(route);
}
if (!this.flutterView.getFlutterNativeView().isApplicationRunning()) {
FlutterRunArguments args = new FlutterRunArguments();
args.bundlePath = appBundlePath;
args.entrypoint = "main";
this.flutterView.runFromBundle(args);
}
return true;
} else {
return false;
}
}
可以得出,只要打开FlutterActivity页面时候,通过Intent传入一个key为route
的字符串值,就可以跳转到flutter项目中定义的对应route值的页面了。
如果我们需要自己封装带有自定义属性和动作的FlutterFragmentActivity的子类,可以这样子定义
/**
* author: wangzh
* create: 2018/12/20 19:46
* description: flutter的基类
* version: 1.0
*/
public abstract class BaseFlutterActivity extends FlutterFragmentActivity implements LifecycleOwner {
protected Lifecycle mLifecycle;
private static final String ROUTE_ACTION ="android.intent.action.RUN";
@Override
protected void onCreate(Bundle savedInstanceState) {
getIntent().putExtra("route", getTargetPage());
mLifecycle = new LifecycleRegistry(this);
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
public static <P extends BaseFlutterActivity> void toPage(Context context, Class<P> target) {
Intent intent = new Intent(context, target);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setAction(ROUTE_ACTION);
intent.addCategory(Intent.CATEGORY_DEFAULT);
context.startActivity(intent);
}
protected abstract String getTargetPage();
@NonNull
@Override
public Lifecycle getLifecycle() {
return mLifecycle;
}
}
加入我们在Flutter项目中。定义了route
为login
的页面,只需要这样子打开即可
/**
* author: wangzh
* create: 2018/12/20 19:44
* description: 登录页面
* version: 1.0
*/
public class LoginActivity extends BaseFlutterActivity {
@Override
protected String getTargetPage() {
return "login";
}
}
//打开flutter中的loginPage
BaseFlutterActivity.toPage(getContext(), LoginActivity.class);
总结
在阅读完FlutterActivity的部分源码以后,得出了以上几个问题的答案。
- 打开FlutterActivity页面时候,通过Intent传入一个key为
route
的字符串值,就可以跳转到flutter项目中定义的对应route值的页面。 - 在FlutterActivityDelegate委托类里,实现了对FlutterActivity和Flutter页面生命周期的管理
- HelloFlutter——MethodChannel(Native&Flutter数据交互)
- FlutterView继承了SurfaceView,使用FlutterNativeView在Android和Flutter之间作为通讯的桥梁,之后调用Skia框架绘制页面。这也是其与RN和其他依赖于WebView的混合开发的框架不同的根源。
本篇文章只是闭门造车的结果,如有任何错误很抱歉!请帮忙指出,多谢了