WebView

webview是Android中的一个显示网页的控件,也叫网页视图.是内置webkit内核的高性能浏览器.

它是前端和客户端之间交互的一个媒介,好处就是即提高业务开发的效率,减轻客户端工作量,也能借助到客户端强大的底层能力,缺点自然是前端的性能和功能局限性,需要权衡好两者之间的一个度

除了本身的方法,还提供了几个常用的工具类的部分API

WebChromeClient

方法 作用
onJsAlert(WebView view,String url,String message,JsResult result) 处理Js中的Alert对话框
onJsConfirm(WebView view,String url,String message,JsResult result) 处理Js中的Confirm对话框
onJsPrompt(WebView view,String url,String message,String defaultValue,JsPromptResult result) 处理Js中的Prompt对话框
onReceivedIcon(WebView view, Bitmap icon) 获得网页的icon
onReceivedTitle(WebView view, String title) 获得网页的标题

WebViewClient

方法 作用
onPageStared(WebView view,String url) 通知主程序网页开始加载
onPageFinished(WebView view,String url,Bitmap favicon) 通知主程序,网页加载完毕
doUpdateVisitedHistory(WebView view,String url,boolean isReload) 更新历史记录
onLoadResource(WebView view,String url) 通知主程序WebView即将加载指定url的资源
onScaleChanged(WebView view,float oldScale,float newScale) ViewView的缩放发生改变时调用
shouldOverrideKeyEvent(WebView view,KeyEvent event) 控制webView是否处理按键时间,如果返回true,则WebView不处理,返回false则处理
shouldOverrideUrlLoading(WebView view,String url) 控制对新加载的Url的处理,返回true,说明主程序处理WebView不做处理,返回false意味着WebView会对其进行处理
onReceivedError(WebView view,int errorCode,String description,String failingUrl) 遇到不可恢复的错误信息时调用

WebSettings

方法 作用
getSettings() 返回一个WebSettings对象,用来控制WebView的属性设置
loadUrl(String url) 加载指定的Url
loadData(String data,String mimeType,String encoding) 加载指定的Data到WebView中.使用"data:"作为标记头,该方法不能加载网络数据.其中mimeType为数据类型如:textml,image/jpeg. encoding为字符的编码方式
loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl) 比上面的loadData更加强大
setWebViewClient(WebViewClient client) 为WebView指定一个WebViewClient对象.WebViewClient可以辅助WebView处理各种通知,请求等事件。
setWebChromeClient(WebChromeClient client) 为WebView指定一个WebChromeClient对象,WebChromeClient专门用来辅助WebView处理js的对话框,网站title,网站图标,加载进度条等

创建webview

public class MainActivity extends AppCompatActivity {

    private WebView webView;
    private long exitTime = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        webView = new WebView(this);
        webView.setWebViewClient(new WebViewClient() {
            //设置在webView点击打开的新网页在当前界面显示,而不跳转到新的浏览器中
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });
        webView.getSettings().setJavaScriptEnabled(true);  //设置WebView属性,运行执行js脚本
        webView.loadUrl("xxx");          //调用loadUrl方法为WebView加入链接
        setContentView(webView);                           //调用Activity提供的setContentView将webView显示出来
    }
}

其中加载页面资源方式有几种

// 加载网页
webView.loadUrl("https://www.xxx.com")
// 加载apk包内资源
webView.loadUrl("file:///xxx/xxx.html")
// 加载手机本地资源
webView.loadUrl("content://xxx.xxx.html")
// 加载页面部分内容
webView.loadUrl(String data, String mimeType, String encoding)

阻止后退

一般在webview点击后退会直接关闭,需要重写控制它历史记录后退

    @Override
    public void onBackPressed() {
        if (webView.canGoBack()) {
            webView.goBack();
        } else {
            if ((System.currentTimeMillis() - exitTime) > 2000) {
                Toast.makeText(getApplicationContext(), "再按一次退出程序",
                        Toast.LENGTH_SHORT).show();
                exitTime = System.currentTimeMillis();
            } else {
                super.onBackPressed();
            }

        }
    }

获取和设置WebView的Cookie数据

@Override
public void onPageFinished(WebView view, String url) {    
    // 读取
    CookieManager cookieManager = CookieManager.getInstance();
    String CookieStr = cookieManager.getCookie(url);
    Log.e("HEHE", "Cookies = " + CookieStr);
    super.onPageFinished(view, url);
    
    // 设置
    CookieSyncManager.createInstance(MainActivity.this);  
    CookieManager cookieManager = CookieManager.getInstance();  
    cookieManager.setAcceptCookie(true);  
    cookieManager.setCookie(url, cookies);  //cookies是要设置的cookie字符串 
    CookieSyncManager.getInstance().sync();
}

Javascript 调用 Native

  1. 定义一个类,用于将数据暴露出来,JS通过该类暴露的方法来调用Native
  2. 我们在WebView所在页面使用下述代码:

    webview.getSettings().setJavaScriptEnabled(true);
    webview.addJavascriptInterface(object,"name");
  3. 然后js或者html中调用对象里的暴露的方法:

