현우의 개발노트

안드로이드 면접2

2018-04-01

Java 메모리구조 Heap, Data, Stack

JavaJava 코드를 컴파일러를 통해 바이트코드로 변환시킨 뒤 JVM 위에 실행시키는 구조이다. JVM은 자바 바이트코드를 실행시키는 가상머신으로서 자바가 플랫폼에 독립적으로 실행될 수 있게 한다. JVM 구조에는 Class Loader, Execution Engine, Garbage Collector, Runtime Data Areas 가 있다.

JVM 구조

Class Loader

컴파일러에 의해 바이트코드로 변환된 코드를 Runtime Data Areas 에 클래스 단위로 로드시키고, Link 를 통해 적절히 배치시키는 작업을 한다. Class Loader 로 인해 동적으로 클래스를 로드할 수 있다.

Execution Engine

Runtime Data Areas 에 배치된 바이트 코드를 실행시키는 역할을 한다. 메모리에 올라온 코드를 명령어 단위로 실행한다.

Garbage Collector

어플리케이션이 생성한 객체의 생존여부를 판단하여 더 이상 사용되지 않는 객체의 메모리를 반환함으로써 메모리를 자동적으로 관리하는 역할을 한다.

Runtime Data Areas

운영체제로부터 할당받은 메모리를 관리하는 영역이다. JVM 에서 관리하는 메모리 영역은 Method(Static or Class) Area, Runtime Constant Pool, Heap Area, Stack Area, PC Register, Native Method Stack Area 으로 나뉜다.

Method (Static or Class) Area

호출한 클래스와 인터페이스에 대한 Runtime Constant Pool, 메소드와 필드, Statoc 변수, 메소드 바이트 코드 등을 저장한다.

Runtime Constant Pool

Method Area 영역에 포함되는 공간이다. 클래스와 인터페이스 상수, 메소드와 필드에 대한 모든 reference를 저장한다.

Runtime Data Areas 구조

Heap Area

런타임에서 동적으로 할당하는 객체를 저장하는 공간이다. new연산을 통해 생성되는 객체와 배열을 저장하며 GC의 주 대상이 된다.

Heap Area 메모리 관리

  • Young Generation - 객체가 생성되자마자 저장되는 공간이다. 시간이 지날수록 우선순위가 낮아지며 Old 영역으로 내려가게 된다. 이곳에서 객체가 사라지면 Minor GC 가 발생한다.
  • Old Generation - 오래된 객체가 저장되는 공간이다. 이곳에서 객체가 사라지면 Major GC 가 발생한다.
  • Permanent Generation - Class Loader 에 의해 로드되는 클래스나 메소드에 대한 Meta 정보가 저장되는 영역이다. Reflection을 이용하여 동적으로 클래스를 로드하는 경우 자주 사용된다.
Stack Area

Stack 구조의 저장공간이다. 함수 호출시 발생하는 지역변수, 매개변수, 연산 데이터 등을 저장하는 공간이다. 함수를 호출하면 push를 통해 Stack 에 저장하고 함수 호출이 종료되면 다음 실행할 함수를 pop하여 함수를 실행한다. 스레드 별로 저장공간을 따로 생성하여 관리한다.

PC Register

현재 수행 중이거나 다음에 실행할 인스트럭션 주소를 저장한다. 연산 수행 중 발생하는 데이터를 레지스터에 저장하였다가 CPU가 필요할 때 가져다 쓴다. 스레드 별로 공간을 만들어 관리한다

Native Method Stack Area

자바가 접근할 수 없는 영역은 C와 같은 Low Level 언어로 작성되어 있다. 따라서 Native 코드를 실행시키면서 발생하는 데이터를 Stack 구조로 저장하기 위한 공간이다. 스레드 별로 생성된다.

참조

JVM과 메모리 구조(성능 개선을 이한 GC의 활용)

Lambda 식이란

Lambda 는 함수에 대한 이론적 기반을 세우는 수학적 추상화를 뜻하고 이것을 프로그래밍에 녹여서 표현한 것이 Lambda 표현식이다. 프로그래밍에서 Lambda 에 근간을 두어 설계한 패러다임이 함수형 프로그래밍이다. 함수형 프로그래밍은 함수의 입력만을 의존하여 출력을 만드는 프로그래밍 패러다임이다. 함수 개념에 근간을 두어 설계하였기에 다음과 같은 특징을 가진다.

  1. 함수를 객체로 사용함으로써 값으로 할당하거나 파라미터로 전달이 가능하다. (고계함수)
  2. 함수를 불변상태로 규정하여 부작용을 최소화하고 멀티 스레드 환경에서 동기성 문제에 안전하다. (순수함수)
  3. 코드를 간결하게 하여 가독성을 높임으로써 로직에 집중시킨다. (익명함수)

Java 8 이전에는 함수에 대한 특별한 개념이 없었고 지원하는 기능도 없었다. 하지만 Java 8 에서부터 람다식과 함수형 인터페이스라는 개념을 도입하여 인터페이스 안에 단 하나의 함수를 선언하여 이것을 객체처럼 사용하고 람다식으로 표현이 가능하도록 지원하였다. 또한 단 하나의 함수만 들어가는 것을 보장하기 위해 @FunctionalInterface 라고 선언함으로써 컴파일 타임에 에러를 잡을 수 있게 하였다.

참조

위키백과 - 함수형 프로그래밍

Medium - 함수형 프로그래밍이란?

Java 8 람다 표현식 자세히 살펴보기

