美文网首页
app中内嵌h5常见问题的一些思考

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

作者: 罄竹难输 | 来源:发表于2018-09-25 14:02 被阅读0次
    一.引子

    随着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页面打交道所引发的一些小的思考,如果对你有所帮助,欢迎你的点赞,也希望得到更多的想法交流。

    相关文章

      网友评论

          本文标题:app中内嵌h5常见问题的一些思考

          本文链接:https://www.haomeiwen.com/subject/hmxloftx.html