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.
         * <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项目中。定义了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的混合开发的框架不同的根源。
    本篇文章只是闭门造车的结果,如有任何错误很抱歉!请帮忙指出,多谢了
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,105评论 0 10
  • 本文重点介绍应用程序的启动过程,应用程序的启动过程实际上就是应用程序中的默认Activity的启动过程,本文将详细...
    天宇sonny阅读 369评论 1 0
  • 女儿两岁了,最近休息带她时,发现一个状况重复发生:当有其他小朋友抢走她手里的玩具时,女儿不会有任何反抗,而是噘着嘴...
    多多余儿阅读 388评论 0 0
  • Chrome应该是很多人都喜欢用的浏览器,特别是玩前端的同学。Chrome上的各种插件也是很好用的。对于Chrom...
    吃土的小此方阅读 532评论 0 6
  • 只打甜蜜区的球——精进自己的可执行,可以做好的方面。组织中补短板是为了组织更加的好,但是个人成长的话更多的需要发展...
    B型血兔子007阅读 174评论 0 0