ListView의 문제점

  1. ViewHolder Pattern 을 강요하지 않는다.
  2. Vertical 모드만 지원한다.
  3. Animation 을 따로 지원하지 않기에 뷰에 대해 직접 구현해야 한다.
  4. 데이터가 변경될때 notifyDataSetChanged() 를 통해 뷰를 갱신하는데 모든 뷰를 갱신하기에 비용이 매우 크다.

이러한 문제를 해결하고자 RecyclerView 가 나왔다. RecyclerView 의 장점은 RecyclerView.ViewHolder, LayoutManager, ItemAnimator, ItemDecoration, DiffUtil, OnItemTouchListener 가 있으며 이것을 통해 문제점을 개선하였다.

RecyclerView.ViewHolder

RecyclerView 에서는 ViewHolder 를 강제하고 있기에 뷰에 대한 findViewById() 작업 비용을 줄일 수 있다. ListView 에서도 직접 패턴을 구현하여 적용할 수 있지만, 강제하지 않기 때문에 잘못 사용될 위험이 공존한다.

LayoutManager

LayoutManager를 통해 Liear, Grid, Staggered 형식으로 다양하게 리스트뷰를 확장할 수 있다. Liear에서는 Horizontal, Vertical 모드를 지원하기에 오픈 소스를 찾거나 커스텀 할 필요없이 쉽게 구현이 가능하다.

ItemAnimator

animateAdd, animateRemove, animateMove 를 구현함으로써 추가, 삭제, 변경시 적용할 애니메이션을 쉽게 적용할 수 있다.

ItemDecoration

ListView 에서는 xml 파일에 android:divider 항목을 추가함으로써 구현할 수 있지만 모양과 크기를 유연하게 커스텀하기 어렵다. ItemDecoration 은 뷰가 그려질 때 공백을 넣거나, 경계선을 추가하는 등 뷰를 꾸미는 작업을 쉽고 다양하게 적용할 수 있다. 따라서 별도의 뷰를 추가하여 성능 저하가 발생하거나, padding 을 통해 뷰에 공백을 추가하여 애니메이션이 어색해지는 문제를 해결할 수 있다.

DiffUtil

DiffUtil은 데이터의 변화를 감지하여 뷰를 갱신하는 클래스이다. areItemsTheSame, areContentsTheSame, getChangePayload 등을 오버라이딩하여 데이터간 변화를 감지한다. RecyclerView.Adatper 에서 notifyItemMoved, notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved 가 호출되면 DiffUtil 에서 해당 범위의 데이터의 변화를 감지하고 뷰를 선택적으로 갱신함으로써 비용이 큰 notifyDataSetChanged 대신 효율적으로 사용될 수 있다.

OnItemTouchListener

ListViewOnItemClickListener 를 통해 아이템 클릭 이벤트를 쉽게 구현할 수 있었다. 반면 RecyclerViewOnItemTouchListener 에서 GestureDetector 를 이용하여 클릭뿐만 아니라 롱클릭이나 탭 등의 다양한 제스쳐 이벤트를 구현할 수 있다. 하지만 구현이 복잡하다는 단점이 있다.

참조

RecyclerView & ListView basic comparison

StackOverFlow - What are RecyclerView advantages compared to ListView

ProAndroidDev - ItemDecoration in Android

AndroidPub - Smart way to update RecyclerView using DiffUtil

Recyclerview OnItemClick 리스너 구현

자바 동등성과 동일성

두 객체를 비교할때 완전히 같은 객체일 경우 동일성 (identity) 이라하고 객체에 대한 정보가 같을 경우 동등성 (equivalent)이라고 한다. 동등성 검사equals() 를 사용하고 동일성 검사hashCode() 를 사용한다. equals() 를 오버라이딩 하지 않을 경우 Object 클래스의 equals() 에 의해 해시값을 비교하기 때문에 동일성 검사가 이루어진다. 따라서 제대로된 동등성 검사를 하기 위해서는 equals() 를 오버라이딩 해야한다. hashCode() 는 객체별 고유의 해시키를 부여함으로써 객체를 구분한다. 동일한 객체가 아니더라도 같은 객체로 인식하고 싶다면 hashCode() 또한 오버라이딩을 해야한다. HashMap, HashSet, HashTable 에서는 객체를 구분할때 equals() 가 아니라 hashCode() 를 사용하여 구분하기에 이러한 자료구조를 사용하기 위해서는 hashCode() 를 오버라이딩하여야 제대로 사용이 가능하다. Map 은 데이터를 추가하는 순간부터 객체의 해시값을 기억하므로 이후에 객체의 데이터가 변경되더라도(해시값이 변경되어도) Map 은 인지를 하지 못한다. 따라서 Map 의 키에 추가되는 데이터는 immutable 해야한다.

참조

Java 의 equals 와 hashCode, 동등성과 동일성

StackOverFlow - Why can hashCode() return the same value for different objects in Java?

자바에서 다중상속을 막은 이유

다중 상속에는 여러가지 문제가 내재되어 있다. 예를 들어 변수명 충돌이나 중복된 클래스 상속으로 인해 오버라이딩이 모호한 다이아몬드 문제가 대표적이다. 무엇보다 자바는 객체지향언어이기에 다중상속을 지원하면 객체지향이 무너질 수 있다. 객체지향의 단일책임 원칙에 의해 클래스는 오직 하나의 기능을 가지고 그 하나의 책임에 집중해야되며, 리스코브 치환 원칙에 따라 자식 클래스를 몰라도 부모 클래스의 함수를 사용할 수 있도록 대치가 가능해야 한다. 하지만 다중상속을 허용하면 클래스의 성질이 복합적으로 섞여 부모와 IS-A 관계가 모호해져 정체성이 불분명해질 수 있다. 이것은 위 객체지향 원칙에 위배되기에 이런 문제를 막고자 다중상속을 금지하였다. 하지만 단일 상속은 클래스를 경직되게 만들고 유연한 구현이 불가하다. 자바는 이런문제를 InterfaceComposition Pattern 을 사용하여 해결할 수 있다. 구현에 대한 책임을 implements 하는 클래스에 위임함으로써 다이아몬드 문제를 해결할 수 있고 상속이 아닌 mixin 개념을 사용하여 기능을 확장시킬 수 있다. mixin 은 클래스가 주 자료형 이외에 추가로 구현할 수 있는 자료형으로써 새로운 기능을 제공할 수 있다. 결국 자료형을 확장함으로써 기능을 추가하되 객체의 정체성은 유지할 수 있다. Composition Pattern 은 구성이라는 개념을 사용한다. 여러 클래스를 멤버필드에 포함시켜 객체의 기능은 확장시키고 부모 클래스로부터 받은 성격은 유지할 수 있다.

