RxJava: Subject, PublishSubject, BehaviorSubject

모바일/안드로이드 2020. 2. 15. 17:14 Posted by 아는 개발자


0. Subject


RxJava에서 Subject 클래스는 구독하고 있는 관찰자(Observer)에게 새로운 값을 전달 할 때 사용하는 클래스다. 따로 Observable로 새로운 값을 만들 필요 없이 Subject 객체에 내장된 onNext 함수로 새로운 값을 옵저버에게 전달할 수 있기 때문에 짧은 코드로도 reactive하게 구현하는 것이 가능하다. 안드로이드에서 제공하는 LiveData와 유사한 역할을 한다.


아래 코드는 Subject 클래스중 하나인 PublishSubject를 이용해서 새로운 값을 갱신하는 예제다.

class Person {
    var publishName: PublishSubject<String>
            = PublishSubject.create()
}

val person = Person()
person.publishName.subscribe {
    Log.d(TAG, "publishName: " + it)
}
person.publishName.onNext("selfish")
person.publishName.onNext("developer")

실행결과 다음과 같이 수정된 값이 출력되는 것을 확인 할 수 있다.



1. PublishSubject vs BehaviorSubject


RxJava에서 제공하는 Subject 함수로 AsyncSubject, PublishSubject, BehaviorSubject, RelaySubject가 있는데 이번 포스트에서는 가장 많이 사용되는 PublishSubject와 BehaviorSubject를 그리고 둘 간의 차이를 소개해보려고 한다. 그런데 바로 글로 쓰는 것 보다는 코드와 출력되는 결과를 보면서 설명을 하는게 더 좋을 것 같다.

class Person {
    var behaviorName: BehaviorSubject<String>
            = BehaviorSubject.create()
    var publishName: PublishSubject<String>
            = PublishSubject.create()

    fun nextName(name: String) {
        behaviorName.onNext(name)
        publishName.onNext(name)
    }
}

person.nextName("selfish")
person.publishName.subscribe {
    Log.d(TAG, "publishName: " + it)
}
person.behaviorName.subscribe {
    Log.d(TAG, "behaviorName: " + it)
}
person.nextName("developer")

Person 클래스에는 BehaviorSubject 객체를 선언해뒀고 Subject 객체의 값을 한 번에 바꾸고자 nextName이라는 함수를 만들었다. 그리고 아래 코드에서는 publishName과 behaviorName을 구독하도록 했는데 기존 코드와 달리 구독하기 전에 이름을 "selfish"로 갱신을 미리 해뒀다. 


이 코드의 출력 결과는 다음과 같다.



BehaviorSubject로 선언 된 객체는 구독 전에 갱신한 "selfish" 문자열을 출력하는 반면 PublishSubject로 선언 된 객체는 구독 이후에 갱신한 "developer" 문자열만 출력하고 있다. 이는 두 객체의 동작 구조가 다르기 때문이다.


2. PublishSubject


PublishSubject 객체의 경우 구독 이후에 갱신된 값에 대해서만 값을 받는다. 아래 다이어그램의 세번째 줄에서 구독하기 이전에 갱신된 빨간공, 초록공은 무시하고 파란 공만 받고 있는 것을 볼 수 있다. 과거에 데이터를 무시하고 새로 갱신되는 값만 보고 싶은 경우 사용하기 유용하다. 대표적으로 버튼을 클릭하는 이벤트를 PublishSubject로 사용하기도 한다.



3. BehaviorSubject


BehaviorSubject 객체의 경우에는 구독하는 시점의 가장 최근에 갱신된 값을 받는다. 다이어그램 세번째 줄에서 구독하면서 가장 최근에 갱신된 초록색 공과 그 이후에 갱신된 파란색 공을 받는것을 볼 수 있다. 구독하는 시점에서 과거에 갱신된 데이터중 가장 최근의 값이 필요할 때 써먹으면 유용하다.



그림 출처


728x90

RxJava: defer, fromCallable

모바일/안드로이드 2020. 2. 15. 16:16 Posted by 아는 개발자

1. defer


Observable 클래스내에 포함된 defer() 함수는 관찰하고 있는 대상의 값을 구독한 이후 시점부터 볼 때 사용한다. 즉 subscribe 함수가 불린 시점부터 대상의 값을 관찰한다. 좀 더 이해를 쉽게 하고자 Person이라는 클래스를 만들어봤다.

class Person {
    var name: String = "None"

    fun observableName(): Observable<String> 
            = Observable.just(name)

    fun observableDeferName(): Observable<String>
            = Observable.defer { Observable.just(name) }
}


Person 클래스에는 수정이 가능한 name 변수와 name을 Observable로 변환해주는 observableName() 함수, 그리고 코드는 거의 비슷한데 앞에 defer 함수가 붙어있는 observableDeferName() 함수가 있다. 이 두 함수의 차이를 알아보고자 onCreate 함수에서 Person 클래스에서 선언한 함수값을 이용해 다음과 같이 코드를 짜봤다. 두 함수를 통해 Observable 객체를 만든 후 person의 name 변수 값을 수정한 다음 Observable 객체에서 구독받은 값을 출력하는 간단한 예제다.

val person = Person()
val observableName = person.observableName()
val observableDeferName = person.observableDeferName()

person.name = "selfish developer"

observableName.subscribe {
    Log.d(TAG, "observableName: " + it)
}

observableDeferName.subscribe {
    Log.d(TAG, "observableDeferName: " + it)
}

실행 결과 출력되는 로그는 다음과 같다.



observableDeferName은 subscribe된 시점부터 값을 보기 때문에 수정한 person 객체의 name 값이 갱신되었고 observableName은 생성 시점의 값을 받아오기 때문에 초기 값으로 세팅된 값을 가져온다. 


그런데 아래 코드는 똑같은 값을 출력한다.

person.name = "selfish developer"
person.observableName().subscribe {
    Log.d(TAG, "person.observableName(): " + it)
}

person.observableDeferName().subscribe {
    Log.d(TAG, "person.observableDeferName(): " + it)
}


그 이유는 관찰하고 있는 두 객체의 생성 시점이 모두 name 변수값이 업데이트 된 이후기 때문이다. 관찰용 객체를 함수로 선언하지 않고 Person 클래스 내의 변수로 바꾸면 앞서 보인 예시와 동일하게 서로 다른 값을 출력하게 된다.

class Person {
    var name: String = "None"

    val observableName 
            = Observable.just(name)
    val observableDeferName 
            = Observable.defer { Observable.just(name) }
}

person.name = "selfish developer"

observableName.subscribe {
    Log.d(TAG, "observableName: " + it)
}

observableDeferName.subscribe {
    Log.d(TAG, "observableDeferName: " + it)
}


2. fromCallable


Observable 클래스의 형제격인 Maybe, Flowable, Single 클래스에서는 fromCallable 함수가 defer와 같은 역할을 한다. 테스트를 해보고자 Observable과 동일하게 코드를 짜봤다.

class Person {
var name: String = "None"

fun singleName(): Single<String>
        = Single.just(name)

fun singleCallableName(): Single<String>
        = Single.fromCallable { name }
}

val singleName = person.singleName()
val singleCallableName = person.singleCallableName()

person.name = "selfish developer"

singleName.subscribe { it ->
    Log.d(TAG, "singleName: " + it)
}

singleCallableName.subscribe { it ->
    Log.d(TAG, "singleCallableName: " + it)
}

실행 결과 defer와 동일하게 fromCallable이 붙은 함수는 구독한 시점 이후에 갱신된 값을 읽어온다.




3. 총평


실행 결과는 신기하기도 하지만 실제로 사용할때는 꽤 실수가 잦을 것 같은 기능인 것 같다. 가능하면 매번 새로운 Observable 객체를 생성하는 함수를 따로 변수로 만들어두지 않고 바로 구독하게 해 만들어서 갱신 타이밍 이슈를 피하는게 좋지 않을까 싶다.

728x90

RxJava: mapper function returned null 에러

모바일/삽질 기록 2020. 2. 14. 17:10 Posted by 아는 개발자

RxJava로 여러 객체의 변화를 보고 있다 보면 아래 파란 버그 처럼 The mapper function returned a null value 에러를 보게되는 경우가 종종 있다.



이 경우는 Observable 객체 내부의 map 함수에서 null을 리턴해주고 있기 때문에 발생한다. 앱이 죽는 크래쉬 에러까지는 아니지만 RxJava에서 null이 되는 경우에 대해 에러 로그를 출력한 만큼 map 함수에서 null이 발생할만한 경우를 사전에 막는 것이 좋다

728x90
TAG RxJava

코틀린 apply, also, let, run, with

모바일/안드로이드 2020. 2. 9. 14:21 Posted by 아는 개발자


자바에 비해 코틀린이 가지는 가장 큰 장점은 코드를 간결하게 작성 할 수 있는 것이라고 생각하는데 모든 객체에 기본적으로 제공하는 범위함수인 apply, also, let, run, with 들이 이 이점을 살리는데 큰 도움이 된다.


이 함수의 차이점에 대해서 설명한 글은 코틀린 공식 문서도 있고 다른 개발 블로그에도 무수히 많지만, 범위 함수에서 강조하는 수신객체와 람다식과 관련된 내용은 문서를 읽는 것 보다는 직접 코드를 짜면서 체험해 볼 때 이해하기가 쉽다. 이번 포스트에서는 apply, also, let, run, with를 언제 사용해야하는지에 대해서 수신객체에 관련된 내용을 제외하고 사용이 필요한 경우만 간략하게 소개해보려고 한다.


1. apply 


apply는 객체의 property 값을 적용할 때 사용한다. 어떤 객체를 선언할 때 생성자만으로 값을 세팅할 수 없다면 apply를 통해서 값을 따로 붙여서 연속적으로 값을 세팅할 수 있다. 아래의 두 코드는 모두 동일한 결과를 가지는데 apply 함수를 사용한 경우가 더 명시적이다.


val adam = Person("Adam").apply { 
    age = 20     
    city = "London"
}

val adam = Person("Adam")
adam.age = 20
adam.city = "London"


2. also 


also는 속성 변경을 허용하지 않고 로그를 출력하거나 값의 유효성을 검증하고 싶을 때 사용한다. 아래 코드처럼 also 문 앞에 있는 코드는 property를 바꿀 수 없다. 나도 모르게 저지르는 실수를 사전에 차단하고 싶을 때 사용하면 유용하다.


val numbers = mutableListOf("one", "two", "three")
numbers
    .also { println("The list elements before adding new one: $it") }
    .add("four")


3. let 


객체가 null이 아닌 코드를 실행하는 경우 사용한다. Null pointer 에러로 크래쉬가 나는걸 막을 때 꽤나 유용한 범위함수다. 주로 많이 사용하는 코드다. Java의 Optional.ofNullable 이렇게 길게 쓸 필요가 없어 간결해서 좋다.


val str: String? = "Hello"   
//processNonNullString(str)       // compilation error: str can be null
val length = str?.let { 
    println("let() called on $it")        
    processNonNullString(it)      // OK: 'it' is not null inside '?.let { }'
    it.length
}


4. run 


객체에 포함된 함수를 실행하고 그 결과를 반환할 때 사용한다. 아래 코드를 보면 리스트 타입인 numbers에 포함된 add 함수를 run 내부에서 실행하고 있고 마지막 구문에 e로 끝나는 문자열의 개수를 countEndsWithE 변수에 넣어주고 있다. 실행 결과는 주석으로 처리된 부분을 읽어보면 된다.


나는 이 함수는 자주 사용하지는 않는다. 굳이 함수를 먼저 실행한 다음에 리턴타입이 필요할 일도 없어서. apply랑 비슷한것 같기는 한데 그만한 유용성은 못찾겠다.


