다크모드 적용기

개발/안드로이드 2021. 10. 14. 20:00 Posted by 아는 개발자

최근 기존 프로젝트에 다크모드를 적용하는 일을 진행했었다. 꽤 오랜 시간이 소요될 줄 알았지만 안드로이드에서 제공하는 기능을 통해서 생각보다 빠르게 적용할 수 있었다. 이번 포스트에서는 다크모드를 적용하면서 유용했던 기능을 정리해봤다.

 

0. 동일한 이름으로

 

다크모드를 적용할 때 주요하게 변경하는 부분은 텍스트 색깔, 그림 파일 형태의 앱 내 아이콘, XML로 생성한 그림 파일(사각형이나 그라데이션 등등) 이다. 다크모드를 적용하더라도 각각의 파일 이름은 그대로 가져가야 앱 내에서 수정하는 부분을 최소화 할 수 있기 때문에 리소스의 이름은 동일하게 유지하고 라이트모드, 다크모드 각각의 상태에서는 다른 파일을 사용하도록 진행했다.

 

1. 아이콘 리소스 분리

 

안드로이드에서는 리소스 폴더에 키워드를 넣어서 앱의 Configuration에 따라 다른 리소스를 사용하도록 설정할 수 있다. 해상도에 따라서 다른 아이콘을 적용하고 싶은 경우에는 drawable-xhdpi, drawable-xxhdpi 이런식으로 적용할 수 있는데 다크모드를 사용하는 경우에는 night 키워드를 넣어서 분기할 수 있다. drawable-night-xhdpi 이런 형태의 폴더를 만들면 xhdpi 해상도에서 다크모드인 경우에 이 폴더의 아이콘을 사용하게 된다.

 

추가한 폴더

 

2. 색상 분리 작업

 

라이트모드에서 다크 모드로 전환 할 때 색깔별로 어느정도 규칙이 생긴다. 예를 들면 라이트 모드에서 검은색 텍스트를 사용했다면 다크 모드에서는 흰색으로 바뀌게 된다. 나의 경우엔 이 부분에 대해선 디자인 팀에서 가이드라인을 받았고 규칙에 맞게 모드에 따라서 색상 파일로 분리한 다음에 각각의 리소스에 등록해주었다.

 

색상 정보를 가지고 있는 파일은 colors.xml 이었고 values 폴더 내에 있었다. 아이콘과 마찬가지로 이 폴더도 values-night로 하면 다크모드일때만 바라보는 폴더를 만들 수 있었다. 이 안에 colors.xml 을 추가하고 동일한 색상 이름이 다른 hexa 값을 가지도록 변경했다.

 

<!-- values/colors.xml -->

    <color name="hashtag_selected">#07c3ff</color>
    
<!-- values-night/colors.xml -->

    <color name="hashtag_selected">#404245</color>

 

이렇게 구분한 색상 값을 뷰 클래스에 적용하면 모드의 상태에 따라 다른 색상을 보여줄 수 있다. 텍스트뷰나 XML로 만든 그림 파일에도 동일하게 적용 가능하다. 

 

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="@color/hashtag_selected" />
    <corners android:radius="18dp" />
</shape>
728x90

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

다크모드 적용기  (0) 2021.10.14
RxJava - Disposable Deep Dive!  (0) 2021.09.17
RxJava dispose()  (0) 2021.09.16
ListAdapter, DiffUtil  (0) 2021.08.20
Coroutine + Retrofit | Coroutine + Room  (0) 2021.07.22
suspend fun  (0) 2021.07.22

Kafka - Event Streaming Platform

기술 2021. 10. 13. 20:30 Posted by 아는 개발자

 

Event Streaming Platform

 

Kafka를 간단히 설명하면 Event Streaming Platform이다. 그래서 Kafka에 대해서 이해하려면 먼저 Event Streaming Platform이 무엇인지 먼저 이해할 필요가 있다. Kafka 공식 문서에서는 Event Streaming을 다음과 같이 정의한다. 

 

Technically speaking, event streaming is the practice of capturing data in real-time from event sources like databases, sensors, mobile devices, cloud services, and software applications in the form of streams of events; storing these event streams durably for later retrieval; manipulating, processing, and reacting to the event streams in real-time as well as retrospectively; and routing the event streams to different destination technologies as needed. Event streaming thus ensures a continuous flow and interpretation of data so that the right information is at the right place, at the right time.

 

간단히 요약하면 다양한 비즈니스 로직에서 발생하는 이벤트 데이터들을 적합한 장소에 실시간으로 전달해줄 수 있는 기술을 Event Streaming 이라고 한다. Kafka 는 Event Streaming을 지원하기 위해 세가지 핵심 기능을 제공한다. 

 

