用户输入时自动搜索

7
我有一个Activity,用户在其中输入EditText,点击搜索按钮,应用程序查询Web服务并将结果放入ListView中。
我想取消搜索按钮。
显然,我不希望用户输入的每个字符都命中Web服务。我只想在用户完成输入时执行1次Web服务调用。
我实现这个功能的方法如下:
我有一个成员变量,保存一个AsyncTask。当EditText中的文本发生更改时,AsyncTask会启动。在doInBackground()中,调用Thread.sleep()。这个休眠时间本质上是一个计时器,等待看看用户是否还要输入其他内容。在sleep调用之后,如果AsyncTask没有被取消,那么就会调用Web服务。如果用户输入了另一个字母,则在AsyncTask上调用cancel()(停止调用Web服务),将保存AsyncTask的成员变量设置为null,并创建AsyncTask的新实例。
我有几个问题:我是否泄漏了内存?这种方法有什么特别糟糕的地方吗?我知道它可能不是最有效的,但我会严重拖慢某人的手机吗?有没有更好的方法来实现这个功能?
private SearchTask mSearchTask = null;

...

    mSearchText.addTextChangedListener(new TextWatcher() {

        public void onTextChanged(CharSequence s, int start, int before, int count) {
            // Auto-generated method stub
        }

        public void beforeTextChanged(CharSequence s, int start, int count,
                int after) {
            // Auto-generated method stub
        }

        public void afterTextChanged(Editable s) {
                if (s != null && s.length() > 0) {
                    // stop any current search thread
                    if (mSearchTask != null && !mSearchTask.isCancelled()) {
                        mSearchTask.cancel(false);
                    }


                        // search for products
                        SearchCriteria crit = new SearchCriteria();
                        crit.strSearchWord = mSearchText.getText().toString().trim();
                        mSearchTask = null;
                        mSearchTask = new SearchTask();
                        mSearchTask.execute(crit);
                }
        }
    });

...

private class SearchTask extends AsyncTask<SearchCriteria, Integer, Boolean> {
    protected Boolean doInBackground(SearchCriteria... params) {
        SearchCriteria crit = null;
        if (params.length > 0) {
            crit = params[0];

            if (crit != null) {
                try {
                    Thread.sleep(1000L);
                    if (!isCancelled()) {
                        // perform search
                        return true;
                    }
                }
                catch(Exception e) {
                }
            }
        }

        return false;
    }

    protected void onPostExecute(Boolean success) {
        if (success != null && success == true) {
            // do something
        }
        else {
            // do something else
        }
    }   
}
4个回答

9

我更倾向于在x毫秒后启动一个线程并进行检查,而不是立即启动线程并在其中加入休眠。

private Handler mMessageHandler = new Handler();

private Runnable mSearchRunnable = new Runnable() {
        public void run() {
           if (!isCancelled()) {
               // perform search
           }
        }
    };

然后您可以将这个放在您的afterTextChanged中:

mMessageHandler.postDelayed(mSearchRunnable, 1000);

如果用户输入更多数据,您可以使用以下代码取消线程:

 mMessageHandler.removeCallbacks(mSearchRunnable);

我建议不要这样做,使用AsyncTask比使用Runnables并自己处理线程更安全。 - blindstuff
1
我并不是说你的方法有错。AsyncTask正是Android团队设计的,让你不必使用Handlers和同时管理多个线程。 虽然你的方法可以工作,但你有更方便的工具可供使用。 - blindstuff
啊 - 通过使用旧的熟悉代码重新发明轮子的一个例子 :) - Martyn
1
我会尝试一下这个方法。如果有更好的方法,我想了解一下。正如您在原始帖子中所看到的,我已经实现了它,但是在使用一段时间后似乎会导致性能问题。 - Andrew
isCancelled() 方法的实现在哪里? - fpanizza
显示剩余2条评论

3
你应该考虑在任务等待时或者调用web服务器时调用cancel(true)来尝试关闭任务。这可能会节省一些处理周期,但你的web服务器可能对错误的调用感到不满。
如果可能的话,你可以重复使用你的SearchCriteria对象以节省一些gc周期。
除此之外,我没有看到任何内存泄漏。你的对象生命周期短暂且不缓存它们。唯一可能出现的问题是有太多并行的异步任务正在运行http请求,这将导致内存不足。我们曾经在一个应用程序中遇到过这个问题,在猴子测试期间发生了这种情况。

2

