2016년 9월 30일 금요일

안드로이드 Adapter View의 작동원리

RecyclerView가 등장한 이후로, ListView, GridView는 잘 이용되지 않제 됬지만 ListView를 이해하고 있으면 RecyclerView도 쉽게 이해할 수 있기때문에, Adapter View를 먼저 정리해보았다.

Adapter View?

  • 화면이 작은 모바일 장비에서, 다양한 정보를 한번에 표현하기 위해 이용하는 View들
  • 많은 정보를 효과적으로 처리하기 위해, View에 직접 정보를 주입하지 않고, Adapter라는 중간 매개체를 이용하기 때문에 붙여진 이름이다.
  • AdapterView는 ViewGroup을 상속받으므로, 내부적으로 많은 뷰들을 담을 수 있다.
  • 대표적인 AdapterView의 서브 클래스 : ListView, GridView, Spinner, Gallery

Adapter?

  • 데이터의 원본을 받아서 관리하고, 어댑터뷰가 출력할 수 있는 형태로 데이터를 제공하는 중간 객체
  • ListAdapter, SpinnerAdapter 인터페이스를 구현한 BaseAdapter 추상클래스의 서브클래스들을 이용해 다양한 데이터들을 다룰 수 있고, 원한다면 BaseAdapter를 직접 상속받아서 구현할 수도 있다.
    • ArrayAdapter : T 타입의 배열 데이터를 이용한 Adapter. List<T>T[] 타입 둘다 가능하다.(T[] 타입일 경우 데이터의 변경이 불가능하다.)
    • CursorAdapter : Database의 Cursor를 이용한 Adapter.
    • SimplAdapter : XML파일의 정적인 데이터를 이용한 Adapter.
  • 제공받은 원본을 가공하여 여러 어댑터뷰와 연결하는 기능을 제공한다.
    • 어댑터는 결국 받은 원본을 화면에 어떻게 보여줄지 정해주는 객체.
  • 어댑터와 연결된 원본 데이터가 변경되면, notifyDataSetChanged 메서드를 호출하여 원본이 변경되었다고 어댑터뷰에 알려주어 화면이 다시 그려지도록 해야한다.
    • 즉, 어댑터뷰를 변경하고 싶으면 원본을 변경하고 화면을 다시 그려야 한다.

동작원리

AdapterView
  • 어댑터뷰는 화면 드로잉에 필요한 정보를 어댑터에게 요청하게 되고, 어댑터는 자신이 가지고 있는 데이터를 가지고 요청받은 정보를 어댑터뷰에 리턴한다.
  • 다음의 메서드는 BaseAdapter를 상속받게 되면 반드시 구현해야 하는 메서드로, 어댑터뷰에서 화면 드로잉을 할때 어댑터에게 정보를 요청하는 메서드이다.
    • int getCount() : 화면에 표시해야 하는 데이터의 갯수 반환
    • T getItem(int position) : 인자로 받은 위치의 데이터 반환
    • long getItemId(int position) : 인자로 받은 위치의 데이터 id 구분자 반환
    • View getView(int position, View convertView, ViewGroup parent) : 인자로 받은 위치의 데이터가 화면에 표시될 뷰 반환
  • getCount 메서드로부터 반환된 데이터의 갯수만큼 나머지 메서드들이 호출되며, 화면을 그린다.

getView에 대하여

  • 원본의 각 아이템들이 어댑터뷰에 어떻게 보일지 뷰를 그려서 반환하는 메서드이다.
  • 화면이 그려져야할 때 어댑터뷰로부터 호출된다.
  • 기존에 화면에 그렸던 뷰가 존재하지 않으면 convertView는 null값이 넘어오며, 이전에 화면에 그려졌다가 보이지 않게 된 뷰가 존재한다면 null이 아닌 값이 넘어온다.
    • 4.0 이전에 convertView는 무작위 순서로 넘어왔으나, 4.0 이후에는 화면에서 사라지는 순서대로 넘어오는 듯하다.
    • 이 Recycling 기법으로, 어댑터뷰는 화면에 표시할 최소한의 뷰만을 생성하게 된다.

getView최적화1 - View Holder 패턴

  • 뷰 전개(inflating)은 매우 비싼 연산이므로, getView에서 findViewById 등, 뷰 전개하는 연산을 반복적으로 하는 것은 퍼포먼스를 떨어뜨린다.
  • View Holder 패턴은 convertView를 처음 생성할 때, 미리 뷰를 전개하여 View의 태그에 참조객체를 저장하는 방법이다.
    • 뷰 전개를 view 생성시에 딱 한번만 하므로, 퍼포먼스가 많이 향상된다.
// 전개된 뷰의 참조값을 저장할 객체
private class ViewHolder {
    private TextView textName;
    private TextView textType;
}

// 어댑터의 getView 메서드
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;

 if(convertView == null) {
     // convertView가 null이면 Holder 객체를 생성하고
        // 생성한 Holder 객체에 inflating 한 뷰의 참조값을 저장
        holder = new ViewHolder();

        convertView = inflater.inflate(R.layout.layout_list_item, parent, false);
        holder.textName = (TextView) convertView.findViewById(R.id.text_item_name);
        holder.textType = (TextView) convertView.findViewById(R.id.text_item_type);
        
        // View의 태그에 Holder 객체를 저장
        convertView.setTag(holder);
    } else {
     // convertView가 null이 아니면 뷰를 생성할때 태그에 저장했던 Holder 객체가 존재
        // 이 Holder 객체는 자신을 inflating한 참조값(다시 전개할 필요가 없다.)
        holder = (ViewHolder) convertView.getTag();
    }

 // 속성값 변경
    ParcelableModel parcelableModel = getItem(position);
    holder.textName.setText(parcelableModel.name);
    holder.textType.setText(Integer.toString(parcelableModel.type));
    return convertView;
}

getView최적화2 - 지연 로딩(Lazy Loading)

  • 항목중에 네트워크로부터 받아와야 하는 파일이 있을 경우에, 네트워크 작업을 getView에서 진행하게 되면 getView가 리턴되지 않아서 어댑터뷰의 화면이 그려지지 않아 화면이 멈추게 된다.
  • 이런 경우, AsyncTask 등의 방법을 이용해 작업을 비동기로 처리한다. 비동기 처리시 뷰를 바인딩하여, 작업이 완료된 후 자동적으로 뷰의 속성을 변경되도록 구현한다.

댓글 2개: