Kotlin에서 제공하는 apply 범위 함수를 이용해서 클래스 내부 속성 값을 간결하게 선언할 수 있지만 DSL(Domain Specific Language) 언어인 점을 응용하면 여러 클래스를 중첩한 클래스의 속성값에 대해서 더욱 간결하게 값을 설정 할 수 있다. 얼마나 간결한지 글로 길게 설명하는 것 보다는 간단한 예시로 보는게 좋을 것 같다.

 

Kotlin의 Builder 패턴을 사용하면 아래와 같이 선언된 data 클래스들을

 

data class Group(
    val name: String,
    val company: Company,
    val members: List<Member>
)

data class Company(
    var name: String = ""
)

data class Member(
    val name: String,
    val alias: String,
    val year: Int
)

 

이렇게 선언 하는 것이 가능하다.

 

val redVelvet = group {
    name { "레드벨벳" }
    company {
        name { "SM Entertainment" }
    }
    members {
        member {
            name { "슬기" }
            alias { "곰슬기" }
            year { 1994 }
        }

        member {
            name { "아이린" }
            alias { "얼굴 천재" }
            year { 1991 }
        }

        member {
            name { "웬디" }
            alias { "천사" }
            year { 1994 }
        }
    }
}

 

이렇게 간결하게 코드를 만들기 위해선 각각의 클래스에 대해서 Builder 클래스와 lambda 함수를 사용한 내부에 셋팅 함수를 선언해야한다. Member 함수부터 구현 방법을 살펴보자.

 

1. MemberBuilder

 

class MemberBuilder {
    private var name: String = ""
    private var alias: String = ""
    private var year: Int = 0

    fun name(lambda: () -> String) {
        name(lambda)
    }

    fun alias(lambda: () -> String) {
        alias(lambda)
    }

    fun year(lambda: () -> Int) {
        year(lambda)
    }

    fun build() = Member(name, alias, year)
}

 

MemberBuilder 클래스 내부에는 Member 데이터 클래스와 동일하나 name, alias, year 를 변수로 가지는데 보면 셋팅하는 함수들의 인자가 lambda로 선언되어 있고 바로 내부 변수를 초기화해준다는 점만 다르다. lambda가 포함된 함수는 아까 레드벨벳 초기화 코드에서 확인 할 수 있듯이 간단히 primitive 인자값을 전달하는 방식 만으로도 선언이 가능하다. build() 함수는 현재까지 초기화된 정보로 Member 클래스를 만드는 작업이다. 다른 곳에서 호출 받게 된다.

 

2. MemberListBuilder

 

class MemberListBuilder {
    private val employeeList = mutableListOf<Member>()

    fun member(lambda: MemberBuilder.() -> Unit) =
        employeeList.add(MemberBuilder().apply(lambda).build())

    fun build() = employeeList
}

 

Group의 데이터 클래스에 Member가 리스트 형태로 선언돼있기 때문에 Member의 개수는 1개 이상이 될 수 있다. 그래서 복수의 Member에 대해서 처리할 수 있는 MemberListBuilder가 이 부분을 담당한다. 레드벨벳 초기화 코드에서 member { ... } 로 호출한 부분은 바로 이 MemberListBuilder 클래스의 내부 함수를 호출한 것이다. 함수 내부를 보면 받아온 정보를 가지고 바로 MemberBuilder() 클래스 내부의 build() 함수를 통해 멤버를 생성하고 내부 배열 변수(employeeList)에 추가한다. build() 함수에서는 가지고 있는 배열 정보를 리턴한다.

 

3. CompanyBuilder

 

class CompanyBuilder {
    private var name = ""

    fun name(lambda: () -> String) {
        this.name = lambda()
    }

    fun build() = Company(name)
}

 

CompanyBuilder는 말단 노드라 MemberBuilder랑 생긴게 거의 비슷하다. 굳이 다시 한번 설명하지 않아도 될 것 같다. lambda 인자로 값을 받고 build() 에서 현재 클래스 값을 전달한다.

 

4. GroupBuilder 

 

class GroupBuilder {
    private var name = ""
    private var company = Company("")
    private val employees = mutableListOf<Member>()

    fun name(lambda: () -> String) {
        name = lambda()
    }

    fun company(lambda: CompanyBuilder.() -> Unit) {
        company = CompanyBuilder().apply(lambda).build()
    }

