2

さて、私のアプローチはこのようなものです。ローカルに保存された暗号化およびシリアル化されたオブジェクトを でデコードAsyncTaskしていActivityます。は、データを表示するために (HistoryAdapter) をActivity使用します。BaseAdapterは、デコードが完了AsyncTaskするまで a を示します。ProgressDialogonProgressUpdate()最初に呼び出されると、ProgressDialogはキャンセルされます。ここまでは順調ですね。次に、onProgressUpdate()では、HistoryAdapter に共通の方法で変更が通知され、そのgetView()メソッドがトリガーされます。HistoryAdapter のでは、作成された を変更し、データを に設定するためにgetView()2 番目のコマンドAsyncTaskが実行されます。convertViewView

ここですべてが失敗します。で最終的なレイアウトを膨らませ、ここonProgressUpdate()でプロパティとデータを適切に設定しconvertViewます。すべてのデータが設定されているにもかかわらず、変更は表示されません...

したがって、AsyncTaskHistoryAdapter 自体は実際には完全に機能し、変更は表示されません。SO で言及されている多数の提案を試しました。たとえば、を無効化しconvertViewたり、参照を渡したりListView、使用したりしinvalidateViews()ます(永久ループが発生しますが、目に見える変化はありません。これは理にかなっています)。

データが利用可能になる前に、画像プレースホルダーを含むレイアウトをロードしたくないので、これが本当に必要です。私は仕事をしましたが、厄介に見え、簡単な方法のように見えます。ListViewそのため、進行状況が完了したときにのみ更新 (アイテムを追加) する必要があります。何か案は?

編集:明確にするために:データは適切なタイミングでアダプターに設定されます。問題は、アダプターがView最初に空白 (プレースホルダー) を作成することです (他の方法はわかりません。そうしないと、getView で NullPointerException が発生しViewますView) onProgressUpdate()。2番目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 を変更できることと、ビューへの参照を多くの場所で渡す必要がないことはわかっていますが、やはりコードが進行中です。簡単な例...

編集

さて、そもそも私のアプローチが貧弱だったようで、結果AsyncTaskとしてHistoryAdapter. これを解決するためにいくつかの問題に対処しました。

  1. @Delyan の提案に基づいて、実際に必要になる前にデータをロード/復号化するのが良いと判断しました。これには a を使用しPropertyChangeListenerています。これは を実装してOnSharedPreferenceChangeListener、必要なデータへの変更が通知されるようにします。変更は、関心のあるリスナーに伝播されます。データはアプリケーションの起動時に復号化され、アプリケーション全体でアクセス可能なグローバル変数に格納されます。これを彼が言及した「メモリキャッシュ」と見なしてください。
  2. コメントと受け入れられた回答に基づいて、復号化はバックグラウンドで行われるようになったため、AsyncTasks.
  3. アダプターのパフォーマンスをさらに最適化するために、 に必要なイメージを に保存してListViewいるSparseArrayため、作成および保存は 1 回だけです。これには a を使用しないでくださいHashMap。さらに、画像がまだView存在しない場合にのみ、現在の画像が作成され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 は、画像の遅延ロード、つまり大きな画像を表示し、ダウンロードが完了するまで text+imageplaceholder を表示するために (ただし別の方法で) 使用されます。

最初の AsyncTask でアダプターのデータソースに徐々に入力しようとしていますが、データセットの変更についてオブザーバーに通知するたびに、データセット内のすべてのアイテムに対して getView 呼び出しの別のサイクルが発生します。ダメ。

まず、決して、決して!getView が、まさにこの位置のために以前に埋められた convertview を返すと仮定します。そのため、convert ビューに新しい値を追加するか、パフォーマンスの最適化をオフにして、要求されるたびに新しいビューを提供する必要があります。これは ListView の本質であり、その上に構築されている機能であるため、ListView がリサイクル試行をオフにする方法はありません。

2 つ目 (最初の結果)、時間のかかる (またはユーザー入力) データを新しく作成したビューのみに保存することは絶対に避けてください。ビューは行ったり来たりしますが、高価なデータを取得するために長い道のりを歩きたくない (または単にユーザー入力を失う) ことは望ましくありません。唯一の部分的な除外は、大きな画像の遅延読み込みの単純で効果のない実装です。メモリ使用量を減らすために、現在ユーザーに表示されている画像のみをダウンロードします。より効果的な実装では、ListView 以外のキャッシュを使用します。

そのため、ListView 内の項目を一度に 1 つずつ追加したいが、完全な状態で追加したい場合は、次のようにする必要があります。

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 が null の場合にビューを同期的に膨張させ、すべて の場合に、HistoryHolder.getTag() から取得した DecryptedRecord からのデータをビューに取り込みます。包括的に、ImageViews で setImageBitmap() を呼び出し、対応するリストの必要なビットマップをインデックスで指定します。

これはあなたが望むことをするはずです。エラー/問題がある場合は、質問に完全なコードを含めるようにしてください。そうしないと、状況の詳細を理解することが非常に難しくなります。それが役立つことを願っています。

于 2013-05-21T21:53:09.277 に答える
0

まず、非同期タスクが onPostExecute を実行するときに、データをアダプターに設定し、notifydatasetchanged() する必要があります。プログレスバーを作成し、次のようにリストビューに設定するなどの方法を使用して空のリストビューをもう少しクリーンにします。

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