해당 포스팅에서 언급된 내용은 Ndroid 에서 제공합니다.

https://github.com/skaengus2012/Ndroid


매일 코딩을 하던 중 같은 것을 반복한다고 느끼는 경우가 있습니다.


그 중 하나가 아마 예외처리를 하는 것이라 생각합니다.


예외처리는 개발자가 의도적으로 어떤상황이 일어났을 때의 상황을 처리 못하니, 위의 stack 에서 알아서 하라고 던지는(throw) 행위입니다. (사실 이 행위는 프로그램코드가 아닌 실세계에서도 많이 일어납니다. ㅡㅡ^)


일단 예외처리에 대한 제 입장은 이러한 예외처리를 꼼꼼하게 계속해야 한다고 생각합니다. 


조금 회사에서 일을 해보니, 프로그램 작성 시에는 일부로 어떠한 경우들을 일부로 체크안해서 UnCheckException (꼭 처리를 안해도 되는 예외) 을 내보내도 되지 않을까(쉽게는 NullPointerException)생각하여 작성해보았는데 디버깅 시, 유지보수가 더 쉽지가 않음을 깨달았습니다. 


로직의 스탭(모듈 등)간의 관계에 대하여 강결함(?) 보장이 의도적으로 되야 하는데, 의도적으로 예외를 내지 않아버리면 갑자기 어디선가 죽어버리는 녀석을 계속 찾아야하는 것 같습니다. (같은 NullPointer 에 대한 예외를 처리한다 하더라도, 아무것도 안하고 Runtime 중 일으키는 것보다는 의도적으로 RuntimeException 이라도 넘기는 것이 좋다고 생각합니다. 메시지도 같이 말이죠....)


어쨌든 이러한 처리를 아마 아래와 같이 코드를 작성하곤 합니다.


1
2
3
if (a == null) {
    throw new MessageException("잘못된 접근입니다.");
}
cs


비지니스로직에 흔히 작성될 수 있는 코드입니다. 

간단해보이지만 아래와 같이 체크해야할 경우가 많다면 어떨까요?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (a == null) {
    throw new MessageException("잘못된 접근입니다.");
}
 
if (list == null || list.isEmpty()) {
    throw new MessageException("잘못된 접근입니다.");
}
 
if (string == null || string.toString() == null || string.toString().isEmpty()) {
    throw new RuntimeException("잘못된 접근입니다.");
}
 
if (map == null || map.isEmpty()) {
    throw new MessageException("잘못된 접근입니다.");
}
 
if (!map.containsKey("a")){
   throw new RuntimeException("잘못된 접근입니다.");
}
cs


뭔가 꼼꼼하긴 하지만, if 블럭 안에 예외 한개씩 입력하고 있는 공통점이 있어보입니다. 이러한 단순 작업을 요즘은 툴(인텔리J)이나 어노테이션(NonNull)등으로 어느정도 커버할 수 있지만, 안드로이드나 웹프로젝트에서 같이 작업하는 입장에서 생각할 때는 호스트코드에 작성하는 방식을 아에 무시할 수는 없어보입니다. 

(잘 모르는 것일 수도 있습니다. ~ @.@ ~)


일단은 같이 일하는 선배님께서 이러한 부분을 간결하게 해보자는 것을 제안하셔서, 아래와 같은 코드를 한번 배포해 보았습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * 단순한 NullCheck 매크로
 *
 * @param object
 * @param message
 */
public void NullCheck(Object object, String message) throws MessageException {
   if (object == null) {
      throw new MessageException(message);
   }
}
 
 
NullCheck(vo, "잘못된 접근입니다.");
cs


MessageException은 CheckedException 종류로 컨트롤러로 이 예외가 던져진다면, 프론트에서 사용자가 메시지를 볼 수 있도록 합니다.


이 코드로 인하여, 예외를 처리하는 것은 매우 단순하게 간결 해졌습니다. 


복잡한 방식을 간결하게 바꾸는 시도는 여러가지로 시도 되고 있고(인터페이스를 람다로 치환한다는 등..), 이러한 방식을 제안한 선배님께는 감사하고 있습니다.


단순하게 Null 뿐 아니라, string 빈 값, Container 의 빈 상태 등 자주 사용하는 여러 상황을 체크하는 메소드를 만들었고, 잘 사용하고 있었지만 이 코드에는 문제가 있음을 깨닫게 되었습니다.


간결 예외처리 코드는 아쉽게도 MessageException 밖에 출력을 못합니다. 

경우에 따라서는 RuntimeException 을 내보내야할 때도 있으며, 안드로이드와 라이브러리를 공유한다고 했을 때 웹에서만 사용하는 MessageException 을 안고 갈 수는 없었습니다. 


이를 해결하기 위해 예외의 형태를 제네릭으로 받아 동적으로 예외를 만들자는 생각이 들었습니다. 아래와 같은 초안 메소드를 작성하게 되었고, 오버로딩으로 간략하게 사용할 수 있는 메소드도 같이 제공하였습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * Check with CustomException.
 *
 * @param check
 * @param message
 * @param exceptionClass
 * @param <T>
 * @throws T
 */
public final static <extends Exception> void Check(
            @NonNull Boolean check
            , @NonNull String message
            , @NonNull Class<T> exceptionClass) throws T{
    if (!check) {
        final T exception;
 
        try {
            exception = exceptionClass.getConstructor(String.class).newInstance(message);
        } catch (Exception e) {
            throw new RuntimeException(e);
         }
 
        throw exception;
    }
}
 
/**
 * Check.
 *
 * @param check
 */
public final static void Check(@NonNull Boolean check) {
    Check(check, ERROR_BAD_ASSESS, RuntimeException.class);
}
 
 
Check(object != null"[에러] object 는 Null 일 수 없음", RuntimeException.class);
Check(object != null);
cs


이러한 형태로 자주 사용하는 예외체크 타입을 간략화한 유틸클래스를 릴리즈하였으며, 사용은 아래와 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Check
CheckUtil.Check(object != null"Occurred Error! Please ask administrator!", RuntimeException.class);
 
// Null check
CheckUtil.NullCheck(object, "Occurred Error! Please ask administrator!", MessageException.class);
 
// String empty check.
CheckUtil.EmptyToStringCheck(object);
 
// Maybe Empty check.
CheckUtil.EmptyMaybeCheck(MaybeUtil.JustNullable(object));
 
