3

Honeycomb の後、Google は、ビットマップはヒープによって管理されると述べました (ここで説明されています)。そのため、ビットマップにアクセスできなくなった場合、GC がそれを処理して解放すると想定できます。

listView レクチャー (ここから) で示されたアイデアの効率を示すデモを作成したかったので、小さなアプリを作成しました。アプリでユーザーがボタンを押すと、リストビューが一番下までスクロールし、10000 個のアイテムがあり、その内容は android.R.drawable アイテム (名前と画像) です。

なんらかの理由で、画像を保存していないのにメモリ不足になるので、私の質問は次のとおりです。私が見逃しているのは何ですか?

Galaxy S III でアプリをテストしましたが、アダプターのネイティブ バージョンを使用するとメモリ不足の例外が発生し続けます。何も保存していないので、なぜ発生するのかわかりません。

コードは次のとおりです。

public class MainActivity extends Activity
  {
  private static final int LISTVIEW_ITEMS =10000;
  long                     _startTime;
  boolean                  _isMeasuring   =false;

  @Override
  public void onCreate(final Bundle savedInstanceState)
    {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final ListView listView=(ListView)findViewById(R.id.listView);
    final Field[] fields=android.R.drawable.class.getFields();
    final LayoutInflater inflater=(LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    // listen to scroll events , so that we publish the time only when scrolled to the bottom:
    listView.setOnScrollListener(new OnScrollListener()
      {
        @Override
        public void onScrollStateChanged(final AbsListView view,final int scrollState)
          {
          if(!_isMeasuring||view.getLastVisiblePosition()!=view.getCount()-1||scrollState!=OnScrollListener.SCROLL_STATE_IDLE)
            return;
          final long stopTime=System.currentTimeMillis();
          final long scrollingTime=stopTime-_startTime;
          Toast.makeText(MainActivity.this,"time taken to scroll to bottom:"+scrollingTime,Toast.LENGTH_SHORT).show();
          _isMeasuring=false;
          }

        @Override
        public void onScroll(final AbsListView view,final int firstVisibleItem,final int visibleItemCount,final int totalItemCount)
          {}
      });
    // button click handling (start measuring) :
    findViewById(R.id.button).setOnClickListener(new OnClickListener()
      {
        @Override
        public void onClick(final View v)
          {
          if(_isMeasuring)
            return;
          final int itemsCount=listView.getAdapter().getCount();
          listView.smoothScrollToPositionFromTop(itemsCount-1,0,1000);
          _startTime=System.currentTimeMillis();
          _isMeasuring=true;
          }
      });
    // creating the adapter of the listView
    listView.setAdapter(new BaseAdapter()
      {
        @Override
        public View getView(final int position,final View convertView,final ViewGroup parent)
          {
          final Field field=fields[position%fields.length];
          // final View inflatedView=convertView!=null ? convertView : inflater.inflate(R.layout.list_item,null);
          final View inflatedView=inflater.inflate(R.layout.list_item,null);
          final ImageView imageView=(ImageView)inflatedView.findViewById(R.id.imageView);
          final TextView textView=(TextView)inflatedView.findViewById(R.id.textView);
          textView.setText(field.getName());
          try
            {
            final int imageResId=field.getInt(null);
            imageView.setImageResource(imageResId);
            }
          catch(final Exception e)
            {}
          return inflatedView;
          }

        @Override
        public long getItemId(final int position)
          {
          return 0;
          }

        @Override
        public Object getItem(final int position)
          {
          return null;
          }

        @Override
        public int getCount()
          {
          return LISTVIEW_ITEMS;
          }
      });
    }
  }

@all: このコードには (convertView と viewHolder デザイン パターンを使用した) 最適化があることを知っています。これは、Google が作成した listView のビデオで言及したとおりです。私を信じてください、私は何が良いか知っています。これがコードの要点です。

上記のコードは、あなた (およびビデオ) が示しているものを使用する方が良いことを示しているはずです。しかし、最初に単純な方法を示す必要があります。私はビットマップやビューを保存しておらず、Google も同じテストを行っているため (したがって、パフォーマンス比較のグラフを取得したため)、素朴な方法でも機能するはずです。

4

4 に答える 4

7

ティムからのコメントは的を射ています。コードがconvertViewそのBaseAdapter.getView()メソッドを利用せず、毎回新しいビューを拡張し続けるという事実は、最終的にメモリ不足になる主な原因です。

前回チェックしたときListViewは、メソッドによって返されたすべてのビューをgetView()内部の「ごみ箱」コンテナーに保持します。このコンテナーListViewは、ウィンドウから切り離された場合にのみクリアされます。この「ごみ箱」は、それらすべてを生成し、適切なときconvertViewに戻す方法です。getView()

テストとして、ビューに画像を割り当てるコード部分をコメントアウトすることもできます:

                // final int imageResId = field.getInt(null);
                // imageView.setImageResource(imageResId);

そして、あなたはまだある時点でメモリ割り当ての失敗を得るでしょう:)

于 2012-07-09T02:15:34.580 に答える
2

コードには2つのポイントがあります。

  1. 以前の回答で述べたように、あなたは非常に多くの新しいオブジェクトを作成しようとしています。それがOutOfMemory問題の主な理由です。

  2. あなたのコードは、すべてのオブジェクトを継続的にロードするのに十分効率的ではありません(スクロールのために上下にスワイプするなど)、まあ、それは遅れています。

これらの2つの一般的な問題を修正するためのヒントを次に示します。

Field field = fields[position % fields.length];
View v = convertView;
ViewHolder holder = null;

if (v == null) {
    v = inflater.inflate(R.layout.list_item,null);
    holder = new ViewHolder();
    holder.Image = (ImageView) inflatedView.findViewById(R.id.imageView);
    holder.Text = (TextView)inflatedView.findViewById(R.id.textView);
    v.setTag(holder);
} else {
    holder = (ViewHolder) v.getTag();
}
return v;

これは簡単ViewHolderで効率的ListViewです。

static class ViewHolder {   
    ImageView Image;
    TextView  Text;
}

非常にシンプルですが、非常に効果的なコーディングです。

于 2012-07-09T04:37:11.087 に答える
0

あなたの例外eをキャッチしますが、OutOfMemoryErrorはエラーであり、例外ではありません。したがって、 OutOfMemory をキャッチしたい場合は、次のように書くことができます

catch(Throwable e){}

また

catch(OutOfMemoryError e){}
于 2012-12-07T08:40:45.673 に答える