참조

자바가 다중 상속을 지원하지 않는 이유?

객체지향 개발 5대원리:SOLID

해시코드란

객체를 구분하기 위해 고유한 숫자를 제공하는 함수이다. 내부적으로는 해시 알고리즘를 통해 임의의 데이터를 고정된 길이의 데이터로 매핑하여 고유한 숫자를 만들어낸다. 객체의 주소는 해시값으로 인덱싱이 되어 있어 메모리 주소를 16진수의 해시값으로 반환된다. hashCode 는 객체의 주소를 Integer 값으로 변환하기에 만들수 있는 고유값의 개수는 32비트로 만들어질 수 있는 경우의 수이다. 따라서 이보다 더 많은 개수를 인덱싱할 경우 다른 객체이지만 같은 해시값을 반환하는 오류가 내재되어 있다. 이런 문제를 hash collision 이라고 하며 이것을 해결하기 위해 chaining 이라는 기법을 사용한다. chaining 은 해시값이 가리키는 주소에 값을 저장하는 것이 아니라 LinkedList 를 참조하게하여 충돌이 발생하면 노드간 참조를 통해 연결구조를 만든다. 따라서 키값에 매칭하는 해시값으로 LinkedList 에 접근할때 두개 이상의 값이 존재하면 탐색을 하여 키값이 일치하는 Entry 를 찾아 반환한다. 이로인해 충돌을 해결할 수 있다.

참조

해쉬 알고리즘 요약 정리

StackOverFlow - Java hashCode method maximum return value

Map, HashMap 그리고 SparseArray

Object 클래스에 무슨 함수가 있는가

객체를 위한 메소드와 스레드를 위한 메소드로 나뉜다. 스레드를 위한 함수는 final 로 선언되어 있어 오버라이딩이 불가하다.

equals(Object obj)

두 개의 객체를 비교

toString()

객체에 대한 정보를 문자열로 반환한다. 오버라이딩하지 않을 경우 객체의 해시코드를 출력한다.

protected clone()

객체를 복제하여 새로운 객체를 반환한다. 단지 필드 값만 복사하기에 필드의 값이 배열이나 객체이면 제대로된 깊은 복사가 이루어지지 않는다. 제대로 사용하려면 Cloneable 을 구현하여야 한다.

protected finalize()

GC 에 객체를 반환한다.

getClass()

클래스 자료형을 반환한다.

hashCode()

객체의 고유 해시값을 반환한다. 해시값은 객체의 주소 인덱싱에 사용되기에 주소값을 16진수로 변환해서 반환한다.

notify()

wait된 스레드 실행을 재개한다.

notifyAll()

wait된 모든 스레드를 실행한다.

wait()

다른 스레드가 notify 를 호출때까지 스레드를 일시적으로 중지한다.

wait(long timeout)

다른 스레드가 notify 를 호출하거나 일정시간이 지날때까지 스레드를 중지한다.

wait(long timeout, int nanos)

다른 스레드가 notify 를 호출하거나 일정시간이 지나거나 인터럽트를 당할때까지 일정시간만큼 스레드를 중지한다.

참조

Object 클래스

Class를 immutable 하게 하려면 어떻게 해야되는가

클래스 선언시 final 키워드를 사용하여 변경을 막습니다. 멤버변수 선언 또한 final 을 사용함으로써 멤버변수의 변경을 막아야한다. 대표적인 Immutable ClassString, Integer, Boolean, Float, Long, Double 등이 있다. 클래스를 불변하게 만들면 멀티 스레드 환경에서 안정적인 사용이 보장된다. Immutable Class 는 멤버변수를 초기화를 주의해야한다. 생성자를 통해 멤버변수를 초기화를 할때 그대로 가져다 쓸 경우 생성자의 파라미터로 넘겼던 객체를 변경하면 Immutable Class 의 멤버변수도 변경하는 문제가 생긴다. 따라서 멤버변수는 반드시 복사를 하여 초기화를 해야된다.

참조

hashcode - 자바에서 Immutable이 뭔가요?

디자인 패턴에 대해 설명과 구현

싱글톤

객체를 단 하나만 생성하도록 제한하고자할때 사용한다. 멀티 스레드에서 동기화 문제가 유발되는 클래스이거나 객체 생성시 리소스가 매우 큰 경우 하나만 생성하고 이것을 재사용함으로써 자원의 효율과 동기화 문제를 막을 수 있다. 하지만 멀티 스레드 환경에서 하나의 객체만 생성하도록 컨트롤하는 것이 매우 어렵고 복잡하다는 단점이 있다. 안드로이드에서는 Network Connection 클래스를 싱글톤으로 생성하여 사용한다.

팩토리