// Container Check.
CheckUtil.EmptyContainerCheck(Collections.emptyList());   // Collection
CheckUtil.EmptyContainerCheck("Test""Test2");           // Array
CheckUtil.EmptyContainerCheck(Collections.emptyMap());    // map
cs


RxJava2 에서 제공하는 Maybe 개념까지 CheckUtil 에 넣게 되었습니다.


보다 자세한 내용은 아래 url 에서 확인하실 수 있습니다.



이 포스팅이 보다 간결한 코딩을 하는 것에 도움이 되길 바래요 ~ @.@ ~

반응형
Posted by N'

해당 포스팅에서 언급된 내용은 Ndroid 에서 제공합니다.

https://github.com/skaengus2012/Ndroid


Java 에서 시간 관련 클래스 (Calendar, Date) 들은 꽤 많이 사용됩니다. 


하지만 언제나 사용할 때마다 헷갈리는 부분이 존재하며, 간단한 기능도 직관적이지 않은 패턴을 사용해야합니다. (예를들면, Calendar 에서 달을 출력하려면? 현재 년도에 특정 값을 더하고 싶다면?)


1
2
3
4
5
Calendar calendar = Calendar.getInstance();
        
calendar.get(Calendar.MONTH); // 출력되는 달은 0-11 입니다. (1-12 가 안나온다는 것을 기억해야하죠.
        
calendar.add(Calendar.YEAR, 2); // 년도를 더하고 싶습니다. 덧셈을 하고 싶다면, Calendar 의 상수를 이용해야하죠.
cs


제 생각에 직관적이지 못하다고 생각하는 이유는 메소드명을 보고 행위를 하는 것이 아닌, 상수를 넣고 처리를 해야하기 때문입니다. 물론 Calendar 클래스에 익숙하다면 능숙하게 사용하겠지만, 날짜 연산에 대해 조금만 복잡하게 계산을 한다고 하면 저 상수를 이용한 연산을 줄줄이 입력해야할 수도 있습니다. 


Ndroid 프로젝트에서는 조금 더 이를 편하게 사용하고 싶었습니다. 


Calendar 의 사용목적이 결국 날짜 데이터를 핸들링하는 것이고, 일련의 연산과정 중 최종으로 핸들링된 날짜만 알고 싶습니다. 이를 마치 질의를 하는 것과 같은 선언형식으로 할 수 있다면 매우 코드가 아름다워지지 않을까라는 기대가 있었습니다.


Ndroid 의 TimeBuilder 모듈은 위와 같은 요구사항을 담도록 하였습니다. 


예를 들어 이러한 요구사항이 있다고 합시다.


- String 으로 된 시간을 파싱할 것.

- 파싱한 날짜에 년도를 1 덧셈

- 파싱한 날짜에 달을 5 덧셈.

- 파싱한 날짜에 일을 1 뺄셈

- 시,분,초는 생략할 것.

- 날짜는 영어권으로 보여줄 것.


기존 자바 코드로 작성을 한다면, 이 정도 되겠네요.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// simple java code.
 String dateString = "2017-3-26 16:40";
 
try {
    // parse.
    Date formatData = new SimpleDateFormat("yyyy-MM-dd hh:mm").parse(dateString);
 
    // calculating
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(formatData);
    calendar.add(Calendar.YEAR, 1);
    calendar.add(Calendar.MONTH, 5);
    calendar.add(Calendar.DAY_OF_MONTH, -1);
 
    // to yyMMdd
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);
 
    System.out.println(new SimpleDateFormat("yyyy.MM.dd (hh,mm,ss a)", Locale.ENGLISH).format(calendar.getTime()));
 
catch (ParseException e) {
    e.printStackTrace();
}
cs


간단하지는 않네요. 사용하려면 상수의 기능부터 알아야하고 (DAY_OF_MONTH, HOUR_OF_DAY 등은 한 번 찾아봐야겠죠 .ㅡㅡ^), SimpleDateFormat 클래스의 기능도 알아야합니다.


위의 기능을 TimeBuilder 를 통해 작업해보도록 하겠습니다.


1
2
3
4
5
6
7
8
TimeBuilder.Create(dateString, "yyyy-MM-dd hh:mm").
                addYear(1).
                addMonth(5).
                addDay(-1).
                setLocale(Locale.ENGLISH).
                to_yyMMdd().
                getStringFormat("yyyy.MM.dd (hh,mm,ss a)").
                subscribe(System.out::println);
cs


Builder 클래스에서 제공하는 메소드를 사용함으로써, 상수들을 알 필요가 없어졌으며 파싱 및 포맷을 위해서 사용할 SimpleDateFormat 역시 몰라도 됩니다.


파이프라인 메소드 중 getStringFormat 의 출력 타입은 RxJava 의 Maybe 입니다. 

포맷의 형식이 잘못되어 포맷에 실패할 수도 있으며, 이런 결과를 이전 MaybeUtil 에서 제공하던 기능들과 같이 값이 있거나 없거나의 문제로 보았습니다.


MaybeUtil 의 기능은 아래에서 참고 



TimeBuilder 의 인스턴스를 만드는 방법은 아래와 같이 다양합니다.


1
2
3
4
5
6
7
8
9
10
11
 // non-param : current time.
TimeBuilder currentTimeBuilder = TimeBuilder.Create();
 
// param : Calendar.
TimeBuilder calendarBuilder = TimeBuilder.Create(TimeUtil.GetCalendar());
 
// param : date
TimeBuilder dateBuilder = TimeBuilder.Create(new Date());
 
// param : string, format
TimeBuilder stringBuilder = TimeBuilder.Create("2017-3-26""yyyy-MM-dd");
cs


보다 자세한 내용은 아래 url 에서 확인하실 수 있습니다.



이 기능을 제작하게 된 첫 번째 배경은 JAVA8 에서 Date 관련 API 의 개편이 있던 것으로 알았고 (아직 공부해보지는 않았습니다. 아마 다음 올릴 포스팅이 해당 부분일 것입니다.), 안드로이드 진영에서는 아직 JAVA8 을 온전히 지원하지 않기 때문에 추가하고자 하는 배경이 되었습니다.


JAVA8 의 기능을 보고 TimeBuilder 의 기능이 더 추가될 여지가 있길 바랍니다. :-)


반응형
Posted by N'

해당 포스팅에서 언급된 내용은 Ndroid 에서 제공합니다.

https://github.com/skaengus2012/Ndroid


