关于 android Webview

关于 android Webview

基本使用

加载html四种方式

webView.loadUrl("http://139.196.35.30:8080/OkHttpTest/apppackage/test.html");//加载url

webView.loadUrl("file:///android_asset/test.html");//加载asset文件夹下html

//方式3:加载手机sdcard上的html页面
webView.loadUrl("content://com.ansen.webview/sdcard/test.html");

//方式4 使用webview显示html代码
webView.loadDataWithBaseURL(null,"<html><head><title> 欢迎您 </title></head>" +
        "<body><h2>使用webview显示 html代码</h2></body></html>", "text/html" , "utf-8", null);

简单使用

在AndroidManifest.xml中添加网络权限

<uses-permission android:name="android.permission.INTERNET" />

布局文件 activity_main.xml

        <RelativeLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent">

            <FrameLayout
                android:id="@+id/xwalk_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/white"
                android:focusable="true"
                android:focusableInTouchMode="true" />

            <ProgressBar
                android:id="@+id/web_progress"
                style="?android:attr/progressBarStyleHorizontal"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:indeterminate="false"
                android:indeterminateOnly="false"
                android:maxHeight="2dp"
                android:minHeight="2dp"
                android:progressDrawable="@drawable/progress_bar_horizontal" />
        </RelativeLayout>

使用webview 动态加载,防止放生内存泄漏

MainActivity.java webview 动态加载


//初始化
WebView      mWebView = new WebView(getApplicationgContext());  
FrameLayout frameLayout      = findViewById(R.id.xwalk_view);  
frameLayout.addView(mWebView);

protected void onDestroy() {  
      super.onDestroy();  
      mWebView.removeAllViews();  
       ((ViewGroup) mWebView.getParent()).removeView(mWebView);
        mWebView.setTag(null);
        mWebView.clearHistory();
        mWebView.destroy();
        mWebView= null;
}

WebViewClient与WebChromeClient区别

WebViewClient主要帮助WebView处理各种通知、请求事件的,有以下常用方法:

  • onPageFinished 页面请求完成
  • onPageStarted 页面开始加载
  • shouldOverrideUrlLoading 拦截url
  • onReceivedError 访问错误时回调,例如访问网页时报错404,在这个方法回调的时候可以加载错误页面。
  //WebViewClient主要帮助WebView处理各种通知、请求事件
    private WebViewClient webViewClient=new WebViewClient(){
        @Override
        public void onPageFinished(WebView view, String url) {//页面加载完成
            progressBar.setVisibility(View.GONE);
        }

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {//页面开始加载
            progressBar.setVisibility(View.VISIBLE);
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            Log.i("ansen","拦截url:"+url);
            if(url.equals("http://www.google.com/")){
                Toast.makeText(MainActivity.this,"国内不能访问google,拦截该url",Toast.LENGTH_LONG).show();
                return true;//表示我已经处理过了
            }
            return super.shouldOverrideUrlLoading(view, url);
        }

    };

WebChromeClient主要辅助WebView处理Javascript的对话框、网站图标、网站title、加载进度等,有以下常用方法。

  • onJsAlert webview不支持js的alert弹窗,需要自己监听然后通过dialog弹窗
  • onReceivedTitle 获取网页标题
  • onReceivedIcon 获取网页icon
  • onProgressChanged 加载进度回调
//WebChromeClient主要辅助WebView处理Javascript的对话框、网站图标、网站title、加载进度等
    private WebChromeClient webChromeClient=new WebChromeClient(){
        //不支持js的alert弹窗,需要自己监听然后通过dialog弹窗
        @Override
        public boolean onJsAlert(WebView webView, String url, String message, JsResult result) {
            AlertDialog.Builder localBuilder = new AlertDialog.Builder(webView.getContext());
            localBuilder.setMessage(message).setPositiveButton("确定",null);
            localBuilder.setCancelable(false);
            localBuilder.create().show();

            //注意:
            //必须要这一句代码:result.confirm()表示:
            //处理结果为确定状态同时唤醒WebCore线程
            //否则不能继续点击按钮
            result.confirm();
            return true;
        }

        //获取网页标题
        @Override
        public void onReceivedTitle(WebView view, String title) {
            super.onReceivedTitle(view, title);
            Log.i("ansen","网页标题:"+title);
        }

        //加载进度回调
        @Override
        public void onProgressChanged(WebView view, int newProgress) {
            progressBar.setProgress(newProgress);
        }
    };

