Frontend/Android

[안드로이드] 리스트뷰 성능최적화 (Convertview와 ViewHolder Pattern의 이해)

에반황 2016. 7. 5. 05:31


이 글은 PC 버전 TISTORY에 최적화 되어있습니다.




[목차]


    1.  리스트뷰의 개념 및 사용법

    2.  리스트뷰의 성능 최적화




서론

 이전 포스팅에서 안드로이드 애플리케이션에서 가장 많이 애용되는 View 중 하나인 LiestView의 개념과 사용법을 다뤘다.  아이템 별로 많은 정보, 이미지를 사용할 때 매끄럽지 않은 스크롤에 골머리를 앓았던 경험이 있었을 거라고 생각한다.  ListView는 어떤 구조로 동작하고 매끄럽게 동작하기 위해 어떻게 성능을 최적화할 수 있을지에 대해 알아보겠다.
 


ListView 의 작동원리

 리스트뷰는 다른 일반 View들과 다르게 성능을 위해 설계된 View이다. 일반 View의 개념으로 설계된다면, 아이템의 데이터를 보여줄 때마다 inflate를 하게되는데 이는 성능 상에 큰 문제를 야기하여 매끄러운 스크롤을 보장하지 못한다.

 이를 해결하기 위해 ListView 는 아이템의 전체 개수가 아닌 화면에 보여지는 개수 만큼만 View를 그려놓은 뒤 그것을 재활용하는 구조로 설계 되었다. 예를 들어 배열이 100개이고 화면에 보여지는 View들이 5개면 Adapter에 100개의 Object들이 Setting 된 후 모든 Object들의 100개의 View를 생성하여 보여주는 것이 아니라 5개의 Object만 보여준다. 여기서 ListView 스크롤이 내려가게되면 첫 번째 View는 사라지고 6번째의 View가 보여진다. 이 때 6번째의 View도 새로 생성되는 것이 앙니라 기존의 View를 재사용하여 값만 새로이 Setting하는 개념이다.
 




<ListView 동작원리>


 실제 화면에 그려지는 아이템을 ConvertView 라는 배열로 관리하는데, 화면에 보여지는 만큼 Convert View를 생성하고 스크롤시 View를 재활용하기 때문에 성능면으로 우수한 구조이다. ListView의 재활용 View인Convertview는 Adapter의 getView( )를 통해서 관리된다. ListView는 화면에 새로운 아이템을 표시하라 때마다 Adapter의 getView( )를 호출하게 된다. 여기서 getView 메소드는 각 View를 보여줄 때마다 호출되기 때문에 5개의 View를 보여줄 때 무조건 5번의 호출이 이루어지게 된다. ListView는 자원을 재사용 할 때는 null이 아닌 값이 들어오게 되며, null인 경우에는 레이아웃을 inflate한다. ConvertView가 null이 아닌 경우에는 기존의 View 를 재사용하기 때문에 새롭게 View를 inflate 할 필요 없이 데이터만 바꾸는 작업을 진행하면 된다. 




개선된 코드는 아래와 같다.



<코드1>

public class XXXAdapter extends ArrayAdapter { @Override public View getView(int pos, View convertview ViewGroup parent) { if (convertview == null) { LayoutInflater li = ((LayoutInflater) mCx.getSystemService(Context.LAYOUT_INFLATER_SERVICE)); convertview = li.inflate(R.layout.xxx_item, null); } TextView tx1 = (TextView) convertview.findViewById(R.id.xxxx1); TextView tx2 = (TextView) convertview.findViewById(R.id.xxxx2); ImageView img1 = (ImagView) convertview.findViewById(R.id.yyyy1); Item itm = arraylistItems(pos); tx1.setText(itm.tx1); tx2.setText(itm.tx2); img1.setText(itm.img1); return cv; } }




ViewHolder Pattern

 이보다 좀더 개선된 코드가 있는 데 (Google I/O에서 권장됨) 개별 View가 존재하는 경우 View의 setTag / getTag를 이용하여  findViewById를 하지 않는 ViewHolder Pattern 방법이다. ListView에서 아이템이 보여질 때마다 getView( )가 호출된다. 여기서 해당되는 데이터를 View에 표시하기위해 findViewById( )를 통해 해당되는 View를 얻어온 후 데이터를 표시하는 것이 일반적이다. 하지만 아이템 View 구조가 복잡할 경우 매번 findViewById( )를 호출하는 것은 매우 값비싼 작업이기 때문에 매끄러운 스크롤을 방해한다. 아래와 같이 코딩하는 경우 convertview가 없을 경우에만 View를 생성하고 각각의 데이터들을 findViewById를 이용해 찾아주면 되고, convertview가 없는 경우에는 그대로 모든 것을 다 이용하고 데이터의 값만 Setting 해주면 되는 것이다. 




개선된 코드는 아래와 같다.


<코드2>

public static class ViewHolder { TextView tx1; TextView tx2; ImageView img1; } public class XXXAdapter extends ArrayAdapter

{ @Override public View getView(int pos, View convertview, ViewGroup parent) { if (convertview == null) { LayoutInflater li = ((LayoutInflater) mCx.getSystemService(Context.LAYOUT_INFLATER_SERVICE)); convertview = li.inflate(R.layout.xxx_item, null); ViewHolder holder = new ViewHolder(); holder.tx1 = (TextView) convertview.findViewById(R.id.xxxx1); holder.tx2 = (TextView) convertview.findViewById(R.id.xxxx2); holder.img1 = (ImagView) convertview.findViewById(R.id.yyyy1); convertview.setTag(holder); } else { holder = (ViewHolder) convertview.getTag(); } Item itm = arraylistItems(pos); holder.tx1.setText(itm.tx1); holder.tx2.setText(itm.tx2); holder.img1.setText(itm.img1); return convertview; } }






반응형