기존 JAVA 에서는 변수에 값의 존재 여부를 명확하게 표현할 수 없었기 때문에 Null 검사를 해줬어야 했으며, 그럼에도 불구하고 NullPointerException 을 발생시키지 않기가 어려웠습니다.


그러나 JAVA8 에서는 Optional 이라는 Null 을 피하고자하는 새로운 개념이 생겼으며, 이 개념을 통해 어느정도 위의 문제를 해결하거나 비지니스 로직을 더 간소화 시킬 수 있었습니다. 


자세한 내용은 아래 포스팅을 참고! 



그러나, 아쉽게도 안드로이드에서는 Optional 을 API>=24 에서 사용할 수 있습니다.

하지만 언제나 JAVA8 을 대체하는 듯한 라이브러리가 있으니, RxJava 진영에서는 Maybe 를 지원합니다. Maybe 라는 이름이 생소해보일 수 있지만, 다른 언어에서는 Optional 대신 Maybe 라고 표기하는 곳도 있습니다.


Maybe 의 튜토리얼은 아래와 같습니다.


1
2
3
4
5
6
7
8
9
10
// 빈 Maybe 생성.
Maybe<String> emptyMaybe = Maybe.empty();
// 유효 값 Maybe 생성.
Maybe<String> validMaybe = Maybe.just("Test1");
 
// 값이 유효할 때 로그 출력.
validMaybe.subscribe(System.out::println);
 
// 실제 데이터 출력.
String returnValue = validMaybe.blockingGet("Default Value");
cs


그러나 아직 RxJava2 의 버전이 높지 않아, 편의 메소드들이 많이 추가되지 않은 것으로 보입니다. 

그래서 Ndroid 프로젝트에서는 Maybe 를 조금 더 적극적으로 사용해보자는 취지로 MaybeUtil 을 제작하였습니다. 


편의의 기준은 현재 공부하고 있는 [JAVA8 in Action] 의 실무에서 많이 사용하는 예를 참고하여 작성하였습니다.


1. Null 을 포함하는 Just


Maybe 의 사용목적 자체가 값의 존재여부를 확신할 수 없기 때문에 사용한다고 볼 수 있지만, 정작 Maybe 를 생성하기 위해서는 null check 를 수행해야합니다. ㅡㅡ^


Maybe 에 있는 just 는 Null 을 포함할 수 없습니다.


MaybeUtil 에서는 이 이슈 해결을 위해 아래와 같은 튜토리얼을 제공합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
String test = "Test", nullValue = null;
 
// Null 을 포함할 수 있는 just!
MaybeUtil.JustNullable(test).subscribe(System.out::println);
 
Maybe<String> nullValueMaybe = MaybeUtil.JustNullable(nullValue);
nullValueMaybe.subscribe(System.out::println);
        
// 오직 비어있는 Maybe 일 때만 실행!
MaybeUtil.SubscribeEmpty(nullValueMaybe, () -> System.out.println("That value is null!!"));
 
// Maybe 가 유효하다면, 두번째 param, 아니라면 세번째 param 사용
MaybeUtil.Subscribe(nullValueMaybe, System.out::println, () -> System.out.println("That value is null!!"));
cs


2. Map 의 안정적 사용을 위한 Maybe 지원.


Map 에 데이터를 삽입하고, 이를 사용하기 위해서는 containsKey 메소드를 통해, Map 에 실제 데이터가 존재하는지 확인하여야 합니다. 이 문제 역시도 Maybe 의 철학과 같이 값의 존재여부 문제를 따져볼 수 있습니다.


아래의 튜토리얼을 참고하여, Map 사용에 대한 비지니스 로직을 줄여볼 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
HashMap<String, Integer> testMap = new HashMap<>();
testMap.put("key1"1);
testMap.put("key2"1);
testMap.put("key3"1);
 
// 맵에서 데이터 출력 시, Maybe 형태로 반환
ContainerUtil.JustInMap(testMap, "key1").subscribe(System.out::println);
 
// 맵에 데이터가 존재할 때 해야할 Consumer 를 정의할 수 있음.
ContainerUtil.RunMaybeInMap(testMap, "key1"System.out::println);
ContainerUtil.RunMaybeInMap(testMap, "key4"System.out::println);
cs



3. Box Object 의 parse 를 Maybe 지원.


String 클래스를 Integer 나 Double 등, Boxed Object 로 변환하는 parseXXX 메소드는 언제나 XXXFormatException 을 발생시킬 우려가 있습니다. 이 문제 역시도 String 을 각 Boxed Object 으로 변환하여 값의 존재를 따질 수 있냐는 문제로 볼 수 있습니다.


아래 튜토리얼은 안전한 변환을 지원합니다.


1
2
3
4
5
6
7
8
9
// Parse Integer
StringUtil.ParseInteger("1").subscribe(System.out::println);
StringUtil.ParseInteger("String Text").subscribe(System.out::println);
 
// Parse Boolean
StringUtil.ParseBoolean("false").subscribe(System.out::println);
 
// Parse Double
StringUtil.ParseDouble("0.0111111").subscribe(System.out::println);
cs


 2,3 번의 경우 MaybeUtil 에 모든 기능을 추가할 수 있었으나, 


MaybeUtil 의 추 후 확장성


이미 Ndroid 프로젝트에 존재하는 StringUtil 과 ContainerUtil 의 성격과 더 맞다는 점


에서 기능을 나누게 되었습니다.


자세한 내용은 Ndroid ReadMe 에서 확인하실 수 있습니다.

[https://github.com/skaengus2012/N-java/wiki/N-java-v0.2-wiki#maybeutil]


Maybe 를 통해 우리는 안드로이드의 비지니스 로직을 단순화시킬 수 있을 것이라 생각합니다.

Java8 의 새 라이브러리와 RxJava 는 거의 비슷하며, 어느곳에서도 익숙한 개발자가 되는 것은 조금의 노력으로 할 수 있을 것이라 생각합니다.


여러분들의 코드가 새 패러다임을 입고, 뷰티풀해지길 바랍니다. :-)




반응형
Posted by N'

해당 포스팅에서 언급된 내용은 Ndroid 에서 제공합니다.

https://github.com/skaengus2012/Ndroid


RxJava2 와 더불어 안드로이드에서 더욱 Functional Programming 을 할 수 방법이 늘어났습니다. 

점점 JAVA8 에서 언급된 내용들이 지원이 되고 있죠.