网页中有H5播放flash video的时候按下全屏按钮将会调用到这个方法,一般用作设置网页播放全屏操作

@Override
public void onShowCustomView(View view, CustomViewCallback callback)

取消全屏方法

@Override
public void onHideCustomView()
WebChromeClient高级功能实现

GoogleChrome高级使用实例
GitHub - googlearchive/chromium-webview-samples: Useful examples for Developing apps with the Chromium based WebView

让你的webview支持File Input 标签

在Android 5.0 API 21后 借助新的 onShowFileChooser() 方法,您现在不但可以在 WebView 中使用输入表单字段,而且可以启动文件选择器从 Android 设备中选择图片和文件

public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
                                 WebChromeClient.FileChooserParams fileChooserParams) {
    if (mFilePathCallback != null) {
        mFilePathCallback.onReceiveValue(null);
    }
    mFilePathCallback = filePathCallback;

    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        // Create the File where the photo should go
        File photoFile = null;
        try {
            photoFile = createImageFile();
            takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
        } catch (IOException ex) {
            // Error occurred while creating the File
            Log.e(TAG, "Unable to create Image File", ex);
        }

        // Continue only if the File was successfully created
        if (photoFile != null) {
            mCameraPhotoPath = "file:" + photoFile.getAbsolutePath();
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                    Uri.fromFile(photoFile));
        } else {
            takePictureIntent = null;
        }
    }

    Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
    contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
    contentSelectionIntent.setType("image/*");

    Intent[] intentArray;
    if (takePictureIntent != null) {
        intentArray = new Intent[]{takePictureIntent};
    } else {
        intentArray = new Intent[0];
    }
    Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
    chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
    chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
    chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
    startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);
    return true;
}

在选择完图片后回调onActivityResult 获取图片

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode != INPUT_FILE_REQUEST_CODE || mFilePathCallback == null) {
        super.onActivityResult(requestCode, resultCode, data);
        return;
    }

    Uri[] results = null;

    // Check that the response is a good one
    if (resultCode == Activity.RESULT_OK) {
        if (data == null) {
            // If there is not data, then we may have taken a photo
            if (mCameraPhotoPath != null) {
                results = new Uri[]{Uri.parse(mCameraPhotoPath)};
            }
        } else {
            String dataString = data.getDataString();
            if (dataString != null) {
                results = new Uri[]{Uri.parse(dataString)};
            }
        }
    }

    mFilePathCallback.onReceiveValue(results);
    mFilePathCallback = null;
    return;
}

设置webview视频未播放时默认显示占位
@Override
public Bitmap getDefaultVideoPoster() {
    if(getActivity() == null) {
        return null;
    }

    return BitmapFactory.decodeResource(getActivity().getApplicationContext().getResources(),
            R.drawable.video_poster);
}

WebView下载监听

mWebView.setDownloadListener(new DownloadListener() {
    @Override
    public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
    }
});

webView 设置

        webView.addJavascriptInterface(this,"android");//添加js监听 这样html就能调用客户端
webView.setWebChromeClient(webChromeClient);
webView.setWebViewClient(webViewClient);

 WebSettings webSettings=webView.getSettings();
       
// webview启用javascript支持 用于访问页面中的javascript
webSettings.setJavaScriptEnabled(true);

//设置WebView缓存模式 默认断网情况下不缓存
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);


/**
         * LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
         * LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
         * LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
         * LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
 */