val numbers = mutableListOf("one", "two", "three")
val countEndsWithE = numbers.run { 
    add("four")
    add("five")
    count { it.endsWith("e") }
}
println("numbers: " + numbers)
println("There are $countEndsWithE elements that end with e.")
// numbers: [one, two, three, four, five]
// There are 3 elements that end with e.


5. with 


null이 될 수 없는 객체의 값을 출력할 때 사용한다. also랑 거의 차이가 없어서 나는 잘 사용하지 않는다. 


val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    println("'with' is called with argument $this")
    println("It contains $size elements")
}


728x90

브런치나 틱톡처럼 수직으로 스와이프 해서 화면을 넘길 수 있는 ViewPager를 만들려고 인터넷에 검색해보면 ViewPager 클래스에서 상속받는 일부 함수들을 변경하는 답변이 많다. 그런데 대부분의 답변으로 시도해본 결과 아래 이미지처럼 스무스하게 스와이프가 되지 않는 문제가 있었다



0. 기존 ViewPager 클래스의 문제점


원인은 화면을 넘기는 부분을 담당하는 코드가 안드로이드 내부 라이브러리인 ViewPager 클래스 내에 있고 이쪽 코드는 수직으로 넘기는 걸 고려하지 않게 구현되어 있기 때문이다. ViewPager에서 일부 변수들을 오버라이드 할 수 있도록 하지 않았을 까 하는 일말의 기대가 있었지만 스와이프시 넘기는 부분의 threshold 값의 역할을 하는 변수는 오버라이드 할 수 없는 private 으로 구현이되어 있었다. 몇몇 답변에서는 super class의 변수를 바꾸는 방법을 제안했는데 이 방식은 구조적으로 좋은 방법도 아니거니와 최신 디바이스에선 통하지도 않는다.


1. ViewPager2 클래스


구글에서는 ViewPager 코드를 바꾸는게 귀찮았는지 ViewPager2라는 새로운 라이브러리를 내놓았다. ViewPager2는 기존 ViewPager 클래스가 가지고 있던 버그들을 잡고 신규 기능을 추가한 새로운 클래스인데 주요 기능으로 세로모드 지원이 있었다. 코드로 적용을 해보니 아래 그림처럼 간단한 스와이프로도 화면이 슉슉 넘어갈 수 있게 됐다. 



2. 구현방법


2.1 ViewPager2 라이브러리 다운 받기 

implementation 'androidx.viewpager2:viewpager2:1.0.0'

build.gradle에서 소스를 추가한다. 예전에는 alpha 태그를 붙였는데 지금은 정식 버전인 1.0.0이 나왔다.


2.2 ViewPager2용 adapter 구현 

public class ViewPager2Adapter extends FragmentStateAdapter {
    private List<string> stringList = new ArrayList<>();

    public ViewPager2Adapter(@NonNull FragmentActivity fragmentActivity, List<string> stringList) {
        super(fragmentActivity);
        this.stringList = stringList;
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        return MyFragment.newInstance(stringList.get(position));
    }

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

기존 ViewPager Adapter와 차이가 있는 부분은 상속 클래스인 FragmentStateAdapter 일 것이다. ViewPager2를 만들면서 구글에서 추가한 새로운 어댑터인데 정확히 어떤 차이점이 있는지는 아직 모르겠다.


2.3 XML에서 ViewPager2 소스 추가하기 

<androidx.viewpager2.widget.viewpager2 
android:id="@+id/viewPager" 
android:background="#000000" 
android:layout_width="match_parent" 
android:layout_height="match_parent"
</androidx.viewpager2.widget.viewpager2>

원래 ViewPager가 있던 자리에 ViewPager2로 바꿔준다.


2.4 Adapter 초기화 하고 Viewer에 붙이기

viewPager2 = findViewById(R.id.viewPager);
ViewPager2Adapter adapter = new ViewPager2Adapter(this, stringList);
viewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
viewPager2.setAdapter(adapter);

만들어둔 어댑터를 ViewPager2 Viewer에 붙여준다. 여기선 수직으로 스와이프 하려고 setOrientation 함수에 수직 상수값을 넣어줬다. 수평으로 스와이프 하고 싶으면 값을 변경하면 된다.


3. 소스코드


https://github.com/kwony/ViewPager2-Vertically


728x90

'모바일 > 안드로이드' 카테고리의 다른 글

RxJava: defer, fromCallable  (0) 2020.02.15
코틀린 apply, also, let, run, with  (0) 2020.02.09
수직으로 스와이프 가능한 ViewPager 만들기  (0) 2019.12.07
RxJava - flatMap  (0) 2019.11.30
RxJava - Create 함수  (0) 2019.08.11
RxJava - Observable, Observer  (0) 2019.08.10

RxJava - flatMap

모바일/안드로이드 2019. 11. 30. 15:23 Posted by 아는 개발자


RxJava에는 유용한 함수들이 많지만 그중에서 가장 많이쓰고 쓸모있는 것을 고르라면 나는 flatMap을 고르고 싶다. 시중의 RxJava/RxAndroid 강의에서도 다른 함수와 달리 flatMap을 소개 할 때는 분량을 길게해서 소개하는데 이해하기는 어렵지만 그만큼 쓰임새가 많아서 그런 것 같기도 하다.


1. flatMap




flatMap은 Observable 작업을 여러번 연계해서 사용할 때 사용하는 API다. 그러나 이렇게 추상적인 말로 설명하면 쉽게 와닿지가 않는다. 구체적인 예시를 통해서 살펴보도록 하자.


2. flatMap 예시


네이버 뉴스 페이지에서 특정 기사를 클릭하면 안드로이드 앱에선 기사의 텍스트, 사진 또는 동영상, 좋아요 개수, 댓글 정보 그리고 기사의 광고를 서버로부터 읽어올 것이다. 이때 이 정보들이 하나의 api에서 묶어져 있지 않고 텍스트 api 따로, 사진 동영상 api 따로, 좋아요와 댓글 정보 따로 있다면 각각 api로 호출해서 불러야 할 것이다.


각자 api를 따로 부른 다음 페이지에 표시한다면 굳이 flatMap을 쓸 필요는 없을 것이다. 그런데 기사를 불러오는데 실패한 경우에는 기사의 광고를 보여주지 않는다는 요구사항이 추가 됐거나 또는 클라이언트에서 서버에 한번에 여러 요청을 보내는 것이 부담을 주는 문제가 발생했다고 생각해보자. 이런 경우에는 서버로부터 기사 광고를 불러오는 작업이 기사를 불러온 작업 이후에 시행돼야 할 것이다. 


안드로이드 라이브러리를 이용해서 위 요구사항을 해결한다면 방법은 있긴 할텐데 코드양도 길어지고 신경써야 할 부분도 많아 귀찮을 것이다. 그런데 RxJava를 이용하면 한 줄만 추가하면 된다.


3. flatMap 코드 샘플

repository.getArticleContents("newsid")
    .doOnSuccess { it ->  }
    .flatMap { repository.getArticleAds("newsid", "ownerid")}
    .doOnSuccess { it -> }

위 코드는 getArticleContent 함수로 기사 내용을 읽은 다음에 다시 flatMap 함수에서 getArticleAds 함수에서 기사의 광고 내용을 호출한 코드다. 기사 광고를 flatMap을 통해 호출하게 되면 자동으로 기사 내용을 잘 읽어온 경우에만 호출하게 된다. 이처럼 RxJava를 적재적소에 이용하면 코드의 양이 줄어들고 직관적으로 코드를 짤 수 있게 된다


728x90

'모바일 > 안드로이드' 카테고리의 다른 글

코틀린 apply, also, let, run, with  (0) 2020.02.09
수직으로 스와이프 가능한 ViewPager 만들기  (0) 2019.12.07
RxJava - flatMap  (0) 2019.11.30
RxJava - Create 함수  (0) 2019.08.11
RxJava - Observable, Observer  (0) 2019.08.10
안드로이드 Loader  (0) 2019.07.15

RxJava - Create 함수

모바일/안드로이드 2019. 8. 11. 13:36 Posted by 아는 개발자

앞서 작성한 포스트에선 Observable의 역할이 어떤 데이터를 Observer가 처리할 수 있도록 포장해주는 역할 이라고 설명했다. Observable은 데이터를 관찰 할 수 있는 형태로 만들 기 위해 여러 가지 오퍼레이터 함수를 가지고 있다. 이번 포스트에선 이중에서 대표적으로 사용되는 것들만 소개해보려고 한다.


1. create


백그라운드 스레드에서 옵저버가 처리할 넘겨주는 방법. 아래 코드를 보면 create 함수의 인자로 익명 ObservableOnSubscribe 클래스를 선언하고 이 안의 오버라이드 함수 인자인 emitter 변수에 onNext로 0~9까지 값을 넣어 호출 하는 것을 볼 수 있다.


Observable<Integer> observable = Observable
        .create(new ObservableOnSubscribe<Integer>() {
            @Override
            public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
                Log.d(TAG, "subscribe: " + Thread.currentThread().getName());

                for (int i = 0; i < 10; i++) {
                    if (!emitter.isDisposed())
                        emitter.onNext(i);
                }

                if (!emitter.isDisposed())
                    emitter.onComplete();
            }
        })
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());

observable.subscribe(new Observer<Integer>() {
    @Override
    public void onNext(Integer integer) {
        Log.d(TAG, "onNext: " + integer);
    }
});


실행결과 아래와 같은 로그가 출력된다. 0~9까지 값이 옵저버에게 전달 됐다. 추가적으로 subscribe에서 돌고 있는 쓰레드 이름을 출력해보니 RxCachedThreadScheduler-1이란 것이 출력됐다. 이는 subscribeOn 함수에 백그라운드 함수중 하나인 Schedulers.io()를 넣었기 때문이다. 이 함수를 사용하면 Observable한 데이터를 만드는 작업을 백그라운드에서 실행 할 수 있게 된다.



2. just 


최대 10개까지의 배열 데이터를 Observable하게 만들 수 있는 함수. 그런데 10개로 제한돼서 배열을 전달하는 경우는 없고 단일 데이터를 전달 할 때 주로 사용된다.


Observable<Integer> observable = Observable
        .just(1,2,3)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());

observable.subscribe(new Observer<Integer>() {
    @Override
    public void onNext(Integer integer) {
        Log.d(TAG, "onNext: " + integer);
    }
});


이 함수를 실행해보면 다음과 같은 로그가 나온다.



3. range 


범위를 지정해주는 방법. for문을 생성 함수의 하나로 뒀다고 생각하면 쉽다. 단 range 함수를 사용하면 map같은 형변환 오퍼레이터를 사용하지 않으면 옵저버에선 Integer의 형태로 값을 받게 된다.


Observable<Integer> observable = Observable
        .range(0, 5)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());

observable.subscribe(new Observer<Integer>() {
    @Override
    public void onNext(Integer integer) {
        Log.d(TAG, "onNext: " + integer);
    }
});


4. fromIterable 


이미 생성된 배열의 값을 Observable하게 바꿔주는 함수. 앞서 설명한 just는 최대 10개까지 밖에 담지 못한 반면 fromIterable은 개수에 상관 없이 모두 Observable하게 바꿔준다.


List<Integer> integers = new ArrayList<>();
integers.add(1);
integers.add(2);
integers.add(3);

Observable<Integer> observable = Observable
        .fromIterable(integers)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());

observable.subscribe(new Observer<Integer>() {
    @Override
    public void onNext(Integer integer) {
        Log.d(TAG, "onNext: " + integer);
    }
});


728x90

'모바일 > 안드로이드' 카테고리의 다른 글

수직으로 스와이프 가능한 ViewPager 만들기  (0) 2019.12.07
RxJava - flatMap  (0) 2019.11.30
RxJava - Create 함수  (0) 2019.08.11
RxJava - Observable, Observer  (0) 2019.08.10
안드로이드 Loader  (0) 2019.07.15
onSaveInstanceState  (0) 2019.07.15

