在填充AutoCompleteTextView时出现的Android性能问题

3
我有一个AutoCompleteTextView,我通过动态填充来实现。我这样做是因为我有大约10000个建议(街道)要显示,所以我根据它们的第一个字母分割列表。假设有人输入"a",我就用所有以"a"开头的街道填充适配器。这在模拟器中可以工作并且足够快,在我的旧手机Android 2.1上也足够快。突然间我发现列表的填充非常慢。需要大约10秒钟才能填充完毕。但我认为这不是我的代码的问题,而是我的手机的问题。有一段时间我升级到了Android 2.3.7,并使用了CyanogenMod 7.2.0-blade。我绝对确定以前从未遇到过这些问题,因为我不会将某些延迟实现投入生产。当我进行跟踪时,我发现了一些奇怪的事情。所有性能都浪费在了一个名为TextUtils.hasArabicCharacters()的方法上。请看青色条...

performance issue

我没有找到关于这个方法的任何信息。TextUtils没有hasArabicCharacters,所以我猜它是一些专有的东西——> CyanogenMod?如果我在任何模拟器上跟踪相同的代码,则不会调用名为“hasArabicCharacters”的方法,并且自动完成行为非常快。在Android 2.1、2.3.3和4.1.2模拟器下测试通过。

这是调用链(向上):

TextUtils.hasArabicCharacter() -> TextUtils.reshapeArabic() -> Paint.measureText() -> Styled.drawDirectionalRun() -> Styled.measureText() -> BoringLayout.isBoring() -> TextView.onMeasure() -> View.measure() -> ListView.measureScrapChild() -> ListView.measureHeightOfChildren() -> AutoCompleteTextView.buildDropdown() -> AutoCompleteTextView.showDropDown() -> AutoCompleteTextView.updateDropDownForFilter() -> AutoCompleteTextView.access$1700 -> AutoCompleteTextView$PopulateDataSetObserver$1.run() -> Handler.handleCallback() -> Handler.dispatchMessage()

这是我如何填充我的适配器。也许有一些解决方法可以应用。有什么想法吗?

活动:

        final StreetArrayAdapter adapter = new StreetArrayAdapter(this, R.layout.simple_dropdown_item_1line);

        autoCompleteTextView.setAdapter(adapter);
        autoCompleteTextView.setValidator(new Validator());
        autoCompleteTextView.setThreshold(0);
        autoCompleteTextView.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
                streetInfoField.setText("");
            }
        });

        autoCompleteTextView.addTextChangedListener(new StreetTextWatcher(adapter));

......

    class Validator implements AutoCompleteTextView.Validator {

        @Override
        public CharSequence fixText(CharSequence invalidText) {
            return "";
        }

        @Override
        public boolean isValid(CharSequence text) {
            Log.v(TAG, "Checking if valid: " + text);
            String[] streets = StreetNameFactory.getStreetsWithLetter(text.subSequence(0, 1).toString().toUpperCase(Locale.US));

            Arrays.sort(streets);
            if (Arrays.binarySearch(streets, text.toString()) >= 0) {
                return true;
            }

            return false;
        }
    }

StreetNameFactory.getStreetsWithLetter

    public static String[] getStreetsWithLetter(String letter)  {
        Log.i(StreetNameFactory.class.getSimpleName(), "letter:" + letter);
        if ("A".equals(letter))  {
            return StreetNames.STREETS_A;
        }
        if ("Ä".equals(letter))  {
            return StreetNames.STREETS_A;
        }
        if ("B".equals(letter))  {
            return StreetNames.STREETS_B;
        }
.....

StreetTextWatcher:

public class StreetTextWatcher implements TextWatcher {

    private final StreetArrayAdapter adapter;
    private boolean alreadyAdded = false;

    public StreetTextWatcher(StreetArrayAdapter adapter) {
        this.adapter = adapter;
    }

    @Override
    public void afterTextChanged(Editable s) {
        //not used
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        if (s.length() < 1)  {
            adapter.clear();

            alreadyAdded = false;
        }
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {           
        if (s.length() == 1) {
            populateAdapter(s);

            alreadyAdded = true;
        }
    }

    private synchronized void populateAdapter(CharSequence s) { 
        String charSequence = s.toString().toUpperCase(Locale.US);
        if (charSequence.startsWith("A") && !alreadyAdded)  {
            adapter.addAll(StreetNames.STREETS_A);
        }

        if (charSequence.startsWith("Ä") && !alreadyAdded)  {
            adapter.addAll(StreetNames.STREETS_A);
        }

        if (charSequence.startsWith("B") && !alreadyAdded)  {
            adapter.addAll(StreetNames.STREETS_B);
        }

        if (charSequence.startsWith("C") && !alreadyAdded)  {
            adapter.addAll(StreetNames.STREETS_C);
        }

        if (charSequence.startsWith("D") && !alreadyAdded)  {
            adapter.addAll(StreetNames.STREETS_D);
        }

        if (charSequence.startsWith("E") && !alreadyAdded)  {
            adapter.addAll(StreetNames.STREETS_E);
        }
        //more code....

        if (charSequence.startsWith("Z") && !alreadyAdded)  {
            adapter.addAll(StreetNames.STREETS_Z);
        }
        if (Pattern.matches("[1-9]", s.toString()) && !alreadyAdded)  {
            adapter.addAll(StreetNames.STREETS_NUMBERS);
        }
    }
}

StreetArrayAdapter:

public class StreetArrayAdapter extends ArrayAdapter<String> {

    private final String TAG = StreetArrayAdapter.class.getSimpleName();

    public StreetArrayAdapter(Context context, int textViewResourceId) {
        super(context, textViewResourceId);
    }

    public void addAll(String[] streets) {
        Log.i(TAG, "BEGIN LIST FILL at: " + new Date(System.currentTimeMillis()).toString());
        for (String street : streets) {
            add(street);
        }
        Log.i(TAG, "END LIST FILL at: " + new Date(System.currentTimeMillis()).toString());
    }
}

STREETS_A, STREETS_B, STREETS_C是我分配的字符串数组。

[更新]
我找到了一个好的解决方法。当我输入第一个字母时,不能加载列表。当我在输入至少3个字母后加载列表(StreetTextWatcher.onTextChange),我没有冻结,下拉菜单再次非常快速。此外,用户可能甚至不会注意到变化。


TextUtils.hasArabicCharacters() 是从 TextUtils.reshapeArabic() 调用的。哪个方法在 traceview 的 "Parents" 下调用了 TextUtils.reshapeArabic() - Adam Stelmaszczyk
我添加了调用链。请查看更新的帖子。 - Bevor
哪个版本的CyanogenMod? - Adam Stelmaszczyk
CyanogenMod 7.2.0-blade - Bevor
这是什么设备?你尝试过 ezGingerBread1.0 吗? - techi.services
1个回答

1

我认为这是CyanogenMod 7.2的一个缺陷。

查看源代码后发现,在每个(在你的情况下)TextView.onMeasure()调用中,都会调用BoringLayout.isBoring() 方法,该方法调用 Styled.measureText(),该方法又调用 Styled.drawDirectionalRun(),看起来像是一种相当重的方法(它还调用了许多其他方法)。

我认为在您的代码中解决这个问题可能会很困难。我正在寻找一些标志,例如setIsArabic(false),可以设置以省略这些繁重的"阿拉伯语"方法,但我没有找到任何东西。也许您可以替换例如Styled.measureText()的字节码,就像这里所建议的那样,但这似乎非常耗时。
看起来在更新版本中(例如CyanogenMod 10.1),行为是不同的。因此,只需升级您的CyanogenMod即可解决问题。

感谢提供的信息。我不想升级到10.1,因为我需要一个较旧版本的Android(用于应用程序开发)。如果我找不到解决方案,只要这是CyanogenMod或特别是这个版本的CyanogenMod的问题,我就可以接受它。 - Bevor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接