Flutter笔记——FlutterActivity

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类。

MainActivity

该FlutterActivity类是Flutter项目的页面入口,Flutter为Android项目提供了FlutterView和FlutterFragment作为展示页面,附着在Activity上面。而FlutterActivity使用的便是FlutterView。
那么,从开发的角度,接下来引出几个问题?

  1. 继承FlutterActivity只能默认进入Flutter设定的首页?
  2. Flutter页面的生命周期如何管理?
  3. Flutter页面与Android原生页面之间如何通讯?
  4. 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();
      }   
    
    1. FlutterView createFlutterView(Context context):该方法比较直观,就是生成一个Flutter的页面,供Activity展示。但是并没有在源码中找到引用它的地方。FlutterActivity的实现返回值是null。
    2. FlutterNativeView createFlutterNativeView():从字面意思是生成一个Flutter的原生View,但是并没有在源码中找到引用它的地方。FlutterActivity的实现返回值也是null。
    3. boolean retainFlutterNativeView():字面意思,保留Flutter原生页面。是一个boolean类型的值,但是并没有在源码中找到引用它的地方。FlutterActivity的实现返回值是false。
      通过查看FlutterActivity所继承的三个接口,我们并没有找到FlutterActivity中直接生成FlutterView的线索,只能从实例变量中查找。

FlutterActivityDelegate

在进行一番阅读之后,发现该委派类。在Android源码中有很多使用委派模式的地方,该处也算是一个。并且,在FlutterActivity中,FlutterActivityDelegate对象会跟随Activity的生命周期方法被调用同名方法。查看FlutterActivityDelegate的源码

  1. 构造方法
    public FlutterActivityDelegate(Activity activity, FlutterActivityDelegate.ViewFactory viewFactory) {
        this.activity = (Activity)Preconditions.checkNotNull(activity);
        this.viewFactory = (FlutterActivityDelegate.ViewFactory)Preconditions.checkNotNull(viewFactory);
    }
    
    其构造方法需要传入一个Activity对象,还有FlutterActivityDelegate.ViewFactory对象。但在上文已经发现FlutterActivityDelegate.ViewFactory的方法并无引用的地方,这里只需要着重关注Activity对象就好了。
  2. 同名生命周期方法:查看FlutterActivityDelegate类源码,该类定义了一些列对象Activity生命周期函数的同名函数。并分别运行在FlutterActivity类的对应生命周期中,由此可见Flutter页面的生命周期是由该委托类处理的
    • onCreate:该方法中实现了flutterView的生成。查看代码
        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);
                    }
                }
    
            }
        }
    
    从FlutterActivity实现的ViewFactory方法我们已经得知,传递给委托类FlutterActivityDelegate实例的ViewFactory并没有生成FlutterView可供FlutterActivityDelegate使用。所以只能继续查看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.
         * 

* 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. *

* 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. *

* 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. *

* 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. *

* 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 

void toPage(Context context, Class

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项目中。定义了routelogin的页面,只需要这样子打开即可

/**
 * 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的部分源码以后,得出了以上几个问题的答案。

  1. 打开FlutterActivity页面时候,通过Intent传入一个key为route的字符串值,就可以跳转到flutter项目中定义的对应route值的页面。
  2. 在FlutterActivityDelegate委托类里,实现了对FlutterActivity和Flutter页面生命周期的管理
  3. HelloFlutter——MethodChannel(Native&Flutter数据交互)
  4. FlutterView继承了SurfaceView,使用FlutterNativeView在Android和Flutter之间作为通讯的桥梁,之后调用Skia框架绘制页面。这也是其与RN和其他依赖于WebView的混合开发的框架不同的根源。
    本篇文章只是闭门造车的结果,如有任何错误很抱歉!请帮忙指出,多谢了

你可能感兴趣的:(Flutter笔记——FlutterActivity)