RxJava - Observable, Observer

모바일/안드로이드 2019. 8. 10. 14:52 Posted by 아는 개발자


좀더 리액티브한(reactive) UI를 만들기 위해서 프로젝트에 RxJava, RxAndroid를 도입하면서 가장 어려웠던 점은 Observable과 Observer 클래스를 개념적으로 이해하는 것이었다. 옵저버 패턴을 응용한 라이브러리이기 때문에 익숙할 것이라고 여러 문서에서 설명하고 있는데 애초 옵저버 패턴을 많이 써보지 않았고 또 디자인 패턴 책을 여러 번 보고 난 뒤에 문서를 읽어봐도 쉽사리 와닿지 않았다.


여러 번의 블로그 방문과 삽질을 반복한 후 다행히 유튜브 강좌를 통해서 어느정도 개념을 잡을 수 있었다. 혹시나 나처럼 어려움을 겪고 계신 분은 Coding with Mitch 라는 유튜브 채널 강좌를 들어보시면 도움이 될 것 같다. 모두 듣고 소화하는데 하루 정도 소요되는데 개인적으로 충분히 들어볼만한 강좌였다.


서론이 너무 길었다. 아무튼 이번 포스트에선 앞서 말한 Observable과 Observer 클래스를 개념적으로 정리해보려고 한다. 개인 공부를 위해 정리한 글인 만큼 다른 블로그이나 문서에서 설명한 것과 차이가 있을 수 있을 것 같은데 혹시나 이 글을 읽으신 분들은 이점 참고하셨으면 좋겠다.


1. Observable


이 클래스에 포함된 함수는 매우 다양하나 어떤 데이터를 관찰 할 수 있는 형태로 바꾸는 것이 이 클래스의 기본적인 임무다. Observable의 의미인 '관찰 할 수 있는'을 생각해보면 이 클래스가 어떤 일을 해야하는지 짐작 할 수 있을 것이다. 이 클래스는 개념적으로는 어떤 데이터를 Observer가 처리할 수 있도록 포장하는 작업을 담당한다고 보면 이해하기엔 편하다. 추가적인 기능으로는 값을 변형시키고 다른 타입으로 바꾸기도 하고... 등등 많은데 일단 이 정도만 이해하도록 하자.


2. Observer


스타크래프트의 정찰용 옵저버 유닛을 생각하면 이해하기 어려워진다. 게임 유닛은 잊고 순수 의미인 '관찰자'의 의미에서 보면 Observer는 Observable에서 관찰 할 수 있는 형태로 전달한 데이터를 받고 이에 대한 행동을 취한다. 전달 받은 데이터를 가지고 화면 UI를 업데이트 하든지 아니면 어떤 인자를 서버에 요청해보는지 등등.. 최종적으로 처리할 작업은 이 클래스에서 담당한다


3. Observable & Observer Diagram


출처: 깃헙


위 그림을 보면 ObservableonNext, onComplete, onError 같은 함수를 통해 Observer에게 무언가를 전달하고 있는 것을 볼 수 있다. onNext는 데이터를 포장할 때마다  Observer에게 완료된 작업물을 전달하는 것이고 onComplete는 포장 작업이 끝날 때 호출하는 함수다. onError는 Observable 내에서 어떤 문제가 생겼을 때 호출되는 함수다.


4. 코드 


아래 코드를 보면 Observable 이라는 변수는 String형태의 Observable 클래스 타입이다. 앞서 설명한 내용 대로라면 Observable은 String 형태의 데이터 타입을 Observable 하게 만들어야 한다. 이를 위해 just라는 함수를 통해 관찰할 수 있는 형태로 포장할 값으로 "selfish"와 "developer"를 넣었다. subscribeOn과 observeOn 함수는 어떤 쓰레드에서 작업을 실행할 지 정하는 함수인데 이번 포스트와는 관련이 없으니 일단 무시하자.


선언 후 subscribe 함수 내에 익명의 Observer 객체를 선언했다. 이는 만들어둔 Observable과 Observer를 매핑하는 함수다. Observer 객체를 보면 onNext, onComplete 처럼 앞의 그림에서 설명한 함수들이 등장한다. Observable에서 전달 받은 작업을 처리하기 위한 함수들이다.


Observable<String> observable = Observable
        .just("selfish", "developer")
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());

