Github에 Android 와 관련된 샘플을 업로드할 예정이다.

시리즈 중 1편으로 RecyclerView 와 CardView를 활용한 간단한 리스트 화면을  만들어 봤다.  

(https://github.com/kajsss/android-example)





Layout 작성

레이아웃은 RecyclerVIew를 포함하고 있는 메인 페이지와 각 아이템을 그리는 카드뷰 레이아웃 2개가 필요하다.


<activity_main> 에서는 적당한 위치에 RecyclerVeiw 를 위치 시켰다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.miloapp.MainActivity">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
tools:context=".MainActivity">

<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
</RelativeLayout>
</LinearLayout>


<item_cardview> 에서는 RecyclerView 안에 그려질 아이템의 레이아웃을 설정한다. 예제에서는 Title과 Description 두개의 TextView로 구성된다.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
card_view:cardCornerRadius="4dp"
card_view:cardBackgroundColor="@color/colorPrimary"
card_view:cardUseCompatPadding="true">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="20dp"
android:textStyle="bold"
android:textSize="36sp"
tools:text="M"/>

<TextView
android:id="@+id/text_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="15sp"
android:text="I"
android:layout_centerInParent="true"/>
</RelativeLayout>
</android.support.v7.widget.CardView>



코드  작성


public class MainActivity extends AppCompatActivity {

@BindView(R.id.recycler_view)
RecyclerView mRecyclerView;

@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);

ItemRepository itemRepository = new ItemRepository();

mRecyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
RecyclerViewAdapter recyclerViewAdapter = new RecyclerViewAdapter(getApplicationContext(), itemRepository.getAllItems());
mRecyclerView.setAdapter(recyclerViewAdapter);

}
}

Main Activity에서는 RecyclerView를 Binding 하고 RecyclerView 에 LayoutManager 와 Adapter를 셋팅해주는 코드가 필요하다. 

RecyclerViewAdapter 에는 화면에 그려줄 ObjectList 를 같이 넘겨줘서 화면에 그리도록 한다. 

위 코드에서 itemRepository 에서는  List<Item> 타입을 리턴하게 된다.


@Data
@AllArgsConstructor
public class Item {
String title;
String description;
}

VO 클래스는 lombok annotaiton 을 이용하여 Setter나 Getter를 자동으로 생성하도록 하였다.



public class ItemRepository {

public List<Item> getAllItems(){
return dummyData();
}

private List<Item> dummyData(){
List<Item> dummyList = new ArrayList<>();
dummyList.add(new Item("A","apple"));
dummyList.add(new Item("B","Baggin"));
dummyList.add(new Item("C","Cati"));
dummyList.add(new Item("D","Dadday"));
return dummyList;
}

}
추후 확장을 위해서 구현해놨는데 ItemRepository는 단순히 List<Item> 형태를 리턴한다




public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewHolders> {

List<Item> itemList;
private Context context;

public RecyclerViewAdapter(Context context, List<Item> itemList) {
this.itemList = itemList;
this.context = context;
}

@Override
public RecyclerViewHolders onCreateViewHolder(ViewGroup parent, int viewType) {
View layout = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_cardview, null);
RecyclerViewHolders rcv = new RecyclerViewHolders(layout);
return rcv;
}

@Override
public void onBindViewHolder(RecyclerViewHolders holder, int position) {
holder.title.setText(itemList.get(position).getTitle());
holder.description.setText(itemList.get(position).getDescription());
}
@Override
public int getItemCount() {
return this.itemList.size();
}

class RecyclerViewHolders extends RecyclerView.ViewHolder implements View.OnClickListener {

TextView title;
TextView description;

public RecyclerViewHolders(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
title = (TextView) itemView.findViewById(R.id.text_title);
description = (TextView) itemView.findViewById(R.id.text_description);
}

@Override
public void onClick(View view) {
Toast.makeText(view.getContext(), ((TextView) view.findViewById(R.id.text_title)).getText() + "Item Clicked", Toast.LENGTH_SHORT).show();
}
}
}

가장 중요한 부분인 RecyclerViewAdapter 부분이다.  전체 소스는 위와 같고 아래에서 좀 더 자세히 설명 하도록 하겠다.

RecyclerView Adapter를 상속 받으면 onCreateViewHolder, onBindViewHolder, getItemCount 를 재정의(Override) 해야 한다.



public RecyclerViewAdapter(Context context, List<Item> itemList) {
this.itemList = itemList;
this.context = context;

}

 RecyclerView Adapter의 역할은 RecyclerView에 데이터를 그려주는 역할을 담당한다. 그 역할을 위해서 그릴 대상인 ItemList 를 파라미터로 전달 받았다.



@Override
public RecyclerViewHolders onCreateViewHolder(ViewGroup parent, int viewType) {
View layout = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_cardview, null);
RecyclerViewHolders rcv = new RecyclerViewHolders(layout);
return rcv;
}

onCreateViewHolder는 데이터를 담을 뷰 홀더를 생성하여 리턴한다. 이때 위에서만들었던 카드뷰 Layout을 inflate 한다. 



class RecyclerViewHolders extends RecyclerView.ViewHolder implements View.OnClickListener {

TextView title;
TextView description;

public RecyclerViewHolders(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
title = (TextView) itemView.findViewById(R.id.text_title);
description = (TextView) itemView.findViewById(R.id.text_description);
}

@Override
public void onClick(View view) {
Toast.makeText(view.getContext(), ((TextView) view.findViewById(R.id.text_title)).getText() + "Item Clicked", Toast.LENGTH_SHORT).show();
}
}

뷰홀더는 Layout에 있는 Component와 Adapter 를 연결하기 위한 클래스라고 보면 이해가 쉬울 거 같다.

