天天看点

Hybrid App(混合模式移动应用)

Hybrid App(混合模式移动应用)是指介于web-app、native-app这两者之间的app,兼具“Native App良好用户交互体验的优势”和“Web App跨平台开发的优势”。

简介

“云”时代的来临正在改变App和运营团队之间的关系,一场不能避免的变革正在进行。鉴于移动终端的局限性,移动终端上的APP由本地化应用(Native App),到基于WEB的应用Web App,再到混合型应用(Hybrid APP),这一连串的变化都源于技术的更新和市场的需要[1] 。

Hybrid App是指介于web-app、native-app这两者之间的app,它虽然看上去是一个Native App,但只有一个UI WebView,里面访问的是一个Web App,比如街旁网最开始的应用就是包了个客户端的壳,其实里面是HTML5的网页,后来才推出真正的原生应用。再彻底一点的,如掌上百度和淘宝客户端Android版,走的也是Hybrid App的路线,不过掌上百度里面封装的不是WebView,而是自己的浏览内核,所以体验上更像客户端,更高效。

汽车有混合动力Hybrid,移动应用同样也有混合模式。Hybrid App(混合模式移动应用)兼具“Native App良好用户交互体验的优势”和“Web App跨平台开发的优势”。很多人不知道市场上一些主流移动应用都是基于Hybrid App的方式开发,比如国外有Facebook、国内有百度搜索等。但究竟什么是Hybrid App?如何定义?

综合一下就是:“Hybrid App同时使用网页语言与程序语言开发,通过应用商店区分移动操作系统分发,用户需要安装使用的移动应用”。总体特性更接近Native App但是和Web App区别较大。只是因为同时使用了网页语言编码,所以开发成本和难度比Native App要小很多。因此说,Hybrid App兼具了Native App的所有优势,也兼具了Web App使用HTML5跨平台开发低成本的优势

兴起原因编辑

Hybrid App的兴起是现阶段移动互联网产业的一种偶然。移动互联网的热潮刮起后,众多公司前赴后继的进入。但是很快发现移动应用的开发人员太少,所以导致疯狂的人才争夺。市场机制下移动应用开发人才的待遇扶摇直上,最终变成众多企业无法负担养一个具备跨平台开发能力的专业移动应用开发团队。而HTML5的出现让Web App露出曙光,HTML5开发移动应用的跨平台和廉价优势让众多想进入移动互联网领域的公司开始心动。可是当下基于HTML5的Web App更是雾里看花,在用户入口习惯、分发渠道和应用体验这三个核心问题没解决之前,Web App也很难得以爆发。正是在这样是机缘巧合下,基于HTML5低成本跨平台开发优势又兼具Native App特质的Hybrid App技术杀入混战,并且很快吸引了众人的目光。大幅的降低了移动应用的开发成本,可以通过现有应用商店模式发行,在用户桌面形成独立入口等等这些,让Hybrid App成为解决移动应用开发困境不错的选择,也成为现阶段Web App的代言人。Hybrid App像刺客一样,在Native App和Web App混战之时,偶然间的在移动应用开发领域占有了一席之地。

分类编辑

Hybrid App按网页语言与程序语言的混合,通常分为三种类型:多View混合型,单View混合型,Web主体型。

多View混合型

即Native View和Web View独立展示,交替出现。2012年常见的Hybrid App是Native View与WebView交替的场景出现。这种应用混合逻辑相对简单。即在需要的时候,将WebView当成一个独立的View(Activity)运行起来,在WebView内完成相关的展示操作。这种移动应用主体通常是Native App,Web技术只是起到补充作用。开发难度和Native App基本相当。

单View混合型

即在同一个View内,同时包括Native View和Web View。互相之间是覆盖(层叠)的关系。这种Hybrid App的开发成本较高,开发难度较大,但是体验较好。如百度搜索为代表的单View混合型移动应用,既可以实现充分的灵活性,又能实现较好的用户体验。

