【问题标题】:Android Jsoup Parser very slow on kitkatAndroid Jsoup Parser 在 kitkat 上非常慢
【发布时间】:2014-11-13 16:41:14
【问题描述】:

Jsoup 在 kitkat 上的解析速度似乎比在 kitkat 之前的任何东西上都要慢得多。我不确定它是否是 ART 运行时,但在对解析方法运行速度测试后发现它慢了大约 5 倍,我不知道为什么..

我的这部分代码在异步任务的 doInBackground 中运行。

    JsoupParser parser = new JsoupParser();
    parser.setPath(String.valueOf(application.getCacheDir()));

    Collection<Section> allSections = eguide.getSectionMap().values();
    for (Section section : allSections) {
         parser.createNewAssetList();
         parser.setContent(section.color, section.name, section.text, section.slug);
         if (!TextUtils.isEmpty(section.text)) {
            section.text = parser.setWebViewStringContent();
            section.assets = parser.getAssets();
            for (Asset asset : section.assets)
                asset.heading = section.heading;
         }
    } 

我很久以前写过这个,它可能效率不高,但它设置了解析器,加载了一个 Section 对象列表,对于每个对象,它都会将 html 提取表和图像解析为一个不同对象的列表,这些对象返回到原始部分对象..

这是我的解析器类。

public class JsoupParser{

private List<Asset> assets;
private int assetCount;
private String slug,name,color,path;
private Document doc;

public JsoupParser() {
    assetCount = 0;
    assets = new ArrayList<Asset>();
}

public void setPath(String path) {
    this.path = path;
}

public void setContent(String color, String name, String text, String slug){
    this.color = color;
    this.name = name;
    this.slug = slug;
    doc = Jsoup.parse(text);
}

public void createNewAssetList(){
    assetCount = 0;
    assets = new ArrayList<Asset>();
}

public String setWebViewStringContent() {

    addScriptsAndDivTags();

    //parse images
    Elements images  = doc.select("img[src]");
    parseImages(images);

    //parse tables
    Elements tableTags = doc.select("table");
    parseTables(tableTags);

    return doc.toString();
}

private void addScriptsAndDivTags() {

    Element bodyReference = doc.select("body").first(); //grab head and body ref's
    Element headReference = doc.select("head").first();

    Element new_body = doc.createElement("body");
    //wrap content in extra div and add accodrion tag
    bodyReference.tagName("div");
    bodyReference.attr("id", "accordion");
    new_body.appendChild(bodyReference);
    headReference.after(new_body);
}

private void parseTables(Elements tableTags) {
    if (tableTags != null) {
        int count = 1;
        for (Element table : tableTags) {
            Asset item = new Asset();
            item.setContent(table.toString());
            item.setColor(color);
            item.id = (int) Math.ceil(Math.random() * 10000);
            item.isAsset=1;
            item.keywords = table.attr("keywords");
            String linkHref = table.attr("table_name");
            item.slug = "t_" + slug + " " + count ;
            if(!TextUtils.isEmpty(linkHref)){
               item.name = linkHref;
            }
            else{
               item.name ="Table-" + (assetCount + 1) + " in " + name;
            }
            // replace tables
            String inline = table.attr("inline");
            String button = ("<p>Dummy Button</p>");

            if(!TextUtils.isEmpty(inline)&& inline.contentEquals("false") || TextUtils.isEmpty(inline) )
            {
              table.replaceWith(new DataNode(button, ""));
            }
            else{
                Element div = doc.createElement("div");
                div.attr("class","inlineTableWrapper");
                div.attr("onclick", "window.location ='table://"+item.slug+"';");
                table.replaceWith(div);
                div.appendChild(table);
            }
            assets.add(item);
            assetCount++;
            count++;
        }
    }
}

private void parseImages(Elements images) {
    for (Element image : images) {
        Asset item = new Asset();

        String slug = image.attr("src");
        //remove first forward slash from slug to account for img:// protocol in image linking
        if(slug.charAt(0)=='/')
            slug = slug.substring(1,slug.length());
        image.attr("src", path +"/images/" + slug.substring(slug.lastIndexOf("/")+1, slug.length()));
        image.attr("style", "px; border:1px solid #000000;");
        String image_name = image.attr("image_name");
        if(!TextUtils.isEmpty(image_name)){
           item.name = image_name;
        }
        else{
           item.name ="Image " + (assetCount + 1) + " in " + name;
        }

        // replace tables
        String inline = image.attr("inline");

        String button = ("<p>Dummy Button</p>");
        item.setContent(image.toString()+"<br/><br/><br/><br/>");
        if(!TextUtils.isEmpty(inline)&& inline.contentEquals("false"))
        {
            image.replaceWith(new DataNode(button, ""));
        }
        else{
           image.attr("onclick", "window.location ='img://"+slug+"';");
        }

        item.keywords = image.attr("keywords");
        item.setColor(color);
        item.id = (int) Math.ceil(Math.random() * 10000);
        item.slug = slug;
        item.isAsset =2;
        assets.add(item);
        assetCount++;
    }
}

public String getName() {
    return name;
}

public List<Asset> getAssets() {
    return assets;
}
}

同样,它可能效率不高,但到目前为止我一直无法找出为什么它会对 kitkat 造成如此大的影响。任何信息将不胜感激。 谢谢!

【问题讨论】:

  • 你有没有发现更多关于 KitKat 下的 jsoup 速度问题(现在还有 Lollipop)?我在我的应用程序中使用 jsoup 并发现我所做的所有 HTML 页面解析在 Android 4.3、4.2.2 的旧设备上的运行速度比在 KitKat 和 Lollipop 的相对较新的设备上快 5 到 10 倍。摸不着头脑……顺便说一句,我在 ART 和 Dalvik 之间切换了一个 KitKat 设备,jsoup 解析速度没有区别。
  • @gregko 遗憾的是没有。这是我从未设法解决的少数几个问题之一。现在我得到了生产代码,在旧设备上解析比在新设备上更快。每次看到我都咬着嘴唇。如果您找到解决方案,请传递。 ;)
  • 我正在解决这个问题,我已经在我的产品中找到了一个我能够修复的地方。将尝试识别更多,希望可以在 jsoup 代码中引入一些东西。另请参阅我在 GitHub 上与 jsoup 作者的讨论:github.com/jhy/jsoup/issues/383#issuecomment-88880188

标签: android performance parsing jsoup android-4.4-kitkat


【解决方案1】:

2015 年 4 月 7 日更新 jsoup 的作者将我的建议纳入主干,此时检查 ASCII 或 UTF 编码并跳过慢速(在 Android 4.4 和 5 上)canEncode( ) 调用,所以只需更新您的 jsoup 源代码树并重新构建,或者拉出他最新的 jar。

早期的 cmets 和问题解释:我发现问题出在哪里,至少在我的应用程序中 - jsoup 的 Entities.java 模块有一个 escape() 函数 - 使用例如通过 Element.outerHtml() 调用所有文本节点。除其他外,它会测试每个文本节点的每个字符是否可以使用当前编码器进行编码:

 if (encoder.canEncode(c))
    accum.append(c);
 else...

canEncode() 调用在 Android KitKat 和 Lollipop 上非常慢。由于我的 HTML 输出仅采用 UTF-8 格式,而 Unicode 几乎可以编码任何字符,因此无需进行此检查。我通过在 escape() 函数的开头进行测试来改变它:

boolean encIsUnicode = encoder.charset().name().toUpperCase().startsWith("UTF-");

然后,当需要测试时:

if (encIsUnicode || encoder.canEncode(c))
    accum.append(c);
else ...

现在,我的应用程序在 KitKat 和 Lollipop 上也能像魅力一样工作 - 以前需要 10 秒,现在只需不到 1 秒。我向主 jsoup 存储库发出了拉取请求,其中包含此更改和一些较小的优化。不确定jsoup作者是否会合并它。如果需要,请查看我的 fork:

https://github.com/gregko/jsoup

如果您使用一些您事先知道的其他编码,您可以添加自己的测试(例如,查看字符是否为 ASCII 或其他)以避免代价高昂的 canEncode(c) 调用。

格雷格

【讨论】:

  • 在哪个 jsoup 版本中修复了它?我正在使用 1.8.1 并且有类似的问题。希望在那之后。
  • 请查看更改日志github.com/jhy/jsoup/blob/master/CHANGES - 版本 1.8.2 或 1.8.3 应该有此更改,但不是 1.8.1
【解决方案2】:

你确实使用了大量的字符串连接(这可能是大量数据的杀手)

item.name ="Table-" + (assetCount + 1) + " in " + name;

根据这篇文章:Is it always a bad idea to use + to concatenate strings - 你应该避免在循环中连接 - 你的代码就是这种情况......怎么样:

item.name = String.format("Table-%s in %s",assetCount + 1, name);

【讨论】:

  • 我使用带有 + 运算符的字符串连接和使用 String.format() 对代码进行了一些快速测试 - 在我的 PC 上的 Java VM 和两个 Android 设备上进行了测试。在我的测试中没有显示出显着差异。确实,如果 + 运算符实现得如此糟糕以至于它确实产生了重大影响,我会说这是一个编译器或 VM 解释器错误......但也许在使用大量堆空间的大型应用程序中它会产生影响,可以暂时不说。如果你有更多的证据,请分享。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-05
  • 1970-01-01
  • 1970-01-01
  • 2015-08-22
相关资源
最近更新 更多