//断网情况下加载本地缓存
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);}

//让WebView支持DOM storage API
webSettings.setDomStorageEnabled(true);

//让WebView支持缩放
webSettings.setSupportZoom(true);

//启用WebView内置缩放功能
webSettings.setBuiltInZoomControls(true);

//让WebView支持可任意比例缩放
webSettings.setUseWideViewPort(true);

//让WebView支持播放插件
webSettings.setPluginState(WebSettings.PluginState.ON);

//设置WebView使用内置缩放机制时,是否展现在屏幕缩放控件上
webSettings.setDisplayZoomControls(false);

//设置在WebView内部是否允许访问文件
webSettings.setAllowFileAccess(true);

//设置WebView的访问UserAgent
webSettings.setUserAgentString(WebViewUtil.getUserAgent(getActivity(), webSettings));

//设置脚本是否允许自动打开弹窗
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

// 加快HTML网页加载完成速度 
if (Build.VERSION.SDK_INT >= 19) {   
    settings.setLoadsImagesAutomatically(true);
} else {
    settings.setLoadsImagesAutomatically(false); 
}

// 开启Application H5 Caches 功能 
settings.setAppCacheEnabled(true); 

// 设置编码格式
settings.setDefaultTextEncodingName("utf-8");


返回webView的上一页面

 @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        Log.i("ansen","是否有上一个页面:"+webView.canGoBack());
        if (webView.canGoBack() && keyCode == KeyEvent.KEYCODE_BACK){//点击返回按钮的时候判断有没有上一页
            webView.goBack(); // goBack()表示返回webView的上一页面
            return true;
        }
        return super.onKeyDown(keyCode,event);
    }

内核版本

Android 中的Webview 在 4.4之前是webkit内核, 在4.4 和 4.4之后的版本是chromium 内核。

Webkit内核

WebKit 是一个开源的浏览器引擎,与之相对应的引擎有Gecko(Mozilla Firefox 等使用)和Trident(也称MSHTML,IE 使用)。
同时WebKit 也是苹果Mac OS X 系统引擎框架版本的名称,主要用于Safari,Dashboard,Mail 和其他一些Mac OS X 程序。WebKit 前身是 KDE 小组的 KHTML,WebKit 所包含的 WebCore 排版引擎和 JSCore 引擎来自于 KDE 的 KHTML 和 KJS,当年苹果比较了 Gecko 和 KHTML 后,仍然选择了后者,就因为它拥有清晰的源码结构、极快的渲染速度。Apple将 KHTML 发扬光大,推出了装备 KHTML 改进型 WebKit 引擎的浏览器 Safari。
WebKit 所包含的 WebCore排版引擎和 JSCore 引擎,均是从KDE的KHTML及KJS引擎衍生而来。它们都是自由软件,在GPL条约下授权,同时支持BSD系统的开发。所以Webkit也是自由软件,同时开放源代码。
WebKit的优势在于高效稳定,兼容性好,且源码结构清晰,易于维护。

Chrominum内核

Chromium 是 Google 的chrome浏览器背后的引擎,其目的是为了创建一个安全、稳定和快速的通用浏览器。
Chromium是一个由Google主导开发的网页浏览器。以BSD许可证等多重自由版权发行并开放源代码。Chromium的开发可能早自2006年即开始,设计思想基于简单、高速、稳定、安全等理念,在架构上使用了Apple发展出来的WebKit排版引擎、Safari的部份源代码与Firefox的成果,并采用Google独家开发出的V8引擎以提升解译JavaScript的效率,而且设计了“沙盒”、“黑名单”、“无痕浏览”等功能来实现稳定与安全的网页浏览环境。Chromium是Google为发展自家的浏览器Google Chrome(以下简称Chrome)而开启的计划,所以Chromium相当于Chrome的工程版或称实验版(尽管Chrome自身也有β版阶段),新功能会率先在Chromium上实现,待验证后才会应用在Chrome上,故Chrome的功能会相对落后但较稳定。Chromium的更新速度很快,每隔数小时即有新的开发版本发布,而且可以免安装,下载zip封装版后解压缩即可使用(Windows下也有安装版)。Chrome虽然理论上也可以免安装,但Google仅提供安装版。
Chromium和Chrome所使用的webkit内核是目前公认的最快的网页浏览方式。
使用Chromium开源代码(基于webkit内核)的浏览器有360极速浏览器、枫树浏览器、太阳花浏览器、世界之窗极速版、傲游浏览器和UC浏览器电脑版等。搜狗高速浏览器和qq浏览器官网未提及Chromium,只是说采用webkit内核,经网友测试这两款浏览器极有可能也是使用的Chromium,只是官方不承认而已。