객체를 만드는 일을 서브 클래스에 위임할때 사용한다. 구조적으로 보면 팩토리 인터페이스를 정의하고 인터페이스를 구현하는 클래스에서 여러 클래스에 대해 객체를 생성하는 구조를 가진다. 팩토리 패턴을 사용하면 객체를 직접 생성하는 것이 아니라 서브 클래스가 생성하도록 설계함으로써 클래스간 결합도를 낮출 수 있다. 안드로이드에서는 프래그먼트를 생성 및 관리할때 팩토리 메소드 패턴을 사용할 수 있다.

참조

디자인패턴 - 팩토리 패턴(factory pattern)

빌더

생성자에서 파라미터의 수가 많아지면 코드 작성이 어렵고 읽기가 어려워진다. 이러한 문제를 해결하기 위해 자바빈 패턴이 사용되었다. 파라미터를 set() 함수를 통해 각각 초기화함으로써 읽기도 쉽고 코드 작성도 수월하지만 1회의 함수 호출로 생성를 끝낼 수 없는 단점이 생겼다. 이 단점은 객체의 일관성을 일시적으로 깰 수 있는 치명성이 있는데, 만약 일관성이 깨진 객체가 잘 못 사용된다면 어디서 문제가 발생하였는지 디버깅하기가 매우 어려워진다. 따라서 이러한 문제를 해결하려면 변경 불가능 클래스 성격이 더해져야한다. 이를 위해 freezing 개념이 들어간 패턴이 빌더 패턴이다. build() 함수를 호출하기 전까지 파라미터를 선택적으로 초기화할 수 있되 Builder 클래스를 반환함으로써 객체를 얼릴 수 있다. build() 함수를 호출하면 내부적으로 파라미터 검사를 추가적으로하여 오류를 잡을 수 있다. 파라미터를 초기화하는 함수명도 변수명과 일치시켜 가독성을 매우 높일 수 있다는 장점도 있다. 안드로이드에서는 Request Query 를 만드는 RequestBody 클래스가 빌더패턴으로 구현되어있다.

어댑터

클래스의 인터페이스를 사용자가 기대하는 다른 인터페이스로 변환하는 패턴이다. 인터페이스 호환성 문제로 같이 사용할 수 없는 클래스를 연결할때 사용한다.

어댑터 패턴

참조

디자인 패턴 - 어댑터 패턴(adapter pattern)

컴포지트

객체들을 트리 구조로 구성하여 개별 객체와 복합 객체를 같은 방식으로 처리하는 패턴이다. Component 인터페이스를 통해 표현할 요소를 정의하고 LeafComposite 클래스가 이것을 구현함으로써 개별 및 복합 객체를 동일한 Component 로 처리할 수 있다. 안드로이드에서는 ViewViewGroup 이 컴포지트 패턴으로 사용된 예이다.

컴포지트 패턴

참조

컴포지트 패턴(Composite Pattern)

컴포지트 패턴(composite pattern)

반복자

사용자가 내부 구조를 알 필요없이 내부 데이터에 동일한 방식으로 접근할 수 있도록 설계하는 패턴이다. 내부 구현 코드를 숨길 수 있기에 정보은닉이 가능하고 사용자는 편리한 인터페이스를 경험할 수 있다. 자바의 컬렉션에서 데이터 탐색이 Iterator 로 구현되어 있다. List, Set, Map 의 내부구조를 알 필요없이 같은 방식으로 순차접근이 가능하여 쉽게 사용할 수 있다.

옵저버

한 객체의 상태를 체크하다가 변화가 생기면 여기에 의존하는 다른 객체에 이벤트를 보내어 변화에 대해 갱신을 하는 패턴이다. 일대다 의존성을 보이는 특징이 있다. 상태를 저장하는 주제객체(Subject 또는 Publisher)와 주제객체에 의존하여 변화를 감지하는 여러개의 옵저버 객체가 있는 구조이며 옵저버 객체는 옵저버 인터페이스를 통해 구현한다. 주제객체와 옵저버 객체간의 결합은 느슨한 결합을 유지함으로써 주제는 옵저버가 어떤 클래스인지 알 필요가 없다. 다만 옵저버인지만 확인할 뿐이다. 따라서 옵저버의 추가와 제거가 자유롭고 서로의 변경에도 영향을 끼치지 않는 독립성을 가질 수 있는 장점이 있다. 주제객체는 옵저버 객체에게 PUSH 를 통해 변화를 알리면 옵저버 객체가 변화에 대응하는 방법이 있고, 주제객체를 감지하는 감시자(Watcher)가 PULL 을 통해 주제객체의 상태정보를 가져와서 옵저버 객체들을 갱신시키는 방법이 있다.

옵저버 패턴

참조

디자인패턴:옵저버패턴(OBSERVER PATTERN)

스트래티지

알고리즘군(또는 기능)을 정의하고 각각을 캡슐화하여 교환할 수 있도록 만드는 패턴이다. 알고리즘(기능)을 사용하는 클라이언트와 독립적으로 알고리즘을 변경할 수 있는 장점이 있다. 인터페이스구성을 사용한다. 인터페이스를 통해 새로운 알고리즘(기능)을 정의하고 이것을 구현한 클래스(기능 클래스)를 멤버필드로 포함(구성)시키는 구조이다. 구현이 아닌 구성을 활용하였기에 코드 재사용성이 높다. 또한 캡슐화에 의해 알고리즘 내부 구조와 상관없이 클래스가 제공하는 메소드(클래스가 달라도 동일한 메소드)를 사용한다. 따라서 인터페이스가 편리하다.

스트래티지 패턴

참조

디자인패턴 - 스트래티지 패턴(strategy pattern)

곱셉을 할때 속도를 빠르게 하려면 어떻게 할 것인가