0
@Override
public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    setContentView(R.layout.main);
    lv1 = (ListView) findViewById(R.id.ListView01);
    ed = (AutoCompleteTextView) findViewById(R.id.EditTextSearch);

    // AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.autocomplete_country);
        ArrayAdapter<String> adapter1 = new ArrayAdapter<String>(this, R.layout.list_item, countryName);
        ed.setAdapter(adapter1);

    this.getWindow().setSoftInputMode(
            WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
    final List<HashMap<String, String>> fillMaps = new ArrayList<HashMap<String, String>>();
    for (int i = 0; i < countryName.length; i++) {
        HashMap<String, String> map = new HashMap<String, String>();
        map.put("flag", "" + imageId[i]);
        map.put("country", countryName[i].toString());
        map.put("capital", capitalName[i].toString());
        map.put("countrytime",
                convertDateTimeToGMT(GMTplusMinusInMillisecond[i],
                        plusMinus[i]));
        map.put("GMT", GMTplusMinus[i].toString());

        fillMaps.add(map);
    }

    // fill in the grid_item layout
    SimpleAdapter adapter = new SimpleAdapter(this, fillMaps,
            R.layout.grid_item, from, to);
    lv1.setAdapter(adapter);

    ed.addTextChangedListener(new TextWatcher() {

        public void afterTextChanged(Editable s) {
        }

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

        public void onTextChanged(CharSequence s, int start, int before,
                int count) {
            fillMaps.clear();
            textlength = ed.getText().length();
            for (int i = 0; i < countryName.length; i++) {
                if (textlength <= countryName[i].length()) {
                    if (ed.getText()
                            .toString()
                            .equalsIgnoreCase(
                                    (String) countryName[i].subSequence(0,
                                            textlength))) {
                        HashMap<String, String> map = new HashMap<String, String>();
                        map.put("flag", "" + imageId[i]);
                        map.put("country", countryName[i].toString());
                        map.put("capital", capitalName[i].toString());
                        map.put("countrytime",
                                convertDateTimeToGMT(
                                        GMTplusMinusInMillisecond[i],
                                        plusMinus[i]));
                        map.put("GMT", GMTplusMinus[i].toString());

                        fillMaps.add(map);
                    }
                }
            }
            if(!fillMaps.isEmpty())
            {
            SimpleAdapter adapter = new SimpleAdapter(
                    WorldClockActivity.this, fillMaps, R.layout.grid_item,
                    from, to);
            lv1.setAdapter(adapter);
            }
            else
            {      String[] COUNTRIES = new String[] {"No record found"};
            lv1.setAdapter(new ArrayAdapter<String>(WorldClockActivity.this,R.layout.list_item, COUNTRIES));
            }

            // lv1.setAdapter(new
            // ArrayAdapter<String>(WorldClockActivity.this,android.R.layout.simple_list_item_1
            // , arr_sort));

        }
    });
}

public static String convertDateTimeToGMT(long millis, int plusMinus) {

    Calendar CalGMT;
    TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
    CalGMT = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
    CalGMT.get(Calendar.DAY_OF_MONTH);
    CalGMT.get(Calendar.MONTH);
    CalGMT.get(Calendar.YEAR);
    CalGMT.get(Calendar.HOUR_OF_DAY);
    CalGMT.get(Calendar.MINUTE);
    CalGMT.get(Calendar.SECOND);

    if (plusMinus == 1) {
        CalGMT.setTimeInMillis(CalGMT.getTimeInMillis() + millis);
    } else if (plusMinus == 0) {
        CalGMT.setTimeInMillis(CalGMT.getTimeInMillis() - millis);
    }
    String sendDateTimeInGMT = CalGMT.get(Calendar.HOUR_OF_DAY) + ":"
            + CalGMT.get(Calendar.MINUTE) + ":"
            + CalGMT.get(Calendar.SECOND);

    return sendDateTimeInGMT;
}

}

我已经使用上述代码完成了这个应用程序,在这个应用程序中,我使用了AutoCompleteTextView来提供列表视图中的搜索功能,然后列表视图显示所有国家的名称,用户可以通过国家名称搜索国家,当用户在AutoCompleteTextView中输入时,相关的搜索结果会显示在列表视图中。

例如,如果用户想在世界国家列表中搜索加拿大,则用户只需在AutoCompleteTextView中键入ca,然后另一个列表将出现在AutoCompleteTextView下方,并显示以“ca”开头的所有国家名称,然后用户选择该列表中的加拿大,就可以在列表视图中获取有关加拿大的所有信息。


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