observable.subscribe(new Observer<String>() {
    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(String s) {
        Log.d(TAG, "onNext: " + s);

    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onComplete() {
        Log.d(TAG, "onComplete: ");
    }
});


위 코드를 안드로이드 에뮬레이터로 실행해보면 다음과 같은 로그가 나온다.




728x90

'모바일 > 안드로이드' 카테고리의 다른 글

RxJava - flatMap  (0) 2019.11.30
RxJava - Create 함수  (0) 2019.08.11
RxJava - Observable, Observer  (0) 2019.08.10
안드로이드 Loader  (0) 2019.07.15
onSaveInstanceState  (0) 2019.07.15
JAVA의 static  (0) 2019.04.03

안드로이드 Loader

모바일/안드로이드 2019. 7. 15. 21:20 Posted by 아는 개발자

앞선 포스트에서 onSaveInstanceState 콜백을 통해 화면을 전환하는 경우에도 데이터를 저장할 수 있는 방법을 배웠다. 그런데 AsyncTask 처럼 진행중인 작업에 대해서는 데이터를 저장할 수 있는 기능이 무의미할 것이다. 어디까지 데이터 작업을 처리 했으니 이때부터 다시 시작하라고 세세하게 할 수도 없는 노릇이고.


그래서 안드로이드에서는 Loader라는 라이브러리를 뒀다. 공식 문서에서는 FragmentActivity에 넣을 디스플레이 소스를 로드할 수 있는 기능으로 소개되고 있는데 일단은 별도의 쓰레드에서 돌아 Activity 생성 주기에 영향을 받지 않는 컴포넌트 정도로 이해하면 될 것 같다. 사용법은 아래와 같다.


1. implements LoaderManager.LoaderCallbacks<String>


MainActivity는 Loader API를 사용하는 Activity임을 명시 해둬서 Loader 콜백 함수들을 호출하도록 만든다.

public class MainActivity extends AppCompatActivity implements
        LoaderManager.LoaderCallbacks<string> { {

2. public Loader<String> onCreateLoader 


Activity에서 사용할 Loader 객체를 생성하는 콜백 함수를 구현한다. Loader는 AsyncTaskLoader와 CursorLoader가 있는데 CursorLoader 의 경우에는 DB에서 값을 읽어올 때 사용하고 AsyncTaskLoader 는 좀더 범용적으로 사용된다. 


2.1 protected void onStartLoading()


AsyncTaskLoader 가 생성 되면서 가장 먼저 실행되는 함수다. AsyncTask의 onPreExecute() 의 역할을 하는 것과 비슷하다. 백그라운드 작업 실행 하기 전에 필요한 셋팅 작업을 여기에 넣는다.


2.2 public String loadInBackground()


백그라운드 작업을 실행하는 함수다. 이름을 보면 감이 오겠지만  AsyncTask의 doInBackground(Void... voids)  와 동일한 기능을 하는 함수다. 반환 타입으로 세팅된 String은 결과 값의 타입이며 앞서 콜백을 implements 할 때 어떤 타입을 넣느냐에 따라 바꿀 수 있다.


2.3 public deliverResult(String result)


결과 값을 전달하는 함수다. 이 함수내에는 반드시 super.deliverResult(result);  가 포함되어 있어야지 결과 값이 최종적으로 전달 된다. 인자인 result 는 loadInBackground()에서 반환한 값이다


3. public void onLoadFinished(Loader<String> loader, String data)


AsyncTaskLoader 작업이 끝난 후에 불리는 함수이며 함수의 인자로 결과 값을 전달 받는다.  결과값을 화면에 업데이트 할 때 이 콜백 함수 내에 작업을 넣는다.


4. initLoader,restartLoader


생성한 Loader가 실행 될 수 있도록 호출한다. 아래 코드는 initLoader,restartLoader 함수를 실행한 예제 코드다.


LoaderManager loaderManager = getSupportLoaderManager();
Loader<string> searchLoader = loaderManager.getLoader(SEARCH_LOADER);
if (searchLoader == null) {
    loaderManager.initLoader(SEARCH_LOADER, queryBundle, this);
} else {
    loaderManager.restartLoader(SEARCH_LOADER, queryBundle, this);
}

함수의 첫번째 인자 값은 ID다. Loader마다 가지고 있는 고유한 Key값에 해당한다. 두번째 인자 값은 Bundle형태의 데이터 값이다. AsyncTaskLoader에게 이 데이터 값을 통해 값을 전달 할 수 있다. 세번째는 콜백함수다. 현재는 Activity가 Loader 콜백 함수를 구현해뒀기 때문에 this로 입력했다.

728x90

'모바일 > 안드로이드' 카테고리의 다른 글

RxJava - Create 함수  (0) 2019.08.11
RxJava - Observable, Observer  (0) 2019.08.10
안드로이드 Loader  (0) 2019.07.15
onSaveInstanceState  (0) 2019.07.15
JAVA의 static  (0) 2019.04.03
안드로이드 Service  (0) 2019.03.19

onSaveInstanceState

모바일/안드로이드 2019. 7. 15. 20:39 Posted by 아는 개발자


onCreate() onDestroy() 는  애플리케이션이 생성될 때와 종료 될 때 한 번씩만 불리는 콜백 함수로 알려져 있지만 디바이스의 설정 값이 갑자기 바뀌어 화면을 처음부터 새로 그려줘야 할 때도 불린다. 대표적으로 스마트폰을 회전 시키는 경우(rotate)가 이에 해당한다. 



확인해보기 위해 테스트 애플리케이션의 콜백 함수들에 로그를 넣고 에뮬레이터(오레오 8.1) 에 설치한 후 오른쪽으로 회전 해봤다. onPause() 함수부터 불리는 부분이 회전 후에 나온 로그며 이중에는 onCreate() onDestroy()도 포함되어 있다.


onCreate() onDestroy() 함수가 다시 불린다는 뜻은 회전하기 전까지 설정해둔 변수 값들이 모두 초기화 된다는 것을 의미하기도 한다. 만약 애플리케이션 화면에 특정 값을 바꾼 상태로 회전을 했다면 처음 애플리케이션을 실행한 상태로 화면이 바뀌기 때문에 지금까지 작업한 것들이 모두 날라가게 된다.


안드로이드에선 이런 상황을 대처하기 위해 onSaveInstanceState(Bundle outState) 라는 콜백 함수를 뒀다. 이 함수는 모든 인자들이 초기화 되는 onDestroy() 함수 호출 전에 실행되며 함수의 인자에 key-value로 여러가지 데이터를 넣을 수 있다. 변경된 인자는 최종적으로 onCreate(Bundle savedInstanceState) 함수의 인자 값으로 전달 된다.


구구절절히 코드로 보는 것이 더 이해하기 쉬울 것 같다. 먼저 onSaveInstanceState 함수내에 아래와 같이 임의의 문자열 데이터 값을 입력했다.

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    logAndAppend("onSaveInstanceState");

    outState.putString(SAVE_INSTANCE_KEY,
            "onSaveInstanceState is called!\n");
}

그리고  onCreate 함수에선 인자에 key 값이 저장되어 있는지 확인 한 후 있으면 그 값을 TextView에 표시하도록 했다. 

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

    mLifecycleDisplay = (TextView) findViewById(R.id.tv_lifecycle_events_display);

    if (savedInstanceState != null
            && savedInstanceState.containsKey(SAVE_INSTANCE_KEY)) {
        String savedString = savedInstanceState.getString(SAVE_INSTANCE_KEY);
        mLifecycleDisplay.setText(savedString);
    }
    logAndAppend(ON_CREATE);
}

그 결과 아래와 같이 회전 후 TextView에 로그 메시지가 추가되는 것을 확인 할 수 있었다



728x90

'모바일 > 안드로이드' 카테고리의 다른 글

RxJava - Observable, Observer  (0) 2019.08.10
안드로이드 Loader  (0) 2019.07.15
onSaveInstanceState  (0) 2019.07.15
JAVA의 static  (0) 2019.04.03
안드로이드 Service  (0) 2019.03.19
AsyncTask  (0) 2019.03.13

JAVA의 static

모바일/안드로이드 2019. 4. 3. 21:50 Posted by 아는 개발자

0. JAVA Static

절차 지향인 C언어에서 static 변수는 함수 또는 변수를 작성 중인 파일 내에서만 사용하고 싶을 때만 사용하기 때문에 사용법이 비교적 간단했는데 JAVA에서는 객체지향 개념과 연관되어 있어 사용 시 다소 주의가 필요하다. 이번 포스트에서는 JAVA 언어에서 static의 쓰임새에 대해서 정리를 해본다.


1. Static/Non-static 멤버

자바에서는 클래스 내의 함수 또는 변수를 멤버(Member)라고 통칭해서 부르며 앞에 static 변수를 붙이면 static 멤버, 붙이지 않으면 Non-static 멤버로 분류한다.

public class MainActivity extends AppCompatActivity {
  static int staticVar = 0;       // static Member
  static void staticMethod() {}   // static Member

  int nonStaticVar = 0;           // Non-static Member
  void nonStaticMethod() {}       // Non-static Member
}

Static 멤버는 별도의 객체 생성과정 없이 바로 사용할 수 있고 클래스당 하나씩만 존재해 동일한 클래스로 생성된 복수의 객체가 동일한 변수의 값을 공유하게 된다. C/C++의 전역 변수와 함수와 원리가 비슷한데 이와 같은 특징은 Static 멤버들이 객체를 생성하기 전에 이미 메모리에 로딩되며 객체가 제거와 상관없이 프로그램이 종료될 때까지 메모리에 남아있는 구조적인 특징 때문에 발생한다.

Non-Static 멤버는 Static 멤버와 원리가 정반대다. 반드시 객체를 생성한 후에 사용할 수 있으며 생성된 객체별로 멤버의 값이 다를 수 있다. 이는 멤버 인스턴스들이 객체의 생명주기에 따라서 메모리에 생성되고 제거되는 원리 때문이다. Non-Static 멤버들은 객체지향적인 관점에 근거해서 동작하는 인스턴스다.


2. 사용 예시

2.1 Singleton Pattern

프로그램에서 클래스당 하나의 객체만 사용하도록 유도하는 Singleton Pattern은 static을 적절히 사용한 대표적인 예다. getInstance 함수를 별도의 객체 생서 없이 호출할 수 있도록 static 멤버로 선언했으며 클래스 변수인 instance도 static으로 선언해 여러개의 객체가 생성되더라도 동일한 객체만 리턴할 수 있도록 했다.

public class Singleton {
  private static Singleton instance;
  private LazySingleton() {}
  public static synchronized Singleton getInstance() {
      if(instance == null) {
          instance = new Singleton();
      }
      return instance;
  }
}

2.2 Math

private int getAbs(int a) {
	return Math.abs(a);
}

별다른 객체 생성 없이 바로 실행하는 Math 클래스의 함수들은 모두 static 멤버들로 선언된 것들이다. JAVA는 객체지향 관점에 따라서 모든 함수들은 반드시 객체를 통해서 호출되기 때문에 위와 같이 static 함수를 적극적으로 활용해 라이브러리를 호출할 수 있도록 했다.


3. 주의사항

3.1 Out of Memory

객체가 제거되도 static 멤버는 메모리에 남아있기 때문에 코드 상에서 별도로 처리해두지 않으면 모든 메모리 공간을 static 인스턴스가 차지하는 메모리 Leak이 발생할 수 있다.

public class OomClass {
  static Vector oomVector;
  static void addOomVector(int a) {
      oomVector.add(a);
  }
  public OomClass() {
      for (int i = 0; i < 100000; i++)
          addOomVector(i);
  }
}

위의 OomClass는 생성 할 때마다 static 변수는 oomVector 벡터 클래스에 0~99999까지 엔트리를 추가하는데 OomClass 한 두 개 정도 생성할 때는 별 문제가 되지 않지만 그 수가 많아지면 메모리에서 차지하는 비율이 높아져 OOM이 발생할 수 있다.

3.2 Static 함수의 제약사항

Static 함수 내에서는 Static 멤버만 사용할 수 있고 this 호출이 불가능하다. 이런 제약사항은 역으로 Non-Static 멤버도 접근 할 수 있고 this를 호출 할 경우 어떤 문제점이 발생할지 예측해보면 이해하기 쉽다. Non-Static 멤버들은 객체가 생성되어야 메모리에 한자리 차지하게 되는데 객체 생성 없이 바로 호출되는 static 함수에서 생성 됐는지 확인되지 않는 멤버 변수들에게 접근한다면 이는 invalid access가 된다. 구조적인 특징에 대해서 다시 한번 생각해보게 된다.



728x90

'모바일 > 안드로이드' 카테고리의 다른 글

안드로이드 Loader  (0) 2019.07.15
onSaveInstanceState  (0) 2019.07.15
JAVA의 static  (0) 2019.04.03
안드로이드 Service  (0) 2019.03.19
AsyncTask  (0) 2019.03.13
ViewPager와 PageAdapter  (0) 2019.03.05

안드로이드 Service

모바일/안드로이드 2019. 3. 19. 21:42 Posted by 아는 개발자

0. 소개


Service 클래스는 안드로이드 앱에서 백그라운드 작업을 수행할 때 대표적으로 사용하는 라이브러리다. 앞서 설명한 AsyncTask와는 사용법이 완전히 다른데 2~3초 이내로 짧게 끝내야하는 AsyncTask와는 달리 Service는 오래도록 실행 할 수 있으며 AsyncTask처럼 UI 쓰레드와 Interaction 할 수 있는 기능은 없다. 그래서 화면과 무관한 작업인 네트워크 트랜잭션 처리나 음악을 재생할 때 사용된다.


1. 생성주기


액티비티와 독립된 생성주기를 가지고 있으며 별도의 컴포넌트로 동작하기 때문에 애플리케이션을 사용하지 않은 상태(UI가 화면에 있지 않은 상태) 여도 백그라운드에서 실행 될 수 있다. 폰 게임을 하는 중에도 카카오톡 메시지가 오고 쿠팡에서 쇼핑을 하는 도중에도 멜롬의 음악을 들을 수 있는 이유가 바로 이런 서비스 클래스의 특징 덕분이다. 경우에 따라선 애플리케이션 내에서 별도의 프로세스로 분리할 수도 있다.


2. 콜백함수 


Service 클래스의 콜백 함수를 통해 간단히 사용 방법을 익혀보자.


public void onCreate()


서비스가 생성 될 때 가장 먼저 실행되는 함수. 생성시 딱 한번만 호출되며 서비스 동작에 필요한 인스턴스를 선언하는 작업으로 주로 사용 한다.


public void onDestroy()


서비스가 종료될 때 호출되는 함수. onCreate에서 수행한 작업과 반대로 서비스가 종료하면서 인스턴스에 정리할 작업을 처리하는 일을 한다.


public int onStartCommand(Intent intent, int flags, int startId)


사용자의 요청을 처리하는 함수. 서비스를 처음 생성할 경우에는 onCreate 함수 다음에 불리나 생성 이후에는 이 함수로 바로 불린다. 전달 인자인 Intent에 필요한 작업을 전달할 수 있다.


3. 주의사항


단 주의할 것이 무한루프처럼 오래 걸리는 작업을 onStartCommand에 넣으면 ANR(Application Not Responding)에러가 발생하게 된다. 오래 실행하는 작업을 수행할 때 사용되는 라이브러린데 정작 사용하려니 에러가 발생하는게 생뚱맞게 들릴 수 있는데 이는 서비스 클래스 컴포넌트의 구조적인 이유 때문이다. onStartCommand함수는 서비스 작업 뿐만 아니라 사용자 interaction도 처리하는 메인쓰레드에서 담당하는데 이 함수가 빨리 종료되지 않는다면 사용자는 UI 작업을 처리하지 못하게 된다.


이러한 이유 때문에 오래 걸리는 작업은 onStartCommand에서 처리하되, 아래의 안드로이드 소스처럼 별도의 쓰레드를 생성해서 작업을 처리하도록 한다.


 
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    new Thread() {
        @Override
        public void run() {
            while (true) {
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }.start();

    return START_STICKY;
}


4. IntentService 


매번 쓰레드를 만들어서 작업을 처리한다면 여러 가지 작업을 동시에 처리할 수 있다는 이점도 있지만 개수가 많아짐에 따라 시스템 자원을 소모하게 되고 경우에 따라선 처리 시간은 중요하지 않고 작업이 순차적으로 처리하는 일이 필요한 경우 불필요한 오버헤드를 발생시키기도 한다. 차라리 이런 경우에는 여러 개의 쓰레드를 만들지 말고 하나의 쓰레드에서 작업을 하나씩 전달해주는 방식이 좋을 것 같기도 하다. 안드로이드에서는 이미 이런 경우를 고려해 IntentService 라는 클래스를 만들어 뒀다.


IntentService를 사용하면 메인 thread와 따로 도는 workqueue쓰레드가 생성되며 액티비티로부터 전달 받은 Intent를 순차적으로 아래의 onHandleIntent 함수에서 처리한다. 


protected void onHandleIntent(Intent intent)


onStartCommand 함수에서는 긴 작업을 별도의 쓰레드에서 처리했어야 했다면 IntentService의 onHandleIntent 함수안에서는 바로 길게 실행되는 작업을 둬도 상관 없다. 안드로이드가 알아서 작업을 처리해준다.

728x90

'모바일 > 안드로이드' 카테고리의 다른 글

onSaveInstanceState  (0) 2019.07.15
JAVA의 static  (0) 2019.04.03
안드로이드 Service  (0) 2019.03.19
AsyncTask  (0) 2019.03.13
ViewPager와 PageAdapter  (0) 2019.03.05
px, dp, sp 개념 정리  (0) 2019.02.16

AsyncTask

모바일/안드로이드 2019. 3. 13. 23:57 Posted by 아는 개발자

0. 소개


AsyncTask는 파일 다운로드 완료후 사용자에게 완료됐다는 Toast 메시지를 보내는 유스케이스처럼 특정 백그라운드 작업이 종료되고 사용자에게 화면으로 알림을 전달해야하는 경우 유용한 클래스다. 코드가 직관적이고 응용하기 쉬워 자주 사용되는 라이브러리지만 몇초 이내에 종료될 수 있는 작업인 경우에만 사용이 가능하며 그것보다 긴 작업은 다른 클래스를 사용해야 한다.


안드로이드 공식문서의 예제 코드를 통해 사용 방법을 익혀보자.


private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     protected void onPreExecute(Integer... progress) {
         /* do nothing */
     }
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) break;
         }
         return totalSize;
     }
     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }
     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }



1. 상속 함수


클래스의 작업은 진행 순으로 크게 네가지로 구분된다.


protected void onPreExecute


이름에서 직감할 수 있듯이 AsyncTask 실행시 가장 먼저 불리는 함수다. 주로 여기에 백그라운드를 시작하기 전에 초기화해야하는 변수나 객체를 선언하는 용도로 사용한다.


protected Long doInBackground


백그라운드 작업이 실행되는 함수다. 예제 코드에서 확인 할 수 있듯이 여러 개의 인자를 전달받을 수 있으며 작업이 종료되면 특정 자료형의 결과 값을 리턴한다. 파라미터와 결과의 타입을 설정하는 방법은 다음 챕터에서 설명할 예정이다.


protected void onPostExecute

 