- 다른 시스템에서 이벤트를 배포하고(write) 구독할 수 있다(read) 

- 사용자가 원할 때 까지 Stream을 견고하게 저장할 수 있다

- Event stream 각각은 발생할 때마다 처리할 게 한다. 

 

그리고 모든 기능은 분산화되고 확장가능하며 장애 복구가 가능하고(Faule Tolerant) 보안이 제공되는 형식이다. 카프카는 순수 하드웨어, 가상 머신, Container에 설치가 가능하다. 

 

Main Concepts and Terminology 

 

Event 

 

비즈니스에서 일어난 어떤 일에 대한 기록이다. Kafka에 데이터를 발행하거나 구독할 때 Event의 형태를 사용하게 된다. 개념적으로 이벤트는 키, 값, 시간, 그리고 추가데이터로 구성된다.

카프카 공식 문서에서 예시로 든 이벤트

Producers

 

Kafka에 이벤트를 발행하는 클라이언트 애플리케이션이다. 

 

Consumer

 

Producer에서 추가한 이벤트를 처리하고 구독하는 주체다 .

 

Producers - Consumer 

 

Producer와 Consumer는 완전히 분리되는데 이는 Kafka가 높은 확장성을 가질 수 있는 핵심 디자인 요소다. 예로 들면 이렇게 분리된 형태 덕분에 Producer는 Consumer를 기다리지 않고도 새로운 이벤트를 발행 할 수 있게 된다.

 

Topic 

 

이벤트가 조직화되고 저장되는 장소다. 간단하게 설명하면 파일시스템의 폴더이며 이벤트는 이 폴더 내의 파일로 존재한다. 예로 Topic의 이름은 payments가 될 수 있다. Topic은 내부에 이벤트를 추가할 수 있는 여러개의 Producers를 가질 수 있으며 마찬가지로 이벤트를 구독 할 수 있는 여러개의 Consumer를 가질 수 있다.

 

Topic은 Partition 돼있는데 여러개의 다른 Kafka broker의 Bucket을 저장할 수 있는 형태로 되있다. 이렇게 분산화된 데이터 저장은 확장성에 매우 중요한데 왜냐하면 클라이언트 애플리케이션이 여러개의 Broker로부터 데이터를 읽고 쓸 수 있게 해주기 때문이다. 새로운 event가 Topic에 추가되면 실제로 Topic의 파티션중 하나에 추가된 것이다. 동일한 이벤트 키를 가진 Event는 동일한 Partition에 기록되며 Kafka는 주어진 topic partition의 Consumer는 반드시 해당하는 Partition의 Event를 읽도록 해 Event가 발행된 순서대로 처리할 수 있게 한다. 

 

728x90

'기술' 카테고리의 다른 글

Kafka - Event Streaming Platform  (0) 2021.10.13
JAVA 파일 생성/읽기/쓰기  (0) 2018.11.25
스택, 힙, 코드, 데이터영역  (6) 2018.11.10
VNC와 RDP  (2) 2018.09.12
jupyter notebook 소개  (0) 2018.08.04
URI (Uniform Resource Identifier)  (0) 2018.07.02

RxJava - Disposable Deep Dive!

개발/안드로이드 2021. 9. 17. 20:00 Posted by 아는 개발자

1. Disposable 클래스의 역할

 

 

RxJava 공식 문서에서는 Observable과 Observer의 관계를 위 그림으로 표현한다. Observable에서 데이터를 전달 할 때는 onNext() 함수가, 더이상 전달할 값이 없을 때는 onComplete() 함수가 마지막으로 에러가 발생하면 onError()가 호출되는 방식이다. 이런 설명 방식도 조금 디테일하게 분석해보면 Observable과 Observer 사이에 Disposable 객체를 추가하는 것이 조금 더 정확할 것 같다.

 

 

Disposable 객체는 Observable에서 노출할 자원을 갖고 있고 Observer에게 이벤트로 전달하는 객체다. 그래서 RxJava 내부 소스코드를 분석해보면 첫번째 그림에 보여진 Observabe -> Observer에서 호출되는 함수는 사실 Disposable를 구현한 클래스 객체 내부에서 호출되고 있다. Observable은 Disposable을 생성하기 전까지 스트림을 대신 관리해주는 클래스고 실질적으로 값을 보내는 작업은 Disposable 내부 클래스에서 실행되고 있다.

 

2. 짧은 RxJava 코드

 

구체적으로 설명하기 위해 짧은 RxJava 코드 실행시 생성되는 객체들의 연관관계를 그려봤다. 

 

 

1. Observable.Just 는 단일 아이템을 생성하는 Observable 객체다. 이 객체를 생성하면 ObservableJust가 생성된다. 

