我有一个动态填充的AutoCompleteTextView。我动态地这样做,因为我有大约10000个建议(街道)要显示,所以我根据他们的第一个字母拆分列表。假设有人输入“a”,我填充适配器,所有街道都以“a”开头。这在模拟器中运行得足够快,在我的旧手机Android 2.1上足够快。突然,我意识到列表的填充速度非常慢。填充大约需要10秒钟。但我认为这不是我的代码问题,而是我手机的问题。前段时间我用CyanogenMod 7.2.0-blade升级到Android 2.3.7。我绝对相信我以前从来没有遇到过这个问题,因为我从来没有在生产上放置过一些滞后的实现。当我做出追踪时,我发现了一些奇怪的东西。所有表现都被称为TextUtils.hasArabicCharacters()
的方法。看青色吧......
我没有找到任何关于此方法的信息。 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 只是我指定的字符串数组。
[UPDATE]
我找到了一个很好的解决方法。输入第一个字母时,我不能加载列表。当我输入至少3个字母(StreetTextWatcher.onTextChange)后加载列表时,我没有冻结,下拉列表再次非常快。此外,用户可能甚至不认识到这种变化。
答案 0 :(得分:1)
我认为这是CyanogenMod 7.2的一个缺陷。
查看源代码显示,在每个(在您的情况下)TextView.onMeasure()
调用中,调用BoringLayout.isBoring()
,调用Styled.measureText()
,调用{ {3}},看起来像一个非常重的方法(它也调用了很多其他方法)。
我认为在您的代码中解决此问题可能很困难。我正在寻找一些标志,也许你可以设置省略这种沉重的“阿拉伯”方法,像setIsArabic(false)
,但我没有找到任何东西。也许你可以替换例如Styled.measureText()
的字节码,就像建议的Styled.drawDirectionalRun()
一样,但它看起来非常耗时。
看起来在较新的版本中,例如CyanogenMod 10.1,行为是不同的。所以也许只是升级你的CyanogenMod,问题就会消失。