특히 RxJava 진영에서 JAVA8 의 기본 함수형 인터페이스를 제공해주기 때문에, 더욱 개발은 편해질 수 있을 것이라 생각합니다. (없었다면 람다를 인스턴스로 가지고 있기 위해, 함수형인터페이스를 만들어주었어야 할 것입니다.)


하지만 RxJava 의 함수형 인터페이스를 사용하는데 있어서 불편한 점이 두 가지가 있었습니다.


1. 함수형 인터페이스의 함수들이 모두 throws Exception


RxJava 에서 지원하는 함수형 인터페이스들은 모두 예외를 필수적으로 출력하도록 되어 있습니다. 기존 JAVA8 에서는 그렇지 않았으며,  RxJava 의 Observerable 이 더이상 NULL 을 허용하지 않겠다는 정책이 만들어짐에 따라 생긴 특성일 것이라 생각합니다.


물론 어떤면에서는 좋겠지만 이 행위는 람다를 쓸 때마다 Exception 에 대한 처리를 해야함을 말합니다. Exception 이 생길 일이 없는데 무조건 처리해야한다는 것은 안타까운 일입니다.


1
2
3
4
5
6
7
8
9
10
11
@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u) throws Exception
}
 
try {
      BiFunction<Integer, Integer, Integer> biFunction = (Integer a, Integer b) -> a + b;
     biFunction.apply(25);
catch (Exception e) {
    // Exception Required. ㅡㅡ^
}
cs


Ndroid 이를 위해 JAVA8 에서 지원하는 기본 함수형 인터페이스를 다시 만들었습니다. 


Ndroid 에서 지원하는 기본형들은 모두 인터페이스임을 명시하기 위해 Ixxxx 형식을 사용합니다.

(ex. IPredicate, IFunction)


상황에 따라 RxJava 의 람다와 섞어 사용할 수 있을 것이라 생각합니다.


1
2
3
4
5
6
7
8
9
@FunctionalInterface
public interface IBiFunction<T, U, R> {
    R apply(T t, U u) throws Exception
}
 
// Too simple. None Exception
IBiFunction<Integer, Integer, Integer> biFunction = (Integer a, Integer b) -> a + b;
biFunction.apply(25);
 
cs



2. 람다 조합 불가 


람다를 조합할 수 있다는 것은 매우 편리한 기능입니다. 


각종 조건들에 대해서, 더이상 논리연산자를 복잡하게 추가하지 않아도 되며, 이는 한결 유지보수에 도움이 될 수 있습니다.



그러나 JAVA8 의 디폴트, 정적 메소드들이 안드로이드 24버전부터 지원함에 따라 해당 기능을 사용할 수 없었습니다. 


RxJava 역시 해당 기능을 지원하지 않기 때문에 조합을 할 수 없는 방법이 없을까 고민하던 찰나, 해당 기능을 지원하는 Builder 를 만들기로 했습니다.


Ndroid 의 LambdaUtil 은 람다에 대한 조합을 할 수 있으며, Java8 에서 사용가능한 모든 기능을 지원합니다.


1
2
3
4
IPredicate<Integer> predicate = LambdaUtil.PredicateBuilder((Integer a) -> a >= 5).
                                    and(a -> a < 10).
                                    or(a -> a == 0).
                                    get();
cs


LambdaUtil 은 기본적으로 Ndroid 에 정의된 기본형 인터페이스들을 지원합니다. 


하지만 조합된 람다는 RxJava 의 Observable 과도 호환되야 하기 때문에, Rx 스타일의 람다 역시 지원하도록 하였습니다.


1
2
3
4
5
6
7
// a % 5 == 0 && a > 40 || a < 20
Observable.range(060).filter(
                LambdaUtil.PredicateBuilder((Integer a) -> a % 5 == 0).
                        and(a -> a > 40).
                        or(a -> a < 20).
                        getRx()).
                subscribe(System.out::println);
cs


자세한 내용은 Ndroid 의 ReadMe 에서 확인할 수 있습니다.

[https://github.com/skaengus2012/N-java/wiki/N-java-v0.2-wiki#lambda-combination]


비록 안드로이드에서 JAVA8 의 모든 기능을 사용할 수 없지만, 조금씩 그 격차는 줄어들고 있습니다. 

우리의 코드는 계속해서 자리를 잡을 것입니다. 언제나 그랬던 것 처럼 말이죠. :-)


반응형
Posted by N'

Java8 의 등장으로 드디어 Java를 사용하는 어플리케이션에서, 함수형 프로그래밍을 할 수 있게 되었습니다. Lambda 를 사용할 수 있으면서, Collection 처리를 선언형으로 처리할 수 있다면 매우 매력적일 것이라 생각합니다.


그러나 아쉽게도 안드로이드에서는 안좋은 소식이 있습니다. 강력한 Stream API 를 24 버전 이 후부터 사용할 수 있습니다. 하위호환성이란 문제를 생각해야하는 입장에서 24버전을 minimum 으로 맞춰두고 개발하기란 쉽지 않을 것 같습니다.


Call requires API level 24 (current min is 15): java.util.Collection#stream


하지만 이를 커버할 수 있는 방법 중 하나는 안드로이드에서 반응형 프로그래밍을 할 수 있는 RxAndroid2(RxJava2 포함) 를 사용하는 방법이라 할 수 있을 것 같습니다. 


글 작성 기준 (2017.02.27) RxJava2 가 새로 등장하였으며, 이를 기준으로 Stream 과 호환할 수 있는 메소드 사용법에 대해 포스팅해보려 합니다. (간단한 예제는 생략합니다. ㅡㅡ^)


일단 gradle 로 다음 RxJava 라이브러리들을 다운 받습니다. 


RxAndroid2 : https://github.com/ReactiveX/RxAndroid


Rx-java-extension : https://github.com/jacek-marchwicki/rx-java-extensions



1. RxJava2 에서의 매칭


Collection 클래스 내에서 매칭을 하여 결과를 알 수 있다는 것은 꽤 유용한 방법이었습니다. 


해당 결과를 가진 데이터가 존재하는가? 혹은 모든 데이터가 해당 결과를 만족하는가? 등 우리는 Java8 에서 단순히 선언만으로 데이터를 처리할 수 있었습니다.



RxJava2 에서도 크게 다르지는 않습니다.


다음과 같이 any 와 all 을 사용할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
List<Integer[]> testT = Arrays.asList(new Integer[]{25},new Integer[]{38}, new Integer[]{3,5});
            
// 일부 매칭.
Observable.fromIterable(testT).
        map(arrays -> Arrays.asList(arrays)).
        any(list -> !list.contains(8)).blockingGet();
 
// 전체 매칭
Observable.fromIterable(testT).
        map(arrays -> Arrays.asList(arrays)).
        all(list -> !list.contains(8)).blockingGet();
cs



2. flatMap 을 통한 컬렉션 join


컬렉션 내부의 제네릭객체를 join 할 수 있는 flapMap 에 대해서 소개한 적이 있습니다. 



RxJava2 에서도 방법은 크게 다르지 않습니다. 호환성있게 지원이 됩니다. 아래와 같이 말이죠.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
List<MemberVo> memberVoList = Arrays.asList(
        new MemberVo(1"Doohyun Nam",28)
        , new MemberVo(2"Duckhyung Yuu"27));
 
List<PersonalInfoVo> personalInfoVoList = Arrays.asList(
        new PersonalInfoVo(1"My Way Functional Programming")
        , new PersonalInfoVo(2"My Way Spring FrameWork"));
 
Observable.fromIterable(memberVoList).
        flatMap(memberVo -> Observable.fromIterable(personalInfoVoList).
                                filter(personalInfoVo -> personalInfoVo.getMemberSubjectSn().equals(memberVo.getMemberSubjectSn())).map(personalInfoVo -> {
                            MemberPersonalVo memberPersonalVo = new MemberPersonalVo();
                            memberPersonalVo.setGoal(personalInfoVo.getGoal());
                            memberPersonalVo.setName(memberVo.getName());
                            return memberPersonalVo;
                        })).
        forEach(System.out::println); 
 
// Result
// Doohyun Nam : My Way Functional Programming
// Duckhyung Yuu : My Way Spring FrameWork
cs



3. Grouping 과 Partitioning 


컬렉션 데이터를 특정 조건으로 분류하고, 그 결과를 Map 으로 만들어주던 이 기능은 많은 비지니스 로직개선에 도움이 되었습니다. Collector 의 팩토리 메소드 몇개만 사용하면 쉽게 이용할 수 있었습니다.



그러나 RxJava2 에서는 이를 활용하기가 조금 까다롭습니다. Java8 의 Stream 처럼 collect 메소드를 지원하지만, 같이 사용가능한 Collectors 군의 팩토리 메소드들과 같은 유틸성 기능을 제공하지 않습니다.


대신에 toMap, toMultiMap, groupby 등의 메소드를 사용할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
List<SubjectRelation> subjectRelationList = Arrays.asList(
                    new SubjectRelation(11001"Doohyun Nam"1)
                    , new SubjectRelation(11002"Dolkin"2)
                    , new SubjectRelation(11003"hshawng"1)
                    , new SubjectRelation(11004"spKwon"1)
                    , new SubjectRelation(21005"Other Person1"3)
                    , new SubjectRelation(21006"Other Person2"4)
            );
 
// create Map
Map<Integer, Collection<String>> mapTest = Observable.fromIterable(subjectRelationList).
           toMultimap(SubjectRelation::getCompanySubjectSn, SubjectRelation::getMemberName).
           blockingGet();
 
 
// only subscribe
Observable.fromIterable(subjectRelationList)
            .groupBy(SubjectRelation::getCompanySubjectSn).subscribe(group -> {
                  System.out.println(group.getKey());
                group.map(SubjectRelation::getMemberName).forEach(System.out::println);
            });
 
// create multi group
 Map<Integer, Map<Integer, Collection<String>>> doubleKeyMap = new HashMap<>();
            Observable.fromIterable(subjectRelationList).
                    groupBy(SubjectRelation::getCompanySubjectSn).
                    blockingSubscribe(group ->
                        doubleKeyMap.put(group.getKey(),
                                group.toMultimap(
                                        SubjectRelation::getOrganizationSubjectSn
                                        , SubjectRelation::getMemberName).blockingGet())
                    );
 
// partitioning
Map<Boolean, Collection<String>> partitioningMap = Observable.fromIterable(subjectRelationList).
             toMultimap(subjectRelation -> subjectRelation.getCompanySubjectSn().intValue() == 1
                 , SubjectRelation::getMemberName).
             blockingGet();
cs


세 번째 라인의 key 두 개를 이용하여 만든 맵의 경우 딱 맵까지 만들어주는 방법을 찾지는 못했습니다. 뭔가 우회해서 해보려고는 했으나, deadlock 현상을 보았습니다. ㅜㅡㅜ


Single 객체는 RxJava2 부터 더이상 null 을 허용하지 않겠다는 것으로 만든 객체로써, 사실 Single로 감싸게 된 맵이라도 실제 사용에는 큰 문제가 없어보입니다.


4. Math Library.


Java8 에서는 collect 나 reduce 를 이용하여 합계, 최소값, 최대값 등 통계 데이터를 구할 수 있었습니다.


RxJava2 에서는 extension 라이브러리에서 이 기능을 수행할 수 있도록 도와줍니다. 


1
2
3
4
5
// 합계
Observable.range(0100).to(MathObservable::sumInt).blockingSingle();
            
// 평균
Observable.range(0100).to(MathObservable::averageFloat).blockingSingle();
cs


5. Optional 대신 Maybe


Java8 에서는 더이상 null 을 허용하지 않겠다는 의도로 값의 존재여부를 표시할 수 있는 Optional 이란 개념이 생겼습니다. RxJava2 에서는 Maybe 를 통해서 이를 수행할 수 있습니다.


1
2
3
4
int number = 5;
 
Maybe<Integer> a = number% 5 == 0 ? Maybe.just(number) : Maybe.just(number);
a.subscribe(System.out::println);
cs


라이브러리나 플랫폼은 달라도 위에 있는 프로그래밍 패러다임의 계승은 비슷합니다.


아직 모든 기능을 알게 된 것은 아니지만, 안드로이드에서도 본격적으로 함수형 프로그래밍을 할 수 있을 것으로 보입니다.

반응형

'개발이야기 > 안드로이드' 카테고리의 다른 글

MVP 패턴을 통한 메모리 누수 관리  (0) 2016.11.24
WeakReference 테스트  (2) 2016.11.15
액티비티 개수 구하기  (0) 2016.11.15
안드로이드 DOZE 모드 만들기  (0) 2016.11.15
Posted by N'

해당 포스팅에서 언급된 내용은 Ndroid 에서 제공합니다.

https://github.com/skaengus2012/Ndroid


안녕하세요. 연휴는 잘 보내고 계신가요?


저는 4일간 연휴 중 조금 마음을 refresh 하고자, 하루는!! 

기존 작성하였던 코드를 리뷰해보고, 리팩토링을 하는 시간을 가지게 되었어요. 


그래서 오늘 포스팅은 이에 대한 결과물로 조금 더 구체적인 안드로이드 개발의 패턴화에 대해 생각해보는 포스팅을 해보려 합니다. (그냥 일개 초보수준 프로그래머가 작성한 것입니다. ㅡㅡ^)


일단 리팩토링 한다고 해서 아무렇게나 작성을 해보기 보단 지난 포스팅 때 다뤘던 두 가지 사례를 적극적으로 사용해보는 시간을 가졌습니다. 아래의 두 포스팅을 말이죠. :-)