2. doOnNext는 앞서 받은 Observable 아이템을 처리하고자 생성하는 루틴인데, 스트림을 유지하고자 ObservableDoOnEach를 만들었다. doOnNext의 내부 루틴은 DoOnEachObserver 객체에서 처리한다. 

3. doOnError도 doOnNext와 마찬가지로 스트림을 유지하고자 ObservableDoOnEach를 만들었다. doOnErro 내부 루틴은 DoOnEachObserver에서 처리한다. 

4. subscribe()가 호출되면 아래 스트림부터 최상단 스트림까지 차례로 구독 관계가 형성된다. ObservableDoOnEach -> ObservableDoOnEach -> ObservableJust 순서로 subscribe가 재귀로 호출되면서 Observer 간의 구독 관계가 완성된다

5. 최상단 ObservableJust는 값 1을 발행하는데 이 이벤트는 ScalarDisposable 에서 담당한다. ObservableJust는 DoOnEachObserver 내부 onSubscribe 함수를 호출해서 스트림 간에 down/upstream 을 구축한다.

6. ScalarDisposable 내부에서는 DoOnEachObserver 내부 onNext 함수를 호출해서 값 1을 전달한다. 

 

3. ScalarDisposable

 

ScalarDisposable 에서 값을 전달하는 부분은 Runnable로 동작하게끔 구현되있다. Observable.just 형태의 스트림을 구독하면 매번 새로운 쓰레드가 생성되서 실행된다.

 

 

4. Memory Leak 가능성

 

다른 Disposable 를 구현한 클래스를 찾아보면 Observable의 역할에 따라서 Runnable인 경우도 있고 Scheduler로 돌리는 경우도 있다. Intervar 처럼 긴 시간 돌리는 작업이면 따로 dispose() 함수를 호출하지 않는 이상 쓰레드가 종료되지 않고 계속 실행된다. 이런 코드가 증가하게 되면 불필요한 쓰레드 개수가 늘어나 Memory Leak이 발생할 소지가 있다. RxJava()를 사용할 때 CompositeDisposable() 객체를 활용해서 dispose() 시키라는 이유가 여기에 있다.

728x90

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

다크모드 적용기  (0) 2021.10.14
RxJava - Disposable Deep Dive!  (0) 2021.09.17
RxJava dispose()  (0) 2021.09.16
ListAdapter, DiffUtil  (0) 2021.08.20
Coroutine + Retrofit | Coroutine + Room  (0) 2021.07.22
suspend fun  (0) 2021.07.22

RxJava dispose()

개발/안드로이드 2021. 9. 16. 20:00 Posted by 아는 개발자

이번 포스트에서는 RxJava를 사용할때 왜 dispose() 함수를 호출해서 메모리 정리를 해야하는지를 사례를 통해서 정리해보고자 한다.

 

class LeakActivity : AppCompatActivity() {
    private var disposable1 : Disposable? = null

    companion object {
        private const val TAG: String = "leak_activity_tag"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val observeSource = Observable.interval(1, TimeUnit.SECONDS)
        disposable1 = observeSource.subscribe { Log.d(TAG, "subscriber1 value: $it") }
        observeSource.subscribe { Log.d(TAG, "subscriber2 value: $it") }
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroyCalled")
        disposable1?.dispose()
        disposable1 = null
    }
}

 

이 액티비티는 생성하면서 1초 마다 이벤트를 보내는 Observable을 생성하고 두개의 subscriber로 구독하고 있다. 그리고 종료될 때는 첫번째 subscriber만 구독 모델을 해지한다. Activity를 종료하기 전까지는 두 subscriber에서 동시에 로그가 출력되는데

 

2021-09-16 17:48:01.106 subscriber2 value: 0
2021-09-16 17:48:01.106 subscriber1 value: 0
2021-09-16 17:48:02.106 subscriber1 value: 1
2021-09-16 17:48:02.107 subscriber2 value: 1
2021-09-16 17:48:03.106 subscriber2 value: 2
2021-09-16 17:48:03.106 subscriber1 value: 2
2021-09-16 17:48:04.106 subscriber1 value: 3
2021-09-16 17:48:04.106 subscriber2 value: 3

 

액티비티를 종료하고 나면 subscriber2에서 계속 로그가 출력된다. 화면이 없어졌는데도 이벤트를 지속적으로 구독하고 있다.

 

2021-09-16 17:48:44.907 onDestroyCalled
2021-09-16 17:48:45.106 subscriber2 value: 44
2021-09-16 17:48:46.107 subscriber2 value: 45
2021-09-16 17:48:47.106 subscriber2 value: 46

 

심각한 것은 백버튼으로 앱을 종료한 후 다시 실행해도 계속 구독하고 있게 된다는 것이다.  아래 로그를 보면 subscriber2 로그가 두번씩 찍히는데 이것은 이전에 남아있는 액티비티에서 구독한 subscriber가 계속 출력되기 때문이다.

 