Web主体型

即移动应用的主体是Web View,主要以网页语言编写,穿插Native功能的Hybrid App开发类型。这种类型开发的移动应用体验相对而言存在缺陷,但整体开发难度大幅降低,并且基本可以实现跨平台。Web主体型的移动应用用户体验的好坏,主要取决于底层中间件的交互与跨平台的能力。国外的appMobi、PhoneGap和国内的WeX5、AppCan和Rexsee都属于Web主体型移动应用中间件。其中Rexsee不支持跨平台开发。appMobi和PhoneGap除基础的底层能力更多是通过插件(Plugins)扩展的机制实现Hybrid。AppCan除了插件机制,还提供了大量的单View混合型的接口来完善和弥补Web主体型Hybrid App体验差的问题,接近Native App的体验。而WeX5则在揉合PhoneGap和Bootstrap等主流技术的基础上,对性能进一步做了深度优化,不但完全具备Native App对本地资源的调用能力,性能体验也不输原生;WeX5所开发出来的app具备完全的跨端运行能力,可以无需任何修改直接运行在各种前端环境上。

Hybrid App(混合模式移动应用)

从分析可见,Hybrid App中的Web主体型只要能够解决用户体验差的问题,就可以变成最佳Hybrid App解决方案类型。

hybrid app开发工具编辑

1、AppCan

AppCan是国内Hybrid App混合模式开发的倡导者,AppCan应用引擎支持Hybrid App的开发和运行。并且着重解决了基于HTML5的移动应用”不流畅”和”体验差”的问题。使用AppCan应用引擎提供的Native交互能力,可以让HTML5开发的移动应用基本接近Native App的体验。[3]

AppCan作为中国Hybrid混合应用开发、移动平台、移动云平台的倡导者和领导者,以“免费+开源+开放”的互联网模式,为广大开发者提供一站式的移动应用开发支持服务。[4] 与此同时,从移动应用开发、管理、运营、安全四个方面,为各级政府和企事业单位,构建运营一体化的企业移动平台,企业通过个性化的移动运营门户,增强客户服务品质,提升整体经营管理水平。

现在,正益移动AppCan行业解决方案已成功应用于金融、航空、政府、石化、传媒等领域,客户包括东方航空、国家电网、中化集团、泰康人寿、新华社等众多大型企业,赢得了市场广泛认可,是国内企业移动信息化领域的龙头企业。

Hybrid开发效率高、跨平台、低层本

Hybrid从业务开发上讲,没有版本问题,有BUG能及时修复

移动应用开发的三种方式比较

移动应用开发的方式,目前主要有三种:

Native App: 本地应用程序(原生App)

Web App:网页应用程序(移动web)

Hybrid App:混合应用程序(混合App)

Hybrid App(混合模式移动应用)

图1:三种移动应用开发方式

如图1所示,三种移动应用开发方式具体比较如表2所示:

Hybrid App(混合模式移动应用)

表2:三种移动应用开发方式比较

混合开发应用场景

(1)折中考虑——如果企业使用 Hybrid 开发方法,就能集Native 和web两者之所长。一方面,Native 让开发者可以充分利用现代移动设备所提供的全部不同的特性和功能。另一方面,使用 Web 语言编写的所有代码都可以在不同的移动平台之间共享,使得开发和日常维护过程变得集中式、更简短、更经济高效。

(2)内部技能——许多企业都拥有Web 开发技能。如果选择 Hybrid 开发方法,在合适解决方案的支持下,Web 开发者只要仅仅运用 HTML、CSS 和 JavaScript 等 Web 技能,就能构建 App,同时提供 Native 用户体验。

(3)考虑未来——HTML5的可用性和功能都在迅速改进。许多分析师预测,它可能会成为开发前端 App 的默认技术。如果用 HTML 来编写 App 的大部分代码,并且只有在需要时才使用 Native 代码,公司就能确保他们今天的投入在明天不会变得过时,因为 HTML 功能变得更丰富,可以满足现代企业一系列更广泛的移动要求。