처음부터 곱셈결과를 배열에 저장해놓고 그것을 가져다 쓰도록 하여 속도를 향상시킨다.

오버라이딩과 오버로딩의 차이 (시그니처)

오버라이딩과 오버로딩은 자바의 다형성을 구현하는 대표적인 방법이다. 오버라이딩은 하위 클래스의 성격에 맞게 부모 클래스의 함수를 재정의하는 것을 말한다. 이때 부모 클래스와의 시그니쳐가 완전히 같지만 내부 구현이 달라지는 형태이다. 오버로딩은 함수 이름만 같고 나머지 시그니쳐가 다른 것을 말한다. 파라미터 자료형이나, 개수 등을 다르게 정의함으로써 확장된 함수를 새로 정의하고 구현하는 것을 말한다.

OOP 란 (의존성 역전)

참조

객체지향 개발 5대 원리:SOLID

리플렉션이란

Serialization 이란

Set vs List

Looper 와 Handler

메인 스레드가 아닌 스레도에도 루퍼가 있는가? 어떻게 만들 수 있나?

Looper.quit() 와 quitsafely 차이

새로운 안드로이드 버전 특징 (8 ~)

안드로이드 4대 컴포넌트

안드로이드를 구성하는 구성요소로 Activity , Service , Content Provider , Broadcast Recevier 가 있습니다.

ActivityUI 를 가지는 화면을 나타냅니다. 화면마다 Activity 로 구현되며 xml 파일을 통해 뷰와 레이아웃을 구현하고 setContentView() 를 통해 파일을 메모리로 Infalte 시켜 화면에 출력합니다.

Service 는 화면 없이 백그라운드에서 실행되는 구성요소로써 오랫동안 작업을 하거나 원격 작업을 수행할 경우 사용됩니다. 보통 음악을 재생하거나 화면과 별개로 타이머를 재거나 네트워크 통신을 할 경우 사용됩니다.

Content Provider 는 공유된 앱 데이터를 관리합니다. 어플리케이션간 데이터를 접근하기 위해 사용됩니다. 파일 시스템이나 SQLite 데이터베이스, 기타 저장소 위치에서 앱이 접근 가능한 저장소의 데이터를 읽거나 쓸 수 있습니다. 예를 들어 연락처 앱의 연락처 정보를 가져올 수 있고 갤러리 앱의 사진 파일을 가져오거나 외부 저장소에 파일을 저장할 수 있습니다.

Broadcast Receiver 는 시스템이 보내는 브로드캐스트 알림을 응답하는 구성요소입니다. 예를 들어 화면이 꺼지거나 배터리가 부족하거나 사진을 캡쳐하는 등의 이벤트에 대해 시스템이 브로드캐스트를 날리면 앱에서 Receiver 를 통해 원하는 브로드캐스트를 수신하여 이에 맞는 처리를 가능케 합니다. 앱 내에서도 LocalBroadcastManager 를 통해 브로드캐스트에 데이터를 담은 Intent 를 보내어 다른 컴포넌트에서 수신할 수도 있습니다.

액티비티와 프래그먼트 차이

태블릿이 나오면서 큰 화면에 대한 액티비티의 비효율성이 부각되었습니다. 태블릿의 큰 화면에 여러 액티비티를 보여주기 위해 나온 것인 프래그먼트입니다. 하나의 액티비티에 여러 레이아웃을 배치하여 구성할 수도 있지만 프래그먼트를 사용하면 다른 액티비티에서도 재사용이 가능하고 자체 생명 주기를 가지기에 생명주기에 따라 다양한 구현이 가능합니다. 또한 자체 입력 이벤트를 가지기에 다이나믹한 인터렉션이 가능합니다. 프래그먼트의 생명주기는 액티비티의 생명주기에 종속적이기에 액티비티에서 onCreate() 가 호출되면 프래그먼트에서는 onActivityCreated() 가 호출되고 onPause() 되면 프래그먼트도 onPause() 됩니다. 프래그먼트에서 액티비티와 통신을 하려면 getActivity() 를 호출하면 액티비티 객체를 사용할 수 있으며 프래그먼트에서 발생하는 이벤트는 onAttach() 에서 interfactionListener 를 호출하고 액티비티에서 이것을 구현함으로써 이벤트에 대한 처리와 프래그먼트간 통신이 가능합니다. 액티비티를 관리하는 스택이 있듯이 프래그먼트에도 백스택이라는 스택 구조가 존재하며 이것은 액티비티가 관리합니다. FragmentTransaction 객체의 addToBackStack() 함수를 사용하여 프래그먼트를 백스택에 저장하면 사용자가 뒤로가기 버튼을 눌렀을때 프레그먼트의 이전 상태로 되돌려주는 기능을 합니다.

프래그먼트

프래그먼트 생명주기

Intent Flag 설명 (SingleTop, ClearTop)

Intent 는 안드로이드 컴포넌트간 주고받는 메세지라고 말할 수 있습니다. Intent 에는 명시적 인텐트와 암시적 인텐트로 구분됩니다. 명시적 인텐트는 새로운 액티비티를 시작하거나 서비스를 시작하는 등 특정 구성요소를 시작하기 위해 사용하는 인텐트입니다. 반면 암시적 인텐트는 setAction() 을 통해 작업을 지정하여 기기에서 해당 작업을 수행할 수 있는 모든 앱을 호출하는 것입니다. 공유를 하거나 메일을 보내는 등의 작업에 사용됩니다. flag 에 대해 알려면 먼저 TaskAffinity 에 대한 개념이 있어야합니다.