    fun members(lambda: MemberListBuilder.() -> Unit) =
        employees.addAll(MemberListBuilder().apply(lambda).build())

    fun build() = Group(name, company, employees)
}

 

GroupBuilder 클래스에는 지금까지 만들어왔던 builder를 포함하고 있다. name 함수에서는 Group의 이름을 정하고, company 함수에서는 CompanyBuilder를 통해서 company 속성값 정보를 셋팅한다. members 함수에서도 마찬가지로 MemberListBuilder 클래스를 통해 현재 입력된 모든 멤버의 정보를 입력한다. GroupBuilder() 또한 build() 함수를 호출해서 현재 Group 클래스를 최종적으로 반환한다. 

 

GroupBuilder 클래스의 build() 함수를 효출하는 부분은 따로 함수를 만들어야하는데 이렇게 만들면 된다.

 

fun group(lambda: GroupBuilder.() -> Unit): Group {
    return GroupBuilder().apply(lambda).build()
}

 

선언부에서 알 수 있듯이 가장 먼저 호출한 함수는 group {..} 이었다.

 

전체 코드는 다음과 같다.

 

Builder 패턴 구현 부분 코드

 

데이터 초기화 부분 코드

 

 

'컴퓨터공부 > 안드로이드' 카테고리의 다른 글

안드로이드 그림자(Shadow) 효과 넣기  (0) 2020.04.18
Kotlin - Coroutine  (0) 2020.04.15
Kotlin으로 깔끔한 Builder를 만들어보자  (0) 2020.04.14
Exoplayer2 사용하기  (0) 2020.04.12
FragmentManagers Android  (0) 2020.04.06
ViewModelProviders.of deprecated  (0) 2020.04.06

Exoplayer2 사용하기

컴퓨터공부/안드로이드 2020. 4. 12. 15:02 Posted by 아는 개발자

0. ExoPlayer란?

 

안드로이드에서 영상 재생을 위해 사용하는 플레이어로 기본 내장 라이브러리인 MediaPlayer가 있었는데 스트리밍 서비스가 주류를 이루면서 구글에서 DASH와 SmoothStreaming을 지원하는 ExoPlayer 라이브러리를 도입했다. 유튜브, 네이버 동영상 프레임들도 Exoplayer를 사용하고 있다고 하니 앞으로 안드로이드 동영상 플레이어는 ExoPlayer가 주류를 이룰 것 같은 예감이다. 아니면 이미 그런지도 모르겠고.

 

ExoPlayer는 MediaPlayer에서 이미 지원하는 기능에서 새로운 기능을 추가한 것이기 때문에 로컬/인터넷 동영상 파일 재생은 당연히 가능하고 Android Media Codec 기반으로 작업을 해서 Media Codec가 도입되기 시작한 안드로이드 기기 (API16 이상)에선 대부분 문제 없이 동작 한다. 물론 일부 기능은 더 높은 API 버전이 필요하긴 하지만 이는 거의 특수한 경우인 것 같다. 이번 포스트에서는 ExoPlayer를 사용하는 방법을 간단히 다룰 예정이다.

 

1. Components 

 

ExoPlayer: ExoPlayer의 라이브러리중 Renderer, 즉 화면에 뿌려주는 역할을 하는 컴포넌트다. ExoPlayer 인터페이스로 커스텀하게 만들 수 있으며 SimpleExoPlayer는 ExoPlayer에서 제공하는 컴포넌트다. 특별히 커스터마이즈 할 것이 아니면 이걸 그냥 가져다 쓰는게 좋다. 아래 함수를 통해 만들 수 있다.

 

val simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(requireContext(), trackSelector)

 

TrackSelector: SimpleExoPlayer 생성 과정에서 두번째 인자로 전달된 클래스는 영상의 Track 정보를 세팅하는 역할을 한다. 이 정보라면 예를 들면 선호하는 오디오 언어는 어떤 것인지, 비디오 사이즈는 무엇인지, 비디오 bitrate는 어떤 것으로 할지 등등 이런 것들을 말한다. 이것도 Renderer와 동일하게 따로 커스터마이즈 할 수 있긴 하나 특별한 이유가 없다면 라이브러리에서 기본으로 만들어 둔 것을 쓰는게 가장 좋다.

 