混合开发框架和层次结构图

混合开发结构图

Hybrid App(混合模式移动应用)

1)移动终端web壳(以下简称“壳”):壳是使用操作系统的 API 来创建嵌入式 HTML的渲染引擎。壳主要功能是定义Android应用程序与网页之间的接口,允许网页中的JavaScript调用Android应用程序,提供基于web的应用程序的Android API,将Web嵌入到Android应用程序中。

2)前端交互js:包括基础功能js和业务功能js。

3)前端适配器:适配不同的终端:Pad、android、ios、wap。

为何需要hybrid开发

下面我们简单看一下Native开发中存在的弊端以及使用hybrid开发方式的好处,通过对比你就能知道了hybrid开发的优势,当然了,这里不是推崇使用hybrid开发方式,native也有native开发的优势,hybrid开发也有hybrid开发的劣势,这里只是简单的看一下hybrid相对于native开发的优势。

使用Native开发的方式人员要求高,只是一个简单的功能就需要iOS程序员和Android程序员各自完成;

使用Native开发的方式版本迭代周期慢,每次完成版本升级之后都需要上传到App Store并审核,升级,重新安装等,升级成本高;

使用hybrid开发的方式简单方便,同一套代码既可以在IOS平台使用,也可以在Android平台使用,提高了开发效率与代码的可维护性;

使用hybrid开发的方式升级简单方便,只需要服务器端升级一下就好了,对用户而言完全是透明了,免去了Native升级中的种种不便;

通过对比可以发现hybrid开发方式现对于native实现主要的优势就是更新版本快,代码维护方便,当然了这两个优点也是我们推崇使用hybrid开发app的主要因素。知道了hybrid开发的好处之后,我们如何在Android中实现hybrid开发呢?下面我们就将介绍这个问题。

Android中如何实现Bybird开发

其实在Android开发中使用hybrid模式开发app,也是有两种方案的:

使用第三方hybrid框架

自己使用webview加载

通过这两种方案实现hybrid开发各有利弊,具体如下:

使用PhoneGap、AppCan之类的第三方框架,其实现的原理是以WebView作为用户界面层,以JavaScript作为基本逻辑,以及和中间件通讯,再由中间件访问底层API的方式,进行应用开发。相当于为我们封装了webview与相应的native组件;

使用webview控件加载H5网页的内容,其中客户端的webview只是作为一个加载H5页面的壳子,具体的实现效果是由H5实现的,这个需要Native程序员和H5程序员一起合作完成;

使用第三方框架的方式的好处是许多功能已经被集成好了,只需要简单的调用即可,但是这种方式集成度高,不容易定制化处理,而且性能上也是一个打的问题;

使用webview加载H5页面,定制化程度高,问题可控,但是相对与第三方框架集成度不够高,但是其已经可以满足我们日常的开发功能需要了,目前还是比较推荐使用这种方式实现hybrid开发;

下面我们就看一下如何在Android系统中通过webview实现对H5页面的加载操作。

hybrid开发简单实现

在AndroidManifest.xml中定义网络请求权限

注意这个权限是必须的,因为加载webview页面一般而言经常是网络上的H5页面,这时候的网络请求权限就是必须的了,好多时候测试webview加载网络H5页面失败,找了半天不知道是什么原因,最后才发现是网络权限没有添加…

在Layout布局文件中定义Webview控件

<WebView 
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:id="@+id/webView"
    />
           

这里的WebView控件就是Android原生的webview控件了,其和普通的Android控件的使用没有什么不同都是在布局文件中定义,然后在Activity代码中获取并执行初始化操作等等。

在代码中获取Webview控件加载本地或者网络H5资源

加载本地H5页面

/**
 * 加载本地H5资源文件
 */