Task 는 액티비티들을 스택 구조로 저장하는 컬렉션입니다. 뒤로가기를 누르거나 다른 액티비티로 넘어가면 현재 액티비티는 Task 에 스택 구조로 쌓이게 되어 다시 호출할때 재사용되거나 액티비티 이동간 흐름을 유지시켜주는 역할을 합니다.

Affinity 는 액티비티간의 친화력을 의미합니다. 같은 Affinity 를 가지는 액티비티끼리 태스크에 따로 저장함으로써 기능별로 액티비티 흐름을 만들 수 있습니다.

Intent Flag 는 인텐트에 대한 메타데이터 기능을 하는데 액티비티가 어떻게 실행될지에 대한 정보를 담습니다. 대표적으로 FLAG_ACTIVITY_CLEAR_TOP , FLAG_ACTIVITY_SINGLE_TOP , FLAG_ACTIVITY_REORDER_TO_FRONT , FLAG_ACTIVITY_NO_HISTORY , FLAG_ACTIVITY_NEW_TASK , FLAG_ACTIVITY_MULTIPLE_TASK 등이 있습니다.

FLAG_ACTIVITY_CLEAR_TOP 속성으로 액티비티를 호출할때 호출하려는 액티비티가 Task 내에 존재하면 새로운 액티비티를 사용하되 Task 에 존재하는 동일한 액티비티부터 상위에 존재하는 모든 액티비티를 Destroy 시킨다. 호출하는 액티비티는 onCreate() 부터 시작한다.

FLAG_ACTIVITY_SINGLE_TOP 속성으로 액티비티를 호출할때 직전과 동일한 액티비티가 호출되면 새로운 액티비티를 스택에 쌓지 않고 Task 에 저장되어 있는 기존 액티비티를 재사용한다. 액티비티는 onPause() 호출 후에 onNewIntent() 를 호출하고 뒤따라 onResume() 을 호출한다. _CLEARTOP 과 _SINGLETOP 을 같이 사용하면 Task 에 쌓여있는 액티비티를 재사용하며 상위에 쌓여있는 액티비티는 제거한다. 기존의 액티비티는 onNewIntent() 부터 시작한다.

FLAG_ACTIVITY_REORDER_TO_FRONT 속성으로 액티비티를 호출할때 Task 에 동일한 액티비티가 존재하면 새로운 액티비티는 스택에 쌓지 않고 기존의 액티비티를 최상위로 재배치시켜 재사용한다. 액티비티는 onResume() 부터 시작한다.

FLAG_ACTIVITY_NO_HISTORY 속성으로 액티비티를 호출하면 당장의 변화는 없다. 하지만 새로운 액티비티를 호출하여 스택에 쌓인 뒤(중복여부 상관 없음) 이것이 뒤로가기를 통해 사라질때 _NOHISTORY 속성으로 호출된 액티비티도 같이 사라진다. 즉, 해당 속성으로 호출된 액티비티는 자신이 부른 새로운 액티비티가 사라질때 같이 사라지는 속성이다.

FLAG_ACTIVITY_NEW_TASK 속성으로 액티비티를 호출하면 새로운 Task 에 액티비티를 쌓는다. 만약 기존의 Task 들 중 호출하려는 액티비티와 동일한 Affinity 를 가지는 Task 가 있다면 해당 Task 로 액티비티가 쌓이게 된다.

FLAG_ACTIVITY_MULTIPLE_TASK 속성은 FLAG_ACTIVITY_NEW_TASK 와 같이 사용되어야 효과가 발생한다. 두 속성으로 액티비티를 호출하면 Affinity 와 상관없이 새로운 Task 를 생성하여 그곳에 액티비티를 쌓는다.

참고

Android Developers - 인텐트 및 인텐트 필터

Android 태스크란? 어피니티란? 플래그란?

Android Flag Activity 사용법 및 주의사항

Activity 생성시에 사용되는 Intent Flag 정리

Context 란

어플리케이션의 정보에 접근하기 위한 인터페이스입니다. Context 를 사용하면 getPackageName() , getResource 등과 같이 어플리케이션에 대해 시스템이 관리하는 정보에 접근할 수 있으며 startActivity() , bindService 등과 같이 안드로이드가 제공하는 시스템 서비스를 사용할때도 사용됩니다. 보통 다른 프로그램에서 시스템 레벨에서 제공하는 전역 정보에 접근하기 위해 System 정적 클래스에 접근하는 것과 비슷한 맥락입니다. 안드로이드에서는 어플리케이션 관리를 시스템에서 하는 것이 아니라 별도의 ActivityManagerService 라는 또다른 어플리케이션이 관리하고 있기 때문에 어플리케이션과 연관된 시스템 레벨의 함수를 호출하려면 ActivityManagerService 를 통해야 합니다. 따라서 ActivityManagerService 에게 자신이 어떤 어플리케이션인지 알리고 시스템 레벨의 함수에 접근하기 위해서 Context 객체가 필요한 것입니다.

참고

Android Context 란?

MVC, MVP, MVVM

MVCModelView, Controller 구조를 가지는 디자인 패턴입니다. Model 에서 데이터와 상태, 비즈니스 로직을 구현하며 ViewController 에 종속되지 않고 여러 곳에서 재사용 될 수 있습니다. View 는 화면을 구현하는 컴포넌트입니다. Model 에 대한 이해없이 오로지 사용자의 인터렉션을 받아 반응합니다. ViewModel 은 최대한 종속되지 않고 독립적이게 구현해야 합니다. ControllerViewModel 을 연결하는 역할을 합니다. View 에서 사용자와의 인터렉션이 발생하면 컨트롤러가 이것을 인지하고 모델을 갱신하는 역할을 합니다. 안드로이드에서 Controller 는 주로 액티비티에 해당하고 Viewxml 파일이 되며 Model 은 데이터 클래스 등이 되겠습니다. 원래 MVC 는 ViewModel 을 완전히 분리시키고 Model 이 어디에도 종속되지 않지만 안드로이드에서 ViewController 가 액티비티에 같이 구성되어 있어 사실상 하나의 모습을 보입니다. 따라서 ViewModel 이 의존적이게 되어 테스트가 어렵고 액티비티 안에 대부분의 처리가 구현되어 스파게티 코드가 될 수 있습니다.