작업을 했던 내용은 다음과 같습니다.


1. Activity에 조금 더 최적화된 EasyWeakReference


코드를 작성하다보면 종종 어떤 일들은 안드로이드의 메인스레드에서 실행되어야할 경우가 많이 존재합니다. 특히 병렬처리를 하려 한다면, 멀티스레드 작업의 결과를 View 에 반영해야하는데 이 작업은 꼭 메인스레드에서 이루어 져야 하는 작업이죠!!


안드로이드에서 이에 접근할 수 있는 방법은 여러 종류가 있지만 저는 그중에서 runOnUiThread 를 주로 애용하는 편입니다. 


이 개념을 저는 일전에 제작한 NxWeakReference 에 추가하고 싶었으며(보다 더 쉽게 일하기 위해 ㅡㅡ^), 이를 위해 저는 해당 클래스의 확장체를 만들게 되었습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * Activity 전용 약한참조 객체
 *
 * Created by Doohyun on 2017. 1. 28..
 */
 
public class NxActivityWeakReference<extends Activity> extends NxWeakReference<T> {
    /**
     * 단순한 상속에 대한 생성자 제작
     * @param referent
     */
    public NxActivityWeakReference(T referent) {
        super(referent);
    }
 
    /**
     * 단순한 상속에 대한 생성자 제작
     *
     * @param referent
     * @param q
     */
    public NxActivityWeakReference(T referent, ReferenceQueue<T> q) {
        super(referent, q);
    }
 
