在AsyncTask完成时强制BaseAdapter更新它的视图

时间:2013-05-21 19:26:48

标签: android android-listview android-asynctask baseadapter

好的,所以我的方法是这样的。我正在AsyncTask Activity中解码本地存储的加密和序列化对象。 Activity使用BaseAdapter(HistoryAdapter)来显示数据。 AsyncTask显示ProgressDialog,直到解码完成。首次调用onProgressUpdate()时,ProgressDialog将被取消。到现在为止还挺好。接下来,在onProgressUpdate()中,HistoryAdapter以常用方式通知更改,触发它的getView()方法。在HistoryAdapter的getView()中,运行第二个AsyncTask来修改创建的convertView并将数据设置为View

这就是我失败的地方。我在onProgressUpdate()中填充了最终版面,并在convertView上设置了属性和数据就好了。即使所有数据都已设置,这些更改也不会显示...

因此,HistoryAdapter中的AsyncTask本身实际上是完美的,这些变化是不可见的。我尝试了在SO上提到的多项建议,比如使convertView无效,传递对ListView的引用并使用invalidateViews()(导致永久循环,但没有明显的变化,这是有意义的)。

我真的很想要这个,因为我真的不想在数据可用之前加载带有图像占位符的布局。我工作了,但看起来很讨厌,喜欢简单的出路。所以我需要ListView仅在进度完成时更新(添加项目)。有什么想法吗?

编辑:澄清:数据是在恰当的时间在适配器上设置的。问题是,适配器首先创建一个空白View(占位符)(不知道任何其他方式,否则你将在getView中得到NullPointerException),然后这个View被另一个膨胀/替换View中的onProgressUpdate()。第二个View是应该可见的人。这有点工作,因为我可以在新膨胀的视图上获取和设置属性。这些更改是不可见的,我仍然看到空白的,最初创建的视图。我想在每个添加的项目上更新ListView,而不是在所有项目都完成加载后更新...

public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {  
        //convertView = mInflater.inflate(R.layout.history_list_item_holo_dark, null);  
        convertView =  mInflater.inflate(R.layout.blank, parent, false); // CHEAT: LOAD BLANK/ EMPTY LAYOUT
        HistoryHolder item  = history.get(position);
        new AsyncRequest(convertView, position).execute(item);
    }       
    this.parent = parent;
    return convertView;
}//end method

static class ViewHolder {       
    TextView TITLE;
    TextView SUMMARY;
    TextView DATE;
    ImageView CONTACT_ICON;
    ImageView OPTIONS_ICON;
    ImageView TYPE_ICON;
}//end class

private class AsyncRequest extends AsyncTask<HistoryHolder, View, View> {
    ViewHolder holder           = null;
    String title                = "";
    String summary              = "";
    String date                 = "";
    long id                     = 0;
    private View convertView    = null;
    private String type         = "";
    private int position        = -1;

    public AsyncRequest(View superView, int position){
        this.convertView = superView;
        this.position = position;
    }//end constructor

    @Override
    protected View doInBackground(HistoryHolder... item) {
        Thread.currentThread().setName(getClass().getSimpleName()); 

        if (item[0].TYPE.equals("log")){
            //massive decrypting going on here 50-100 ms
            //values like title and summray set here
        }
        if (item[0].TYPE.equals("sms")){
            //massive decrypting going on here 50-100 ms
            //values like title and summray set here
        }
        publishProgress(convertView);
        return convertView;
    }// end method      

    @Override
    protected void onProgressUpdate(View... view) {
        super.onProgressUpdate(view);
    }// end method

    @Override
    protected void onPostExecute(View result) {
        super.onPostExecute(result);

        if (!MathUtils.isEven(position)){
            result .setBackgroundColor(context.getResources().getColor(R.color.darker)); //this works as expected, list items vary in color
        } else {
            result .setBackgroundColor(context.getResources().getColor(R.color.medium_dark));
        } //this works as expected, list items vary in color
        result      = mInflater.inflate(R.layout.history_list_item_holo_dark, parent, false);
        result.setTag(id);
        holder                  = new ViewHolder();
        holder.TITLE            = (TextView)    result .findViewById(R.id.title);
        holder.SUMMARY          = (TextView)    result .findViewById(R.id.summary);
        holder.DATE             = (TextView)    result .findViewById(R.id.date);
        holder.CONTACT_ICON     = (ImageView)   result .findViewById(R.id.icon);
        holder.TYPE_ICON        = (ImageView)   result .findViewById(R.id.type);
        holder.OPTIONS_ICON     = (ImageView)   result .findViewById(R.id.options);         
        holder.OPTIONS_ICON.setFocusable(false);
        holder.OPTIONS_ICON.setTag(id);         

        holder.TITLE.setText(title); //this change doesnt show
        holder.SUMMARY.setText(summary); //and so on

        result .setTag(holder);
    }//end method
}//end inner class

而且我知道我可以修改我的AsynTask并且我不需要在很多地方传递对View的引用,但是再次,它正在进行中。简化示例......

修改

