hybrid_stack_manager浅析

在混合工程中,flutter和原生之间的页面栈如何管理呢,由于我们采用的是hybrid_stack_manager,这里简单分析下整个流程

一、原生跳Flutter

1、使用

原生

HashMap<String,Object> m = new HashMap<>();
m.put("flutter", true);
HashMap<String,Object> p = new HashMap<>();
p.put("title", "测试");
XURLRouter.sharedInstance().openUrlWithQueryAndParams("hrd://test", m, p);

Flutter

Router.sharedInstance().routerWidgetHandler =
    ({RouterOption routeOption, Key key}) {
  print(routeOption.url);
  print(routeOption.query);
  print(routeOption.params);
  switch (routeOption.url) {
    case "hrd://test":
      return MaterialApp(...);
  }
  return null;
};
2、源码

XURLRouter是个单例,先看openUrlWithQueryAndParams方法

public boolean openUrlWithQueryAndParams(String url, HashMap<String, Object> query, HashMap<String, Object> params){
    Uri tmpUri = Uri.parse(url);
    if(!kOpenUrlPrefix.equals(tmpUri.getScheme()))
        return false;
    //会走这个if
    if(query!=null && query.containsKey("flutter") && (Boolean) query.get("flutter")){
        //这里可以看到原生跳flutter页面,其实是跳转到FlutterWrapperActivity,而且会把query、params两个HashMap带过去
        Intent intent = new Intent(mAppContext,FlutterWrapperActivity.class);
        intent.setData(Uri.parse(url));
        intent.setAction(Intent.ACTION_VIEW);
        intent.putExtra("query", query);
        intent.putExtra("params", params);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mAppContext.startActivity(intent);
        return true;
    }
    ...
    return false;
}

再看FlutterWrapperActivity的onCreate方法

@Override
protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.flutter_layout);
    checkIfAddFlutterView();
    fakeSnapImgView = (ImageView) findViewById(R.id.flutter_snap_imageview);
    fakeSnapImgView.setVisibility(View.GONE);
    //Process Intent Extra
    Intent intent = getIntent();
    Bundle bundle = intent.getExtras();
    Uri uri = intent.getData();
    //会走这个if
    if (uri != null) {
        //把获取到的url、query、params传到HybridStackManager
        HybridStackManager.sharedInstance().openUrlFromFlutter(uri.toString(), (HashMap) intent.getSerializableExtra("query"), (HashMap) intent.getSerializableExtra("params"));
    } else if (bundle != null) {
        HybridStackManager.sharedInstance().openUrlFromFlutter(intent.getStringExtra("url"), (HashMap) intent.getSerializableExtra("query"), (HashMap) intent.getSerializableExtra("params"));
    }
    ...
}

HybridStackManager是个单例,然后我们看openUrlFromFlutter方法

public void openUrlFromFlutter(String url, HashMap query, HashMap params) {
    //从这里我们看到参数是通过methodChannel传递给Flutter的,而assembleChanArgs方法会把url、query、parmas组装成一个新的HashMap
    HybridStackManager.sharedInstance().methodChannel.invokeMethod("openURLFromFlutter", assembleChanArgs(url, query, params));
}

接下来我们Flutter中的openURLFromFlutter方法,在Flutter端我们会先调用Router.sharedInstance(),该方法会调到setupMethodChannel方法,方法里边会设置MethodChannel的回调setMethodCallHandler

  void setupMethodChannel(){
    HybridStackManagerPlugin.hybridStackManagerPlugin
        .setMethodCallHandler((MethodCall methodCall)async{
      String method = methodCall.method;
      //这里我们看到了openURLFromFlutter方法,即原生会调到
      if (method == "openURLFromFlutter") {
        //获取组装后的Map
        Map args = methodCall.arguments;
        if (args != null) {
          bool animated = (args["animated"] == 1);
          //参数传到pushPageWithOptionsFromFlutter方法
          Router.sharedInstance().pushPageWithOptionsFromFlutter(
              routeOption: new RouterOption(
                  url: args["url"],
                  query: args["query"],
                  params: args["params"]),
              animated: animated ?? false);
        }
      } else if (method == "popToRoot") {
        Router.sharedInstance().popToRoot();
      } else if (method == "popToRouteNamed") {
        Router.sharedInstance().popToRouteNamed(methodCall.arguments);
      } else if (method == "popRouteNamed") {
        Router.sharedInstance().popRouteNamed(methodCall.arguments);
      }
      else if(method == "fetchSnapshot"){
         ...
      }
    });
  }