    /**
     * Activity 에서 runOnUiThread 로 실행함
     *
     * @param weakReferenceConsumer
     */
    public void runOnUiThread(IWeakReferenceConsumer<T> weakReferenceConsumer) {
        final T activity = this.get();
 
        if (activity != null) {
            activity.runOnUiThread(() -> weakReferenceConsumer.accept(activity));
        }
    }
}
cs


간략하게 리뷰를 하자면, 생성자에서 Activity 를 상속받은 인스턴스를 강제로 입력하도록 제작하였으며, runOnUiThread 메소드를 통해 보다 더 쉽게 mainThread 에서 비지니스로직을 수행할 수 있습니다.



2. 안드로이드 MVP 규칙 정의


일전에 한번 JANDI 블로그(http://tosslab.github.io/android/2015/03/01/01.Android-mvc-mvvm-mvp.html)를 통해 protoType 화 했던 MVP 패턴에 대한 규칙을 조금 더 명세하였습니다. 규칙은 다음과 같습니다.


- View 에 해당하는 Activity는 오직 View 자체 변경 메소드만 작성할 것!


View 는 버튼 클릭 등에 의한 이벤트로 부터 해야할 비지니스 로직을 Presenter 로 위임합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class LoginActivity extends MitActivity {
 
    // 하위 View
    private RelativeLayout ryDialogBg;
    private LoginLoadingDialog loginLoadingDialog;
    private Button btnLogin;
 
    // Presenter
    private LoginPresenter loginPresenter;
    
    .....
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        loginPresenter = new LoginPresenter(this);
       
        // 이벤트에 대한 비지니스 로직을 Presenter 로 위임! 
        btnLogin.setOnClickListener(loginPresenter::clickLoginButton);
    }
 
    /**     
     * View 자체의 변경 메소드
     *
     * 키보드 숨기기
     */    
    public void showLoginLoadingDialog() {
        loginLoadingDialog.show();
        ryDialogBg.setVisibility(View.VISIBLE);
    }
 
}
cs


- Presenter 는 View 와 Model 의 연결고리, 즉 기존의 Controller 


Presenter 는 View 로 부터의 요청 시, Modeler 로 명령을 위임합니다. 


또한 Modeler 로부터  받은 정보를 View 로 반영하기 위해, View 에 정의된 메소드를 직접 실행합니다. 이를 위해 Presenter 는 View 에 대한 약한참조를 가지고 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class LoginPresenter implements ILoginServiceObserver {
 
    private NxActivityWeakReference <LoginActivity> loginActivityEasyWeakReference;
 
    public LoginPresenter(LoginActivity loginActivity) {
  // Activity 와 약한참조 유지
        loginActivityEasyWeakReference = new NxActivityWeakReference <>(loginActivity);
    }
 
    /**
     * 로그인 시도
     * 
     * <pre>
     *  View 인 Activity에서 Click Event로 할 일을 위임한 메소드
     * </pre>
     * @param view
     */
    public void clickLoginButton(View view) {
       final UserInfo userInfo = getCurrentInputUserInfo();
 
       if (userInfo != null) {
          // View 변경 메소드 사용
          loginActivityMidasWeakReference.run(LoginActivity::showLoginLoadingDialog);
          // Modeler 로 실제 비지니스 로직 위임.
          LoginModeler.GetInstance().tryLogin(userInfo);  
       }
    }
}
cs


Presenter 는 다른 컴포넌트의 옵저버 역할을 수행합니다. 다른 컴포넌트의 알람의 결과(멀티스레드의 콜백결과 등..)로 View 가 변경되야 할 때, 이를 Presenter 로 전달을 하여 View 를 변경시킬 수 있도록 합니다.


- Model 을 담당하는 Modeler 개념 추가 


Modeler 는 스프링에 있던 Service 와 같은 역할을 수행합니다. (안드로이드에는 서비스가 있으니 서비스라는 이름은 차마 사용할 수 없었습니다. ㅜㅡㅜ)