2021-09-16 17:50:58.964 subscriber2 value: 0
2021-09-16 17:50:58.964 subscriber1 value: 0
2021-09-16 17:50:59.106 subscriber2 value: 178
2021-09-16 17:50:59.964 subscriber1 value: 1
2021-09-16 17:50:59.965 subscriber2 value: 1
2021-09-16 17:51:00.106 subscriber2 value: 179
2021-09-16 17:51:00.963 subscriber1 value: 2
2021-09-16 17:51:00.963 subscriber2 value: 2
2021-09-16 17:51:01.106 subscriber2 value: 180
2021-09-16 17:51:01.963 subscriber2 value: 3
2021-09-16 17:51:01.963 subscriber1 value: 3
2021-09-16 17:51:02.106 subscriber2 value: 181

 

단발성 이벤트를 구독했다면 큰 문제는 되지 않는다 하지만 위 코드처럼 지속적으로 이벤트를 보낸다면 그리고 subscriber 내부에서 메모리 할당 작업이 포함돼있었다면 메모리 릭이 발생하게 된다. 

728x90

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

다크모드 적용기  (0) 2021.10.14
RxJava - Disposable Deep Dive!  (0) 2021.09.17
RxJava dispose()  (0) 2021.09.16
ListAdapter, DiffUtil  (0) 2021.08.20
Coroutine + Retrofit | Coroutine + Room  (0) 2021.07.22
suspend fun  (0) 2021.07.22

IoC container and Bean

개발/spring 2021. 9. 6. 21:00 Posted by 아는 개발자

IoC Containner and Bean

 

In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC 
container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application. Beans, and the dependencies among them, are reflected in the configuration metadata used by a container.

 

IoC Container는 Spring에서 객체 의존성을 대신 관리해주는 플랫폼이고 이 플랫폼 내에서 생성되고(instantiated) 조립되고(assembled) 관리되는(managed) 객체를 Bean 이라고 한다. 간단하게 Bean은 애플리케이션 내에서 존재하느 수 많은 객체인데 Spring Container에서 관리되고 있다고 보면 된다. Spring 을 사용하면 Spring IoC 컨테이너에서 객체의 생성과 의존성 주입을 관리 할 수 있다. 

Application Context

 

The interface org.springframework.context.ApplicationContext represents the Spring IoC container and is responsible for instantiating, configuring, and assembling the aforementioned beans. The container gets its instructions on what objects to instantiate, configure, and assemble by reading configuration metadata. The configuration metadata is represented in XML, Java annotations, or Java code. It allows you to express the objects that compose your application and the rich interdependencies between such objects.

 

ApplicationContext 인터페이스를 통해 Spring IoC container를 만들수 있고, 이 인터페이스는 Bean 객체를 생성하고(instantiating) 설정하고 (configuration) 조립하는 (assembling)한다. 설정 metadata를 읽어서 이 작업을 처리하는데 meta data 포맷은 XML이나 Java 어노테이션 또는 자바 코드를 통해서 가능하다.  

 

Dependencies 

 

Dependency injection (DI) is a process whereby objects define their dependencies, that is, the other objects they work with, only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse, hence the name Inversion of Control (IoC), of the bean itself controlling the instantiation or location of its dependencies on its own by using direct construction of classes, or the Service Locator pattern.

 

Dependency Injection은 객체 간의 의존성을 정의하는 작업인데 즉 다른 객체간 어떻게 일하는지 의존하는 관계를 객체의 생성자나 setter 함수를 통해서 정해줄 수 있다. Container는 Bean 객체를 생성할 때 이 의존성을 대신 주입해준다. 이 과정은 완전히 역전된 관계라서 Inversion of Control 이라고 부른다. Dependency Injection을 사용하면 코드가 깔끔해지고 디커플링도 효율적으로 수행할 수 있게돼 결과적으로 클래스가 테스트하기 쉬워진다.

 

DI 방법은 생성자를 이용하는 방법과 Setter 함수 기반이 있다.

 

 

https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/beans.html

 

5. The IoC container

The BeanFactory provides the underlying basis for Spring's IoC functionality but it is only used directly in integration with other third-party frameworks and is now largely historical in nature for most users of Spring. The BeanFactory and related interfa

docs.spring.io

 

728x90

'개발 > spring' 카테고리의 다른 글

IoC container and Bean  (0) 2021.09.06
@Bean vs @Component  (0) 2021.09.06
Node.js vs Spring Boot  (5) 2021.03.13
Spring 테이블 칼럼이 아닌 필드 데이터 받아오기  (0) 2021.03.05