아래 코드는 TrackSelector를 만들 때 AdaptiveTrackSelection 팩토리를 사용한 예시다. AdaptiveTrackSelection 팩토리 클래스는 현재 bandwidth 정보를 이용해 현재 선택된 track에서 최상의 퀄리티를 제공하는 역할을 한다고 한다. 더 자세한 내용은 라이브러리 내부 주석을 살펴보는 것이 좋을 것 같다. Streaming 서비스를 한다면 이쪽 클래스를 주요하게 보게될 것 같다.

 

val bandwidthMeter = DefaultBandwidthMeter()
val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory(bandwidthMeter)
val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory)

 

MediaSource: 영상에 출력할 미디어 정보를 가져오는 클래스다. ExtractorFactory 클래스를 통해 만드는데 이 클래스는 DataSource 클래스를 주입해서 만든다. 여기서 사용한 DefaultDataSource도 다른 라이브러리처럼 ExoPlayer에서 uri 형태로된 데이터를 읽어오기 위해 기본적으로 제공하는 라이브러리다. 특별한 형태의 DataSource 클래스를 사용하고 싶다면 커스터마이즈가 가능하다. 

 

val extractorFactory = ExtractorMediaSource.Factory(DefaultDataSourceFactory(context, Util.getUserAgent(context, context!!.applicationInfo.packageName)))
val mediaSource = extractorFactory.createMediaSource(Uri.parse(mediaPath))

 

Player: 영상 재생을 위해선 미디어를 읽어오는 작업뿐만 아니라 영상을 UI 상에 뿌려줄 수 있는 뷰어가 필요한데 ExoPlayer용 뷰어가 따로 있다. 아래 코드를 XML에 넣으면 된다. 재생바, 앞으로 당기기기 같은 기본적인 UI 기능도 지원한다.

 

<com.google.android.exoplayer2.ui.PlayerView
        android:id="@+id/fr_main_player"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#000"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

 

2. Play Video 

 

앞서 소개한 컴포넌트들을 하나의 코드로 조합하면 재생이 가능하다. 코드의 순서는 설명한 순서와 조금 다른데 이는 클래스 생성 후에 주입하기 위함이다. exoPlayer.prepare(mediaSource)는 영상 정보를 가져오는 작업이고 fr_main_player.player.playWhenReady는 준비되면 영상을 시작하는 함수다.

 

val mediaPath = "http://somewhere...."
val bandwidthMeter = DefaultBandwidthMeter()
val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory(bandwidthMeter)
val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory)
val exoPlayer = ExoPlayerFactory.newSimpleInstance(requireContext(), trackSelector)
val extractorFactory = ExtractorMediaSource.Factory(
    DefaultDataSourceFactory(
        context,
        Util.getUserAgent(context, context!!.applicationInfo.packageName)
    )
)
val mediaSource = extractorFactory.createMediaSource(Uri.parse(mediaPath))

fr_main_player.player = exoPlayer
exoPlayer.prepare(mediaSource)
fr_main_player.player.playWhenReady = true

 

3. Extension 

 

Player.Listener: 영상 재생중 로딩에 실패하거나 Track 속성이 바뀌거나 혹은 영상 재생이 완료된 경우에 대해서 리스너를 등록해줄 수 있는데 이 경우들은 뷰어에 리스너를 등록해서 구현이 가능하다. 아래 코드를 통해 어떤 경우에 대해서 콜백 호출이 가능한지 확인 해볼 수 있다. 추가로 아래 코드에선 영상 재생이 완료된 경우 다시 재생하도록 구현했다.

 

fr_main_player.player.addListener(object: Player.EventListener{
    override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters?) {}
    override fun onSeekProcessed() {}
    override fun onTracksChanged(trackGroups: TrackGroupArray?, trackSelections: TrackSelectionArray?) {}
    override fun onPlayerError(error: ExoPlaybackException?) {}
    override fun onLoadingChanged(isLoading: Boolean) {}
    override fun onPositionDiscontinuity(reason: Int) {}
    override fun onRepeatModeChanged(repeatMode: Int) {}
    override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {}
    override fun onTimelineChanged(timeline: Timeline?, manifest: Any?, reason: Int) {}
    override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
        if (playbackState == Player.STATE_ENDED) {
            fr_main_player.player.seekTo(0)
            fr_main_player.player.playWhenReady = true
        }
    }
})

 