Presenter 의 요청으로 내부DB 의 CRUD 를 수행하는 DAO 클래스를 사용하거나, network 작업을 수행합니다. 물론 요청이 콜백이었다면, 데이터를 전달하는 역할도 수행하겠죠?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class LoginModeler {
    /**
     * 로그인을 시도한다
     *
     * <pre>
     *     userInfo 가 넘어오는 것에 성공했다면 정확한 정보를 보장함!
     *     유효성 검사 필요 없음
     * </pre>
     *
     * @param userInfo
     */
    public void tryLogin(@NonNull UserInfo userInfo) {
        // 실제 비지니스 로직 수행 (네트워크 접근)
        BindServiceManager.GetInstance().runRMI(iMessengerBinder -> iMessengerBinder.loginProcess(userInfo.getUserId(), userInfo.getPwd()));
    }
 
    /**
     * 유저 정보를 저장한다.
     *
     * <pre>
     *     userInfo 는 null 이 아닌 한 정확한 정보를 보장함!
     *     유효성 검사 필요 없음
     * </pre>
     * @param userInfo (null 허용)
     * @return
     */
    public boolean saveUserInfo(@Nullable UserInfo userInfo) {
        // 실제 비지니스 로직 수행 (DB CRUD)
        final boolean resultYn;
 
        if (userInfo != null) {
            resultYn = TxManager.GetInstance().runTransaction(() -> {
                UserInfoServiceManager.GetInstance().insert(userInfo.getUserId(), userInfo.getPwd());
                SettingServiceManager.GetInstance().insert();
            });
        } else {
            resultYn = false;
        }
 
        return resultYn;
    }
}
cs



계속해서 이 패턴화 를 견고하게 작업해 볼 생각입니다. 


보다 유연하고 질 좋은 코드를 꿈꾸며 말이죠. :-)


이상 설연휴 중 삽질이었습니다. ㅡㅡ^ 



반응형
Posted by N'

해당 포스팅에서 언급된 내용은 Ndroid 에서 제공합니다.

https://github.com/skaengus2012/Ndroid


안드로이드 개발을 진행하다보면 Activity 의 생명주기와 스레드 사용지점이 맞지 않아 누수가 되곤 합니다. 그러한 이유로 WeakReference 를 사용하란 이야기가 많이 나오곤 합니다.


보통 Activity 인스턴스를 약한참조 관계로 가지고 있습니다. 그리고 해당 인스턴스의 메소드를 사용하기 위해 우린 객체를 꺼내서 다음과 같이 사용합니다.


1
2
3
4
5
6
MyActivity activity = easyWeakReference.get();
        
if (activity != null) {
    // 메모리가 해제되어 더이상 존재하지 않는 객체인지 확인할 필요 존재!
    activity.func1();
}
cs


심플하게 사용할 수 있지만, 


프로그램의 덩치가 커지고 WeakReference 를 사용할 일이 많아진다면,


별 거 아닌 것 같은 저 null check 는 매우 귀찮고 중복작업일 수 있습니다.


이 문제는 아래의 WeakReference 의 상속체를 제작함으로 간단하게 해결할 수 있습니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
public class NxWeakReference<T> extends WeakReference<T> {
 
    /**
     * 단순한 상속에 대한 생성자 제작
     * @param referent
     */
    public NxWeakReference(T referent) {
        super(referent);
    }
 
    /**
     * 단순한 상속에 대한 생성자 제작
     * @param referent
     * @param q
     */
    public NxWeakReference(T referent, ReferenceQueue<super T> q) {
        super(referent, q);
    }
 
    /**
     * 약한 참조 속 실제 객체의 사용
     * @param weakReferenceConsumer
     */
    public void run(IWeakReferenceConsumer<T> weakReferenceConsumer) {
        T t = this.get();
 
        if (t != null) {
            weakReferenceConsumer.accept(t);
        }
    }
 
    /**
     * 약한 참조 속 실제 객체의 사용
     *
     * <pre>
     *     call back 의 개념 사용
     * </pre>
     *
     * @param weakReferenceFunction
     */
    public Object call(IWeakReferenceFunction<T> weakReferenceFunction) {
        Object result = null;
 
        T t = this.get();
 
        if (t != null) {
            result = weakReferenceFunction.accept(t);
        }
 
        return result;
    }
 
    /**
     * 약한 참조 속 실제 객체의 사용
     *
     * <pre>
     *      call back 개념 사용
     *      defaultValue 사용
     * </pre>
     *
     * @param weakReferenceFunction
     * @param defaultValue
     * @return
     */
    public Object call(IWeakReferenceFunction<T> weakReferenceFunction, Object defaultValue) {
        final Object result = call(weakReferenceFunction);
 
        if (result == null) {
            return defaultValue;
        } else {
            return result;
        }
    }
 
    /**
     * WeakReference 의 내부객체를 사용하기 위한 Consumer 인터페이스
     * @param <T>
     */
    @FunctionalInterface
    public interface IWeakReferenceConsumer<T> {
        void accept(T t);
    }
 
    /**
     * WeakReference 의 내부객체를 사용하기 위한 Function 인터페이스
     * @param <T>
     */
    @FunctionalInterface
    public interface IWeakReferenceFunction<T> {
        Object accept(T t);
    }
}
 
cs


상속 후 새로 제작된 accept 는 위의 null 체크를 수행하도록 하며, 실제 비지니스 로직은 함수형 인터페이스로 동작파라미터화 시켰습니다. 


동작 파라미터화가 무엇인지 궁금하다면, 이곳으로 이동!!



이제 비지니스 로직은 다음과 같이 단순화하여 개발할 수 있습니다.


1
easyWeakReference.run(MyActivity::func1);
cs

함수형 프로그래밍을 조금만 알면 쉽게 작업은 단순화시킬 수 있습니다. 




반응형
Posted by N'

현재 개발하고 있는 앱의 메모리 누수가 감지되고 있습니다. ㅡㅡ^ 


메모리 누수 감지는 


https://github.com/square/leakcanary


를 통해서 프로파일링을 하고 있고, 대부분 문제가 스레드가 활동하는 동안 Context 가 살아남아서 생기는 문제가 있습니다.


뭐 자바의 패러다임으로 볼 때, 메모리는 VM 이 관리해주기 때문에 딱히 개발자가 건들 필요는 없어보이지만, 안드로이드 메모리가 제한적이고 각 객체가 더이상 쓰이지 않을 때 바로바로 해제를 해주어야 한다는 측면에서 적절한 작업이 이뤄줘야하는 것으로 보입니다. 특히 액티비티 인스턴스는 용량이 크기 때문에 잘 해제가 되야 합니다. ㅡㅡ^, 


해당 문제를 해결하기 위해, 아래 포스트처럼 WeakReference 를 통해 해결을 하려 하고 있으며, 이를 적용하기 위한 일괄된 패턴이 필요했습니다.



그래서 이번에 시도해보고 있는 것은 

잔디 블로그 (http://tosslab.github.io/android/2015/03/01/01.Android-mvc-mvvm-mvp.html

에서 소개한 MVP 패턴 사례를 참고하여, 메모리 누수를 막아보려고 하고 있습니다.


제가 하려고 했던 아래 코드입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SplashActivity extends Activity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
    }
 
    @Override
    protected void onResume() {
        super.onResume();
 
        Handler hd = new Handler();
        hd.postDelayed(() -> {
            // 심플하지만, 이 곳 핸들러의 역할 때문에 메모리 누수가 존재
            setResult(RESULT_OK);
            finish();
 
            overridePendingTransition(R.anim.abc_fade_in, R.anim.abc_fade_out);
        }, 1000);
    }
}
cs



핸들러 내부에서 Context 가 살아남아 있으며, 실제 프로파일러에서는 Handler 에서 메모리 릭이 감지되었다고 나오고 있습니다.


이러한 이유로, 해야할 일을 나누어 봅시다.


WeakReference 관리는 Presenter 가 담당합니다. 아래와 같이 말이죠.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SplashPresenter {
    private Handler hd;
    private WeakReference<SplashActivity> splashActivityWeakReference;
 
    public SplashPresenter(SplashActivity splashActivity) {
        splashActivityWeakReference = new WeakReference<>(splashActivity);
        hd = new Handler();
    }
 
    /**
     * 1초 후에 해당 액티비트를 종료한다.
     */
    public void delayFinished() {
        hd.postDelayed(() -> {
            SplashActivity splashActivity = splashActivityWeakReference.get();
 
            if (splashActivity != null) {
                splashActivity.smoothFinishActivity();
            }
 
        }, 1000);
    }
}
cs


잔디 블로그의 경우에는 Presenter 를 인터페이스화 시킨 후 구현화하였지만,


- 저의 경우는 Activity 와 Presenter 가 1:1 관계이고, 

- Activity 에서 View 관련 업데이트를 해야할 종류가 많을 경우 interface 에 그만큼 메소드를 정의해야하기 때문에 불편하다는 생각이 있었으므로, 


Activity 와 Presenter 를 단순 has-a 관계로 처리하고자 하였습니다.


그리하여, Activity 구현 부는 다음과 같이 View 만 업데이트하는 구조를 취하게 되었습니다. Activity 자체가 WeakReference 관리를 할 필요는 없는것이죠.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class SplashActivity extends Activity {
    private SplashPresenter splashPresenter;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
 
        splashPresenter = new SplashPresenter(this);
    }
 
    @Override
    protected void onResume() {
        super.onResume();
        splashPresenter.delayFinished();
    }
 
    /**
     * Activity 종료
     * <p>
     * <pre>
     *     Activity 를 페이드아웃 애니메이션을 사용하며 종료.
     * </pre>
     */
    public void smoothFinishActivity() {
        setResult(RESULT_OK);
        finish();
        overridePendingTransition(R.anim.abc_fade_in, R.anim.abc_fade_out);
    }
}
cs


다음과 같은 구조를 통해, View 를 담당하는 Activity 클래스는 WeakReference 를 고려할 필요가 없게 되었으며, Activity instance 는 GC Time에 메모리 회수가 가능하게 되었습니다.



반응형
Posted by N'

Rx의 도입과 액티비티를 옵저버로 관리하게 되니, 메모리 누수라는 골치아픈게 발생하고 있습니다.


WeakReference 클래스를 사용하면 된다고 나오지만, 정확히 이해가 가지 않아, 테스트 코드를 작성해보았습니다.


일단, WeakReference 를 사용하지 않을 경우.

- 확인해보면 GC 가 강제 실행되었지만, A 내부 B 객체가 살아있음을 알 수 있습니다. (컬렉션이 물고 있기 때문이죠.)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Test {
 
    public static class A {
        private B b = new B();
 
        public B getB() {
            return b;
        }
 
        public void setB(B b) {
            this.b = b;
        }
 
        public static final class B {
            public String text = "아직 살아있다.";
        }
    }
 
    public static void main(String[] args) {
        // TODO Auto-generated method stu
        A a = new A();
        HashSet<A.B> abc = new HashSet<>();
 
        abc.add(a.getB());
        a = null;
        System.gc(); // GC 강제 발생
 
        for (A.B b : abc) {
            System.out.println(b.text);
        }
    }
}
 
// 아직 살아있다.
 
cs


WeakReference 로 참조를 유지할 경우.

- GC 가 돌며, A 의 인스턴스가 삭제되었고, 참조하고 있던 B의 인스턴스도 삭제가 되었음을 알 수 있습니다. (안드로이드에서도 이 힌트를 가지고, 메모리 릭을 방지할 수 있을 것이라 생각합니다. ㅡㅡ^)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Test {
 
    public static class A {
        private B b = new B();
 
        public B getB() {
            return b;
        }
 
        public void setB(B b) {
            this.b = b;
        }
 
        public static final class B {
            public String text = "아직 살아있다.";
        }
    }
 
    public static void main(String[] args) {
        // TODO Auto-generated method stu
        A a = new A();
        HashSet<WeakReference<A.B>> abc = new HashSet<>();
 
        abc.add(new WeakReference<>(a.getB()));
 
        a = null;
        System.gc();
 
        for (WeakReference<A.B> b : abc) {
            System.out.println(b.get().text);
        }
    }
}
 
// Exception in thread "main" java.lang.NullPointerException
    at Test.main(Test.java:31)
 

cs


반응형
Posted by N'

안드로이드에서 메모리 릭이 발생하고 있는지 아닌지를 다음 코드를 통해 확인합시다.


기종 따라 다르지만, instanceCount 가 무한대로 증가한다면, 문제가 있는 겁니다. ㅡㅡ^


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestInstanceActivity extends Activity {  
           public static int instanceCount = 0;
           public TestInstanceActivity () {
                     super();
                     instanceCount++;
                     Log.d("test""TestInstanceActivity() instanceCount: " + instanceCount);
           }
 
 
           @Override
           protected void finalize() throws Throwable {
                     super.finalize();
                     instanceCount--;
                     Log.d("test""finalize() instanceCount: " + instanceCount);
           }
}
cs


반응형
Posted by N'