app中内嵌h5常见问题的一些思考

一.引子

随着h5技术这几年的蓬勃发展,大多数移动端开发工程师没少和它打过交道。h5的好处我们就不多提了,相信你一定知道它的优点(开发快速,随时发布更新),加载消耗内存,体验比不上native的大众问题我们这里也不去讨论,技术优化我们也不去详解(单进程,预加载,缓存...)。Stop!What?那我们在这里要讲什么?我相信你大抵是经历过这么一个过程:app不断快速发展、迭代,内嵌h5页越来越多,承担的业务越来越复杂,相应的App内h5页面管理也越来越混乱。

二.目前的暴露的问题

下面我由我所经手项目暴露的一些问题来概括上述叙说。

1. 布局显示问题。

h5页面中原生的布局通常只有一个Titlebar,所以布局显示问题,大体上可以归纳为titlebar的问题。


正常显示

错误显示

上面两个页面都是内嵌h5订单页面,bug产生原因也不复杂。在最初版本app中,订单页入口处是课程详情页,最初协议的结果就是移动端不提供titlebar,加载一个全屏的h5页面,由h5自己实现titlebar的导航工作。后来随着app业务发展,App内有了运营活动,在banner或者其他入口进入的h5页面是由原生提供导航条。而在用户进行下单操作时,h5页面进行了页面复用,Duang ~~,双下巴问题就这样产生了。(是不是浓浓山寨风扑来?)

当然,除了上述问题,还一个很突出的案例是在Titlebar上显示一个菜单按钮。根据当前不同的业务场景,显示不同的菜单,点击菜单进行相应的操作。比如h5页面A具有分享功能,在TitleBar显示一个分享按钮,点击按钮可以分享链接。h5页面B具有一个意见反馈功能,在TitleBar显示一个反馈按钮,点击按钮可以提交意见反馈。这类功能算是App比较常用的功能。试问,当你面临这些需求的时候,你会怎么做呢?(PS:App的WebView页面只是h5页面的容器,不要去做任何业务相关的代码)

2. 业务流程问题(业务支持能力)

在客户端开发中,页面都是以栈的方式进行管理的,每新打开一个页面,进行一次入栈操作,退出页面时,进行一次出栈操作。这是操作系统默认维护的,我们在软件开发过程中通常不需特殊去处理它。但是,产品的有些业务需求对于页面跳转路径会有相关要求,比如某种情况直接返回首页。或者页面A—>B—>C,C中要求直接返回页面A,这些需求相信每一位客户端开发者都有遇到,对于Android,通常是借助Activity的启动模式或者Intent_Flag加以实现。

同样的,WebView同样使用栈来维护加载过的路径,每打开一个链接,会进行一次入栈操作,我们通常在对WebView页面返回时,会判断是否包含历史路径,存在历史路径返回历史加载路径操作(出栈操作)。

Android代码如下:

@Override
public void onBackPressed() {
    if (mWebView.canGoBack()) {
        mWebView.goBack();
        return;
    }
    super.onBackPressed();
}

我们知道,产品需求是不区分移动端和h5的,h5作为内嵌app内的一部分,也面临着同样业务路径跳转的要求。而对于内嵌的h5来说,只对应我们Android端的一个WebView页面,更加难以实现产品的需求,在当前条件下App内不具有处理这类问题能力,产品不得不在需求上做了某些妥协。所以这同样值得引发我们思考。

3. 编码影响

