天天看點

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);
           

繼續閱讀