好的,所以我的方法似乎开始很差,导致需要AsyncTask HistoryAdapter。我解决了一些问题来解决这个问题。

  1. 根据@Delyan的建议,我认为在实际需要之前加载/解密数据是件好事。我正在使用PropertyChangeListener。这实现了OnSharedPreferenceChangeListener,以便通知我需要的数据更改。然后将更改传播给任何感兴趣的听众。数据在应用程序启动时解密并存储在全局变量中,该变量可在整个应用程序中访问。将此视为他所提及的“内存缓存”。
  2. 根据评论和接受的答案,现在解密是在后台完成的,因此不再需要AsyncTasks
  3. 为了进一步优化我的适配器的性能,我将ListView中所需的图像存储在SparseArray中,因此它们只会被创建并存储一次。不要使用HashMap!此外,只有当前View的图像不在HashMap中时才会创建图像(图像不是唯一的)。

  4. public class HistoryAdapter extends BaseAdapter{    
    
        private static Context context                  = ApplicationSubclass.getApplicationContext_(); 
        private Contacts contacts                       = Contacts.init(context);
        private SparseArray<Drawable> c_images          = new SparseArray<Drawable>();
        private HashMap<Long, Drawable> contact_imgs    = new HashMap<Long, Drawable>();
        private ArrayList<HistoryHolder> history;
        private LayoutInflater mInflater;
    
        public HistoryAdapter(Context context) {
            HistoryAdapter.context = context;
            mInflater   = LayoutInflater.from(context);
        }//end constructor
    
        ...
    
        public View getView(int position, View convertView, ViewGroup parent) {     
            final HistoryHolder item = history.get(position);
    
            Drawable d = null;
            if (c_images.get(position) == null){
                if (!contact_imgs.containsKey(item.CONTACT_ID)){
                    if (item.IN_CONTACTS && item.CONTACT_ID != 0 && item.CONTACT_ID != -1){ 
                        Bitmap photo = contacts.getContactPhotoThumbnailByContactId(item.CONTACT_ID);
                        if (photo != null){
                            d = Convert.bitmapToDrawable(context, photo, 128, 128);
                        } else {
                            d = context.getResources().getDrawable(R.drawable.ic_contact_picture);
                    }
                    } else {
                        d = context.getResources().getDrawable(R.drawable.ic_contact_picture);              
                    }
                    contact_imgs.put(item.CONTACT_ID, d); 
                }
            }
            c_images.put(position, contact_imgs.get(item.CONTACT_ID));
    
            ViewHolder holder;
            if (convertView == null) {
                convertView             = mInflater.inflate(R.layout.history_list_item_holo_dark, null);            
                holder                  = new ViewHolder();
                holder.POSITION         = position;
                holder.TITLE            = (TextView)    convertView.findViewById(R.id.title);
                holder.SUMMARY          = (TextView)    convertView.findViewById(R.id.summary);
                holder.DATE             = (TextView)    convertView.findViewById(R.id.date);
                holder.CONTACT_ICON     = (ImageView)   convertView.findViewById(R.id.icon);
                holder.CONTACT_ICON.setTag(position);
                holder.OPTIONS_ICON     = (ImageView)   convertView.findViewById(R.id.options);         
                holder.OPTIONS_ICON.setFocusable(false);
                holder.OPTIONS_ICON.setTag(position);
    
                convertView.setTag(holder); 
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
    
            holder.CONTACT_ICON.setBackgroundDrawable(c_images.get(position)); 
    
            holder.TITLE.setText(item.TITLE);
            holder.SUMMARY.setText(item.SUMMARY);
            holder.SUMMARY.setMaxLines(2);
            holder.DATE.setText(item.DATE);   
    
            if (!MathUtils.isEven(position)){
                convertView.setBackgroundColor(context.getResources().getColor(R.color.darker));
            } else {
                convertView.setBackgroundColor(context.getResources().getColor(R.color.medium_dark));
            }
    
            return convertView;
        }//end method
    
        static class ViewHolder {       
            TextView TITLE;
            TextView SUMMARY;
            TextView DATE;
            ImageView CONTACT_ICON;
            ImageView OPTIONS_ICON;
            int POSITION;
        }//end inner class
    }//end class
    

4 个答案:

答案 0 :(得分:1)

您需要分开您的疑虑。解密/解码数据在UI层中没有位置。

一般情况下,您目前使每个项目使用AsyncTasks的方法很难,并且(有些人会说)由于多种原因而出错:

  1. Honeycomb及以上版本,除非您明确设置执行程序,否则在任何一个时间点都只运行一个AsyncTask。

  2. 更重要的是,通过保持对convertView的引用,您正在从ListView中泄漏抽象 - 您持有引用的视图可能会被重用于其他位置。除非您小心翼翼地取消AsyncTasks并确保正确的结果传递,否则会给您带来麻烦。

  3. 如上所述,解密/解码数据在UI层中没有位置(我考虑了Adapters UI层,因为它们对执行速度有类似的限制)。

  4. 如果我是你,我会使用解密数据的内存缓存,随着需求的变化扩展/收缩。然后,我只需要在适配器的getView()方法中获取解密数据。为了避免在滚动时解密项目,您可以在ListView上设置滚动侦听器,这样您只能在列表不移动时显示项目。在ApiDemos中有一个类似的演示。

    编辑: 至于您明显的问题,您需要重新定义视图(result)而不将其添加到列表项(任务中的convertView字段)。您可以通过将其添加到convertView(例如,在空布局中)来解决此问题。同样,这在所有情况下都不会像您预期的那样有效。

答案 1 :(得分:1)

Dmmh,你说的是什么,你想做什么(所以我需要ListView才能在进度完成时更新(添加项目))你正在做什么(getView中的AsyncTask)是完全相反的事情。 / p>

在getView的末尾使用AsyncTask(但是以不同的方式)用于延迟图像加载,即显示大图像并显示文本+图像占位符,直到下载完成。

您正尝试在First AsyncTask中逐渐填写适配器的数据库,但每次通知观察者数据集中的更改时,您将为数据集中的每个项目提供另一个getView调用循环。不好。

首先,从不,从不!!! 假设getView将为您提供一个转换视图,之前为此位置填充。因此,您必须使用新值重新填充转换视图,或者在每次要求时关闭性能优化并提供新视图。 ListView没有办法关闭回收尝试,因为这是ListView的本质,它是构建它的功能。

第二个(由第一个产生),完全避免将时间昂贵(或用户输入)数据存储到新创建的视图中。视图来来去去,你不想走很长的路来获取昂贵的数据(或者只是丢失用户输入)。唯一的部分排除是大图像延迟加载的简单无效实现。为了减少内存使用量,他们只下载用户当前可见的图像。更有效的实现使用off-ListView缓存。

所以,如果你真的想让你的ListView中的项目一次添加一个,但是要完全荣耀,你应该:

0 *。如果你必须加载用户提供的图标并且这需要花费大量时间来加载(我还没有理解你的初始帖子),请创建一个空的ArrayList来缓存加载的图标并通过索引访问它们。如果某些索引已经提供了所有图像,请忽略此事。

class DecryptedRecord {
    String title                = "";
    String summary              = "";
    String date                 = "";
    int contactViewIndex        = -1;
    int contactOptionsIndex     = -1;
    int contactImageIndex       = -1;
    int typeImageIndex          = -1;
    int optionsImageIndex       = -1; //if option icon varies. ignore otherwise
}
  1. 声明DecryptedRecord类,包含填写视图的必要数据,例如:

  2. 在AsyncTask中:加载每个HistoryHolder后,执行“heavy decrypting”并使用值填充新的DecryptedRecord。如果需要加载cusom图像(参见no.0 *),请在此处加载并将其索引存储在缓存中(如果0 *不相关则忽略此项)。使用setTag()将填充的DecryptedRecord附加到HistoryHolder。调用publishProgress(HistoryHolder)

  3. 在onProgressUpdate中,只需将HistoryHolder添加到Adapter并调用historyAdapter.notifyDataSetChanged();

  4. 在getView()同步中,如果convertView为null,则为视图充气,并且 IN ALL CASES 使用从HistoryHolder.getTag获取的DecryptedRecord中的数据填充它( )。包含在您的ImageViews中调用setImageBitmap()按索引在相应列表中添加必要的位图。

  5. 这应该做你想要的。如果您有错误/问题,请尝试在您的问题中包含完整的代码,否则很难理解您的具体情况。希望有所帮助。

答案 2 :(得分:1)

  1. 您可以创建一个内容提供程序,它将您的解码数据存储在db / temp数据结构中,并使用它来更新您的视图。解码等可以通过服务/线程在后台进行。适配器可以通过此提供程序与解码数据通信。这与上面提到的alexei burmistrov想法有关。

  2. 不是一个好的选择 - 使用当前的布局 - 一个既有空白又有空白的布局文件。填充布局。首次显示布局时,空白视图可见。当任务完成时,可见性设置为消失。

  3. 此解决方案不是最佳的,因为每次调用getView时都会运行asynctask。 Android会重新使用视图,因此可能会根据您在UI等方面的滚动方式多次使用。

    示例代码:

        public void getView(){
    
        result      = mInflater.inflate(R.layout.history_list_item_holo_dark, parent, false);
    
        holder                  = new ViewHolder();
        holder.TITLE            = (TextView)    result .findViewById(R.id.title);
        holder.SUMMARY          = (TextView)    result .findViewById(R.id.summary);
        ..
        holder.OPTIONS_ICON.setTag(id); 
    
        result.setTag(holder);
        result.setTag(R.id.view_id ,id);
    
        AsyncTask.execute(result);
        convertView = result;
    
     }
    
        AsyncTask(View ...){
                onPostExecute(View view){
                         ViewHolder holder = view.getTag();
                        if(holder != null){
                                //set visibility of views in holder
                                //update Text & data
                        }
                }
        }
    

答案 3 :(得分:0)

首先,当异步任务运行onPostExecute时,您必须将数据设置为适配器和notifydatasetchanged()。 使用像创建progressBar之类的东西,当列表视图为空时,现在更清洁一点,并将其设置为listview,如下所示:

listView.setEmptyView(progressbar);