setJavaScriptEnabled在Android 4.4以前的系统才有效

从Android 4.4开始,Android中的WebView不再是基于WebKit的,而是开始基于Chromium,这个改变 使得WebView的性能大幅提升,并且对HTML5,CSS,JavaScript有了更好的支持!

虽然chromium完全取代了以前的WebKit for Android,但Android WebView的API接口并没有变, 与老的版本完全兼容。这样带来的好处是基于WebView构建的APP,无需做任何修改, 就能享受chromium内核的高效与强大。

Android 4.4后WebView的一些注意事项

1.多线程

如果你在子线程中调用WebView的相关方法,而不在UI线程,则可能会出现无法预料的错误。 所以,当你的程序中需要用到多线程时候,也请使用runOnUiThread()方法来保证你关于 WebView的操作是在UI线程中进行的:
runOnUiThread(newRunnable(){
@Override
publicvoid run(){
   // Code for WebView goes here
   }
});

2.线程阻塞

永远不要阻塞UI线程,这是开发Android程序的一个真理。我们却往往不自觉的一个开发中常犯的错误就是:在UI线程中去等待JavaScript 的回调。 例如:

// This code is BAD and will block the UI thread
webView.loadUrl("javascript:fn()"); 
while(result ==null) {  
    Thread.sleep(100); 
}

Android 4.4中,提供了新的Api来做这件事情。 evaluateJavascript() 就是专门来异步执行JavaScript代码的

3.evaluateJavascript() 方法

mWebView.evaluateJavascript(script, new ValueCallback<String>() {
 @Override
 public void onReceiveValue(String value) {
      //TODO
 }
});

4.处理WebView中url的跳转

新版WebView对于自定义scheme的url跳转,新增了更为严格的限制条件。 当你实现了 shouldOverrideUrlLoading() 或 shouldInterceptRequest() 回调,WebView 也只会在跳转url是合法Url时才会跳转

// The URL scheme should be non-hierarchical (no trailing slashes)
 privatestaticfinalString APP_SCHEME ="example-app:";
 @Override 
 publicboolean shouldOverrideUrlLoading(WebView view,String url){
     if(url.startsWith(APP_SCHEME)){
         urlData =URLDecoder.decode(url.substring(APP_SCHEME.length()),"UTF-8");
         respondToData(urlData);
         returntrue;
     }
     returnfalse;
}

5.UserAgent变化

mWebView.getSettings().setUserAgentString(ua);
mWebView.getSettings().getUserAgentString();

6.使用addJavascriptInterface()的注意事项

从Android4.2开始。 只有添加 @JavascriptInterface 声明的Java方法才可以被JavaScript调用

class JsObject {
    @JavascriptInterface
    public String toString() { return "injectedObject"; }
}

webView.addJavascriptInterface(new JsObject(), "injectedObject");
webView.loadData("", "text/html", null);
webView.loadUrl("javascript:alert(injectedObject.toString())");

7.Remote Debugging

新版的WebView还提供了一个很厉害的功能:使用Chrome来调试你运行在WebView中的程序

UIWebView

这是iOS2推出的东西,存在严重的性能和内存消耗问题直到iOS8之后逐渐被WKWebView所取代,所以就不必介绍了

WKWebView

优势

1、在性能、稳定性、功能方面有很大提升
2、更多的支持 HTML5 的特性
3、官方宣称的高达60fps的滚动刷新率以及内置手势
4、Safari 相同的 JavaScript 引擎
5、将 UIWebViewDelegate 与 UIWebView 拆分成了14类与3个协议,包含该更细节功能的实现。

实践

以编程方式创建WKWebView

import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate {
    
    var webView: WKWebView!
    
    override func loadView() {
        let webConfiguration = WKWebViewConfiguration()
        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.uiDelegate = self
        view = webView
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let myURL = URL(string:"https://www.apple.com")
        let myRequest = URLRequest(url: myURL!)
        webView.load(myRequest)
    }}

要允许用户对历史记录进行前进后退操作,可以使用goBack()goForward()方法作为按钮操作,如果不想用户进行对应行为可以使用canGoBackcanGoForward属性进行禁止

一般来说,网页视图会自动将页面内容中出现的电话号码转为号码链接,点击的时候会自动唤起拨号面榜进行拨号,可以使用不包含phoneNumber标志的WKDataDetectorTypes位字段设置dataDetectorTypes属性

您还可以使用setMagnification(_:centeredAt:)来以编程方式设置web内容第一次在web视图中显示时的比例。然后,用户可以使用手势改变比例。

还有其他API如下,具体可以查看https://developer.apple.com/d...

配置和偏好

  • WKWebViewConfiguration
  • WKPreferences

进程分配

  • WKProcessPool

页面导航管理

  • WKNavigationDelegate
  • WKNavigation
  • WKNavigationAction
  • WKNavigationResponse
  • WKBackForwardList
  • WKBackForwardListItem

用户界面

  • WKUIDelegate
  • WKWindowFeatures