这点在产品层面看可能无关痛痒,但是对开发人员来说却不得不说是个灾难。灾难在哪里?我们都知道,做软件开发,对外暴露接口应该尽量简单、明了。要让团队其他人员方便调用不会出现歧义,减少开发维护成本。但是如果titlebar使用原生和h5的不确定性,title是业务控制还是h5控制不确定性,app提供的api是这样的:

   public class WebIntentHelper {
       
       private WebIntentHelper() {
           throw new IllegalStateException("不能初始化");
       }

       /**
        * 开启一个内嵌web页面,默认包含titlebar
        *
        * @param context 上下文
        * @param url     h5链接
        */
       public static void startWeb(Context context, String url) {
           startWeb(context, url, true);
       }

       /**
        * 开启一个内嵌web页面
        *
        * @param context     上下文
        * @param url         h5链接
        * @param enableTitle 是否有标题
        */
       public static void startWeb(Context context, String url, boolean enableTitle) {
           startWeb(context, url, enableTitle, BaseWebActivity.class);
       }

       /**
        * 开启一个内置web页面,使用本地titlebar,传入的title作为标题
        *
        * @param context 上下文
        * @param url     h5链接
        * @param title   h5显示的标题
        */
       public static void startWeb(Context context, String url, String title) {
           startWeb(context, url, title, BaseWebActivity.class);
       }


       /**
        * 开启一个内嵌web页面,默认包含titlebar
        *
        * @param context 上下文
        * @param url     h5链接
        * @param clz     继承BaseWebActivity的页面 Class
        */
       public static void startWeb(Context context, String url, Class<? extends BaseWebActivity> clz) {
           startWeb(context, url, true, clz);
       }

       /**
        * 开启一个内嵌web页面
        *
        * @param context     上下文
        * @param url         h5链接
        * @param enableTitle 是否有标题
        * @param clz         继承BaseWebActivity的页面 Class
        */
       public static void startWeb(Context context, String url, boolean enableTitle, Class<? extends BaseWebActivity> clz) {
           Intent intent = new Intent(context, clz);
           intent.putExtra(BaseWebActivity.EXTRA_KEY_URL, url);
           intent.putExtra(BaseWebActivity.EXTRA_KEY_NEED_TITLE, enableTitle);
           context.startActivity(intent);
       }


       /**
        * 开启一个内置web页面,使用本地titlebar,传入的title作为标题
        *
        * @param context 上下文
        * @param url     h5链接
        * @param title   h5显示的标题
        */
       public static void startWeb(Context context, String url, String title, Class<? extends BaseWebActivity> clz) {
           Intent intent = new Intent(context, clz);
           intent.putExtra(BaseWebActivity.EXTRA_KEY_URL, url);
           intent.putExtra(BaseWebActivity.EXTRA_KEY_NEED_TITLE, true);
           intent.putExtra(BaseWebActivity.EXTRA_KEY_TITLE, title);
           context.startActivity(intent);
       }
}

是不是感觉还好?只有有一点小乱?好吧,那我可以告诉你,这还是在不考虑大数据埋点的情况下提供的Api,如果当初再把大数据埋点添加进来会多少呢?答案很简单,当前数量 * 2。如果再填入其他的东西呢?那么答案还是很简单,再用当前 数量 * 2。面对这样的api,你还会去想调用它么?

三.What to do?

前面问题已经抛出,现在我们应该埋坑了。如何解决上面这些问题,其实很简单,我们只需要理出h5和native的相互关系就可以想出有效的解决办法。h5作为展现在用户面前的页面,承载着用户的交互与业务需求,无疑在体系内起着主导作用。而native客户端呢?客户端作为h5的一个载体,在体系内起辅导作用。可以理解为客户端只为h5提供一些公共功能,不做任何业务逻辑操作,而所有业务交互,逻辑控制需要h5页面自己去处理。这样才可以支持起更强大的业务功能。具体做法如下:

  1. 对于titlebar(或其他布局),无特殊需求建议全部使用客户端的布局。客户端向h5提供设置标题,设置标题右侧菜单等的功能。h5页面根据当前承担的业务功能动态的去跟原生通信,指定当前titlebar(或其他布局)状态。
  2. 当原生titlebar(或其他布局)有任何事件触发,将事件消息转发给h5页面,h5页面接收到消息根据业务做具体操作。包含titlebar右侧菜单按钮,titlebar左侧的返回按钮(或android设备的物理返回键)。
先看一下下面2个场景解决方式:
1. h5页面控制状态栏菜单图示如下:
显示菜单
2. h5页面返回流程控制图示如下:
客户端返回逻辑
可以看出,解决问题的核心就在于h5和native的通信,客户端作为h5的载体,不负责逻辑处理,将所有事交由h5去处理。而h5页面作为业务承载方,除了负责页面内的业务逻辑,还要做的就是控制客户端的视图状态。
四.关于jsbridge

如何进行h5和native的通信呢?答案是使用jsbridge。关于jsbridge的实现在这里不做详细阐述,网上有很多文章有详细介绍。这里只提以下几点:1. jsbridge库设计根本在于方便本地与h5进行通信,所以具体协议要根据公司具体情况去制订。

  1. 设计jsbridge时候要考虑解耦,在api尽量方便使用同时,不要掺杂业务逻辑。
  2. 如果公司项目已经组件化,设计jsbridge库要考虑组件化,尽量使jsbridge具有更加强大的业务支撑能力。
五.总结

​好了,就到这里了,本篇文章并没有向你阐述很牛逼的技术,只是作者个人最近经常跟app的h5页面打交道所引发的一些小的思考,如果对你有所帮助,欢迎你的点赞,也希望得到更多的想法交流。

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,004评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,710评论 2 59
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,094评论 1 32
  • 从小就喜欢武侠片,尤其是徐克的,一刀一剑、一景一物、一恶一善,江湖情仇尽显,好不快哉。恨自己不是生在古代,不然就可...
    崇德先生阅读 779评论 1 5
  • 2017.03.08 老婆你此身眼不浊, 万绿丛中选中我。 共度一生时相伴, ...
    8b7535d983e2阅读 437评论 1 1