doInBackground  함수가 종료된 후 실행되는 함수다. doInBackground 함수의 결과값을 파라미터로 받으며 주로 예제 코드처럼 실행 완료메시지를 사용자에게 알림하는데 사용한다.


protected void onProgressUpdate


doInBackground 내에서 publishProgress 함수를 호출 할 때 불리는 함수이며 주로 작업의 진행 상황을 표시하는데 사용된다. 예제 코드에서는 다운로드 완료를 퍼센테이지로 전달했다.  



2. AsyncTask 인자


예제 코드의 DownloadFilesTask 클래스는 AsyncTask<URL, Integer, Long>을 상속하는데 여기서 부모클래스의 template 인자의 타입은 순서에 따라서 앞서 설명한 네가지 작업의 파라미터의 타입으로 매핑된다. 


첫번째 인자 (URL)


백그라운드로 실행하는 작업인 doInBackground 함수의 파라미터 타입이다. 주로 처리해야할 작업의 데이터를 전달하는 용도로 사용한다. 예제 코드에서는 URL 타입으로 설정해서 다운로드 받을 파일의 주소를 전달하는 용도로 사용하고 있다.


두번째 인자 (Integer)


진행 상황을 업데이트하는 onProgressUpdate 함수의 파라미터 타입이다. 예제 코드의 doInBackground 함수에서는 다운로드 진행 상황을 publishProgress 함수로 퍼센트로 전달하고 있으며 onProgressUpdate 함수는 Integer 형태로 받아서 화면에 표시하고 있다.


세번째 인자 (Long)


백그라운드 작업 종료후 실행하는 onPostExecute 함수의 파라미터 타입이다. doInBackground 함수의 리턴값이 onPostExecute 함수의 인자 값으로 전달되며 주로 작업의 정상종료 유무를 전달하기 위해 사용한다.



728x90

'모바일 > 안드로이드' 카테고리의 다른 글

JAVA의 static  (0) 2019.04.03
안드로이드 Service  (0) 2019.03.19
AsyncTask  (0) 2019.03.13
ViewPager와 PageAdapter  (0) 2019.03.05
px, dp, sp 개념 정리  (0) 2019.02.16
Device screen dpi 값에 따라 처리하기  (0) 2019.02.16

ViewPager와 PageAdapter

모바일/안드로이드 2019. 3. 5. 23:38 Posted by 아는 개발자


0. ViewPager와 PagerAdapter


ViewPager와 PagerAdapter는 아래 네이버 앱의 뉴스, 연애 스포츠 탭처럼 큰 화면 안에서 여러 개의 작은 화면을 좌우로 움직이는 UI를 구성할 때 사용하는 안드로이드 라이브러리다. ViewPager는 TextView, Button과 같이 화면의 인터페이스로 사용자가 조작할 수 있는 컴포넌트고 PagerAdapter는 ViewPager가 생성한 공간을 채우는 데이터 공간으로 보면 된다. 


특별히 화면에 이펙트를 넣을 생각이 없으면 ViewPager는 안드로이드에서 제공하는 라이브러리를 그대로 쓰고 내가 넣고 싶은 화면을 담은 별도의 PagerAdapter를 만들어서 ViewPager에 연결 시키면 된다.


이버의 뉴스, 연예, 스포츠 탭은 ViewPager와 PagerAdapter로 만들었을 것 같다. (아닐수도 있고)


1. Fragment + PagerAdapter 


큰 화면의 단위는 Activity이고 작은 화면의 단위는 주로 Fragment를 사용하기 때문에 안드로이드에서는 PagerAdater를 Fragment를 사용해서 구현하기 쉽도록 FragmentPagerAdatper를 만들어 뒀다. 이것과 이름이 비슷한 FragmentStatePagerAdapter가 있는데 Fragment 메모리 관리를 어떤식으로 할 것인가에 따라서 조금 차이가 난다. 깊게 들어가면 어려울 수 있으니 간단하게 ViewPager가 관리하는 페이지가 얼마 되지 않는 경우에는 FragmentPagerAdapter를, 많으면 FragmentStatePagerAdapter를 사용하도록 하자. 이 둘로 나눴지만 상속해서 작성하게될 Custom FragmentPagerAdatper의 코드상의 차이는 거의 없다.


2. Custom FragmentPagerAdapter


앞서 말한대로 여러 개의 화면을 좌우로 슬라이드 할 수 있는 화면을 만들기 위해선 별도의 PageAdapter가 필요하다. 안드로이드에서 FragmentPagerAdapter 클래스를 만들어 뒀으니 이를 상속해서 Custom PageAdapter를 만들 수 있다. 몇가지 오버라이드 함수들만 살펴보자.


public Fragment getItem(int position)


n번째 포지션의 Fragment Item 오브젝트 정보를 리턴한다. 채울 화면의 순서를 변경하고 싶다면 여기서 return 하는 Fragment 객체를 position 에 따라서 변경하면 된다. 반드시 구현해야하는 함수다.

    @Override
    public Fragment getItem(int position) {
        Fragment tab = null;

        switch (position) {
        case 0:
            tab = new PreviewTab();
            break;
        case 1:
            tab = new CodeTab();
            break;
        }

        return tab;
    }


public int getCount()


ViewPager에 넣을 화면의 개수를 리턴하는 함수다. 이것도 반드시 구현해야 하는 함수다.

    @Override
    public int getCount() {
        return this.mNumOfTabs;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return mTitles[position];
    }


public Object instantiateItem


View에 넣을 Page 객체를 리턴해주는 작업. 이미 FragmentPagerAdapter에서 구현을 해뒀기 때문에 따로 오버라이드 할 필요는 없다. 커스텀한 fragment를 사용할 수도 있긴 할텐데 가능하면 부모 함수를 바로 쓰는 것을 추천한다.

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        registeredTabs.put(position, (IMarkdownTab)fragment);
        return fragment;
    }


public void destroyItem


생성한 Fragment를 제거하는 작업이다. instantiateItem 함수에서 부모 함수를 이용했다면 destroyItem 에서도 부모 함수를 이용해서 처리하는 것이 좋다. 그래야만 부모 함수의 변수들과 싱크를 맞출 수 있으니까. 마찬가지로 가능하면 부모 함수를 바로 쓰는 것을 추천한다.

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        super.destroyItem(container, position, object);
    }


전체 코드는 여기서 볼 수 있다.


3. ViewPager + Custom PageAdapter 


이제 만든 CustomPageAdapter를 xml의 ViewPager과 연결하면 된다. 아래와 같이 adapter 객체를 선언해준 후 ViewPager의 setAdapter 함수를 통해 연결 해줄 수 있다. 직관적인 코드라 이해하는데 별로 어렵지 않을 것 같다


adapter = new MarkdownPagerAdapter(getSupportFragmentManager(), Titles,
        selectedImgSrc, unselectedImgSrc, Titles.length);
viewPager = findViewById(R.id.view_pager);
viewPager.setAdapter(adapter);


4. ViewPager 페이지 선택 콜백함수


ViewPager 객체 내에 PageChange 리스너를 등록해서 페이지가 변경 될 때마다 특정한 작업을 하도록 만들 수 있다.


viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    }

    @Override
    public void onPageSelected(int curPosition) {
        IMarkdownTab curImt = adapter.getRegisteredTab(curPosition);
        IMarkdownTab preImt = adapter.getRegisteredTab(prePosition);

        if (curPosition == prePosition || curImt == null)
            return;

        if (preImt!=null)
            preImt.cbPageUnSelected();

        curImt.cbPageSelected();
        prePosition = curPosition;
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }
});


이또한 함수명으로 충분히 언제 불리는지 이해할 수 있을 것 같다. onPageSelected 함수에 넣은 코드는 사용자가 선택한 페이지의 화면을 새로고침 하도록 만든 작업이다.  상황에 맞춰서 커스터마이즈하면 된다. 아 그리고 리스너는 여러 개를 등록 할 수 있다. 난 이 점을 이용해 아래 그림처럼 화면 포커스를 바꿀 때 새로고침과 동시에 바의 위치와 그림을 바꾸기도 했다.      

     

   

리스너를 이용해 화면 포커스가 바뀔 때 그림 이미지와 바의 위치를 변경 할 수 있었다

728x90

'모바일 > 안드로이드' 카테고리의 다른 글

안드로이드 Service  (0) 2019.03.19
AsyncTask  (0) 2019.03.13
ViewPager와 PageAdapter  (0) 2019.03.05
px, dp, sp 개념 정리  (0) 2019.02.16
Device screen dpi 값에 따라 처리하기  (0) 2019.02.16
안드로이드 선 추가하기  (0) 2019.01.26

px, dp, sp 개념 정리

모바일/안드로이드 2019. 2. 16. 16:04 Posted by 아는 개발자

XML 파일 안에서 UI 컴포넌트의 크기를 정할 때 dp, sp, px 단위의 원리를 제대로 알지 않고 사용하는 경우가 있는데 이렇게 막 사용하다 보면 디바이스 해상도가 변경 될 때 화면이 깨지는 경우가 생긴다. 이번 포스트에서는 지금까지 막무가내로 사용해온(?) dp, sp, px 단위를 총 정리 해보려고 한다.


0. px (Pixel)



주소화 할 수 있는 화면의 가장 작은 단위. 점을 찍을 수 있는 가장 작은 단위라고 생각하면 된다. 화면 해상도와 관계 없이 컴포넌트의 크기를 절대값으로 세팅할 수 있으며 위의 그림으로 알 수 있듯이 오른쪽으로 갈 수록 폰의 해상도가 높아지면서 텍스트 상자와 이미지 아이콘이 작아진 것을 알 수 있는데 값을 픽셀 단위로 설정했기 때문에 그렇다. 이런 단위로는 여러 디바이스 환경마다 아이콘의 크기가 천차만별이 될 수 있어서 안드로이드 환경에서는 잘 사용하지 않는다.


1. dp (Density-independent Pixels)


화면의 해상도값에 따라서 크기가 조정되는 단위다. 160dpi 스크린 해상도에서는 1px만큼의 크기를 가지고 해상도 값이 커질수록 픽셀의 값이 커진다. 320dpi 해상도면 2px 만큼의 크기를 가지고 480dpi 면 3px만큼 가지는 원리다. 위의 그림에서도 보이듯이 해상도가 높아져도 텍스트상자와 이미지의 크기가 일정한 것을 알 수 있는데 이는 해상도에 따라서 크기가 재설정 됐기 때문이다. 해상도에 따라 크기가 자동으로 늘어나고 줄어들 수도 있으니 스펙이 다양한 안드로이드 디바이스 환경에서 적용할 때 유용한 단위다.


2.  sp (Scale-independent Pixels)


dp랑 비슷한 원리로 동작하며 사용자의 글짜 크기(font-size)를 조정할 때 사용되는 단위다. 

728x90

'모바일 > 안드로이드' 카테고리의 다른 글

AsyncTask  (0) 2019.03.13
ViewPager와 PageAdapter  (0) 2019.03.05
px, dp, sp 개념 정리  (0) 2019.02.16
Device screen dpi 값에 따라 처리하기  (0) 2019.02.16
안드로이드 선 추가하기  (0) 2019.01.26
SharedPreference  (0) 2019.01.10

Device screen dpi 값에 따라 처리하기

모바일/안드로이드 2019. 2. 16. 14:46 Posted by 아는 개발자

안드로이드 앱을 만들때면 무수히 많은 종류의 화면과 모두 호환 할 수 있는 UI를 만드는 일이 꽤나 골칫 거리다. 아이폰과 달리 안드로이드 폰은 제조사가 한군데만 있는 것도 아니고 또 같은 회사에서 만든 폰이라도 디스플레이 스펙이 제각각이라 이에대한 고려 없이 막만들었다간 화면이 깨져버리는 참사가 벌어지고 만다.


Android에서는 UI 개발자들이 이런 점들에 대해서 유연하게 대처 할 수 있도록 여러가지 API를 제공하는데 이번 포스트에서는 getResources().getDisplayMetrics().density 에 대해서 알아보려고 한다.


이 값은 디바이스의 160dpi 대비 디바이스의 screen density (dpi) 의 비율을 나타내며 실행되고 있는 디바이스 화면 스펙별로 값이 다르다. 디바이스의 화면이 320dpi면 2이고 420dpi면 2.625의 값을 가진다. 이 비율 값을 이용하면 다양한 하드웨어 스펙 별로 화면 값을 자동으로 조정 할 수 있게 된다.


int padding = (int) (TAB_IMAGE_VIEW_PADDING_PIV 
       / (getResources().getDisplayMetrics().density * 2));
imageView.setPadding(padding, padding, padding, padding);


이 값을 이용해 애플리케이션 오른쪽 상단 아이콘의 image view 주변 패딩 값을 조절하는데 사용 해봤다. View 클래스의 public void setPadding 함수는 인자를 절대 수치인 pixel 단위로 값을 받기 때문에 변환된 값을 전달해야한다. 그래서 getResources().getDisplayMetrics().density 값을 나눠서 화면 스크린 화질이 좋을 수록 값이 작아지도록 변경했다. 결과 아래 그림처럼 다른 화질을 가진 환경에서도 아이콘의 크기를 맞춰줄 수 있게 됐다.


728x90

'모바일 > 안드로이드' 카테고리의 다른 글

AsyncTask  (0) 2019.03.13
ViewPager와 PageAdapter  (0) 2019.03.05
px, dp, sp 개념 정리  (0) 2019.02.16
Device screen dpi 값에 따라 처리하기  (0) 2019.02.16
안드로이드 선 추가하기  (0) 2019.01.26
SharedPreference  (0) 2019.01.10

안드로이드 선 추가하기

모바일/안드로이드 2019. 1. 26. 15:19 Posted by 아는 개발자

안드로이드 앱을 사용하다보면 기사나 댓글 화면에는 여러 개의 아이템을 구분짓기 위해서 빨간 상자로 표시한 것처럼 선을 사용한다.

 

저렇게 단순한 선 정도는 따로 그림 파일을 가져올 필요 없고 안드로이드에서 만들어 줄 수 있다. XML 파일로 만들면 된다. 선을 표현하는 방법은 다양하게 있는데 가장 쉬운 방법은 View 클래스에 백그라운드 컬러색을 넣고 가로와 세로 영역을 잡아주는 방법이 있다.

 

하지만 나는 귀찮더라도 resource에 파일을 추가해서 간단히 세로가 짧은 사각형을 만드는 것으로 구현 해봤다. 

 

1. 코드

 

line_sample.xml

<shape
    android:shape="rectangle"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#000000" />
</shape>

 

위 리소스 파일은 그냥 검은색 사각형이다. 이 검은색 사각형을 실제 화면 뷰를 담당하는 XML 파일에 가로영역을 길게하고 세로 영역을 짧게 하면 선처럼 보이게 된다. 아래 코드처럼 ImageView에 넣어도 되고 아니면 View 클래스에 넣어도 괜찮다.

 

<ImageView
        android:src="@drawable/line_sample"
        android:layout_width="match_parent"
        android:layout_height="5dp"
        android:layout_margin="20dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        />

 

적용하면 아래처럼 정중앙을 관통하는 선이 하나 생긴다.

 

 

2. 확장 

 

리소스 파일의 property 값을 수정해 선의 가장자리를 더 둥글게 만들 수 있다. 실제로 꼭지점에 radius값을 넣어준 사각형을 만드는 것과 동일한 원리다. 코드에 한줄만 추가하면 된다.

 

<shape
    android:shape="rectangle"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="10dp"/>
    <solid android:color="#000000" />
</shape>

 

 그 결과 아래 그림처럼 양끝이 둥근 선이 만들어졌다.

 

728x90

'모바일 > 안드로이드' 카테고리의 다른 글

AsyncTask  (0) 2019.03.13
ViewPager와 PageAdapter  (0) 2019.03.05
px, dp, sp 개념 정리  (0) 2019.02.16
Device screen dpi 값에 따라 처리하기  (0) 2019.02.16
안드로이드 선 추가하기  (0) 2019.01.26
SharedPreference  (0) 2019.01.10

SharedPreference

모바일/안드로이드 2019. 1. 10. 21:52 Posted by 아는 개발자

애플리케이션을 종료한 이후에도 수정한 값을 유지하려면 데이터를 간단한 텍스트 파일에 써두거나 특정 데이터베이스의 형태로 저장해야 한다. 안드로이드에서는 관계형 데이터베이스로는 SQLite를, 그리고 단순히 key-value 값을 저장할 수 있는 용도인 Shared Preference를 제공한다.


Shared Preference 


애플리케이션의 설정 값처럼 데이터가 key-value의 형태이고 단순한 타입을 사용하며(boolean, int, float, long, string) 기록해야할 양이 얼마 되지 않는다면 관계형 데이터베이스를 사용하는 것보다 SharedPreference를 쓰는 것이 훨씬 편하다. SharedPreferece는 복잡하고 길고긴 쿼리문 없이 객체와 key값을 이용해 데이터를 쉽게 수정하고 가져올 수 있다.


1. 객체 생성


settings = Context.getSharedPreferences(PREF_NAME, MODE);


데이터를 수정할 수 있는 SharedPreference 객체는 Context 객체내에 내장된 getSharedPreference 함수를 통해 가져올 수 있다. 상황에 따라서 다르나 나는 주로 getApplicationContext를 사용한다.


인자 값으로는 두개를 받는데 첫번째는 PREF_NAME, SharedPreference로 저장되는 데이터베이스의 이름을 말한다. 이 값이 달라지면 서로 다른 데이터베이스 파일에 저장되게 되고 별도의 key값으로 관리된다. PREF_NAME은 storage에 XML파일의 이름으로 들어간다.


두번째 인자 값은 동작하는 형태다. 주로 0을 넣거나 MODE_PRIVATE, MODE_WORLD_READABLE, MODE_WORLD_WRITEABLE을 혼합해서 사용한다.


2. 기록


SharedPreferences.Editor editor = settings.edit();
editor.putLong(FILE_ID, fileId);
editor.commit();


값을 기록할 때는 생성한 SharedPreference 객체로부터 editor 객체를 가져온다. 이 객체내에는 putInt, putBoolean, putString ... 함수가 있는데  모두 key값에 해당하는 데이터가 없으면 데이터를 추가하고 있으면 업데이트 하는 함수다. 저장하는 데이터 타입은 함수 명으로 유추할 수 있다.


첫번쨰 인자 값은 key값이고 두번째 인자 값은 저장할 데이터의 값이다. key값은 나중에 데이터를 읽어올 때 필요하니 static 변수로 저장해두는 것이 편하다.


putXXX 함수를 호출한 다음에는 에디터 함수 내의 commit 또는 apply함수를 호출해서 데이터 값을 최종적으로 기록한다. 호출하지 않으면 수정한 값이 파일에 저장되지 않는다. commit은 동기 방식으로 apply는 비동기 방식으로 값을 업데이트하니 이 또한 상황에 따라서 적절히 활용하면 된다.


3. 읽어오기 


return settings.getLong(FILE_ID, -1);


값을 수정할 때 처럼 SharedPreference 함수에 내장된 getXXX 함수를 통해서 읽어올 수 있다. 첫번재 인자는 읽어올 key 값이고 두번째 인자는 만약 key값에 해당하는 데이터가 없거나 읽어오는데 실패했을 때 기본으로 얻게될 데이터 값이다. 


4. 주의사항


공식문서에는 SharedPreference의 동작 형태중 하나인 MODE_MULTI_PROCESS 가 API23 이후로 Deprecated 됐고 앞으로 안드로이드에서도 지원할 생각이 없다고 한다.


MODE_MULTI_PROCESS 에 해당하는 대표적인 예로 안드로이드 UI 컴포넌트와 안드로이드 서비스가 별도의 프로세스로 분리된 경우가 있다. 개발하다보면 안드로이드 액티비티와 서비스가 데이터를 공유해야하는 일이 종종 생기는데 Context와 데이터 이름만 지정하면 얻어올 수 있는 SharedPreference 객체를 사용하는 것이 가장 심플하게 구현할 수 있는 방법이라 자주 사용된다.


액티비티와 서비스가 동일한 프로세스라면 상관 없지만 둘이 별도의 프로세스인 경우엔 실행도중에 동기화 문제가 발생한다. 글쓴이가 몸소 체험한 동기화 문제는.. 서비스를 먼저 실행시킨 다음 액티비티 에서 값을 변경했더니 백그라운드 서비스에서 수정한 값을 읽어오지 않고 예전 값을 계속 읽어오던 일이 있었다. 처음에는 데이터 수정이 제대로 되지 않았는지가 의심스러워 commit() 함수 리턴값만 확인 했었는데 알고보니 별도의 프로세스 환경에서는 사용할 수 없었다. 이것 때문에 몇시간을 잡았는지 모른다.



728x90

'모바일 > 안드로이드' 카테고리의 다른 글

AsyncTask  (0) 2019.03.13
ViewPager와 PageAdapter  (0) 2019.03.05
px, dp, sp 개념 정리  (0) 2019.02.16
Device screen dpi 값에 따라 처리하기  (0) 2019.02.16
안드로이드 선 추가하기  (0) 2019.01.26
SharedPreference  (0) 2019.01.10

addr2line

모바일/삽질 기록 2018. 12. 22. 12:58 Posted by 아는 개발자

개발하다보면 에러가 발생한 물리 주소만 떡하니 알려주고 바이너리의 어떤 함수에서 죽었는지는 알려주지 않는 로그 메시지를 보게될 때가 있다. 이런 경우에는 addr2line 커맨드를 이용해 어떤 파일 몇번째 라인에서 에러가 발생했는지 확인 할 수 있다.


아래의 예제 코드를 통해 addr2line의 사용법을 익혀보자.


void func(void) {
}

int main() {
    printf("func:addr %p\n", func);
}


위 코드를 gcc에 -g 옵션을 주고 컴파일해서 실행해보면 특정 숫자 값을 출력하게 되는데 이 값은 func함수의 물리 주소 값이다. 이 값을 addr2line에 넣어서 실행해보면 func함수가 어떤 파일 몇번째 라인에 불렸는지 확인 할 수 있다. -e 옵션은 디버깅할 바이너리를 지정하는 값이고 마지막에 붙은 숫자는 주소 값이다.


728x90

gcc로 pthread API 컴파일하기

모바일/삽질 기록 2018. 10. 30. 21:59 Posted by 아는 개발자


왠만한 C 코드들은 'gcc 파일명' 명령어로 빌드가 가능한데 코드 안에서 pthread API를 사용하고 컴파일을 하면 아래와 같은 에러 메시지가 나온다.


kwony@kwony:~$ gcc thread.c


thread.c:(.text+0x79): undefined reference to `pthread_create'

thread.c:(.text+0xa9): undefined reference to `pthread_join'

collect2: error: ld returned 1 exit status


gcc 컴파일 스크립트에 디폴트로 pthread 라이브러리가 포함되지 않아서 발생하는 에러다. `-lpthread` 를 옵션으로 줘서 pthread 라이브러리를 포함시켜 빌드하면 해결 할 수 있다.


gcc thread.c -lpthread


아래 코드로 테스트를 해봤다.

#include <pthread.h>
#include <stdlib.h>
void *test(void *data)
{
}
int main()
{
    int a = 100;
    pthread_t thread_t;
    int status;

    if (pthread_create(&thread_t, NULL, test, (void *)&a) < 0)
    {
        perror("thread create error:");
        exit(0);
    }
    pthread_join(thread_t, (void **)&status);
    printf("Thread End %d\n", status);
    return 1;
}




728x90
  1. makerj (ohenwkgdj@gmail.com) 2019.08.13 00:26  댓글주소  수정/삭제  댓글쓰기

    gcc thread.c -lpthread 가 아니라 gcc -pthread thread.c 가 맞습니다.
    전자는 단순히 함수를 연결해 주는 것 뿐이지만, 후자는 스레드 모델을 pthread로 하겠다는 명확한 의미가 포함됩니다. 해당 코드를 리눅스에서만 사용한다면 차이가 없겠지만, 다른 OS에서도 사용하게 된다면 반드시 필요한 설정입니다.


이력서를 웹사이트에 공개하는 방법으로 LinkedIn을 사용하는 것이 가장 간단하고 대중적이지만 포맷이 정해져 있어 심심하고 다른 사람들과 차별화를 줄 포인트가 없다는 것이 단점이다. 그래서 일부 개발자들은 자신의 이력서를 HTML 형태로 이쁘게 디자인하고 호스팅을 해서 다른 사용자가 쉽게 볼 수 있게끔 만든다.


링크드인 보다 덜 식상한 것 같다


그런데 이력서 한 장 띄우자고 유료 웹 호스팅 서비스를 이용하는 건 부담스러운 일이라 개발자들은 Github에서 저장소 별로 제공하는 페이지 기능을 활용해서 이력서를 만든다. 깃허브 페이지 기능이 궁금하신 분들은 이 사이트에서 친절히 설명하는 영상이 있으니 참고하길 바란다. 자세한 사용 방법은 알려주지 않는 것 같다.


사실 페이지 기능을 사용하는 법을 몰라도 이미 개발자들이 resume-template을 많이 만들어 둬서 가져다가 쓰면 간편하다 나는 https://github.com/jglovier/resume-template  template을 사용하고 있는데 템플릿이 이쁘고 수정하는 방법도 쉬워 잘 사용하고 있다. 이번 포스트에서는 이 템플릿을 이용하는 방법을 간단히 다룬다.


0. Github 계정 만들기


계정이 없다면 https://github.com/ 사이트로 가서 계정을 하나 만든다. 이미 있으면 패쓰 


1. Github resume template 저장소 fork하기


https://github.com/jglovier/resume-template 저장소를 내 저장소로 Fork한다. Fork는 원격 저장소를 미러링한 형태로 내 저장소에 복사하는 기능인데 원리는 몰라도 상관 없다. 아래 그림의 맨 오른쪽 버튼을 누르자.



2. 저장소 이름 바꾸기


Fork가 완료되면 내 저장소에 resume-template이라고 저장소가 생긴다. 저장소 이름을 토대로 이력서 웹페이지 주소가 설정되니 괜찮은 이름으로 바꾸자. resume-template 버튼을 클릭하고 Setting Tab을 누르면 이름을 수정 할 수 있다. 




3. 내 페이지가 보이는지 테스트


변경한 이름을 토대로 저장소의 웹페이지에 접속해보자. { 사용자 ID }.github.io/{ 변경한 저장소 이름 } 으로 접속하면 아래 그림과 같은 페이지가 나온다. 


Github 404 접속 에러 페이지가 나오는 경우도 있는데 이런 경우에는 10~15분 정도 후에 다시 접속하면 된다. 적용되는데 시간이 어느정도 소요 되는 것 같다.




4. 프로필 정보 업데이트하기


Lisa Simpson으로 되어 있는 현재 페이지를 내 프로필에 맞춰서 바꾸자. _config.yml 파일과 _data/ 폴더 안의 내용들을 바꾸면 현재 포맷을 유지한 상태로 쉽게 업데이트 할 수 있다. 코드가 직관적이니까 영어만 사용할 줄 알면 부담 없이 수정이 가능하다.


ruby와 jekyll에 능숙한 사람들은 포맷을 간단히 수정해봐도 좋을 것 같다. 나는 잘 모르겠다오. 


5. 로컬 서버에서 확인


수정한 정보를 서버에 올리기 전에 아래 명령어를 이용해서 로컬 서버에서 확인 해보고 가자. 


0. cd "이력서 저장소 위치"
1. bundle install // 처음에만
2. bundle exec jekyll serve


첫번째 명령어는 저장소에서 들고있는 ruby 패키지들을 설치하는 작업이니 딱 한번만 실행하면 된다. 두번째 명령어는 현 저장소에 있는 jekyll 프레임워크(이렇게 부르는게 맞나?)를 실행하는 명령어다. 실행하고 나서 크롬이나 파이어폭스를 열고 주소 창에 localhost:4000을 입력하자. 코드에 문제가 없으면 아래처럼 이력서 페이지가 나온다.




* 가끔 bundle install 하다가 1.9.21 버전을 설치 할 수 없다는 에러가 뜨는데 이때는 이 패키지들을 설치하면 된다.

sudo apt-get install ruby-dev
sudo apt install libffi-dev


6. 수정된 정보 업데이트


로컬 서버에서 문제가 없으면 수정한 코드를 저장소에 커밋한다. 1~2분 정도 지나야 커밋한 내용이 반영되니 바로 업데이트가 되지 않았다고 초조해하지 마시길. 


커밋하는 방법이 모르시는 분은 간단히 아래의 코드를 입력해서 업데이트 할 수 있다.


git add -A
git commit -m "Update resume info"
git push origin gh-pages


728x90


Conda 환경에서 tensorflow를 설치하는 방법


0. Prerequsite


- Anaconda를 설치한다.

- GPU를 사용할 경우 tensorflow를 사용할 수 있도록 CUDA, cuDNN 라이브러리를 설치한다.


1. tensorflow 가상 환경 생성


tensorflow를 사용하는 가상 환경을 하나 생성한다. 별도로 설정하고 싶은 환경이 있으면 생략하고 아니면 만들자.

kwony@kwony:~$ conda create -n tensorflow pip \ 
python={ python 버전 입력 ex) 2.7, 3.4

2. 가상환경 활성화


1에서 만들어둔 가상 환경을 활성화한다. activate 명령어가 안먹히는 경우는 anaconda의 바이너리가 export가 제대로 안된것이니 확인해보도록 하자.

kwony@kwony:~$ conda activate tensorflow
(tensorflow)$  # tensorflow로 명령어 프롬프트가 변형된다

3. tensorflow 설치


활성화된 명령어 프롬프트에서 tensorflow를 설치한다. tensorflow 공식 홈페이지에서 설치하려는 tensorflow의 종류와 파이선 버전에 해당하는 저장소를 입력하도록 하자.

(tensorflow) kwony@kwony:~$ pip install --ignore-installed \
 --upgrade { URL }

4. jupyter 설치


원래 아나콘다를 설치할 때 기본으로 포함된 jupyter-notebook에서 tensorflow를 쓰려고 하면 없는 모듈이라고 나온다. 방금전에 설치한 tensorflow는 현재 사용중인 가상 환경에만 적용되기 때문이다. 현재 가상 환경에서 사용하는 jupyter notebook을 설치하면 방금 전에 설치한 tensorflow를 사용 할 수 있다.

(tensorflow) kwony@kwony:~$ pip install jupyter

5. jupyter notebook 실행 후 테스트


jupyter 노트북으로 tensorflow 모듈을 사용해보면 문제없이 동작하는 것을 확인 할 수 있다.

(tensorflow) kwony@kwony:~$ jupyter-notebook 



728x90

최상위 커밋은 git commit --amend로 수정이 가능한데 중간에 있는 커밋은 수정하기가 참 난감하다. 가장 간단하면서 무식한 방법은 git format-patch로 수정하려는 커밋 전까지 패치 파일을 만들어둔 후 reset으로 수정하려는 커밋까지 쌓인 커밋들을 모두 지운 뒤 git commit --amend로 수정한 다음, reset 하기 전에 패치로 만들어둔 커밋을 다시 적용하는 방법이 있다. 이 방법은 매우 번거롭고 복잡하며 이미 Pull Request가 진행중인 브랜치에서는 적용할 수 없다는 문제가 있다.


이런 경우에는 git rebase의 interactive 옵션을 이용하면 Pull Request가 진행중인 브랜치의 중간 커밋을 수정 할 수 있다. 사용 방법은 간단하다.



위 그림에서 맨 아래 "appear action bar and use customized one in user activity"라는 커밋만 수정해보고 싶다고 해보자. 그러면 내가 수정하려는 커밋의 번호를 복사한 후 command에 아래와 같이 입력하자


// git rebase -i {커밋 id}^
git rebase -i 1372d858b7d84708f54da356035cec35a4375f07^


실행하고 나면 nano 에디터가 열리면서 아래와 같은 메시지가 나온다.


pick 1372d85 appear actionbar and use customized one in user activity
pick 0ba4172 cut side space in actionbar
pick 64d006d Fix bug: Change error message on dialog user name input
pick 5be8b91 Replace textview to imageview in sliding tab layout
pick c6d7a5e Add postfix string in actionbar title to show detailed information
pick 624baa3 upgrade version to 1.20

# Rebase 382448a..624baa3 onto 382448a (6 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit


메시지에는 수정하려는 커밋부터 최신 커밋까지 "명령" "커밋 번호" "커밋 메시지"의 형태로 쭉 나열 돼어 있다. 현재 상태는 어떤 커밋에 무슨 작업을 할 것인지 설정 할 수 있는 단계다. 아래 Commands의 설명이 나와 있는 것 처럼 'pick'은 commit을 그대로 사용하는 것이고 'edit'은 커밋을 사용하면서 수정하기 위해 멈춘다는 뜻이다. 기본으로는 모두 변경하지 않음인 'pick' 설정이 돼있다.


개발자는 "명령"에 해당하는 문자열 값을 바꿔서 해당 커밋에서 원하는 작업을 수행 할 수 있다.  우리는 가장 처음에 있는 커밋(1372d85)을 수정해야하니 이 문장에 pick을 edit으로 변경하고 빠져나오자. 그럼 아래와 같은 메시지가 뜬다.



해당 커밋에 멈춰 있으니 git commit --amend로 수정을 한 다음에 git rebase --continue를 하라는 뜻이다. git rebase --continue는 현재 커밋에 대한 작업을 수행한 후 다음 커밋의 작업으로 넘어가는 명령어다. 테스트를 위해 README.md 파일만 수정하고 넘어가본다. 그러면 숫자들이 빠르게 움직이고 나서 아래와 같은 메시지가 나온다.



반영이 모두 완료돼었다는 뜻이다. 그런데 이런 완료 메시지가 나오지 않고 충돌이 떴다는 메시지가 나오는 경우도 있다. 중간 커밋의 수정사항 때문에 기존 커밋을 적용하지 못해 충돌이 발생하는 경우다. 이런 경우에는 중간 커밋의 수정사항을 따를 수 있도록 기존 커밋도 바꿔주도록 한다.

728x90

리눅스 디스크, 폴더 용량 확인하기

모바일/삽질 기록 2018. 5. 26. 14:29 Posted by 아는 개발자

매번 구글창에 디스크/파일 용량을 확인하는 단축키를 찾기 번거로워서 이번 포스트로 정리하고 머릿속에 기억해두려고 한다.


1. 디스크 용량 확인, df


시스템상에서 FILE이 있는 곳의 파일시스템에 대한 정보를 보여준다. 즉 시스템에 마운트되어 있는 모든 것들에 대한 사용 정보를 출력한다는 뜻.

그냥 df를 검색하면 아래처럼 나온다. 여기서 1K-blocks는 파일시스템의 전체 용량, Used는 사용중인 크기, Available은 사용가능한 메모리의 크기를 의미한다.



그런데 위 그림은 바이트 단위라서 보기가 어렵다. -h 옵션을 넣어서 human readable하게 변환하자. 훨씬 직관적으로 보인다.



df --help 로 검색하면 다양한 옵션을 발견할 수 있으니 이것도 적극적으로 활용하자. 디스크 용량 검색하는 것은 이걸로 끝. 다른 것들도 있는데 하나만 정확하게 기억하는게 중요할 것 같다.


2. 파일, 폴더내 총용량 확인, du


파일 크기같은 경우 ls를 통해서 용량 정보도 나오고 여기에 -h 옵션만 넣으면 사람이 읽을 수 있는 용량 크기로 나온다. 그런데 폴더내 총용량을 확인할 때는 폴더 디렉토리 자체의 크기만 볼 수 있어서 사용할 수가 없다. 이럴때는 du라는 명령어를 사용하면 편하다. 


du 명령어를 이용해 Pictures 폴더를 검색해봤다.



앞의 416 은 Pictures 폴더내에 있는 파일들의 총 용량 크기다. 단위가 없어서 읽기 불편하니 -h 옵션을 붙여 human readable하게 변형하자. 

이것도 df처럼 용량이 커지면 M, G 단위로 변환해준다.



폴더 내의 파일들이 궁금하면 -a 옵션을 붙이면 된다. 반대로 출력하고 싶지 않다면 -s 옵션을 붙여서 제거한다.


    


728x90

Ubuntu 16.04에서 GPU 가속화를 지원하는 Tensorflow를 설치하는 방법

이 포스트는 Tensorflow 공식 가이드 페이지를 읽고 글쓴이의 컴퓨터에 설치한 과정을 정리한 것이다. PC 환경에 따라 차이가 있을 수 있으므로 포스트를 읽고 설치하기 전에 개인 PC환경이 글쓴이와 어떤점이 차이가 나는지 먼저 확인해두길 바란다. 똑같은 instruction을 사용해도 어떤 PC는 되고 어떤 거는 안되는 경우가 많다. 글쓴이의 PC 환경은 다음과 같다.

CPU: Intel(R) Core(TM) i5-6600 CPU @ 3.30GHz, Quad Core

GPU: GEFORCE GTX1060 (그래픽 드라이버 설치 완료)

RAM: 8GB

Kernel: 4.4.0-122-generic


* 혹시 우분투 커널 버전을 업데이트 했다면 중요한 자료들을 미리 백업해두길 바란다. 글쓴이는 처음에 4.9로 업데이트한 커널로 설치해봤는데 재부팅하니 갑자기 컴퓨터가 안켜지는 문제가 있었다. 아무리 찾아도 해결책을 찾을 수 없어 별 수 없이 커널을 밀고 재설치를 했었다.


1. CUDA Toolkit 다운로드 및 설치

Nvidia 홈페이지에서 tensorflow를 지원하는 cuda 설치 패키지를 다운 받는다. 현재 tensorflow는 cuda 9.0만 지원하고 있으니 이에 유의하도록 하자. 만약 현재 사용중인 NVIDIA driver가 cuda 9.0가 호환하지 않으면 삭제하고 재설치해야 하는 경우도 있다. 이런 경우에는 runfile을 받으면 호환되는 그래픽 카드 드라이버도 같이 받을 수 있다.

* CUDA는 GPGPU에서 사용하는 병렬 처리 알고리즘 기술을 C언어를 비롯한 여러가지 언어에서 사용할 수 있도록 지원하는 기술이다. GPGPU를 사용하기 위한 인터페이스로 볼 수 있다.




2. cuDNN 설치

이것 또한 Nvidia 홈페이지에서 자신의 PC 환경에 맞는 CUDA를 설치해야한다. 그런데 CUDA와 달리 cuDNN 부터는 따로 계정을 생성해야 한다. 가입하고 난 뒤에는 cuDNN 7.0.5 버전을 받도록 한다.

* cuDNN은 CUDA를 딥러닝에 사용할 수 있도록 만든 API다.


설치 가능한 목록에서 자신의 환경을 꼼꼼히 확인하고 다운 받아야 한다. Ubuntu 16.04에서는 Runtime Library와 Developer Library가 있는데 나는 Runtime을 선택해서 설치했다.


3. 그래픽 드라이버 설치

본인이 갖고 있는 그래픽 카드에 맞는 그래픽 드라이버를 설치한다. NVIDIA 홈페이지에서 그래픽 카드 드라이버를 받을 수 있다. 미리 설치 했으면 할 필요 없다. 설치 방법이 apt-get 처럼 단순하지 않다. 설치 방법은 추후 별도의 포스트로 올리겠다.


4. Tensorflow 설치 

Tensorflow 홈페이지에서는 총 네가지 방법(Virutalenv, native, Docker, anaconda)을 지원하는데 이중에서 가상 환경을 만들어서 사용하는 방법(Virtualenv)을 권장하고 있다. Virtualenv는 특정 폴더에 python 환경을 독립적으로 꾸릴 수 있는 환경을 제공하는데 이곳에 tensorflow를 설치하면 다른 파이선 프로그램의 디펜던시가 걸릴 우려가 없어 관리하기가 편하다. 글쓴이는 virtualenv를 이용해서 설치했다.

  1. 먼저 python 패키지 관리 시스템과 virtual env를 apt-get으로 설치한다.

    $ sudo apt-get install python-pip python-dev python-virtualenv # for Python 2.7
    $ sudo apt-get install python3-pip python3-dev python-virtualenv # for Python 3.n

    파이썬 2.7과 3.n 버전이 엄격히 구분되니, 앞으로 사용할 파이선 버전에 따라서 명령어를 잘 선택한다.

  2. python 환경을 독립적으로 꾸릴 폴더에 가상 환경을 설정한다.

    $ virtualenv --system-site-packages targetDirectory # for Python 2.7
    $ virtualenv --system-site-packages -p python3 targetDirectory # for Python 3.n
  3. 다음 명령어중 하나로 Virtualenv 환경을 활성화 한다.
    세가지 명령어를 다 시도하면 virtualenv 환경에서 튕기니까 주의한다.

    $ source ~/{targetDirectory}/bin/activate # bash, sh, ksh, or zsh $ source ~/{targetDirectory}/bin/activate.csh # csh or tcsh $ . ~/{targetDirectory}/bin/activate.fish # fish

  4. 정상적으로 됐으면 아래와 같이 활성화된 창이 뜬다.

    (targetDirectory)$

  5. Virtualenv 환경 내에서 pip가 설치 됐는지 확인해본다.

    (targetDirectory)$ easy_install -U pip
  6. pip 버전에 따라서 tensorflow를 설치한다

    (targetDirectory)$ pip install --upgrade tensorflow-gpu  # for Python 2.7 and GPU
    (targetDirectory)$ pip3 install --upgrade tensorflow-gpu # for Python 3.n and GPU
    

5. 설치 완료 메시지 확인

아래와같은 메시지가 나오면 정상적으로 tensorflow가 설치된 것이다


728x90
  1. CB 2018.08.01 22:49  댓글주소  수정/삭제  댓글쓰기

    안녕하세요,
    runtime library 와 developer library 간의 차이를 알 수 있을까요?
    검색 해본다고 해봤는데 잘 모르겠습니다.

    • 아는 개발자 2018.08.01 23:09 신고  댓글주소  수정/삭제

      Runtime Library: cnDNN을 돌릴 때 필요한 기본적인 API를 설치합니다. Window에서 일반 프로그램 설치 옵션중 하나인 '기본 설치'와 비슷하다고 보시면 됩니다. 이것만 설치해도 Tensorflow를 실행하는데 전혀 지장 없습니다.

      Developer Library: 헤더파일과 일부 코드만 제공하고 나머지는 개발자가 직접 빌드해야 합니다. Tensorflow에서 cuDNN을 사용하는 코드를 변경해서 돌려보고 싶은 경우에 사용하는 라이브러리입니다.

      요약: tensorflow를 API만 사용하는 경우에는 Runtime Library를, 직접 코드를 수정하는 경우에는 Developer Library를 사용하시면 됩니다.

      출처: https://stackoverflow.com/questions/48784645/which-nvidia-cudnn-release-type-for-tensorflow-on-ubuntu-16-04

Ubuntu 안드로이드 스튜디오 설치하기

모바일/삽질 기록 2018. 5. 1. 09:55 Posted by 아는 개발자

Ubuntu 16.04에서 안드로이드 스튜디오 설치하기


Ubuntu에서 안드로이드 스튜디오를 설치하는 방법은 두가지가 있다.

  • 공식 홈페이지에서 .zip 파일로 받아온 후 압축을 풀어서 바이너리를 링크거는 방법. 안드로이드 스튜디오를 개발자가 유연성있게 관리 할 수 있다는 장점이 있지만 환경변수를 일일이 설정하고 추적하는게 번거롭다.
  • ppa 저장소를 추가하고 apt-get으로 받기. vim, terminator 같은 프로그램을 받을 때처럼 원격 저장소에서 모두 받을 수 있는 방법이다. 유연성있게 관리할 수는 없지만 설치 방법이 간단하다.

이 포스트는 두번째 방법에 대해서만 다룬다.


1. 저장소 추가 및 apt-get을 이용한 안드로이드 스튜디오 설치

터미널을 열고 아래의 명령어를 입력한다


sudo apt-add-repository ppa:paolorotolo/android-studio

sudo apt-get update

sudo apt-get install android-studio


2. 재부팅후 Android Studio 설치 확인

시작창을 열고 Android Studio를 검색해보면 아이콘이 나오면 설치가 정상적으로 이뤄진 것이다.


728x90

우분투 16.04 한글 입력하기

모바일/삽질 기록 2018. 5. 1. 09:37 Posted by 아는 개발자


Ubuntu 16.04 에서 한글 입력기를 설치하는 방법


1. 터미널 창에서 ibus 입력기 및 한글 입력기 설치

ibus 입력기용 한글을 시스템에 추가하는 작업. 터미널을 열고 아래 설치 명령어 입력


sudo add-apt-repository ppa:createsc/3beol

sudo apt-get update

sudo apt-get install ibus ibus-hangul


2. Language Support에 한글 추가

한글을 추가하고 한글 입력이 가능한 입력 방식을 변경하는 작업

  1. 시작 -> keyboard -> Language Support
  2. Keyboard input method system 에서 IBus 모드로 변경하기
  3. Language에 한글이 없다면 Install / Remove Languages로 변경



3. Keyboard에 단축키 설정 변경

한글 키로 이동 할 수 있는 단축키 설정하는 작업. 기존 설정을 모두 지우고 한글 단축키를 설정 할 수 있는 환경을 만든다.

  1. 시작 keyboard -> shortcuts -> typing 진입
  2. 모두 disabled mode로 변경한 후, switch to next source 만 선호하는 short key를 입력



4. 시스템상 한글 입력기 등록 및 단축키 설정

시스템 입력기에 한글을 등록하고 단축키를 설정한다

  1. 시작 -> Text Entry 진입
  2. 좌측 하단 '+' 버튼을 눌러 input source에 Korean(Hangul) (IBus) 추가
    * 설치를 했는데도 Korean (Hangul) (IBus)가 목록에 없는 경우가 있는데 이때는 시스템을 재부팅하면 된다.
  3. Switch to next source using 속성에 사용할 단축키를 입력

5. 한글 사용하기

이 포스트는 IBus 한글 입력기를 이용해서 English와 한글을 번갈아 사용하며 Write 했다. 아주 아주 Comfortable 하다.



728x90

nodejs heroku deploy하는 방법

모바일/삽질 기록 2015. 6. 13. 17:41 Posted by 아는 개발자

맨 처음에 push할 때


1. deploy하고 싶은 곳의 위치로 이동


2. git 저장서 생성

git init . 


3. heroku login 해서 아이디 비번 입력


4. 자신의 아이디로 저장소 생성 나의 git 저장소가 heroku와 연결된다


heroku create


4. commit 한다


git add .

git commit -m "first push"


5. push한다


git push heroku master

728x90