脚本注入

  • WKUserContentController
  • WKScriptMessage
  • WKUserScript

缓存数据和持久化

  • WKWebsiteDataStore
  • WKHTTPCookieStore
  • WKHTTPCookieStoreObserver
  • WKWebsiteDataRecord

处理WebKit无法处理的URL Scheme类型的资源

  • WKURLSchemeHandler
  • WKURLSchemeTask

页面结构

  • WKFrameInfo
  • WKSecurityOrigin

内容拦截

  • WKContentRuleList
  • WKContentRuleListStore

3D-Touch预览

  • WKPreviewElementInfo
  • WKPreviewActionItem
  • UIPreviewActionItem

Javascript 调用 Native(以下代码未经验证,仅限示例)

1, 通过webView代理方法,拦截webView加载过程中的信息

Android的 webview 提供了 shouldOverrideUrlLoading 方法提供给 Native 拦截

public class CustomWebViewClient extends WebViewClient {
  @Override
  public boolean shouldOverrideUrlLoading(WebView view, String url) {
  ......
    // 场景一:拦截请求、接收 scheme
    if (url.equals("xxx")) {

       // handle
       ...
       // callback
       view.loadUrl("javascript:setAllContent(" + json + ");")
       return true;
     }
     return super.shouldOverrideUrlLoading(url);
   }
}
当URL即将在当前WebView中加载时,给主机应用程序一个控制控件的机会。如果没有提供WebViewClient,默认的WebView将要求活动管理器选择URL的正确处理程序。如果提供了WebViewClient,返回true会导致当前WebView中止加载URL,而返回false会导致WebView继续像往常一样加载URL。

iOS使用WKNavigationDelegate中的代理方法,拦截自定义的URL来实现JS调用OC方法。

#pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    NSURL *URL = navigationAction.request.URL;
    NSString *scheme = [URL scheme];
    if ([scheme isEqualToString:@"haleyaction"]) {
        [self handleCustomAction:URL];
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }
    decisionHandler(WKNavigationActionPolicyAllow);
}

2. 重写Javascript原生方法(alert、confirm、prompt、console.log)

android拦截事件分别为 onJsAlert、onJsConfirm、onConsoleMessage、onJsPrompt

@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
    AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
    builder.setTitle("")
            .setMessage(message)
            .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    result.confirm();
                }
            })
            .setCancelable(false)
            .create()
            .show();
    return true;
}

iOS因为安全机制,WKWebView 对原生方法做了拦截,需要实现 WKUIDelegate 代理方法,不适用这种方式

注入API

Android 的 Webview 提供了 addJavascriptInterface 方法,支持 Android 4.2 及以上系统。

gpcWebView.addJavascriptInterface(new JavaScriptInterface(), 'nativeApiBridge');
public class JavaScriptInterface {
  Context mContext;

  JavaScriptInterface(Context c) {
    mContext = c;
  }

  public void share(String webMessage){            
    // Native 逻辑
  }
}

Javascript调用如下

window.NativeApi.share(xxx);

iOS 的 UIWebview 提供了 JavaScriptScore 方法,支持 iOS 7.0 及以上系统。WKWebview 提供了 window.webkit.messageHandlers 方法,支持 iOS 8.0 及以上系统。

WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
WKPreferences *preferences = [WKPreferences new];
preferences.javaScriptCanOpenWindowsAutomatically = YES;
preferences.minimumFontSize = 40.0;
configuration.preferences = preferences;
    

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"share"];
      [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"pickImage"];
}
- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self.webView.configuration.userContentController     removeScriptMessageHandlerForName:@"share"];
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"pickImage"];
}

Javascript调用如下

window.webkit.messageHandlers.share.postMessage(xxx);

Native 调用 Javascript(以下代码未经验证,仅限示例)

页面只需要把方法暴露到全局即可,主要需要实现Native调用方式

Android 中主要有两种方式实现。在 4.4 以前,通过 loadUrl 方法,执行一段 JS 代码来实现。在 4.4 以后,可以使用 evaluateJavascript 方法实现。loadUrl 方法使用起来方便简洁,但是效率低无法获得返回结果且调用的时候会刷新 WebView。evaluateJavascript 方法效率高获取返回值方便,调用时候不刷新WebView,但是只支持 Android 4.4+。

webView.loadUrl("javascript:" + javaScriptString);
webView.evaluateJavascript(javaScriptString, new ValueCallback<String>() {
  @Override
  public void onReceiveValue(String value){
    xxx
  }
});

iOS 在 WKWebview 中可以通过 evaluateJavaScript:javaScriptString 来实现,支持 iOS 8.0 及以上系统。

- (void)webView:(WKWebView *)tmpWebView didFinishNavigation:(WKNavigation *)navigation{

    //say()是JS方法名,completionHandler是异步回调block
    [webView evaluateJavaScript:@"say()" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        NSLog(@"%@",result);
    }];
    
}
// objective-c
[jsContext evaluateJavaScript:@"ZcyJsBridge(ev, data)"]

Afterward
621 声望62 粉丝

努力去做,对的坚持,静待结果