Blink内核

Blink 是由 WebKit 内核衍生出来的,是由 Chromium 开发维护的新项目。在官方的博客上有详细的说明:Blink: A rendering engine for the Chromium project。关于这个浏览器的更加详细的特性和目标,可以看这里:http://www.chromium.org/blink。大体就是一大堆的优化,提高速度,各种支持。
之前的 WebKit 内核是由 谷歌 chrome 和 苹果 safari 共同开发,谷歌不干了,从 WebKit 中衍生分离出 Blink 不知道是何居心,不过浏览器渲染内核的多样性是肯定有了,以谷歌的技术,这种多样性应该会促进 Web 的发展。
对于国内来说,Blink 是开源引擎,X60 、X狗 之类的浏览器,又可以再增加一个核了,因为 Blink 对速度做了优化,应该会比 WebKit 还要快,所以可能会被命名为超急速三核切换功能。总之,妈妈再也不用担心我的浏览器核心不够用了。

缓存

常用缓存机制

六种 H5 常用的缓存机制的优势及适用场景
  • Dom Storage(Web Storage)存储机制:
webSettings.setDomStorageEnabled(true);
  • Web SQL Database 存储机制(不推荐)
webSettings.setDatabaseEnabled(true);
final String dbPath = getApplicationContext().getDir("db",Context.MODE_PRIVATE).getPath();
webSettings.setDatabasePath(dbPath)
  • Application Cache 存储机制(不推荐)

Application Cache(简称 AppCache)似乎是为支持 Web App 离线使用而开发的缓存机制。它的缓存机制类似于浏览器的缓存(Cache-Control 和 Last-Modified)机制,都是以文件为单位进行缓存,且文件有一定更新机制。但 AppCache 是对浏览器缓存机制的补充,不是替代.
不过根据官方文档,AppCache 已经不推荐使用了,标准也不会再支持。现在主流的浏览器都是还支持 AppCache的,以后就不太确定了。同样给出 Android 端启用 AppCache 的代码。

WebSettings webSettings = myWebView.getSettings();
webSettings.setAppCacheEnabled(true);
final String cachePath = getApplicationContext().getDir("cache",Context.MODE_PRIVATE).getPath();
webSettings.setAppCachePath(cachePath);
webSettings.setAppCacheMaxSize(5*1024*1024);
  • Indexed Database 存储机制

IndexedDB 也是一种数据库的存储机制,但不同于已经不再支持的 Web SQL Database。IndexedDB 不是传统的关系数据库,可归为 NoSQL 数据库。IndexedDB 又类似于 Dom Storage 的 key-value 的存储方式,但功能更强大,且存储空间更大。

Android 在4.4开始加入对 IndexedDB 的支持,只需打开允许 JS 执行的开关就好了

WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
  • File System API (android不支持)

File System API 是 H5 新加入的存储机制。它为 Web App 提供了一个虚拟的文件系统,就像 Native App 访问本地文件系统一样。由于安全性的考虑,这个虚拟文件系统有一定的限制。Web App 在虚拟的文件系统中,可以进行文件(夹)的创建、读、写、删除、遍历等操作。很可惜到目前,Android 系统的 WebView 还不支持 File System API。