MVPMVC 의 문제점을 보완하고자 나온 디자인 패턴입니다. MVP 에서는 액티비티를 View 의 일부로 간주하여 액티비티 안에 대부분의 처리가 구현되는 것은 극복하였습니다. 액티비티가 View 인터페이스를 구현되어 있기에 가상 뷰를 만들어 Presenter 와 연결시키면 유닛 테스트가 가능합니다. PresenterMVCController 와 역할이 거의 같지만 View 와 연결되어 있지 않고 그저 인터페이스라는 차이가 있습니다. ViewModel 을 완전히 분리시켜줌으로써 서로의 독립성을 보장한다는 점이 장점입니다. 하지만 Presenter 도 시간이 지남에 따라 비즈니스 로직이 모이는 경향을 보입니다. 따라서 문제가 발생하기 쉽고, 분리하기도 어려워 유지보수가 어려워진다는 단점이 있습니다.

MVVMDataBinding 을 이용함으로써 모듈화를 쉽게하고 뷰와 모델을 연결하는 코드를 줄일 수 있는 장점을 가진 디자인 패턴입니다. View 가 ViewModel 에 의해 바인딩되어 ViewModel 의 값이 변경되면 View 가 갱신됩니다. ViewModelModelWrapping 하는 구조이며 동시에 ViewModel 에 이벤트를 전달할 수 있는 훅 기능을 합니다. Command PatternData Binding 을 사용하여 ViewViewModel 의 의존성을 완전히 제거한다는 장점이 있습니다. MVVM 패턴 또한 시간이 지남에 따라 관계 없는 로직이 늘어나 xml 에 코드가 증가하는 문제가 발생합니다. 이것은 유지보수를 힘들게 하기 때문에 뷰 바인딩 표현식에 값을 계산하지 않고 ViewModel 에 직접 값을 가져와 처리해야합니다.

참고

안드로이드의 MVC, MVP, MVVM 종합 안내서

데이터바인딩이란

SparseArray 와 HashMap 차이

HashMap 은 내부적으로 객체를 hashCode 로 인덱싱하는 구조입니다. hashCode 를 사용함으로 해시충돌로 인해 서로 다른 키를 동일한 키로 인지할 수 있습니다. 또한 기본 자료형이 아니라 Wrapper 클래스를 사용하기 때문에 메모리가 비효율적으로 소모됩니다. SparseArray 는 이런 문제점을 어느정도 해결한 배열 자료구조입니다. SparseArray 는 내부적으로 작은 배열 두개(키를 저장하는 int[], 밸류를 저장하는 object[])를 사용하여 값을 저장합니다.(HashMapHash Bucket 이라는 큰 해시 배열을 통해 값을 저장한다.) SparseArray 는 키가 정수여야만 합니다. 해시를 사용하지 않음으로써 충돌 문제가 발생하지 않습니다. 뿐만 아니라 기본자료형을 키로 사용함으로써 Wrapper 클래스로 변환하는 오토박싱의 오버헤드 문제도 해결됩니다. 사용도 편합니다. 일반 배열처럼 인덱스로 접근하여 키와 밸류를 가져올 수 있습니다. 하지만 단점도 존재합니다. 내부적으로 데이터가 뜨문뜨문한 저장되기 때문에 탐색시 이진 탐색을 통하여 키를 찾습니다. 그렇기에 데이터가 많은 구조에 사용하기 부적합하며 배열의 항목을 삽입 삭제해야하기에 HashMap 보다 속도가 느립니다. 최대 수백개까지의 항목을 보유한 컨테이너일때 사용하기를 권장합니다.

HashMap

SparseArray

참조

Map, HashMap 그리고 SparseArray

Youtube - SparseArray Family Ties

Andriod Developers - SparseArray

Why ADT recommends SparseArray rather than HashMap?

TedPermission 작동 원리

  1. 권한체크 액티비티 시작
  2. 체크해야할 권한목록을 가져옴
  3. 이미 해당 권한들이 허가되어있는경우 권한이 허용되었음을 알림
  4. 허용되어 있지 않은경우 rationale 메세지가 세팅되어있다면 권한이 필요한 이유를 보여주고 권한허가를 요청(그렇지않은경우 바로 권한허가를 요청)
  5. onRequestPermissionsResult()에서 거부된 권한목록을 체크
  6. 거부된권한이 하나도 없는경우는 모두 허가된것으로 판단하고 권한이 허용되었음을 알림
  7. 하나라도 권한이 허용되지 않은경우 권한이 허용되지 않았음을 알리고 거부된 권한목록을 함께 보냄
  8. 여기서 권한이 허용/거부 되었음을 알리는 방법으로는 Otto를 사용해서 Event를 날림

TedPermission

권한 요청 결과에 따라 실행할 리스너

1
2
3
4
5
6
7
8
9
10
PermissionListener permissionListener = new PermissionListener() {
@Override
public void onPermissionGranted() {
// TODO: 권한허가시 실행할 코드
}
@Override
public void PermissionDenied(ArrayList<String> deniedPermission) {
// TODO: 권한거부시 실행할 코드
}
}

권한 체크

