Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android產品研發(十七)--)Hybird開發

android產品研發(十七)--)Hybird開發

編輯:關於Android編程

上一篇文章中我們介紹了android開發中經常會涉及到但又常常被忽視掉的開發者模式。主要講解了包括如何打開手機的開發者模式,開發者模式中各個菜單的意義和作用,如何清除手機App數據,以及清除手機App數據具體清除那些數據等知識點

本文將介紹android中hybird開發相關的知識點。hybird開發實際上是混合開發的意思,這裡的混合是H5開發與Native開發混合的意思。下面的文章中我們將逐個介紹一下hybird開發的概念、hybird開發的優勢、android中如何實現hybird開發、簡單的hybird開發的例子,以及在產品實踐中對hybird開發的應用,希望通過本篇文章的介紹讓您能夠對android中的hybird開發有一個基本的認識。

一:hybird開發的概念

在具體介紹hybird開發之前,我們先看一下什麼是hybird開發,在這裡我們先引用一下百度百科中對hybird開發的定義:

Hybrid App(混合模式移動應用)是指介於web-app、native-app這兩者之間的app,兼具“Native App良好用戶交互體驗的優勢”和“Web App跨平台開發的優勢”。

從定義中我們可以看到hybird開發其實就是在App開發過程中既使用到了web開發技術也使用到了native開發技術,通過這兩種技術混合實現的App就是我們通常說的hybird app,而通過這兩種技術混合開發就是hybird開發。

好吧,我們已經知道hybird開發的具體含義,那麼一個問題就產生了,既然我們已經有了native開發了為何還需要hybird開發呢?它有什麼好處麼?答案是肯定的,下面我們就來看一下為何需要hybird開發方式。

二:為何需要hybird開發

下面我們簡單看一下Native開發中存在的弊端以及使用Hybird開發方式的好處,通過對比你就能知道了hybird開發的優勢,當然了,這裡不是推崇使用hybird開發方式,native也有native開發的優勢,hybird開發也有hybird開發的劣勢,這裡只是簡單的看一下hybird相對於native開發的優勢。

使用Native開發的方式人員要求高,只是一個簡單的功能就需要IOS程序員和Android程序員各自完成;

使用Native開發的方式版本迭代周期慢,每次完成版本升級之後都需要上傳到App Store並審核,升級,重新安裝等,升級成本高;

使用Hybird開發的方式簡單方便,同一套代碼既可以在IOS平台使用,也可以在android平台使用,提高了開發效率與代碼的可維護性;

使用Hybird開發的方式升級簡單方便,只需要服務器端升級一下就好了,對用戶而言完全是透明了,免去了Native升級中的種種不便;

通過對比可以發現hybird開發方式現對於native實現主要的優勢就是更新版本快,代碼維護方便,當然了這兩個優點也是我們推崇使用hybird開發app的主要因素。知道了hybird開發的好處之後,我們如何在android中實現hybird開發呢?下面我們就將介紹這個問題。

三:android中如何實現Bybird開發

其實在android開發中使用hybird模式開發app,也是有兩種方案的:

使用第三方hybird框架

自己使用webview加載

通過這兩種方案實現Hybird開發各有利弊,具體如下:

使用PhoneGap、AppCan之類的第三方框架,其實現的原理是以WebView作為用戶界面層,以Javascript作為基本邏輯,以及和中間件通訊,再由中間件訪問底層API的方式,進行應用開發。相當於為我們封裝了webview與相應的native組件;

使用webview控件加載H5網頁的內容,其中客戶端的webview只是作為一個加載H5頁面的殼子,具體的實現效果是由H5實現的,這個需要Native程序員和H5程序員一起合作完成;

使用第三方框架的方式的好處是許多功能已經被集成好了,只需要簡單的調用即可,但是這種方式集成度高,不容易定制化處理,而且性能上也是一個打的問題;

使用webview加載H5頁面,定制化程度高,問題可控,但是相對與第三方框架集成度不夠高,但是其已經可以滿足我們日常的開發功能需要了,目前還是比較推薦使用這種方式實現Hybird開發;

下面我們就看一下如何在android系統中通過webview實現對H5頁面的加載操作。

四:Hybird開發簡單實現

在AndroidManifest.xml中定義網絡請求權限

注意這個權限是必須的,因為加載webview頁面一般而言經常是網絡上的H5頁面,這時候的網絡請求權限就是必須的了,好多時候測試webview加載網絡H5頁面失敗,找了半天不知道是什麼原因,最後才發現是網絡權限沒有添加…

在Layout布局文件中定義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(21)
    @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之後我們來看一下我們在產品中關於Hybird開發的實踐。

友友用車中關於Hybird開發的實踐

Hybird這麼高逼格的東西友友用車怎麼能不涉及呢?在我們的產品開發中也使用到了Webview,並封裝了自己的Webview庫,下面我們就看一下友友用車中關於Hybird開發的實踐。

(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 / 3) {
                            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) != -1) {
            try {
                Uri uri = Uri.parse(url);
                String[] urlSplit = url.split("\\?");
                Map queryMap = new HashMap();
                String h5Url = null;
                if (urlSplit.length == 2) {
                    queryMap = H5Constant.parseUriQuery(urlSplit[1]);
                    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://") != -1) {
            final String number = url.substring("tel://".length());
            Config.callPhoneByNumber(h5Activity, number);
            return true;
        } else if (url.indexOf("tel:") != -1) {
            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(21)
    @Override
    public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
        if (Build.VERSION.SDK_INT >= 21) {
            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 < 23) {
            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);

可以發現在產品實際開發過程中使用webview的頁面都是整個的Activity頁面,也就是說整個Activity頁面只有一個webview控件,所以這時候頁面的內容都是通過H5實現的。
然後當我們需要打開H5頁面的時候可以通過服務器下發H5頁面的url和title,並作為參數傳遞給H5Activity,然後打開該url所表示的網頁。

同時我們使用Fragment用於實現加載H5頁面的所以,所以以後當我們需要在其他地方使用加載H5頁面的時候可以很方便的一直。

在MWebviewClient的shouldOverrideUrlLoading方法中我們攔截了所有的a標簽跳轉,轉而實現我們自身的scheme協議,即a標簽的跳轉鏈接不再是常規的http鏈接,而是我們自定義的scheme協議

總結:

本文中我們介紹了Hybird開發的概念,hybird開發的作用,android中如何實現hybird開發,android實現hybird的例子,產品中對hybird開發的實踐

在定義webview的時候可以設置WebviewSettings,設置WebviewClient,設置WebChromeClient等參數對象

可以在WebviewClient的shouldOverrideUrlLoading方法中攔截a標簽的跳轉並執行相應的邏輯

 

本文以同步至github中:https://github.com/yipianfengye/androidProject,歡迎star和follow


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved