【问题标题】:Android WebView -> Display WebArchiveAndroid WebView -> 显示 WebArchive
【发布时间】:2012-10-04 00:50:27
【问题描述】:

Android 的 WebView 从 API 级别 11 开始就有这个 saveWebArchive 方法:http://developer.android.com/

它可以将整个网站保存为网络档案,这很棒!但是如何将下载的内容恢复到 webview 中?我试过了

webview.loadUrl(Uri.fromFile(mywebarchivefile));

但这只会在屏幕上显示xml。

【问题讨论】:

  • 查看我关于如何为所有 API 保存和加载存档的答案here

标签: android webview webarchive


【解决方案1】:

2014 年 2 月 21 日更新

我在下面发布的答案不适用于在 Android 4.4 KitKat 及更高版本下保存的网络存档文件。 Android 4.4“KitKat”(也可能是较新版本)下的 WebView 的 saveWebArchive() 方法不会将 Web 存档保存在此阅读器代码在下面发布的 XML 代码中。相反,它以 MHT (MHTML) 格式保存页面。读回 .mht 文件很容易 - 只需使用:

webView.loadUrl("file:///my_dir/mySavedWebPage.mht");

就是这样,比以前的方法简单得多,并且与其他平台兼容。

以前发布过

我自己也需要它,而且在我搜索的所有地方,都有这样的悬而未决的问题。所以我不得不自己解决。下面是我的小 WebArchiveReader 类和有关如何使用它的示例代码。请注意,尽管 Android 文档声明 shouldInterceptRequest() 已添加到 API11 (Honeycomb) 中的 WebViewClient 中,但此代码可以正常工作,并且在低至 API8 (Froyo) 的 Android 模拟器中已成功测试。以下是所需的所有代码,我还将完整的项目上传到 GitHub 存储库https://github.com/gregko/WebArchiveReader

文件 WebArchiveReader.java:

package com.hyperionics.war_test;

import android.util.Base64;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import org.w3c.dom.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;

public abstract class WebArchiveReader {
    private Document myDoc = null;
    private static boolean myLoadingArchive = false;
    private WebView myWebView = null;
    private ArrayList<String> urlList = new ArrayList<String>();
    private ArrayList<Element> urlNodes = new ArrayList<Element>();

    abstract void onFinished(WebView webView);

    public boolean readWebArchive(InputStream is) {
        DocumentBuilderFactory builderFactory =
                DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = null;
        myDoc = null;
        try {
            builder = builderFactory.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        }
        try {
            myDoc = builder.parse(is);
            NodeList nl = myDoc.getElementsByTagName("url");
            for (int i = 0; i < nl.getLength(); i++) {
                Node nd = nl.item(i);
                if(nd instanceof Element) {
                    Element el = (Element) nd;
                    // siblings of el (url) are: mimeType, textEncoding, frameName, data
                    NodeList nodes = el.getChildNodes();
                    for (int j = 0; j < nodes.getLength(); j++) {
                        Node node = nodes.item(j);
                        if (node instanceof Text) {
                            String dt = ((Text)node).getData();
                            byte[] b = Base64.decode(dt, Base64.DEFAULT);
                            dt = new String(b);
                            urlList.add(dt);
                            urlNodes.add((Element) el.getParentNode());
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            myDoc = null;
        }
        return myDoc != null;
    }

    private byte [] getElBytes(Element el, String childName) {
        try {
            Node kid = el.getFirstChild();
            while (kid != null) {
                if (childName.equals(kid.getNodeName())) {
                    Node nn = kid.getFirstChild();
                    if (nn instanceof Text) {
                        String dt = ((Text)nn).getData();
                        return Base64.decode(dt, Base64.DEFAULT);
                    }
                }
                kid = kid.getNextSibling();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public boolean loadToWebView(WebView v) {
        myWebView = v;
        v.setWebViewClient(new WebClient());
        WebSettings webSettings = v.getSettings();
        webSettings.setDefaultTextEncodingName("UTF-8");

        myLoadingArchive = true;
        try {
            // Find the first ArchiveResource in myDoc, should be <ArchiveResource>
            Element ar = (Element) myDoc.getDocumentElement().getFirstChild().getFirstChild();
            byte b[] = getElBytes(ar, "data");

            // Find out the web page charset encoding
            String charset = null;
            String topHtml = new String(b).toLowerCase();
            int n1 = topHtml.indexOf("<meta http-equiv=\"content-type\"");
            if (n1 > -1) {
                int n2 = topHtml.indexOf('>', n1);
                if (n2 > -1) {
                    String tag = topHtml.substring(n1, n2);
                    n1 = tag.indexOf("charset");
                    if (n1 > -1) {
                        tag = tag.substring(n1);
                        n1 = tag.indexOf('=');
                        if (n1 > -1) {
                            tag = tag.substring(n1+1);
                            tag = tag.trim();
                            n1 = tag.indexOf('\"');
                            if (n1 < 0)
                                n1 = tag.indexOf('\'');
                            if (n1 > -1) {
                                charset = tag.substring(0, n1).trim();
                            }
                        }
                    }
                }
            }

            if (charset != null)
                topHtml = new String(b, charset);
            else
                topHtml = new String(b);
            String baseUrl = new String(getElBytes(ar, "url"));
            v.loadDataWithBaseURL(baseUrl, topHtml, "text/html", "UTF-8", null);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    private class WebClient extends WebViewClient {
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
            if (!myLoadingArchive)
                return null;
            int n = urlList.indexOf(url);
            if (n < 0)
                return null;
            Element parentEl = urlNodes.get(n);
            byte [] b = getElBytes(parentEl, "mimeType");
            String mimeType = b == null ? "text/html" : new String(b);
            b = getElBytes(parentEl, "textEncoding");
            String encoding = b == null ? "UTF-8" : new String(b);
            b = getElBytes(parentEl, "data");
            return new WebResourceResponse(mimeType, encoding, new ByteArrayInputStream(b));
        }

        @Override
        public void onPageFinished(WebView view, String url)
        {
            // our WebClient is no longer needed in view
            view.setWebViewClient(null);
            myLoadingArchive = false;
            onFinished(myWebView);
        }
    }
}

这里是如何使用这个类,示例 MyActivity.java 类:

package com.hyperionics.war_test;

import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import java.io.IOException;
import java.io.InputStream;

public class MyActivity extends Activity {

    // Sample WebViewClient in case it was needed...
    // See continueWhenLoaded() sample function for the best place to set it on our webView
    private class MyWebClient extends WebViewClient {
        @Override
        public void onPageFinished(WebView view, String url)
        {
            Lt.d("Web page loaded: " + url);
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        WebView webView = (WebView) findViewById(R.id.webView);
        try {
            InputStream is = getAssets().open("TestHtmlArchive.xml");
            WebArchiveReader wr = new WebArchiveReader() {
                void onFinished(WebView v) {
                    // we are notified here when the page is fully loaded.
                    continueWhenLoaded(v);
                }
            };
            // To read from a file instead of an asset, use:
            // FileInputStream is = new FileInputStream(fileName);
            if (wr.readWebArchive(is)) {
                wr.loadToWebView(webView);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    void continueWhenLoaded(WebView webView) {
        Lt.d("Page from WebArchive fully loaded.");
        // If you need to set your own WebViewClient, do it here,
        // after the WebArchive was fully loaded:
        webView.setWebViewClient(new MyWebClient());
        // Any other code we need to execute after loading a page from a WebArchive...
    }
}

为了让事情更完整,这是我的 Lt.java 调试输出小类:

package com.hyperionics.war_test;

import android.util.Log;

public class Lt {
    private static String myTag = "war_test";
    private Lt() {}
    static void setTag(String tag) { myTag = tag; }
    public static void d(String msg) {
        // Uncomment line below to turn on debug output
        Log.d(myTag, msg == null ? "(null)" : msg);
    }
    public static void df(String msg) {
        // Forced output, do not comment out - for exceptions etc.
        Log.d(myTag, msg == null ? "(null)" : msg);
    }
}

希望这有帮助。

2013 年 7 月 19 日更新

某些网页没有指定文本编码的元标记,然后我们上面显示的代码无法正确显示字符。在此代码的 GitHub 版本中,我现在添加了字符集检测算法,它在这种情况下猜测编码。再次,请参阅https://github.com/gregko/WebArchiveReader

格雷格

【讨论】:

  • 太棒了!我希望有一个内置的解决方案,但缺乏这个,这似乎是一个可靠的选择。谢谢!
  • 谢谢你,Jouke!我很高兴我能够发布一些有用的东西。我也将示例项目上传到了github:github.com/gregko/WebArchiveReader
  • 感谢您的回答,但 saveWebArchive 可从 api 11 获得,我想支持保存网页表单 api 8 及更高版本。如果您对此有任何解决方案,请帮助我。我看到你的代码运行良好
  • 实际上我上面发布的代码没有使用 saveWebArchive(),因此即使在 API 8 下也可以使用它来读取保存在其他地方的档案。对于旧平台下的 saveWebArchive(),我没有一个好的解决方案,除了查看 Android WebView 源代码,看看您是否可以自己复制和调整此功能的代码以适应旧平台。如果您成功了,请也为我们所有人发布!
  • @gregko 谢谢你的 git 项目真的很有帮助,你节省了我很多时间:)
【解决方案2】:

我发现了一种读取已保存网络档案的未记录方式。做吧:

String raw_data = (read the mywebarchivefile as a string)

然后调用

webview.loadDataWithBaseURL(mywebarchivefile, raw_data, "application/x-webarchive-xml", "UTF-8", null);

参考: http://androidxref.com/4.0.4/xref/external/webkit/Source/WebCore/loader/archive/ArchiveFactory.cpp

适用于 Android 3.0,API 级别 11。

【讨论】:

  • 我知道这是一篇旧文章,但这对我使用 Xamarin Android 非常有用,感谢上帝,因为我真的不想先将存档转换为非 base64 编码的字符串!跨度>
  • 在我将“application/x-webarchive-xml”替换为“multipart/related”之后,这对我有用,这似乎是新格式档案中的正确类型。但是由于 javascripts 的错误,webview 在显示存档后挂起 - stackoverflow.com/questions/27986868/…
猜你喜欢
  • 2019-01-31
  • 2013-02-27
  • 2013-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-19
  • 2011-08-31
  • 2014-01-17
相关资源
最近更新 更多