CacheDataSource: 인터넷으로 영상을 받는 경우 여러번 재생을 할 때 마다 동일한 데이터를 계속 인터넷으로 불러오게돼 데이터를 낭비할 수도 있는 문제가 있다. ExoPlayer에서는 이 문제점을 해결하고자 별도의 미디어 데이터 저장 공간으로 Cache를 뒀다. 이것도 다양하게 커스터마이즈 할 수 있으나 가장 기본적인 사용 방법은 아래 코드와 같다. 

 

ExtractorMediaSource.Factory 함수에서 호출 할 수 있도록 임의의 클래스를 DataSource.Factory의 인터페이스를 구현한 형태로 만든다. 리턴 값으로는 CacheDataSource가 되는데 여기서 생성자에서 캐시가 가져야할 정보를 입력하게 된다. 아래 코드 보면 캐시의 크기도 설정 할 수 있도 플래그를 넣을 수 있는 것도 확인 할 수 있다.

 

private class CacheDataSourceFactory internal constructor(
    private val context: Context,
    private val defaultDataSourceFactory: com.google.android.exoplayer2.upstream.DataSource.Factory,
    private val maxCacheSize: Long,
    private val maxFileSize: Long,
    private val url: String
) : com.google.android.exoplayer2.upstream.DataSource.Factory {
    override fun createDataSource(): com.google.android.exoplayer2.upstream.DataSource {
        val evictor = LeastRecentlyUsedCacheEvictor(maxCacheSize)
        val simpleCache = SimpleCache(File(context.cacheDir, "media"), evictor)
        return CacheDataSource(
            simpleCache,
            defaultDataSourceFactory.createDataSource(),
            FileDataSource(),
            CacheDataSink(simpleCache, maxFileSize),
            CacheDataSource.FLAG_BLOCK_ON_CACHE or CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR,
            null
        )
    }
}

 

위 코드를 이용한 호출부는 다음과 같다. 앞서 설명한 CacheDataSource 팩토리 클래스에서 두번째 인자로 DataSourceFactory를 넣었는데 아래 구현부 코드를 확인해보면 이전에 만든 DefaultDataSourceFactory 클래스를 넣는 것을 볼 수 있다. 외부 데이터를 불러오는 작업은 기존 데이터 클래스를 따라 간다는 뜻이다.

 

val extractorCacheFactory = ExtractorMediaSource.Factory(
    CacheDataSourceFactory(requireContext(), DefaultDataSourceFactory(
        context,
        Util.getUserAgent(context, context!!.applicationInfo.packageName)
    ), MAX_CACHE_SIZE, MIN_CACHE_SIZE, mediaPath)
)

val mediaSource = extractorCacheFactory.createMediaSource(Uri.parse(mediaPath))

 

'컴퓨터공부 > 안드로이드' 카테고리의 다른 글

Kotlin - Coroutine  (0) 2020.04.15
Kotlin으로 깔끔한 Builder를 만들어보자  (0) 2020.04.14
Exoplayer2 사용하기  (0) 2020.04.12
FragmentManagers Android  (0) 2020.04.06
ViewModelProviders.of deprecated  (0) 2020.04.06
Exoplayer에 stetho 적용하기  (0) 2020.03.16

FragmentManagers Android

컴퓨터공부/안드로이드 2020. 4. 6. 15:56 Posted by 아는 개발자

FragmentManager는 동적인 UI를 제공하기 위한 클래스인 Fragment를 관리하는 컨트롤러 역할을 한다. Manager라는 이름에서 예상 할 수 있듯이 FragmentManager를 사용하면 현재 UI에 Fragment를 추가할 수도 있고 있는 것을 교체할 수도 있으며 제거까지 가능하다. 호출하는 함수는 Activity인지, Fragment인지에 따라 다른데 일반적으로 Activity는 supportFragmentManager를 호출하게 되고, Fragment는 childFragmentManager 또는 parentFragmentManager를 통해 호출한다. 지금까지 개발 할 때는 각각의 차이를 확인하지 않고 '일단 동작부터 되도록' 에 주안점을 뒀는데 이번 포스트를 통해서 각각의 차이점과 적절한 쓰임새를 정리해보려고 한다.

 

0. 정의

 

FragmentManager의 경우에는 안드로이드 문서도 그닥 꼼꼼히 정리 되어있지 않고 스택오버플로우에서도 관심있게 다루는 주제가 아니라 참고할 만한 글이 별로 없었다. 그래서 지금까지 개발하면서 내가 나름대로 내린 사전적(?) 정의는 이렇다.

  • supportFragmentManager(SFM): Activity랑 상호작용하는(interacting) Fragment를 관리하는 클래스. Activity 클래스에서 호출이 가능하며 Activity 고유의 클래스다.

  • parentFragmentManager(PFM): 부모 UI 컴포넌트(Activity일 수도 있고 Fragment 일 수도 있다) 고유의 FragmentManager. Fragment 클래스에서 호출이 가능하다.

  • childFragmentManager(CFM): Fragment 고유의 FragmentManager 클래스. Fragment 별로 모두 다르다.

1. 그림 

 

SFM과 CFM과는 달리 PFM은 고유의 값이 아니라 특정 객체를 가르키는 값이다.  아래의 그림처럼 PartyActivity, PartyFragment 내부에 여러개의 PayFragment를 둔다고 해보자. 그러면 이들의 관계는 PartyActivity가 최상위 부모, PartyFragment는 부모, PayFragment는 자손이 되는 형태가 될 것이다. 

 

PartyActivity -> PartyFragment -> PayFragment

 

그러면 각각의 UI 요소들이 갖고 있는 FragmentManager는 아래와 같은 형태를 따르게 된다. 붉은 선으로 표현한 부분은 서로 동일한 객체인 것을 의미한다. Activity의 SFM을 이용해서 PartyFragment를 관리하고 있으므로 PFM는 부모인 PartyActivity 의 고유 FragmentManager, SFM을 가리키게 된다. 마찬가지로 PayFragment는 PartyFragment의 CFM으로부터 생성 됐으므로 PayFragment의 PFM은 부모인 PartyFragment의 CFM을 가리키게 된다.

 

PayFragment를 PartyFragment의 CFM을 이용해서 생성한 경우

 

로그를 통해 객체의 값을 확인해보면 위와 같은 구조를 가지는 것을 확인 할 수 있다. PartyFragment의 PFM 값은 PartyActivity의 SFM과 동일하고, PayFragment의 PFM값은 PartyFragment의 CFM과 동일하다.

 

UI 클래스 별로 PFM, SFM, CFM의 값을 출력한 결과

2. 주의점

 

PayFragment를 PartyFragment의 CFM으로 생성하지 않고 PFM으로 생성하는 경우 다음과 같이 그림이 달라진다. PartyFragment의 PFM은 PartyActivity의 SFM이며 PayFragment의 생성주체는 PartyActivity의 SFM이기 때문에, PayFragment의 PFM은 자연스레 PartyActivity의 SFM을 가리키게 된다.

 

PayFragment를 PartyFragment의 PFM으로 생성한 경우

 

물론 이런 형태여도 동작하는데는 큰 문제가 없을 것이다. 하지만 FragmentManager는 Fragment와 생성주기를 함께 하기 때문에, PartyFragment가 삭제되도 PayFragment는 SFM의 인스턴스로 남아있게 된다. 물론 이게 남아있는다고 동작상에 크게 흠을 주거나 메모리 릭을 유발하는 것은 아니지만 의도하지 않은 형태로 개발을 하다보면 정체모를 버그가 튀어나올 수 있으니 염두에두고 있는게 좋을 것 같다.

ViewModelProviders.of deprecated

컴퓨터공부/안드로이드 2020. 4. 6. 15:01 Posted by 아는 개발자

ViewModel을 주입할 때 주로 사용하는 ViewModelProviders 클래스는 lifecycle-extension 라이브러리가 2.2.0 버전업 되면서 통째로 Deprecated가 됐다. 하지만 ViewModelProvider(뒤에 s만 빠진 클래스가 맞다) 클래스를 통해 동일한 기능을 수행하도록 할 수 있다.

 

BEFORE

@Module(includes = [BaseActivityModule::class])
abstract class MainActivityModule {
    @Binds
    abstract fun provideActivity(activity: MainActivity): FragmentActivity

    @Module
    companion object {
        @Provides
        @JvmStatic
        fun provideViewModel(activity: FragmentActivity, viewModelFactory: ViewModelFactory): MainViewModel
                = ViewModelProviders.of(activity).get(MainViewModel::class.java)
    }
}

AFTER

@Module(includes = [BaseActivityModule::class])
abstract class MainActivityModule {
    @Binds
    abstract fun provideActivity(activity: MainActivity): FragmentActivity

    @Module
    companion object {
        @Provides
        @JvmStatic
        fun provideViewModel(activity: FragmentActivity, viewModelFactory: ViewModelFactory): MainViewModel
                = ViewModelProvider(activity, viewModelFactory).get(MainViewModel::class.java)
    }
}

 

fragment의 부모 activity 를 넘겨서 fragment와 activity가 동일한 viewmodel을 바라보게하는 기능도 정상적으로 동작한다

 

@Module(includes = [BaseFragmentModule::class])
abstract class PayFragmentModule {
    @Binds
    abstract fun provideFragment(fragment: PayFragment): Fragment

    @Module
    companion object {
        @Provides
        @JvmStatic
        fun provideViewModel(fragment: Fragment, viewModelFactory: ViewModelFactory): PartyViewModel 
                = ViewModelProvider(fragment.requireActivity(), viewModelFactory).get(PartyViewModel::class.java)
    }
}

 

함수단위면 몰라도 클래스 하나를 통째로 Deprecated 하는 것은 흔치 않는 일인 것 같은데 기존에 있는 ViewModelProvider 클래스에만 집중해서 개선하기 위함이지 않을까 조심스럽게 추측해본다. 그리고 기존에 있던 ViewModelProviders 코드도 대부분 ViewModelProvider를 호출하는 형태였기 때문에 둘이 겹치는 점도 많았던 것 같고. 

Exoplayer에 stetho 적용하기

컴퓨터공부/안드로이드 2020. 3. 16. 10:54 Posted by 아는 개발자

0. 소개

 

Exoplayer는 안드로이드 영상 재생플레이어로 많이 사용되는 오픈소스 프로젝트다. 요즘처럼 스트리밍으로 조각된 영상을 받는 경우엔 클라이언트의 재생 플레이어에서도 서버로 여러번 영상에 대한 요청을 보내게 되는데 이때 안드로이드 네트워크 인스펙터인 stetho를 사용하면 Exoplayer에서 보낸 요청들을 볼 수 있어서 디버깅 할 때 편리하다.

 

implementation 'com.google.android.exoplayer:extension-okhttp:2.7.0'

 

1. Exoplayer + Stetho

 

build.gradle 파일에 exoplayer extension 라이브러리를 추가한다. 감사하게도 exoplayer에서 stetho를 이용해 디버깅을 할 수 있도록 사전 작업을 해두었다.

 

 

라이브러리를 추가한 다음에는 안드로이드 Exoplayer 코드에서 http 요청 부분을 아래의 코드로 변경한다. OkHttpDataSourceFacotry는 DefaultHttpDataSourceFactory와 거의 동일해 동작에는 큰 차이가 없다고 봐도 무방하다.

 

+//    private val mMediaDataSourceFactory = DefaultDataSourceFactory(context, bandwidthMeter,
+//            DefaultHttpDataSourceFactory(Util.getUserAgent(context, context.applicationInfo.packageName), bandwidthMeter))
+
+    private val mMediaDataSourceFactory = DefaultDataSourceFactory(context, bandwidthMeter,
+            OkHttpDataSourceFactory(OkHttpClient.Builder().addNetworkInterceptor(StethoInterceptor()).build(), Util.getUserAgent(context, context.applicationInfo.packageName), bandwidthMeter))

 

2. Stetho

 

만약 애플리케이션에 StethoInterceptor를 사용하지 않고 있었다면 아래의 작업을 추가해야한다. build.gradle 파일에 stetho 라이브러리를 추가하고

 

implementation "com.facebook.stetho:stetho-okhttp3:1.5.1"

 

Application으로 선언된 클래스에 Stetho를 초기화해준다

 

public class DemoApp extends DaggerApplication {
@Override
    public void onCreate() {
        super.onCreate();
        Stetho.initializeWithDefaults(this);

 

3. 결과 

 

테스트 한 결과 영상 파일들을 쪼개서 보낸 요청들을 Stetho를 이용해서 확인 할 수 있었다