2

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

这就是我失败的地方。我在 中膨胀最终布局,并在此处onProgressUpdate()设置属性和数据就可以了。convertView即使设置了所有数据,更改也不会显示...

因此,AsyncTaskHistoryAdapter 本身实际上可以完美运行,只是不可见更改。我尝试了 SO 上提到的许多建议,例如使无效convertView、传递对ListView和使用的引用invalidateViews()(导致永恒循环但没有可见的变化,这是有道理的)。

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

编辑:澄清:数据是在正确的时间设置在适配器上的。问题是,适配器首先创建一个空白View(占位符)(不知道任何其他方式,否则您将在 getView 中得到 NullPointerException),View然后ViewonProgressUpdate(). 第二个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. 为了进一步优化我的适配器的性能,我存储了ListViewa所需的图像SparseArray,因此它们只创建和存储一次。不要HashMap为此使用 a !View此外,仅当图像不在a中时才会为当前创建HashMap图像(图像不是唯一的)。

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

4 回答 4

1

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

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

您正尝试在第一个 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 后,执行“重度解密”并用值填充新的 DecryptedRecord。如果需要加载自定义图像(参见 no.0*),请在此处加载并将其索引存储在缓存中(如果 0* 无关,则忽略此)。使用 setTag() 将填充的 DecryptedRecord 附加到 HistoryHolder。调用 publishProgress(HistoryHolder)

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

  4. 在 getView()中,如果 convertView 为空,则同步膨胀视图,并且在所有 情况下,使用从 HistoryHolder.getTag() 获得的 DecryptedRecord 中的数据填充它。包括在内,在您的 ImageViews 上调用 setImageBitmap(),按索引在相应列表中处理必要的位图。

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

于 2013-05-21T21:53:09.277 回答
1

你需要分开你的担忧。解密/解码数据在 UI 层中没有位置。

通常,您当前为每个项目设置 AsyncTasks 的方法很困难,并且(有人会说)由于多种原因是错误的:

  1. Honeycomb 及更高版本,在任何一个时间点都只有一个 AsyncTask 运行,除非您明确设置 Executor。

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

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

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

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

于 2013-05-21T21:38:52.990 回答
1
  1. 您可以创建一个内容提供程序,将您的解码数据存储在 db/temp 数据结构中,并使用它来更新您的视图。解码等可能通过服务/线程在后台发生。适配器可以通过这个提供者与解码的数据对话。这与上面提到的 alexei burmistrov 想法有关。

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

此解决方案不是最优的,因为每次调用 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
                    }
            }
    }
于 2013-05-21T22:24:07.470 回答
0

首先,当异步任务运行 onPostExecute 时,您必须将数据设置到适配器和 notifydatasetchanged()。现在,当列表视图为空时,使用创建progressBar 并将其设置为列表视图,如下所示:

listView.setEmptyView(progressbar);
于 2013-05-21T19:49:08.160 回答