webView = (WebView) findViewById(R.id.webView);
webView.loadUrl("file:///Android_asset/example.html");
           

加载网络H5页面

/**
 * 加载网络H5资源
 */
webView = (WebView) findViewById(R.id.webView);
webView.loadUrl("http://baidu.com");
           

可以发现在获取到webview组件之后直接执行一个loadUrl方法传入一个url地址就可以了,这样在activity页面中就可以展示出webview页面了,契合普通的网页效果没什么不同,这里需要说明的是,webview不但能够加载网页地址,同样的也可以加载html代码,本地html资源等等,相对来说功能还是很强大的。

当然了以上只是最最简单的webview使用的例子,下面我们可以为我们的webview对象设置各种参数:

为Webview控件设置参数

WebSettings webSettings = h5Fragment.mWebView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setLoadWithOverviewMode(true);
        webSettings.setAllowFileAccess(false);
        webSettings.setUseWideViewPort(false);
        webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
        webSettings.setDatabaseEnabled(false);
        webSettings.setAppCacheEnabled(false);
        webSettings.setBlockNetworkImage(true);
           

这里的WebSettings就是webview的设置参数对象,我们是通过它为webview设置各种参数值的,见名知意,看见名字我们就知道各个set方法的意思了。比如设置webview中的html页面js代码是否可用,是否可以访问系统文件,H5缓存是否可用,是否立即加载网页图片等等。

为Webview控件设置WebChromeClient

WebChromeClient对象是webview的关于页面效果回调方法的实现对象,主要用于实现webview页面上一些效果的回调,我们可以看一下其中实现的一些回调方法:

/**
 * 自定义实现WebChromeClient对象
 */
public class MWebChromeClient extends WebChromeClient{

    /**
     * 当webview加载进度变化时回调该方法
     */
    @Override
    public void onProgressChanged(WebView view, int newProgress) {
        super.onProgressChanged(view, newProgress);
    }

    /**
     * 当加载到H5页面title的时候回调该方法
     */
    @Override
    public void onReceivedTitle(WebView view, String title) {
        super.onReceivedTitle(view, title);
    }

    /**
     * 当接收到icon的时候回调该方法
     */
    @Override
    public void onReceivedIcon(WebView view, Bitmap icon) {
        super.onReceivedIcon(view, icon);
    }

    /**
     * 当H5页面调用js的Alert方法的时候回调该方法
     */
    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
        return super.onJsAlert(view, url, message, result);
    }

    /**
     * 当H5页面调用js的Confirm方法的时候回调该方法
     */
    @Override
    public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
        return super.onJsConfirm(view, url, message, result);
    }

    /**
     * 当H5页面调用js的Prompt方法的时候回调该方法
     */
    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        return super.onJsPrompt(view, url, message, defaultValue, result);
    }
}
           

上面的WebChromeClient中我们重写了其中的几个字方法,我们已经在方法中添加了注释标明了各个方法的调用时机,而且通过方法名我们也不难发现各个方法的具体作用,这里就不在具体的介绍了。

为Webview主要设置WebviewClient

/**
 * 自定义实现WebViewClient类
 */
public class MWebViewClient extends WebViewClient {

    /**
     * 在webview加载URL的时候可以截获这个动作, 这里主要说它的返回值的问题:
     *  1、返回: return true;  webview处理url是根据程序来执行的。 
     *  2、返回: return false; webview处理url是在webview内部执行。 
     */
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {

    }

    /**
     * 在webview开始加载页面的时候回调该方法
     */
    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
        super.onPageStarted(view, url, favicon);

    }

    /**
     * 在webview加载页面结束的时候回调该方法
     */
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
    }

    /**
     * 加载页面失败的时候回调该方法
     */
    // 该方法为Android23中新添加的API,Android23中会执行该方法
    @TargetApi()
    @Override
    public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {

    }

    /**
     * 加载页面失败的时候回调该方法
     */
    /**
     * 在Android23中改方法被onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) 替代
     * 因此在Android23中执行替代方法
     * 在Android23之前执行该方法
     * @param view
     * @param errorCode
     * @param description
     * @param failingUrl
     */
    @Override
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {

    }
}
           