1
2
3
4
5
6
7
TedPermission.with(this)
.setePermissionListener(permissionListener)
.setRationaleMessage("권한이 필요한 이유 설명")
.setDeniedMessage("권한을 거부하셨습니다. [설정] > [권한]에서 권한을 허용할 수 있습니다.")
// 요청할 권한 설정(다중 요청 가능)
.setPermissions(Manifest.permission.WRITE_EXTERMAL_STORAGE)
.check();
참조

유용한 라이브러리 - TedPermission(마시멜로우 권한체크) !최신버전!

6.0 마시멜로우 권한체크하고 최적화하기

이벤트 버스란? 대체할 것은?

Retrofit 을 사용한 이유

서버와 통신을 하려면 http 통신을 해야된다. 기본적으로 HttpUrlConnection 을 이용한 네트워크 통신이 있지만 매번 connection 설정, input / ouput stream 생성 및 할당 등 반복적인 작업이 생깁니다. 이것을 도와주는 라이브러리가 Okhttp 입니다. 따라서 초기에는 Okhttp3 를 사용하여 개발하였으나 개발 중반부터 Retrofit 으로 갈아타면서 코드가 섞이게 되었습니다.

Retrofit 으로 갈아타게 된 이유로는 속도, 편의성, 가독성 때문입니다.

Okhttp 사용시 대개 Asynctask 를 통해 비동기로 실행하게되는데 Asynctask 가 성능상 느리다는 이슈가 있었습니다. Retrofit 에서는 Asynctask 를 사용하지않고 자체적인 비동기 실행과 스레드 관리를 통해 속도를 훨씬 빠르게 끌어올렸습니다. 약 3 ~ 10배 차이 난다고 합니다.

또한 Okhttp 에서도 쿼리스트링, request , response 설정 등 반복적인 작업이 필요한데, Retrofit 에서는 이런 과정을 모두 라이브러리에 넘겨서 처리하도록 하였습니다. 따라서 사용자는 함수 호출시에 파라미터만 넘기면 되기에 훨씬 작업량이 줄어들고 사용하기 편리합니다.

마지막으로 가독성이 매우 좋습니다.interfaceannotation 을 사용하여 호출할 함수를 파라미터와 함께 정의해놓고, 네트워크 통신이 필요한 순간에 구현없이 해당 함수를 호출하기만 하면 통신이 이루어지기에 코드를 읽기가 매우 편합니다. Asynctask 를 쓰지 않기에 불필요하게 코드가 길어질 필요도 없으며, 콜백함수를 통해 결과가 넘어오도록 되어있어 매우 직관적인 설계가 장점이라 Retrofit 을 사용하게 되었습니다.

네트워크 통신에 Volley 라이브러리도 있지만 몇가지 문제점이 있었습니다. StringRequest 생성시 파라미터 값을 encoding 해주어야 하는 불편함과 통신실패시 ErrorResponse 안에 ResponseBody 가 담기지 않은 문제를 알게되어 사용하지 않게 되었습니다.

참고

Volley 에서 Retrofit2 로 변경한 이야기

Glide 를 사용한 이유

대표적인 이미지처리 라이브러리는 Glide 와 Picasso 가 있습니다. Glide 와 Picasso 를 비교했을때 메모리 사용면이나 로딩 속도 면에서 Glide 가 우세하여 사용하게 되었습니다. Glide 는 비트맵 포맷이 RGB_565 인데에 반해 Picasso 는 ARGB_8888 을 사용합니다. 따라서 Picasso 가 상대적으로 고화질의 선명한 이미지를 로드시키지만 그만큼 메모리 사용량이 2배 가까이 높기 때문에 많이 이미지를 로드해야했던 저에게 맞지 않았습니다. 또한 Picasso 는 ImageView 에 그대로 로드할 경우 원본 이미지가 메모리에 올라간 상태에서 리사이징을 하기 때문에 비효율적입니다. 이것을 해결하려면 resize() 함수를 사용해야되는데 이것은 자동으로 크기에 따라 리사이징해주는 Glide 에 비해 한단계가 더 필요한 작업이었습니다. 마지막으로 Glide 가 한번 로드한 이미지를 다시 로드할 경우 더 빠른 속도를 보입니다. Glide 에서는 이미지를 캐시할때 원본 이미지와 리사이징된 이미지 두개를 캐싱하기 때문에 같은 ImageView 에 이미지를 로드하면 훨씬 빠른 속도를 보였습니다. 반면 Picasso 는 원본 이미지만 캐시하기 때문에 다시 로드할 경우 시간이 조금 더 지체되었습니다. 저의 경우 리스트뷰의 ImageView 에 매번 같은 이미지를 로드해야했기에 목적면으로 보면 Glide 가 더 적합하였습니다. 하지만 Glide 를 사용하면서 단점도 있었습니다. xml 에 태그를 사용해야했던 경우가 있었는데 Glide 는 내부적으로 태그를 사용하고 있었기 때문에 충돌이 일어난 적이 있었습니다. 이러한 문제가 발생하여 어쩔수 없이 해당 ImageView 는 Glide 를 사용하지 않고 스레드 안에서 URL(str).getContent()BitmapFactory.decodeStream(InputStream) 를 사용함으로써 이미지를 로드해야했습니다. 하지만 Tag 를 사용할 경우가 거의 없었기에 Glide 사용을 고수하였습니다.

참고

Picasso 와 Glide 분석

Java 8에 추가된 것들

메인스레드에 대해 설명

버터나이프, 뷰인젝터 만들어지는 과정

후원

이 포스트가 도움이 되었다고 생각하시면, 위의 버튼을 클릭하여 후원해주세요.

이 포스트를 공유하려면 QR 코드를 스캔하세요.