본문 바로가기
안드로이드

[안드로이드] 앱 오프닝 광고 구현 Version: Kotlin

by keel_im 2022. 1. 14.
반응형

자바를 사용하시거나 기존 수정전 코드를 보고 싶으시면 아래 코드를 먼저 봐주세요!

2021.04.18 - [안드로이드] - [안드로이드] 앱 오프닝 광고 구현 [최근 업데이트]

 

[안드로이드] 앱 오프닝 광고 구현 [최근 업데이트]

오늘은 애드몹에 추가된 앱 오프닝 광고 구현 예시 최신을 적어보고자 합니다. 기존의 적어둔 포스트를 수정하였습니다. 사실 아래 코드는 Google 에서 만든 레퍼런스를 조금 수정하여 저의 애플

keelim.tistory.com

 

앱 오프닝 광고를 사용해보자.

아래 그림처럼 액티비티 생명주기 시마다, 화면을 따로 띄워주는 방식에 광고를 애드몹에서 제공을 하고 있습니다.  물론 샘플 코드도 있고 자바 방식도 저번에 작성한 글에서도 있지만, 무엇인가 아쉬워 코틀린 버전으로 바꾸었습니다. 

앱 오프닝 광고

class AppOpenManager @Inject constructor(): LifecycleObserver {
    private var appOpenAd: AppOpenAd? = null
    private var currentActivity: Activity? = null
    private val AD_UNIT_ID = "ca-app-pub-3940256099942544/3419835294"
    private val adRequest: AdRequest by lazy { AdRequest.Builder().build() }
    private lateinit var application: Application
    private var isShowingAd = false
    private val isAdAvailable: Boolean
        get() = appOpenAd != null && wasLoadTimeLessThanNHoursAgo()
    private var loadTime: Long = 0

    fun initialize(application: Application){
        this.application = application

        application.registerActivityLifecycleCallbacks(object: ActivityLifecycleCallbacks{
            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
            override fun onActivityStarted(activity: Activity) {
                currentActivity = activity
                showAdIfAvailable()
                Timber.d("onStart")
            }
            override fun onActivityResumed(activity: Activity) { currentActivity = activity }
            override fun onActivityStopped(activity: Activity) {}
            override fun onActivityPaused(activity: Activity) {}
            override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
            override fun onActivityDestroyed(activity: Activity) { currentActivity = null }
        })
        ProcessLifecycleOwner.get().lifecycle.addObserver(this)
    }
    /**
     * Request an ad
     * Have unused ad, no need to fetch another.
     */
    fun fetchAd() {
        if (isAdAvailable) {
            return
        }
        /**
         * Called when an app open ad has loaded.
         * @param ad the loaded app open ad.
         */
        /**
         * Called when an app open ad has failed to load.
         * @param loadAdError the error.
         * Handle the error.
         */
        val loadCallback: AppOpenAdLoadCallback = object : AppOpenAdLoadCallback() {
            /**
             * Called when an app open ad has loaded.
             * @param ad the loaded app open ad.
             */
            override fun onAdLoaded(ad: AppOpenAd) {
                appOpenAd = ad
                loadTime = Date().time
            }
            /**
             * Called when an app open ad has failed to load.
             * @param loadAdError the error.
             * Handle the error.
             */
            override fun onAdFailedToLoad(loadAdError: LoadAdError) {}
        }
        AppOpenAd.load(
            application,
            if(BuildConfig.DEBUG){
                AD_UNIT_ID
            } else{
                BuildConfig.AD_OPEN_ID
            },
            adRequest,
            AppOpenAd.APP_OPEN_AD_ORIENTATION_PORTRAIT,
            loadCallback
        )
    }
    /**
     * Only show ad if there is not already an app open ad currently showing
     * and an ad is available.
     */
    fun showAdIfAvailable() {
        if (!isShowingAd && isAdAvailable) {
            Timber.d("Will show ad.")
            appOpenAd!!.apply {
                fullScreenContentCallback = object : FullScreenContentCallback() {
                    /**
                     * Set the reference to null so isAdAvailable() returns false.
                     */
                    override fun onAdDismissedFullScreenContent() {
                        appOpenAd = null
                        isShowingAd = false
                        fetchAd()
                    }

                    override fun onAdFailedToShowFullScreenContent(adError: AdError) {}
                    override fun onAdShowedFullScreenContent() {
                        isShowingAd = true
                    }
                }
                show(currentActivity)
            }
        } else {
            Timber.d("Can not show ad.")
            fetchAd()
        }
    }

    private fun wasLoadTimeLessThanNHoursAgo(numHours: Long = 4): Boolean {
        val dateDifference = Date().time - loadTime
        val numMilliSecondsPerHour = 3600000
        return dateDifference < numMilliSecondsPerHour * numHours
    }
}

저는 위와 같은 코드로 수정하였습니다. 먼저, 수정할때, 중점적으로 생각한 부분은 3가지 입니다. 

1. DI 를 활용하자. 

2. deprecated 된 라이브러리를 대체해보자.

3. 코틀린의 장점을 활용하자.

입니다. 

먼저 DI는 Hilt 를 사용하였습니다. 

class AppOpenManager @Inject constructor()

위와 같이 코드를 작성하게 되면, 가장 간단한 형태로 모듈 생성없이 객체 주입이 가능합니다.  위와 같이 클래스를 선언하게 되면, 디폴트 생성자를 제대로 사용하지 못하기 때문에 initialize() 라는 함수에서 application 인스턴스를 받도록 하였습니다. 

fun initialize(application: Application){
        this.application = application
        application.registerActivityLifecycleCallbacks(object: ActivityLifecycleCallbacks{
            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
            override fun onActivityStarted(activity: Activity) {
                currentActivity = activity
                showAdIfAvailable()
                Timber.d("onStart")
            }
            override fun onActivityResumed(activity: Activity) { currentActivity = activity }
            override fun onActivityStopped(activity: Activity) {}
            override fun onActivityPaused(activity: Activity) {}
            override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
            override fun onActivityDestroyed(activity: Activity) { currentActivity = null }
        })
        ProcessLifecycleOwner.get().lifecycle.addObserver(this)
    }

2. deprecated 된 라이브러리를 지워보자. 

@OnLifecycleEvent(Lifecycle.Event.ON_START)
    public void onStart() {
        showAdIfAvailable();
        Timber.d("onStart");
    }

기존에는 @OnLifecycleEvent(Lifecycle.---) 처럼 라이프사이클을 가지고 있지 않는 AppOpenAdManger가 라이프사이클을 인식할 수 있도록 도와주는 라이브러리가 있었습니다. 하지만, deprecated 되고 이를 해결한 방법은 application.registerActivityLifeCycleCallbacks() 를 사용하여 인식을 할 수 있게하는 방법입니다. 이를 initialize() 에서 선언하면, 대체 가능한 코드를 작성할 수 있습니다. 

3. Kotlin 의 장점을 사용해보자.

사실 코틀린의 장점은 Java 보다 유연하다는 것이라고 생각합니다. 예를 들어 

AppOpenAd.load(
            application,
            if(BuildConfig.DEBUG){
                AD_UNIT_ID
            } else{
                BuildConfig.AD_OPEN_ID
            },
            adRequest,
            AppOpenAd.APP_OPEN_AD_ORIENTATION_PORTRAIT,
            loadCallback
        )

요런 코드 같은 경우 빌드 타입에 따라 테스트 아이디를 넣을지, 아니면 발급 받은 아이디를 넣을지 고민을 해야할때,  Java 에 경우 겉에서 래핑하는 형태이지만, Kotlin 은 위와 같은 방식으로 가능합니다. 

또한, 프로퍼티를 사용함에 있어서도 커스텀으로 적용이 가능하니 참고하시면 좋을 것 같습니다. 

저는 위와 같은 과정으로 최종적으로 맨위에 코드를 작성하였습니다. 


오늘은 앱 오프닝 광고에 대하여 작성하였습니다. 

개인적으로 앱에 화면을 개발하는 느낌하고는 다르게 이게 SDK 쪽으로 안드로이드를 개발하는 것인가에 대한 혹은 이렇게해서 라이브러리를 만드는 것에 대한 재미를 느낄 수 있었습니다. 

 

반응형

댓글