简单的介绍完了上面六种 H5 常用的缓存模式,想必大家能对 Android WebView 所支持的缓存模式有个粗略的了解。如果想和前端更好的配合使用 Android WebView 所支持的缓存,建议看下这篇文章《H5 缓存机制浅析 移动端 Web 加载性能优化》

清理缓存

  /**
     * 清除WebView缓存
     */
    public void clearWebViewCache() {

        //清理Webview缓存数据库
        try {
            deleteDatabase("WebView.db");
            deleteDatabase("WebViewCache.db");

            mWebView.clearHistory();
            mWebView.clearFormData();
            getCacheDir().delete();

            //清空所有Cookie
            CookieSyncManager.createInstance(this);
            CookieManager cookieManager = CookieManager.getInstance();
            cookieManager.removeAllCookie();
            CookieSyncManager.getInstance().sync();

        } catch (Exception e) {
            e.printStackTrace();
        }

        //WebView 缓存文件
        File appCacheDir = new File(getFilesDir().getAbsolutePath() + APP_CACAHE_DIRNAME);

        File webviewCacheDir = new File(getCacheDir().getAbsolutePath() + "/webviewCache");

        //删除webview 缓存目录
        if (webviewCacheDir.exists()) {
            deleteFile(webviewCacheDir);
        }
        //删除webview 缓存 缓存目录
        if (appCacheDir.exists()) {
            deleteFile(appCacheDir);
        }
    }

OOM (内存泄漏)

Webview在加载页面的时候会占用非常大的内存,当页面当中存在大量图片,占用内存的情况会更严重。打开新页面后快速回退,内存也不会释放。这时候就会导致OOM的问题出现。

解决方法:

  1. 通常根治这个问题的办法是为WebView开启另外一个进程,通过AIDL与主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。因为webview引发的 资源无法释放等问题 全部可以解决。

在xml中配置

android:process=":remote"
  1. 使用Crosswalk-lite 或者 Cordova webview代替

  2. 用个反射,自己关掉

public void setConfigCallback(WindowManager windowManager) {  
    try {  
        Field field = WebView.class.getDeclaredField("mWebViewCore");  
        field = field.getType().getDeclaredField("mBrowserFrame");  
        field = field.getType().getDeclaredField("sConfigCallback");  
        field.setAccessible(true);  
        Object configCallback = field.get(null);  
   
        if (null == configCallback) {  
            return;  
        }  
   
        field = field.getType().getDeclaredField("mWindowManager");  
        field.setAccessible(true);  
        field.set(configCallback, windowManager);  
    } catch(Exception e) {  
    }  
}  
public void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setConfigCallback((WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE));  
}  
   
public void onDestroy() {  
    setConfigCallback(null);  
    super.onDestroy();  
}  

缺点是,这个方法是依赖android.webkit implementation,android4.4之后就用chromium内核了,也就是4.4之后这个方法就不适用了。

  1. WebView 动态加载
WebView      mWebView = new WebView(getApplicationgContext());  
FrameLayout frameLayout      = findViewById(R.id.xwalk_view);  
frameLayout.addView(mWebView);
protected void onDestroy() {  
      super.onDestroy();  
      mWebView.removeAllViews();  
       ((ViewGroup) mWebView.getParent()).removeView(mWebView);
        mWebView.setTag(null);
        mWebView.clearHistory();
        mWebView.destroy();
        mWebView= null;
}

第四种方法,如果你需要在WebView中打开链接或者你打开的页面带有flash,获得你的WebView想弹出一个dialog,都会导致从ApplicationContext到ActivityContext的强制类型转换错误,从而导致你应用崩溃。这是因为在加载flash的时候,系统会首先把你的WebView作为父控件,然后在该控件上绘制flash,他想找一个Activity的Context来绘制他,但是你传入的是ApplicationContext。

视频播放

(待续...)

安全相关

源生JSInterface漏洞

漏洞描述

Android WebView的Js对象注入漏洞解决方案 - CSDN博客
在android 4.2 之前,webview存在着漏洞 ,通过js ,可以访问设备SD卡上的任何东西,甚至联系人、短信等。

当JS拿到Android这个对象后,就可以调用这个Android对象中所有的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行

1,WebView添加了JavaScript对象,并且当前应用具有读写SDCard的权限,也就是:android.permission.WRITE_EXTERNAL_STORAGE
2,JS中可以遍历window对象,找到存在“getClass”方法的对象的对象,然后再通过反射的机制,得到Runtime对象,然后调用静态方法来执行一些命令,比如访问文件的命令.
3,再从执行命令后返回的输入流中得到字符串,就可以得到文件名的信息了。然后想干什么就干什么,好危险。核心JS代码如下

function execute(cmdArgs)  
{  
    for (var obj in window) {  
        if ("getClass" in window[obj]) {  
            alert(obj);  
            return  window[obj].getClass().forName("java.lang.Runtime")  
                 .getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);  
        }  
    }  
}  

解决方案

@JavascriptInterface (android 4.2以上)

Android 4.2 以上的系统能通过声明 @JavascriptInterface来解决

class JsObject {  
   @JavascriptInterface  
   public String toString() { return "injectedObject"; }  
}  
webView.addJavascriptInterface(new JsObject(), "injectedObject");  
webView.loadData("", "text/html", null);  
webView.loadUrl("javascript:alert(injectedObject.toString())"); 
WebChromeClient 的 prompt() 进行拦截

常用对话框方法

Android通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调分别拦截JS对话框
(即上述三个方法),得到他们的消息内容,然后解析即可

因为只有prompt()可以返回任意类型的值,操作最全面方便、更加灵活

Js代码:

function clickprompt(){
    // 调用prompt()
    var result=prompt("js://demo?arg1=111&arg2=222");
    alert("demo " + result);
}

在WebChromeClient 中重写 onJsPrompt()

// 设置与Js交互的权限
webSettings.setJavaScriptEnabled(true);
// 设置允许JS弹窗   webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
 @Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// 根据协议的参数,判断是否是所需要的url(原理同方式2)
// 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
//假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)

Uri uri = Uri.parse(message);
// 如果url的协议 = 预先约定的 js 协议
// 就解析往下解析参数
if ( uri.getScheme().equals("js")) {

// 如果 authority  = 预先约定协议里的 webview,即代表都符合约定的协议
// 所以拦截url,下面JS开始调用Android需要的方法
 if (uri.getAuthority().equals("webview")) {

//
// 执行JS所需要调用的逻辑
                                                    System.out.println("js调用了Android的方法");
// 可以在协议上带有参数并传递到Android上
                                                    HashMap<String, String> params = new HashMap<>();
                                                    Set<String> collection = uri.getQueryParameterNames();

//参数result:代表消息框的返回值(输入值)
                                                    result.confirm("js调用了Android的方法成功啦");
}
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
 }

在Android的onJsPrompt()中 ,解析传递过来的信息,再通过反射机制调用Java对象的方法,这样实现安全的JS调用Android代码

prompt中是我们约定的字符串,它包含特定的标识符MyApp:,后面包含了一串JSON字符串,它包含了方法名,参数,对象名等。

问题:

由于通过反射的形式来得到指定对象的方法,他会把基类的方法也会得到,最顶层的基类就是Object,所以我们为了不把getClass方法注入到Js中,所以我们需要把Object的公有方法过滤掉

JSBridge

JSBridge也是使用prompt()方法拦截,只不过它定义了自己的通信协议。
完整的协议定义:

jsbridge://className:callbackAddress/methodName?jsonObj
jsbridge原理图

当js调用native功能时,应当指定native层要完成某个功能调用的类名(className)和方法名(methodName),以及js传递过来的参数(jsonObj)。port值是指当native需要将操作结果返回给js时,在js中定义一个callback,并将这个callback存储在指定的位置上,这个port就定义了callback的存储位置。