这里我们只是暂时看一下WebViewClient中的几个比较重要的方法,shouldOverrideUrlLoading方法,onPageStarted方法,onPageFinished方法,onReceivedError方法等,相关的方法说明已经有注释了,这里就不在做过多的说明了。好了介绍完了相关的API之后我们来看一下我们在产品中关于hybrid开发的实践。

友友用车中关于hybrid开发的实践

hybrid这么高逼格的东西友友用车怎么能不涉及呢?在我们的产品开发中也使用到了Webview,并封装了自己的Webview库,下面我们就看一下友友用车中关于hybrid开发的实践。

(1)定义H5Activity类,用于展示H5页面

/**
 * 自定义实现的H5Activity类,主要用于在页面中展示H5页面,整个Activity只有一个Fragment控件
 */
public class H5Activity extends BaseActivity {

    public H5Fragment h5Fragment = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_h5);
        h5Fragment = new H5Fragment();
        getSupportFragmentManager().beginTransaction().replace(R.id.mfl_content_container, h5Fragment).commit();
    }
}
           

(2)在H5Fragment中具体实现对H5页面的加载操作

/**
 * 具体实现H5页面加载Fragment,只有一个Webview控件
 */
public class H5Fragment extends BaseFragment implements SwipeRefreshLayout.OnRefreshListener {

    @BindView(R.id.sswipeRefreshLayout)
    public SwipeRefreshLayout swipeRefreshLayout;
    /**
     * H5页面 WebView
     */
    @BindView(R.id.mwebview)
    public WebView mWebView = null;
    @BindView(R.id.rl)
    public RelativeLayout rl;
    /**
     * 页面title
     */
    public String title = "";
    /**
     * 页面当前URL
     */
    public String currentUrl = "";
    /**
     * 判断网页是否加载成功
     */
    public boolean isSuccess = true;
    /**
     * 判断前一页H5是否需要刷新
     */
    public boolean isNeedFlushPreH5 = false;

    private BasePayFragmentUtils payFragmentUtils;

    public static final String KEY_DIALOG_WEB_VIEW = "dialog_webView";
    /**
     * 是否是弹窗中的WebView
     */
    private boolean isDialogWebView = false;

    View.OnClickListener errorOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            mProgressLayout.showLoading();
            isSuccess = true;
            reflush();
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        payFragmentUtils = new BasePayFragmentUtils(this, BasePayFragmentUtils.ORDER_TYPE_H5);

