Android WebView注入CSS+JS的项目解决方案

在某个项目中,由于用到第三方的接口,登录和授权的界面也由第三方提供,而我们是在车机产品上使用,分辨率为800X480。第三方并没有针对这个分辨率的布局,并且不为我们进行修改,就算是高德去推也没有推动。因此,只能使用偏门的方法,之所以说偏门,是因为注入css和js的方式是有风险的,假如web端改了页面,可能我们的app就得跟着升级了。好了,下面开始正题

  1. 首先,要在WebView注入JS,我们需要开启JS的支持:
    mWebView = (WebView) findViewById(R.id.wb_login);
    //访问的html中支持js
    mWebView.getSettings().setJavaScriptEnabled(true);
    //设置背景色
    mWebView.setBackgroundColor(0);
    //设置透明度
    mWebView.getBackground().setAlpha(0);
    mWebView.setWebViewClient(new WebViewClient() {
  2. 重写WebViewClient中的关键方法
    shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
    Give the host application a chance to take over the control when a new url is about to be loaded in the current WebView.
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        //在当前的webview中跳转到新的url
    
        //获取cookies
        CookieManager cm = CookieManager.getInstance();
        cookies = cm.getCookie(url);
        Log.d(TAG, "shouldOverrideUrlLoading url=" + url);
        Log.d(TAG, "shouldOverrideUrlLoading cookies ... " + cookies);
        if (url.contains("code=")) {
            mAutoMapCode = utils.getURLValue(url, "code");
            loginLayout.setVisibility(View.GONE);
            registerLayout.setVisibility(View.VISIBLE);
        }
    
        view.loadUrl(url);
        return true;
    }

我在demo中重写shouldOverrideUrlLoading这个函数主要是为了在Web进行重定向时,显示和隐藏一些Android界面的按钮,如果没有特殊的需求,不需要重写这个函数

onPageFinished(WebView view, String url)
Notify the host application that a page has finished loading.
@Override
public void onPageFinished(WebView view, String url) {
    Log.d(TAG, "onPageFinished url = " + url);
    if (url.contains("passport.xiami.com/qrcode-login")) {
        view.loadUrl("javascript:(function() {" +
                "var parent = document.getElementsByTagName('head').item(0);" +
                "var style = document.createElement('style');" +
                "style.type = 'text/css';" +
                "style.innerHTML = window.atob('" + qrcss + "');" +
                "parent.appendChild(style)" +
                "})()");
    } else
        if (url.contains("oauth.xiami.com/authorize")) {
        view.loadUrl("javascript:(function() {" +
                "var parent = document.getElementsByTagName('head').item(0);" +
                "var style = document.createElement('style');" +
                "style.type = 'text/css';" +
                "style.innerHTML = window.atob('" + authcss + "');" +
                "parent.appendChild(style);" +
                "})()");
    } else {
    super.onPageFinished(view, url);
    }
}

我们可以在onPageFinished回调函数中,通过js脚本的方式动态注入css,上面就是示例代码。但是不建议在onPageFinished函数中完成脚本的注入,因为执行到onPageFinished的时候页面已经加载完成了,这样的修改会导致界面有一个闪烁的变化的过程,我们有更好的方式来完成css和js的注入或者替换,下面会说到。

shouldInterceptRequest(WebView view, WebResourceRequest request)
Notify the host application of a resource request and allow the application to return the data.

重头戏来了,shouldInterceptRequest是在非UI线程中运行的,那么我们可以在这里做网络访问等耗时的操作。其次,网络的响应数据是在这里返回的,因此在这里进行处理就可以解决在onPageFinished中的闪烁问题。

@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
    Log.d(TAG, "shouldInterceptRequest url=" + request.getUrl());
    if (request.getUrl().toString().contains("https://passport.xiami.com/qrcode-login")) {
        //通过URL主动请求整个html回来,然后注入CSSJS
        String page = mCssUtils.getUrlData(request.getUrl().toString(),"css/qr_code.css", "js/disableclick.js");
        return new WebResourceResponse("text/html", "utf-8", new ByteArrayInputStream(page.getBytes()));
    }
    else if(request.getUrl().toString().contains("oauth.xiami.com/authorize")){
        //通过URL主动请求整个html回来,然后注入CSSJS
        String page = mCssUtils.getUrlData(request, cookies,"css/auth.css", null);
        return new WebResourceResponse("text/html", "utf-8", new ByteArrayInputStream(page.getBytes()));
    }
    //因为授权界面的修改复杂一些,针对特定的资源一个个修改
    //左方向箭头
    else if (request.getUrl().toString().contains("https://gtms02.alicdn.com/tps/i2")) {
        try {
            return new WebResourceResponse("image/png", "UTF-8", getAssets().open("img/left.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //右方向箭头
    else if (request.getUrl().toString().contains("https://gtms02.alicdn.com/tps/i3")) {
        try {
            return new WebResourceResponse("image/png", "UTF-8", getAssets().open("img/right.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }//加载CSS的时候,附加我们自己的css上去
    else if (request.getUrl().toString().contains("https://oauth.xiami.com/assets/css/oauth-us.css")) {
        String css = mCssUtils.appendCss(request.getUrl().toString(), "css/auth.css");
        return new WebResourceResponse("text/css", "UTF-8", new ByteArrayInputStream(css.getBytes()));
    }
    return super.shouldInterceptRequest(view, request);
}

在这里,我列举了两种方法,对返回的html进行css和js注入。

1、方法一是通过请求的url,主动去访问,请求整个html回来,注入css和js,注意因为这个时候此方法还没有返回,所以界面不会刷新,也就解决了闪烁的问题,当我们返回注入过css和js的WebResourceResponse对象时,界面才会加载。

2、方法二是针对要请求的资源类型,如image、css、js等,拦截后给系统返回我们自定义的css和js,达到注入修改的目的

注入的过程中,注意css注入在head最后位置,js注入在body最后位置,其实就是要让自己的css和js最后执行,防止被后面的代码覆盖。我在项目中注入的html中就在</body>的结束前还有一段js代码,一开始没注意,我注入的js一直未起效。

原始的二维码界面:

经过css和js注入后的界面如下:

原始的授权界面:

注入css和js后的界面:

代码中我自定义了一个cssutil工具类,里面完成了关键代码如下:

/**
 * 描述:根据给定的网址,获取返回的html注入cssjs
 * 作者:LeoHo
 *
 * @param
 * @return
 * @throw
 */
public String getUrlData(WebResourceRequest request, String cookies, String cssPath, String jsPath) {
    String page = getHtml(request, cookies);
    String css = "";
    if (cssPath != null && !cssPath.isEmpty()) {
        css = buildCss(cssPath);
    }
    String js = "";
    if (jsPath != null && !jsPath.isEmpty()) {
        js = buildJS(jsPath);
    }
    page = inject(page, css, js);
    return page;
}
 
 
 
 
/**
 * 描述:请求指定的url并返回html页字符串
 * 作者:LeoHo
 *
 * @param
 * @return
 * @throw
 */
private String getHtml(WebResourceRequest request, String cookies) {
    StringBuilder total = new StringBuilder();
    try {
        URL url = new URL(request.getUrl().toString());
        Map<String, String> headers = request.getRequestHeaders();
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        //设置请求的header
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
            connection.setRequestProperty(entry.getKey(), entry.getValue());
        }
        //扫码请求登录网页cookies一定要设置,不然后台判断登录状态会出错
        if (cookies != null && !cookies.isEmpty()) {
            connection.setRequestProperty("Cookie", cookies);
        }
        connection.setRequestMethod(request.getMethod());
        connection.connect();
        InputStream is = connection.getInputStream();
        String encoding = connection.getContentEncoding();
        if (encoding == null) {
            encoding = mDefaultEncoding;
        }
        BufferedReader r = new BufferedReader(new InputStreamReader(is, encoding));
        String line;
        while ((line = r.readLine()) != null) {
            total.append(line);
        }
        is.close();
        connection.disconnect();
    } catch (Exception e) {
        e.printStackTrace();
    }

    return total.toString();
}
/**
 * 描述:根据指定路径读取css文件
 * 作者:LeoHo
 *
 * @param
 * @return
 * @throw
 */
private String buildCss(String css) {
    StringBuilder contents = new StringBuilder();

    InputStreamReader reader;
    try {
        reader = new InputStreamReader(mContext.getAssets().open(css), mDefaultEncoding);
        BufferedReader br = new BufferedReader(reader);

        String line;
        while ((line = br.readLine()) != null) {
            contents.append(line);
        }
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    return "<style type=\"text/css\">" + contents.toString().trim().replace("\n", "") + "</style>";
}
/**
 * 描述:根据给定路径读取js文件
 * 作者:LeoHo
 *
 * @param
 * @return
 * @throw
 */
private String buildJS(String jsPath) {
    StringBuilder contents = new StringBuilder();

    InputStreamReader reader;
    try {
        reader = new InputStreamReader(mContext.getAssets().open(jsPath), mDefaultEncoding);
        BufferedReader br = new BufferedReader(reader);

        String line;
        while ((line = br.readLine()) != null) {
            contents.append(line);
        }
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    return "<script type=\"text/javascript\">" + contents.toString().trim().replace("\n", "") + "</script>";
}
/**
 * 描述:给定的HTML中插入cssjs代码
 * 作者:LeoHo
 *
 * @param
 * @return
 * @throw
 */
private String inject(String page, String css, String js) {
    int headEnd = page.indexOf("</head>");
    String res;
    if (headEnd > 0) {
        res = page.substring(0, headEnd) + css + page.substring(headEnd, page.length());
    } else {
        res = "<head>" + css + "</head>" + page;
    }

    int bodyEnd = res.indexOf("</body>");
    if (bodyEnd > 0) {
        res = res.substring(0, bodyEnd) + js + res.substring(bodyEnd, res.length());
    }
    return res;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值