JSBridge类管理暴露给前端方法,前端调用的方法应该在此类中注册才可使用。register的实现是从Map中查找key是否存在,不存在则反射取得对应class中的所有方法,具体方法是在BridgeImpl中定义的,方法包括三个参数分别为WebView、JSONObject、CallBack。如果满足条件,则将所有满足条件的方法put到map中。

private static Map<String, HashMap<String, Method>> exposedMethods = new HashMap<>();
public static void register(String exposedName, Class<? extends IBridge> clazz) {
        if (!exposedMethods.containsKey(exposedName)) {
            try {
                exposedMethods.put(exposedName, getAllMethod(clazz));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


JSBridge的基本原理为:
H5->通过某种方式触发一个url->Native捕获到url,进行分析->原生做处理->Native调用H5的JSBridge对象传递回调。如下图

h5与原生页面通过jsbridge交互

第三方内核

TbsX5 (腾讯浏览器服务)

TbsX5基于谷歌Blink内核,并提供两种集成方案:
1)只共享微信手Q空间的x5内核(for share)
2)独立下载x5内核(with download)

当用户已经安装了微信或者手Q ,webview默认会使用 x5内核。反之,则使用系统默认的内核版本。

x5接入

辨别是否使用x5webview的方法:
显示网页文字时,可通过长按选择文字的标识判断,如下水滴状选择效果是x5webview 的标志:

x5成功接入

Crosswalk

Crosswalk是一款开源的Web引擎,其基于 Chromium/Blink 的应用运行环境

Crosswalk 跟 lite 版本大小对比

从官网的说明可以知道,使用Crosswalk后,apk的大小会增加20M 。 而使用Crosswalk Lite版本(移除较不常见的库和特性),apk的大小会增加10-15M。

  • Lite目前只支持Android,并且不支持共享模式
  • Lite只支持x86和ARM的32位版本。x86_64和ARM64都还未支持

因此,Crosswalk 会增大apk的大小 ,而lite 版本则兼容性存在问题。不是很推荐使用。

Cordova

Cordova是贡献给Apache后的开源项目,是从PhoneGap中抽出的核心代码,是驱动PhoneGap的核心引擎。Adobe将会继续以Cordova加上PhoneGap Build和Adobe Shadow的组合提供PhoneGap。

  • 移动应用程序使用Html、Css、JS
  • Cordova提供了一组设备相关的API,通过这组API,移动应用能够以JavaScript访问原生的设备功能,如摄像头、麦克风等。
  • 一次开发多个平台
  • 开源免费

Cordova应用开发
Android使用Cordova进行混合开发

可以在Cordova上面集成 Crosswalk webview插件

随着Cordova Android 4.0.0引入了对插入式webview的支持,现在你可以方便地在你的Cordova应用上使用Crosswalk的webview。通过使用Crosswalk的webview插件,开发者可以享用远程调试的功能,前沿的HTML5特性,例如WebGL, WebAudio和WebRTC,以及在包括Android 4.0 Ice Cream Sandwich(ICS)在内的Android设备上性能的显著提升。

$ cordova plugin add cordova-plugin-crosswalk-webview

缺点
Increased memory footprint
An overhead of ~30MB (as reported by the RSS column of ps)
Increased APK size (about 17MB) 增加apk大小大约17M
Increased size on disk when installed (about 50MB)
Crosswalk WebView stores data (IndexedDB, LocalStorage, etc) separately from System WebView
You'll need to manually migrate local data when switching between the two (note: this is fixed in Crosswalk 15)

优化

资源预加载

在页面加载的工程中,许多外部依赖的JS、CSS、图片等资源需要下载,我们能提前将这些资源下载好,等H5 加载时直接替换

好在从 API 11(Android 3.0)开始,WebView 引入了 shouldInterceptRequest函数,这个函数有两种重载。

  1. public WebResourceResponse shouldInterceptRequest(WebView webView, String url)从 API 11 引入,API 21 废弃

  2. public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)从 API 21 开始引入