        Bundle bundle = getArguments();
        if (bundle != null && bundle.containsKey(KEY_DIALOG_WEB_VIEW)) {
            isDialogWebView = bundle.getBoolean(KEY_DIALOG_WEB_VIEW, false);
        }
    }

    @Override
    public View setView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_h5, null);
        ButterKnife.bind(this, rootView);

        if (getActivity() instanceof H5Activity) {
            H5Activity h5Activity = (H5Activity) getActivity();
            mProgressLayout = h5Activity.mProgressLayout;
        }

        initView();
        initData();
        return rootView;
    }

    @Override
    public void onResume() {
        super.onResume();
        payFragmentUtils.onPayResume();
        if (H5Constant.isNeedFlush == true || isNeedFlushPreH5 == true) {
            H5Constant.isNeedFlush = false;
            isNeedFlushPreH5 = false;
            // 加载数据
            initData();
        }
    }

    /**
     * 执行组件初始化的操作
     */
    private void initView() {
        // 判断下拉刷新组件是否可用
        isSwipeEnable();
        // 初始化WebView组件
        H5FragmentUtils.initH5View(this);
        // 设置WebView的Client
        mWebView.setWebViewClient(new MWebViewClient(this));
        // 设置可现实js的alert弹窗
        mWebView.setWebChromeClient(new WebChromeClient());

        if (isDialogWebView) {
            mProgressLayout.setCornerResId(R.drawable.map_confirm_bg);
        }
    }

    /**
     * 执行初始化加载数据的操作
     */
    private void initData() {
        mProgressLayout.showLoading();
        // 设置title
        H5FragmentUtils.setTitle(this, title);
        // 获取请求URL
        currentUrl = H5FragmentUtils.getUrl(this, currentUrl);
        // 刷新页面
        reflush();
    }

    /**
     * 判断下拉刷新组件是否可用
     */
    private void isSwipeEnable() {
        if (getActivity() == null) {
            return;
        }

        if (isDialogWebView) {
            getActivity().getIntent().putExtra(H5Constant.CARFLUSH, false);
        }
        //判断滑动组件是否可用
        if (getActivity().getIntent().getBooleanExtra(H5Constant.CARFLUSH, true)) {
            swipeRefreshLayout.setEnabled(true);
            swipeRefreshLayout.setColorSchemeResources(R.color.c1, R.color.c1, R.color.c1);
            swipeRefreshLayout.setOnRefreshListener(this);
            mWebView.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if (event.getAction() == MotionEvent.ACTION_DOWN) {
                        int downY = (int) event.getY();
                        if (downY <= DisplayUtil.screenhightPx / ) {
                            swipeRefreshLayout.setEnabled(true);
                        } else {
                            swipeRefreshLayout.setEnabled(false);
                        }
                    }
                    return false;
                }
            });
        } else {
            swipeRefreshLayout.setEnabled(false);
        }
    }

    /**
     * 执行Webview的下拉刷新操作
     */
    @Override
    public void onRefresh() {
        //判断是否执行刷新动作
        reflush();
    }

    /**
     * 刷新当前页面
     */
    private void reflush() {
        if (Config.isNetworkConnected(mContext)) {
            if (!TextUtils.isEmpty(currentUrl)) {
                H5Cookie.synCookies(mContext, currentUrl, H5Cookie.getToken());
                mWebView.loadUrl(currentUrl);
            } else {
                swipeRefreshLayout.setRefreshing(false);
                mProgressLayout.showError(errorOnClickListener);
            }
        } else {
            swipeRefreshLayout.setRefreshing(false);
            mProgressLayout.showError(errorOnClickListener);
        }
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        /*swipeRefreshLayout.removeView(mWebView);
        mWebView.removeAllViews();
        mWebView.destroy();*/
    }
}
           

(3)初始化WebView组件

/**
     * 初始化组件WebView
     *
     * @param h5Fragment
     */
    public static void initH5View(H5Fragment h5Fragment) {
        if (h5Fragment == null || h5Fragment.getActivity() == null) {
            return;
        }

        if (h5Fragment.getActivity().getIntent().getBooleanExtra(H5Constant.SOFT_INPUT_IS_CHANGE_LAYOUT, false)) {
            h5Fragment.getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN | WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
        }
        // 设置H5页面默认能够长按复制
        /*if (!h5Fragment.getActivity().getIntent().getBooleanExtra(H5Constant.OPENLONGCLICK, false)) {

            h5Fragment.mWebView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    return true;
                }
            });
        }*/
        WebSettings webSettings = h5Fragment.mWebView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setLoadWithOverviewMode(true);
        webSettings.setAllowFileAccess(false);
        webSettings.setUseWideViewPort(false);
        webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
        webSettings.setDatabaseEnabled(false);
        webSettings.setAppCacheEnabled(false);
        webSettings.setBlockNetworkImage(true);
    }
           

(4)自定义实现WebviewClient对象

/**
 * 自定义实现WebviewClient类
 */
public class MWebViewClient extends WebViewClient {