뷰홀더에서 각 아이템을 클릭 했을때 Action을 구현하였다.



@Override
public void onBindViewHolder(RecyclerViewHolders holder, int position) {
holder.title.setText(itemList.get(position).getTitle());
holder.description.setText(itemList.get(position).getDescription());
}

onBindViewHolder에서는 전달받은 아이템을 홀더에 값을 셋팅해주는 역할을 수행하게된다.

이때 홀더를 이용하여 값을 셋팅하면 리스트에 보여지게 된다. 


@Override
public int getItemCount() {
return this.itemList.size();
}

전체 아이템 갯수를 리턴하도록 한다. 




------ 끝 ----- 





문제상황 


- Android Project 의 테스트케이스가 많아지면서 어느순간 부터 빌드시 Out of memory 가 발생함

- 로컬 테스트 수행할때는 시간이 오래 걸리지 않았지만, 빌드서버와 로컬 Gradle task "debugBuildTest" Task 수행시에 해당 문제가 발생

- 특정 Activity 의 테스트케이스를 Ignore 했을 경우에는 이상없이 빌드가 수행됨. 이 Activity들에서는 Dialog를 Shadow로 띄워서 테스트를 함

- 빌드서버의 메모리를 높여서 띄워봤지만 문제는 해결 되지 않음



문제해결


- 문제 원인은 하나의 Process로 작업이 진행되면서 GC가 제대로 동작하지 않아서 순간적으로 많은 양의 메모리를 사용하는 테스트가 많이 수행될 경우 Memory leak 현상이 발생된 것으로 보였고, forkEvery 옵션을 주어서 새로 Process를 생성하도록 변경하고나니 Out of Memory 문제가 해결되었고 평화로워 졌다.


같은 문제를 앓고 있는 분들에게 도움이 되었으면 한다.


testOptions {
unitTests.returnDefaultValues = true
unitTests.all {
jvmArgs '-Xmx2g', '-XX:MaxPermSize=1024m', '-XX:+HeapDumpOnOutOfMemoryError'
jacoco {
includeNoLocationClasses = true
}
forkEvery = 10
}



'Programming' 카테고리의 다른 글

[JavaScript] Switch - Fall Through  (0) 2017.05.28
RecyclerView, Cardview Example  (0) 2017.05.28
안드로이드 MVC, MVP Pattern  (0) 2017.04.05
Android Clean Architecture  (0) 2017.04.03
다른 App으로 데이터 전달 하기  (0) 2017.03.28

Kotlin에 대해서 공부를 하면서 MVC와 MVP 패턴에 대해서 생각해 볼 수 있는 기회가 생겼고 정리하고자 글을 포스팅 한다.



MVC 


MVC 모델은 Spring MVC를 통해서 가장 많이 접했던 구조라 말할 수 있다.

Model , View, Controller 세가지로 나누어 정의 한다.

 

Model은 View나 Controller에서 독립되어 Object(Data)의 상태나 비지니스 로직을 담당하게 됩니다. Controller에서는 View에 모델을 통해서 정보를 전달하게 된다.

View는 사용자와의 인터렉션을 하는 영역으로 UI를 그리고 컨트롤러와 통신을 하는 역할을 맡습니다. View는 모델이나 컨트롤러와는 독립적으로 순전히 View 본연의 역할만을 수행하는 것이 좋습니다.

Controller는 Model과 View 를 묶어주는 역할을 합니다. View에서 컨트롤러로 어떠한 요청을 보내고 컨트롤러에서는 요청에 대한 처리를 진행합니다. 이때 모델에 변경이 생겼고 다시 View의 갱신이 필요한경우 컨트롤러는 다시 View에 변경된 모델을 내용을 알려주게 됩니다.

위 설명은 일반적인 MVC 패턴의 구성요소별 설명이라고 생각하면 된다.


반면에 안드로이드에서는 View 와 Controller 의 영역에 대한 구분히 모호합니다. 이와 관련된 역할을 하는것은 Activity 나 Fragment가 담당하게 되는데, 프로젝트를 진행을 하다보면 의식적으로 모델과 관련되어 있는 부분은 Repository로 분리해서 데이터를 다루게 되지만 View와 Controller 는 Activity나 Fragment에서 별도 구분없이 프로그래밍을 하게 된다.. 이러한 작업이 연속으로 이루어지다 보면  거대한 Activity나 Fragment를 만나게 됩니다. 거대한 Activity나 Fragment는 그만큼 거대한 테스트클래스가 필요하다는걸 말한다.  또한 View와 Controller의 역할을 하나의 클래스에서 하게되다보면 View 영역의 변경이 Controller에 미치게되고 그 반대또한 가능하게 된다. 그만큼 유지보수 비용이 많이 발생하게 된다.

이러 문제들을 해결하고자 MVP 모델을 사용한다.





MVP


MVC 모델에서 Model의 역할은 MVC 패턴과 같이 데이터를 저장하고 핸들링하는 부분을 담당한다.

다른점은 View 의 영역을 보다 명확히 하여. Activity 나 Fragment가 View의 일부로서 동작한다는 점이다. 엑티비티는 뷰 인터페이스를 구현하고 Presenter가 인터페이슬 가지고있게 만들어 특정한 뷰에 결합되지 않은 상태에서 가상의 View 를 통해서 독립된 유닛 테스트를 실행할 수 있게 된다.

Presenter는 인터페이스로서 MVC가 가지고있는 테스트의 어려움을 극복하고 모듈화/유연성 문제 또한 해결합니다. 명심해야 할 부분은 Presenter는 인터페이스로 종속성이 발생해서는 안되며 유스케이스를 구현하게 된다.


 

+ Recent posts