我们看Router的pushPageWithOptionsFromFlutter方法

  pushPageWithOptionsFromFlutter({RouterOption routeOption, bool animated}) {
    //这个pageFromOption方法肯关键,它会返回一个Widget接下来会详细看这个方法
    Widget page =
        Router.sharedInstance().pageFromOption(routeOption: routeOption);
    if (page != null) {
      //会走这个if
      GlobalKey boundaryKey = new GlobalKey();
      //XMaterialPageRoute是MaterialPageRoute的子类
      XMaterialPageRoute pageRoute = new XMaterialPageRoute(
          settings: new RouteSettings(name: routeOption.userInfo),
          animated: animated,
          boundaryKey: boundaryKey,
          builder: (BuildContext context) {
            return new RepaintBoundary(key:boundaryKey,child: page);
          });
      //前边我们知道原生跳转flutter其实是跳转了一个FlutterWrapperActivity,然后把参数通过methodChannel传递给flutter,这里可以看出flutter这一端的页面栈其实也是通过Navigator管理的
      Navigator.of(globalKeyForRouter.currentContext).push(pageRoute);
      HybridStackManagerPlugin.hybridStackManagerPlugin
          .updateCurFlutterRoute(routeOption.userInfo);
    } else {
      HybridStackManagerPlugin.hybridStackManagerPlugin.openUrlFromNative(
          url: routeOption.url,
          query: routeOption.query,
          params: routeOption.params);
    }
    NavigatorState navState = Navigator.of(globalKeyForRouter.currentContext);
    List<Route<dynamic>> navHistory = navState.history;
  }

接下来我们看Router的pageFromOption方法,先回忆下我们之前会给routerWidgetHandler赋值

//给routerWidgetHandler赋值
Router.sharedInstance().routerWidgetHandler =
    ({RouterOption routeOption, Key key}) {
  print(routeOption.url);
  print(routeOption.query);
  print(routeOption.params);
  switch (routeOption.url) {
    case "hrd://test":
      //返回一个Widget
      return MaterialApp(...);
  }
  return null;
};
typedef Widget FlutterWidgetHandler({RouterOption routeOption, Key key});
//会给routerWidgetHandler赋值
FlutterWidgetHandler routerWidgetHandler;

pageFromOption({RouterOption routeOption, Key key}) {
  try {
    currentPageUrl = routeOption.url + "?" + converUrl(routeOption.query);
  } catch (e) {}
  routeOption.userInfo = Utils.generateUniquePageName(routeOption.url);
  if (routerWidgetHandler != null)
    //回调routerWidgetHandler方法并且返回Widget
    return routerWidgetHandler(routeOption: routeOption, key: key);
}
3、总结
  • 原生跳转flutter其实是跳转到FlutterWrapperActivity
  • 在FlutterWrapperActivity中参数会通过MethodChannel传递给flutter
  • 在flutter这一端的页面栈其实也是通过Navigator管理的

二、Flutter跳原生

1、使用

Flutter

HybridStackManagerPlugin.hybridStackManagerPlugin
    .openUrlFromNative(
        url: "hrd://test",
        query: {"title": "测试"});

原生

XURLRouter.sharedInstance().setNativeRouterHandler((url, query, params) -> {
    switch (url) {
        case "hrd://test":
            return TestActivity.class;
    }
    return null;
});
2、源码

先看HybridStackManagerPlugin的openUrlFromNative方法

openUrlFromNative({String url, Map query, Map params, bool animated}) {
  //会通过MethodChannel把参数传给原生
  _channel.invokeMethod("openUrlFromNative", {
    "url": url ?? "",
    "query": (query ?? {}),
    "params": (params ?? {}),
    "animated": animated ?? true
  });
}

接着看原生中的openUrlFromNative

@Override
public void onMethodCall(MethodCall call, Result result) {
    if (call.method.equals("openUrlFromNative")) {
        if (curFlutterActivity != null && curFlutterActivity.isActive()) {
            HashMap openUrlInfo = (HashMap)call.arguments;
            String url = (String)openUrlInfo.get("url");
            HashMap query = (HashMap)openUrlInfo.get("query");
            HashMap params = (HashMap)openUrlInfo.get("params");
            //将获取的参数组装成字符串
            String concatUrl = concatUrl(url, query, params);
            //调用FlutterWrapperActivity的openUrl方法
            curFlutterActivity.openUrl(concatUrl, params);
        }
        result.success("OK");
    } else if (call.method.equals("getMainEntryParams")) {
        ...
    }
}

我们看FlutterWrapperActivity的openUrl方法

public void openUrl(String url, HashMap params) {
    HybridStackManager.sharedInstance().curFlutterActivity = null;
    if (url.contains("flutter=true")) {
        //flutter跳flutter需要设置flutter为true
        Intent intent = new Intent(FlutterWrapperActivity.this, FlutterWrapperActivity.class);
        intent.setAction(Intent.ACTION_RUN);
        intent.setData(Uri.parse(url));
        intent.putExtra("params", params);
        this.innerStartActivity(intent, true);
    } else {
        //会走这里
        Uri tmpUri = Uri.parse(url);
        String tmpUrl = String.format("%s://%s", tmpUri.getScheme(), tmpUri.getHost());
        HashMap query = new HashMap();
        for (String key : tmpUri.getQueryParameterNames()) {
            query.put(key, tmpUri.getQueryParameter(key));
        }
        //调XURLRouter的openUrlWithQueryAndParams方法
        XURLRouter.sharedInstance().openUrlWithQueryAndParams(tmpUrl, query, null);
        saveFinishSnapshot(false);
    }
}
public boolean openUrlWithQueryAndParams(String url, HashMap<String, Object> query, HashMap<String, Object> params){
    Uri tmpUri = Uri.parse(url);
    if(!kOpenUrlPrefix.equals(tmpUri.getScheme()))
        return false;
    if(query!=null && query.containsKey("flutter") && (Boolean) query.get("flutter")){
        //原生调会走这里,需要设置flutter为true
        Intent intent = new Intent(mAppContext,FlutterWrapperActivity.class);
        intent.setData(Uri.parse(url));
        intent.setAction(Intent.ACTION_VIEW);
        intent.putExtra("query", query);
        intent.putExtra("params", params);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mAppContext.startActivity(intent);
        return true;
    }
    if(mNativeRouterHandler!=null) {
        //这里会回调XURLRouterHandler的openUrlWithQueryAndParams方法并获取到一个Class,最后通过startActivity进行页面跳转
        Class activityCls =  mNativeRouterHandler.openUrlWithQueryAndParams(url, query, params);
        Intent intent = new Intent(mAppContext,activityCls);
        intent.setData(Uri.parse(url));
        intent.setAction(Intent.ACTION_VIEW);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (query != null) {
            for (String key : query.keySet()) {
                intent.putExtra(key, String.valueOf(query.get(key)));
            }
        }
        if (params != null) {
            for (String key : params.keySet()) {
                intent.putExtra(key, String.valueOf(params.get(key)));
            }
        }
        mAppContext.startActivity(intent);
    }
    return false;
}

我们在使用的时候会给上边的mNativeRouterHandler赋值

public void setNativeRouterHandler(XURLRouterHandler handler){
    mNativeRouterHandler = handler;
}

XURLRouter.sharedInstance().setNativeRouterHandler((url, query, params) -> {
    switch (url) {
        case "hrd://test":
            return TestActivity.class;
    }
    return null;
});
3、总结
  • flutter跳原生其实是通过MethodChannel传值给原生
  • 原生这边接收到参数后会返回一个Class
  • 最终也是通过startActivity实现页面跳转

三、参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容