如何异步设置TextView的文本?

3
我需要用文本信息填充一个ListView,这些信息需要一定时间来收集。我的方法是使用AsyncTask来执行后台任务,但是当结果到达时将文本设置到TextView会减缓列表的速度:每次调用getView()时都会出现卡顿。
以下是我的AsyncTask类:
private class BreadcrumbTask extends AsyncTask<FFile, Void, String>{
    private WeakReference<TextView> mTextView; 
    public BreadcrumbTask(TextView textView){
        mTextView = new WeakReference<TextView>(textView);
    }

    @Override
    protected String doInBackground(FFile... params) {
           // text processing
    }

    @Override
    protected void onPostExecute(String result) {
        if (mTextView != null){
            TextView tv = mTextView.get();
            if (tv != null)
                //this line blocks the UI. if I comment it the lag is gone
                tv.setText(result); 
        }

// 在getView()中创建新任务并执行它。 // 问题显然来自于onPostExecute()中的tv.setText(result)。当我注释掉这个时,列表流畅运行。那么如何更新TextView而不会减慢UI呢?


请检查您在哪里执行AsyncTask .execute(),如果在listView适配器的getView中,这可能会被调用多次。也许在您的getView和AsyncTask中添加一些日志记录代码会有所帮助。仅使用tv.setText()肯定会使事情变得混乱。 - CSmith
@NathanZ 你最终解决了这个问题吗? - bugfixr
3个回答

1

使用ViewHolder模式。

http://developer.android.com/training/improving-layouts/smooth-scrolling.html

在View Holder中保存视图对象

在ListView滚动期间,您的代码可能会频繁调用findViewById(),这可能会降低性能。即使适配器返回一个已经膨胀的视图进行回收利用,您仍然需要查找元素并更新它们。避免重复使用findViewById()的一种方法是使用“view holder”设计模式。

ViewHolder对象将每个组件视图存储在布局的标签字段中,因此您可以立即访问它们,而无需重复查找它们。首先,您需要创建一个类来保存您精确的视图集。例如:

static class ViewHolder {
  TextView text;
  TextView timestamp;
  ImageView icon;
  ProgressBar progress;
  int position;
}

然后填充ViewHolder并将其存储在布局中。
ViewHolder holder = new ViewHolder();
holder.icon = (ImageView) convertView.findViewById(R.id.listitem_image);
holder.text = (TextView) convertView.findViewById(R.id.listitem_text);
holder.timestamp = (TextView) convertView.findViewById(R.id.listitem_timestamp);
holder.progress = (ProgressBar) convertView.findViewById(R.id.progress_spinner);
convertView.setTag(holder);

其他一些例子:

http://xjaphx.wordpress.com/2011/06/16/viewholder-pattern-caching-view-efficiently http://www.jmanzano.es/blog/?p=166


0

你不能从另一个线程更新UI。但是你可以使用Handler动态地更新UI。在你的类中定义一个handler并按照以下方式使用:

声明:

String result = "";
Handler regularHandler = new Handler(new Handler.Callback() {
    public boolean handleMessage(Message msg) {
        // Update UI
   if(msg.what==3){
    if (mTextView != null){
            TextView tv = mTextView.get();
            if (tv != null){
                //this line blocks the UI. if I comment it the lag is gone
                tv.setText(result); 
            }
        }
     }
        return true;
    }
});

你的代码在onPostExecute中

@Override
    protected void onPostExecute(String result) {
                //store the value in class variable result
                this.result = result;
                handler.sendEmptyMessage(3);
        }

我不应该这样做,因为onPostExecute()在UI线程上运行。 - znat
正确。还要注意所有视图都有自己的内置处理程序,因此您也可以轻松地执行tv.post(new Runnable(){...});。但是,在这种情况下确实不需要。 - Edward Falk
使用WeakReference非常聪明,但这可能是问题的原因吗? - Edward Falk
Edward可能有所发现。我在Android开发者网站上读到了关于使用WeakReference缓存位图的注意事项。注意:过去,一种流行的内存缓存实现是使用SoftReference或WeakReference位图缓存,但这并不推荐。从Android 2.3(API级别9)开始,垃圾收集器更加积极地收集软/弱引用,使它们相当无效。http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html - Akos Cz

0
  @Override
protected void onPostExecute(String result) {

 if (mTextView != null){
        TextView tv = mTextView.get();
        if (tv != null)
            //this line blocks the UI. if I comment it the lag is gone
            tv.setText(result); 

}

2
onPostExecute() 已经在 UI 线程上执行,所以这没有任何作用。 - Kevin Coppock

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