考虑到目前大多数 App 还要支持 API 14,所以还是使用 shouldInterceptRequest (WebView view, String url)为例。

mWebView.setWebViewClient(new WebViewClient() {
            @Override
            public WebResourceResponse shouldInterceptRequest(WebView webView, final String url) {
                WebResourceResponse response = null;
                boolean resDown = JSHelper.isURLDownValid(url);
                if (resDown) {
                    jsStr = JsjjJSHelper.getResInputStream(url);
                    if (url.endsWith(".png")) {
                        response = getWebResourceResponse(url, "image/png", ".png");
                    } else if (url.endsWith(".gif")) {
                        response = getWebResourceResponse(url, "image/gif", ".gif");
                    } else if (url.endsWith(".jpg")) {
                        response = getWebResourceResponse(url, "image/jepg", ".jpg");
                    } else if (url.endsWith(".jepg")) {
                        response = getWebResourceResponse(url, "image/jepg", ".jepg");
                    } else if (url.endsWith(".js") && jsStr != null) {
                        response = getWebResourceResponse("text/javascript", "UTF-8", ".js");
                    } else if (url.endsWith(".css") && jsStr != null) {
                        response = getWebResourceResponse("text/css", "UTF-8", ".css");
                    } else if (url.endsWith(".html") && jsStr != null) {
                        response = getWebResourceResponse("text/html", "UTF-8", ".html");
                    }
                }
                return response;
            }
        });
        private WebResourceResponse getWebResourceResponse(String url, String mime, String style) {
        WebResourceResponse response = null;
        try {
            response = new WebResourceResponse(mime, "UTF-8", new FileInputStream(new File(getJSPath() + TPMD5.md5String(url) + style)));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return response;
    }
    public String getJsjjJSPath() {
        String splashTargetPath = JarEnv.sApplicationContext.getFilesDir().getPath() + "/JS";
        if (!TPFileSysUtil.isDirFileExist(splashTargetPath)) {
            TPFileSysUtil.createDir(splashTargetPath);
        }
        return splashTargetPath + "/";
    }

JS本地化延迟加载

(待续)

WebView初始化时间优化

webview

全局WebView

方法:
在客户端刚启动时,就初始化一个全局的WebView待用,并隐藏;
当用户访问了WebView时,直接使用这个WebView加载对应网页,并展示。
这种方法可以比较有效的减少WebView在App中的首次打开时间。当用户访问页面时,不需要初始化WebView的时间。

当然这也带来了一些问题,包括:
额外的内存消耗。
页面间跳转需要清空上一个页面的痕迹,更容易内存泄露。

客户端代理数据请求

方法:
在客户端初始化WebView的同时,直接由native开始网络请求数据;
当页面初始化完成后,向native获取其代理请求的数据。
此方法虽然不能减小WebView初始化时间,但数据请求和WebView初始化可以并行进行,总体的页面加载时间就缩短了;缩短总体的页面加载时间:

webview调试

从Android 4.4 (KitKat)开始,使用Chrome开发者工具可以远程调试Webview网页内容

  1. 将webview 设置成调试模式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    WebView.setWebContentsDebuggingEnabled(true);
}
  1. 使用USB连接PC 与 设备 ,保证adb 一直运行着
  2. 在chrome中输入
chrome://inspect
  1. 点击inspect , 进入调试界面
调试界面

参考:
Android WebView的基本使用与注意点 - 简书
关于Android WebView的那些事 - 简书
Android WebView的Js对象注入漏洞解决方案 - CSDN博客
Android WebView基本使用 - CSDN博客
WebChromeClient常用API与功能使用详解 - 简书
Android WebView:性能优化不得不说的事 - Android - 掘金
Android JSBridge的原理与实现 - CSDN博客
Android中JSBridge的原理与实现 - 简书
WebView性能、体验分析与优化 -

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容