美文网首页Android开发
安卓APP加载HTML5页面解决方案总结

安卓APP加载HTML5页面解决方案总结

作者: 骋_listen | 来源:发表于2019-01-02 15:37 被阅读0次

    由于H5页面在移动端的兼容性及扩展性方面体现出来的优势,又兼得APP中植入H5页面对应用的灵活性有大大的提升(如活动、游戏的更新等),APP开发不可避免的需要加载一些H5页面,但安卓客户端对网页内容的排版、整理、交互等可能会出现一些不可预料的问题。本文将对安卓端加载网页写一些比较通用,可能避免问题的统一的解决方法总结。
    背景
    一般对前端知识有所了解的都清楚,解析网页主要是靠页面渲染引擎和JS解析引擎,前者负责取得网页的内容(HTML、XML、图象等等)、整理信息(例如加入CSS等),以及计算网页的显示方式然后会输出至显示器或打印机,后者负责网页的一些动态交互等。
    安卓操作系统在前端页面加载方面针对开发者提供了webView组件,并给予响应的API去使用,但其底层的内核在4.4以前是webKit内核(对H5的支持还远远不够),4.4以后是chromium内核(JS解析引擎采用的便是如今最常用的V8引擎,对H5的支持也逐渐加大),随着安卓操作系统的不断迭代,5.0已解决通过网页打开本地文件选择器的问题,总之技术在不断的成熟,有兴趣了解细节的可参考http://blog.csdn.net/typename/article/details/40425275

    疑难杂症

    最近做了个项目,里面需要在APP中加载一个叫大转盘的活动,活动页面当然是用H5写的啦,于是就用webView进行加载代码如下:

    package com.example.webviewtest;
     
    import android.annotation.SuppressLint;
    import android.app.Activity;
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.net.http.SslError;
    import android.os.Build;
    import android.os.Bundle;
    import android.support.v4.app.FragmentActivity;
    import android.view.KeyEvent;
    import android.webkit.JsResult;
    import android.webkit.SslErrorHandler;
    import android.webkit.WebChromeClient;
    import android.webkit.WebSettings;
    import android.webkit.WebView;
    import android.webkit.WebViewClient;
     
    @SuppressLint("SetJavaScriptEnabled")
    public class WebViewActivity extends FragmentActivity {
        private WebView webView;
        private Context context;
        private Activity activity;
     
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_webview);
            context = this;
            activity = this;
            initView();
        }
     
        //
        private void initView() {
            webView = (WebView) findViewById(R.id.game_WV);
     
            initWebView();
        }
     
        @SuppressLint("NewApi")
        private void initWebView() {
            
            
            if (Build.VERSION.SDK_INT >= 19) {
                webView.getSettings().setCacheMode(
                        WebSettings.LOAD_CACHE_ELSE_NETWORK);
            }
            webView.setWebChromeClient(new WebChromeClient() {
                @Override
                public void onProgressChanged(WebView view, int newProgress) {
                    super.onProgressChanged(view, newProgress);
                    activity.setTitle("Loading...");
                    activity.setProgress(newProgress * 100);
                    if (newProgress == 100) {
                        activity.setTitle(R.string.app_name);
                    }
                }
     
                @Override
                public boolean onJsAlert(WebView view, String url, String message,
                        JsResult result) {
                    return super.onJsAlert(view, url, message, result);
                }
            });
            webView.setWebViewClient(new GameWebViewClient());
            WebSettings s = webView.getSettings();
            s.setJavaScriptEnabled(true);
            webView.loadUrl(Constants.url);
        }
     
        class GameWebViewClient extends WebViewClient {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view,
                    String url_Turntable) {
                view.loadUrl(url_Turntable);
                return true;
            }
     
            @Override
            public void onReceivedSslError(WebView view, SslErrorHandler handler,
                    SslError error) {
                handler.proceed();
            }
     
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
            }
     
            @Override
            public void onPageFinished(WebView view, String url) {
            }
     
        }
     
        @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
            if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {
                webView.goBack();
                return true;
            }
            return super.onKeyDown(keyCode, event);
        }
     
    }
    

    这里简单介绍下,为何webView既要setWebClient,又要setWebChromeClient,这两者并不是因为4.4以后webView采用了chromium的内核所以以后就舍弃了WebClient(之前有不少童鞋误解了),其实两者是相辅相成的:
    WebViewClient主要帮助WebView处理各种通知、请求事件的,WebChromeClient主要辅助WebView处理Javascript的对话框、网站图标、网站title、加载进度,具体可重写的接口大家可自行查阅SDK文档
    回到正题,原本希望的效果是转盘能转动,各类信息包括用户信息、图片信息均能显示正常,可真正显示出来的竟是这种效果:

    image.png
    转盘无法转动,用户是谁没有显示,可抽奖的次数亦没有显示,但是将链接放入浏览器内却毫无问题,显示正常

    用Chrome浏览器调试

    可以判断前端方面的代码应该是没有问题的,问题应该是出在webView的配置上面,由于我测试的手机是安卓4.4操作系统的,对H5页面及JS的交互的支持相对来说已经相对完善了,而且在手机自带的浏览器上输入相同的链接运行正常,那么初步判定应该是webView的webSettings的问题,也初步排除了是JS的问题,英文代码里已经设置了setJavaScriptEnabled(true)。前面说了安卓4.4以上webView的内核是chronium的,这给我们APP开发者带来的另一个好处是,可以用chorme浏览器调试、debug定位Webview加载h5页面时所遇到的问题,如图,打开chrome浏览器,插上手机,在此简要说明下前提条件(1、手机要是USB调试模式 2、chrome客户端需31以上 3、代码中添加

    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
                WebView.setWebContentsDebuggingEnabled(true);
            }
    
    

    另外可能需要翻墙(这点很。。。,不过貌似只要翻墙一次即可)
    手机装好程序,连接上电脑,打开对应页面(WebViewActivity)。


    image.png

    点击inspect(这里如果不行,可能需要翻墙)
    出现如下界面


    image.png

    我们点击console


    image.png

    接着点进去看下发生了什么


    image.png

    咨询了下做前端的同事,这里是localStorage.getItem这里由于localStorage为空导致的,为什么会这样呢,到这里,需要补充一下 localStorage 的知识。
    HTML5 Storage主要有:
    sessionStorage: 会话 (session) 级别的数据存储,会话结束后,相关的数据就会被清除掉。
    localStorage: 用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。
    作为 HTML5 标准的一部分,绝大多数的浏览器都是支持 localStorage 的,但是鉴于它的安全特性(任何人都能读取到它,尽管有相应的限制,将敏感数据存储在这里依然不是明智之举),Android 默认是关闭该功能的。
    也就是说,针对写了这类代码的H5页面的解析,还需要在上述代码里添加:

    s.setDomStorageEnabled(true);
    
    

    CrossWalk

    上面的问题固然解决了,可是如果出了问题,Chrome的DevTools都检测不出来呢?这是有可能的,前面说了,WebView解析网页最关键的是靠底层的解析引擎,WebView只不过是通过JNI调用了引擎的解析功能并封装出来给咱们安卓的开发者使用。面对4.4之前的手机终端怎么办?亦或是WebView所采用的轻量级的chronium内核本身就解析不出前端开发者的牛逼脚本怎么办?尽管Google在版本迭代中不断修复Bug,不可否认的是,目前大家使用的安卓手机版本参差不齐,高版本的使用WebView的话都可能会遇到些奇葩问题,更别说低版本的了。当然,我们队WebView的未来还是很有信心的,高版本的使用率的增高,以后这些问题会越来越少,但现如今,如果你对WebView作为富文本编辑器在全面性和稳定性还是不太满意的话,可以尝试CrossWalk。

    CrossWalk介绍

    crooswalk的牛逼之处呢,不仅在于他高效、稳定,而且加载同一个界面,字体都会好看些,据说3D效果也是非常的理想。
    关于其牛逼之处,此处不做赘述,有兴趣的童鞋可以看看这篇文章http://dev.yesky.com/24/39285024.shtml,讲述了他的发展历史和优势。

    CrossWalk安卓端使用

    更高阶的使用及了解,可以参考crosswalk的官网,https://crosswalk-project.org/documentation/embedding_crosswalk.html
    CrossWalk包的下载:https://crosswalk-project.org/documentation/downloads.html

    1. 将下载下来的包,和你需要使用该包的工程放在同一个文件夹下,在eclipse中导入包的工程,看的出来其核心引擎来自于libxwalkcore.so库。


      image.png
    2. 在主工程中,添加该库


      image.png
    3. 源码如下

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
        <org.xwalk.core.XWalkView
            android:id="@+id/game_WV"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </LinearLayout>
    
    package com.example.webviewtest;
     
     
     
     
     
    import org.xwalk.core.XWalkResourceClient;
    import org.xwalk.core.XWalkView;
     
    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.os.Bundle;
    import android.support.v4.app.FragmentActivity;
     
    @SuppressLint("SetJavaScriptEnabled")
    public class CrossWalkActivity extends FragmentActivity {
        private XWalkView webView;
        private Context context;
        
     
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_crosswalk);
            context = this;
            initView();
        }
     
        //
        private void initView() {
            webView = (XWalkView)findViewById(R.id.game_WV);
            
            initWebView();
        }
     
        private void initWebView() {
            webView.setResourceClient(new GameWebViewClient(webView));
            webView.load(Constants.url,null);
        }
        
        class GameWebViewClient extends XWalkResourceClient{
     
            public GameWebViewClient(XWalkView view) {
                super(view);
            }
            
            @Override
            public void onLoadStarted(XWalkView view, String url) {
                super.onLoadStarted(view, url);
            }
            
            @Override
            public void onLoadFinished(XWalkView view, String url) {
                super.onLoadFinished(view, url);
            }
            
        }
     
    }
    
    

    crosswalk弊端

    要是说crosswalk有什么弊端,那就是太大了,一个.so动态库就足足有近20M,apk应用本身可能都不足他的1/4。单这也看的出其代码量的巨大,效果全面那就毋庸置疑了。

    Cordova

    如果说你嫌crosswalk太大,又不愿意对webview在代码上进行各种设置以保障适应各种不同的H5页面,也许cordova是不错的选择,从本质上说,cordova其实还是继承webview的,所以在他俩从本质上说是一样的,但是介于我之前并不知如何去调试webview,老大又不想用crosswalk这个极度占空间的库,于是就找来了cordova的JAR包,总之把当时的问题解决了吧,但现在回头看看,cordova在4.0后已经选择和crosswalk合作了,我用的jar包还是3.7的,建议以后无需再使用它。但既然做了,那也就把图和代码贴上来吧。


    image.png
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
        <org.apache.cordova.CordovaWebView
            android:id="@+id/game_WV"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </LinearLayout>
    
    
    package com.example.webviewtest;
     
    import java.util.ArrayList;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
     
    import org.apache.cordova.CordovaChromeClient;
    import org.apache.cordova.CordovaInterface;
    import org.apache.cordova.CordovaPlugin;
    import org.apache.cordova.CordovaPreferences;
    import org.apache.cordova.CordovaWebView;
    import org.apache.cordova.CordovaWebViewClient;
    import org.apache.cordova.PluginEntry;
    import org.apache.cordova.Whitelist;
     
    import android.annotation.SuppressLint;
    import android.app.Activity;
    import android.content.Context;
    import android.content.Intent;
    import android.os.Bundle;
    import android.support.v4.app.FragmentActivity;
    import android.view.KeyEvent;
     
    @SuppressLint("SetJavaScriptEnabled")
    public class CordovaActivity extends FragmentActivity implements CordovaInterface {
        private final ExecutorService threadPool = Executors.newCachedThreadPool();
        private CordovaWebView webView;
        private int activityRequestCode;
        private CordovaPlugin activityResultCallBack;
        private CordovaPreferences prefs = new CordovaPreferences();
        private Whitelist internalWhitelist = new Whitelist();
        private Whitelist externalWhitelist = new Whitelist();
        private ArrayList<PluginEntry> pluginEntries;
        private Context context;
        
     
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_cordova);
            context = this;
            initView();
        }
     
        //
        private void initView() {
            webView = (CordovaWebView)findViewById(R.id.game_WV);
            
            initWebView();
        }
     
        private void initWebView() {
            internalWhitelist.addWhiteListEntry("*", false);
            externalWhitelist.addWhiteListEntry("tel:*",false);
            externalWhitelist.addWhiteListEntry("sms:*", false);
            prefs.set("loglevel", "DEBUG");
            webView.init(this, makeWebViewClient(webView), makeChromeClient(webView), pluginEntries, internalWhitelist, externalWhitelist, prefs);
            
            webView.loadUrlIntoView(Constants.url);
        }
        
        private CordovaWebViewClient makeWebViewClient(CordovaWebView webView){
            return webView.makeWebViewClient(this);
        }
        
        private CordovaChromeClient makeChromeClient(CordovaWebView webView){
            return webView.makeWebChromeClient(this);
        }
     
        @Override
        public Activity getActivity() {
            // TODO Auto-generated method stub
            return this;
        }
     
        @Override
        public ExecutorService getThreadPool() {
            // TODO Auto-generated method stub
            return threadPool;
        }
     
        @Override
        public Object onMessage(String arg0, Object arg1) {
            if(arg0.equals("onPageStarted")){
            }
            if(arg0.equals("onPageFinished")){
            }
            return null;
        }
     
        @Override
        public void setActivityResultCallback(CordovaPlugin arg0) {
            if(activityResultCallBack != null){
                activityResultCallBack.onActivityResult(activityRequestCode, Activity.RESULT_CANCELED, null);
            }
            this.activityResultCallBack = arg0;
        }
     
        @Override
        public void startActivityForResult(CordovaPlugin arg0, Intent arg1, int arg2) {
            setActivityResultCallback(arg0);
            startActivityForResult(arg1, activityRequestCode);
        }
     
     
        @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
            if(keyCode == KeyEvent.KEYCODE_BACK){
            }
            return super.onKeyDown(keyCode, event);
        }
    }
    
    

    小结

    说了这么多,我还是建议大家多使用安卓SDK提供的组件和功能,毕竟功能上可以自己封装,出了问题调试起来也方便,webview目前在不断地更新迭代,大家还是要对它充满信心。

    附件http://download.csdn.net/detail/zcchange1025/9455185

    相关文章

      网友评论

        本文标题:安卓APP加载HTML5页面解决方案总结

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