在单独的线程中从服务获取AutoCompleteTextView建议

6
对于我的AutoCompleteTextView,我需要从Web服务中获取数据。由于这可能需要一些时间,我不希望UI线程无响应,因此我需要以某种方式在单独的线程中获取数据。例如,在从SQLite DB获取数据时,使用CursorAdapter方法- runQueryOnBackgroundThread非常容易完成。我查看了其他适配器(如ArrayAdapterBaseAdapter),但没有找到类似的东西...
有没有简单的方法可以实现这一点?我不能直接使用ArrayAdapter,因为建议列表是动态的 - 我总是根据用户输入获取建议列表,因此它不能被预先获取和缓存以供进一步使用...
如果有人能够就此问题提供一些提示或示例 - 那就太好了!
3个回答

10

使用上述方法时,当我打字非常快时也遇到了这些问题。我猜想这是因为过滤器类通过异步方式进行结果过滤,因此在UI线程修改适配器的ArrayList时进行过滤可能会出现问题。

http://developer.android.com/reference/android/widget/Filter.html

然而,使用以下方法一切都正常工作。

public class MyActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        MyAdapter myAdapter = new MyAdapter(this, android.R.layout.simple_dropdown_item_1line);

        AutoCompleteTextView acTextView = (AutoCompleteTextView) findViewById(R.id.autoCompleteTextView1);
        acTextView.setAdapter(myAdapter);
    }
}

public class MyAdapter extends ArrayAdapter<MyObject> {
    private Filter mFilter;

    private List<MyObject> mSubData = new ArrayList<MyObject>();
    static int counter=0;

    public MyAdapter(Context context, int textViewResourceId) {
      super(context, textViewResourceId);
      setNotifyOnChange(false);

      mFilter = new Filter() {
        private int c = ++counter;
        private List<MyObject> mData = new ArrayList<MyObject>();

        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
          // This method is called in a worker thread
          mData.clear();

          FilterResults filterResults = new FilterResults();
          if(constraint != null) {
            try {
              // Here is the method (synchronous) that fetches the data
              // from the server      
              URL url = new URL("...");
              URLConnection conn = url.openConnection();
              BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
              String line = "";

              while ((line = rd.readLine()) != null) {
                      mData.add(new MyObject(line));
              }
            }
            catch(Exception e) {
            }

            filterResults.values = mData;
            filterResults.count = mData.size();
          }
          return filterResults;
        }

        @SuppressWarnings("unchecked")
        @Override
        protected void publishResults(CharSequence contraint, FilterResults results) {
          if(c == counter) {
            mSubData.clear();
              if(results != null && results.count > 0) {
                ArrayList<MyObject> objects = (ArrayList<MyObject>)results.values;
                for (MyObject v : objects)
                  mSubData.add(v);

                notifyDataSetChanged();
              }
              else {
                notifyDataSetInvalidated();
              }
          }
        }
    };
  }

  @Override
  public int getCount() {
    return mSubData.size();
  }

  @Override
  public MyObject getItem(int index) {
    return mSubData.get(index);
  }

  @Override
  public Filter getFilter() {
    return mFilter;
  }
}

通过覆盖所有基本方法并使用自己的数据结构(mSubData),您失去了子类化的好处,包括超类为数据提供的线程安全性。只需使用超类的add() / addAll() / clear()方法即可。它们将自动通知侦听器,并保留线程安全性。 - Risadinha

9

编辑:添加了一种避免在单击建议时显示下拉列表的简单方法。

我在我的应用程序中做了类似以下的操作:

private AutoCompleteTextView mSearchbar;
private ArrayAdapter<String> mAutoCompleteAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    mAutoCompleteAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line);
    mSearchbar = (AutoCompleteTextView) findViewById(R.id.searchbar);
    mSearchbar.setThreshold(3);
    mSearchbar.setAdapter(mAutoCompleteAdapter);
    mSearchbar.addTextChangedListener(new TextWatcher() {

        private boolean shouldAutoComplete = true;

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            shouldAutoComplete = true;
            for (int position = 0; position < mAutoCompleteAdapter.getCount(); position++) {
                if (mAutoCompleteAdapter.getItem(position).equalsIgnoreCase(s.toString())) {
                    shouldAutoComplete = false;
                    break;
                }
            }

        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void afterTextChanged(Editable s) {
            if (shouldAutoComplete) {
                new DoAutoCompleteSearch().execute(s.toString());
            }
        }
    }
}

private class DoAutoCompleteSearch extends AsyncTask<String, Void, ArrayList<String>> {
    @Override
    protected ArrayList<String> doInBackground(String... params) {
        ArrayList<String> autoComplete = new ArrayList<String>();
        //do autocomplete search and stuff.
        return autoComplete;
    }

    @Override
    protected void onPostExecute(ArrayList<String> result) {
        mAutoCompleteAdapter.clear();
        for (String s : result)
            mAutoCompleteAdapter.add(s);
    }
}

这个解决方案在我的情况下存在一些问题,下拉菜单直到我按软键盘上的“删除”键删除输入的文本后才会显示出来,这太奇怪了。 - Longerian
这真的帮了我很多!谢谢! - ymerdrengene

1

有同样的解决方案,但问题在于一切都很好(当我进行调试时变量被更新),但自动完成填充怪怪的,比如当我输入sco时它会显示结果,但不会显示在列表中,但是当我按回退键时,它会展示sco的结果。在调试中所有的变量都被更新,这只告诉我UI没有得到AutoCompleteTextView的更新。当我按回退键时,它就会触发更新,然后显示早期的计算机列表,接着就用新的搜索字符串更新它的列表项。有人遇到过这个问题吗?


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