    public H5Fragment h5Fragment = null;
    public Activity h5Activity = null;

    public MWebViewClient(H5Fragment h5Fragment) {
        this.h5Fragment = h5Fragment;
        if (h5Fragment.getActivity() == null) {
            h5Activity = Config.currentContext;
        } else {
            h5Activity = h5Fragment.getActivity();
        }
    }

    /**
     * 拦截H5页面的a标签跳转,解析scheme协议
     * 相当于放弃了a标签的使用,转而使用自定义的scheme协议
     */
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        //解析scheme
        if (url.indexOf(H5Constant.SCHEME) != -) {
            try {
                Uri uri = Uri.parse(url);
                String[] urlSplit = url.split("\\?");
                Map<String, String> queryMap = new HashMap<String, String>();
                String h5Url = null;
                if (urlSplit.length == ) {
                    queryMap = H5Constant.parseUriQuery(urlSplit[]);
                    h5Url = queryMap.get(H5Constant.MURL);
                }
                // 解析SCHEME跳转
                {
                    // 若设置刷新,则刷新页面
                    if (queryMap.containsKey(H5Constant.RELOADPRE) && "1".equals(queryMap.get(H5Constant.RELOADPRE))) {
                        h5Fragment.isNeedFlushPreH5 = true;
                    }
                    Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                    h5Activity.startActivityForResult(intent, H5Constant.h5RequestCode);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return true;
        }
        // 打电话
        else if (url.indexOf("tel://") != -) {
            final String number = url.substring("tel://".length());
            Config.callPhoneByNumber(h5Activity, number);
            return true;
        } else if (url.indexOf("tel:") != -) {
            final String number = url.substring("tel:".length());
            Config.callPhoneByNumber(h5Activity, number);
            return true;
        }
        // 其他跳转方式
        else {
            view.loadUrl(url);
            //如果不需要其他对点击链接事件的处理返回true,否则返回false
            return false;
        }
    }

    /**
     * H5页面刚刚开始被webview加载时回调该方法
     */
    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
        super.onPageStarted(view, url, favicon);

    }

    /**
     * H5页面结束被加载时回调该方法
     */
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        h5Fragment.swipeRefreshLayout.setRefreshing(false);
        if (h5Activity.getTitle().toString().equals("找不到网页")) {
            h5Fragment.mProgressLayout.showError(h5Fragment.errorOnClickListener);
            return;
        }
        if (h5Fragment.isSuccess)
            h5Fragment.mProgressLayout.showContent();
        else
            h5Fragment.mProgressLayout.showError(h5Fragment.errorOnClickListener);

        h5Fragment.onLoadFinish(h5Fragment.isSuccess);
        if (h5Fragment.isSuccess) {
            h5Fragment.mWebView.getSettings().setBlockNetworkImage(false);
        }
    }

    // 该方法为Android23中新添加的API,Android23中会执行该方法
    @TargetApi()
    @Override
    public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
        if (Build.VERSION.SDK_INT >= ) {
            if (request.isForMainFrame()) {
                h5Fragment.isSuccess = false;
                h5Fragment.mProgressLayout.showError(h5Fragment.errorOnClickListener);
            }
        }
    }

    /**
     * 在Android23中改方法被onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) 替代
     * 因此在Android23中执行替代方法
     * 在Android23之前执行该方法
     * @param view
     * @param errorCode
     * @param description
     * @param failingUrl
     */
    @Override
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
        if (Build.VERSION.SDK_INT < ) {
            h5Fragment.isSuccess = false;
            h5Fragment.mProgressLayout.showError(h5Fragment.errorOnClickListener);
        }
    }
}
           

(5)打开H5页面

Intent intent = new Intent(context, H5Activity.class);
        intent.putExtra(H5Constant.MURL, currentUrl);
        intent.putExtra(H5Constant.TITLE, title);
        context.startActivity(intent);
           

继续阅读