멈추지 않고 끈질기게
[Unity][Android] Google AdMob 관련 이슈(can only be called from the main thread) 본문
[Unity][Android] Google AdMob 관련 이슈(can only be called from the main thread)
sam0308 2023. 10. 3. 16:09※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.
1. 서론
현재 제작중인 포트폴리오 프로젝트에 구글 애드몹을 이용하여 광고 기능을 추가하였습니다. 다행히 문서도 있고 내용을 정리한 포스팅들도 많아서 구현 자체는 어렵지 않게 할 수 있었습니다. 테스트 ID를 이용하여 에디터 상에서 테스트 광고 시청 및 보상 수령까지 확인하였습니다.
문제는 안드로이드 빌드를 뽑아서 디바이스에서 실행해보았더니, 보상 수령 로직이 제대로 동작하지 않고 있었습니다. 재접속 해보니 재화 및 광고 시청 횟수가 정상적으로 카운트 된 것으로 볼 때, 데이터 수정은 이루어졌지만 UI가 갱신되지 않은 이슈로 보였습니다. Android Logcat에서 확인해 본 결과, 리워드 지급 시점에 다음과 같은 예외가 발생하고 있었습니다.
// 로그 전문
UnityException: get_isActiveAndEnabled can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread when loading a scene.
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
at (wrapper managed-to-native) UnityEngine.Behaviour.get_isActiveAndEnabled(UnityEngine.Behaviour)
at UnityEngine.EventSystems.UIBehaviour.IsActive () [0x00001] in C:\Users\user\TestProject\Library\PackageCache\com.unity.ugui@1.0.0\Runtime\EventSystem\UIBehaviour.cs:28
at TMPro.TextMeshProUGUI.SetVerticesDirty () [0x00001] in C:\Users\user\TestProject\Library\PackageCache\com.unity.textmeshpro@3.0.6\Scripts\Runtime\TextMeshProUGUI.cs:101
at TMPro.TMP_Text.set_text (System.String value) [0x00058] in C:\Users\user\TestProject\Library\PackageCache\com.unity.textmeshpro@3.0.6\Scripts\Runtime\TMP_Text.cs:132
at UI_MainPopUp.UpdateGoldGem () [0x00001] in C:\Users\user\TestProject\Assets\Scripts\UI
2. Google AdMob 관련 이슈
구글링 끝에 하기 구글 개발자 블로그에서 답을 찾아낼 수 있었습니다.
https://ads-developers.googleblog.com/2016/04/handling-android-ad-events-in-unity.html
해당 포스팅의 내용과 제가 아는 유니티 관련 지식을 조합하여 정리한 결과, 이슈 원인은 다음과 같습니다.
- 1) 안드로이드 환경에서는 구글 애드몹의 Ad Event Handler가 메인 스레드에서 실행되지 않는다.
- 2) 그런데 유니티에서 Monobehavior를 상속받는 객체는 반드시 메인 스레드에서 제어해야 한다.
(어길 시 UnityException : ~ can only be called from the main thread 발생)
- 3) 1, 2번에 의해, Ad Event Handler에 Monobehavior 객체를 수정하는 내용을 포함시킬 수 없다.
그런데 보상을 수령하면 당연히 재화가 증가하고, 재화가 증가하면 UI를 갱신해야 합니다. 보상이 꼭 재화가 아니더라도 보상 획득 팝업 등 어떤 식으로든 UI를 갱신하기 마련이므로, 보상 수령 로직을 구글 애드몹에서 제공하는 핸들러를 통해 실행할 수 없다는 결론이 나옵니다. 에디터에서 먼저 테스트해보면서 핸들러가 잘 구현되어 있어서 보상 수령 로직을 전달하기 편리하다고 생각했는데, 정작 빌드를 뽑았더니 핸들러를 이용할 수 없다니 좀 당혹스러웠습니다.
결국 상기 포스팅에서 언급한 해결책은 이벤트 핸들러에서는 플래그만 변경하고, Update()에서 플래그를 체크하여 원하는 로직을 실행하라는 것이었습니다. 다음은 해당 솔루션을 포트폴리오 프로젝트에 적용한 모습입니다.
// 보상 수령용 플래그
public bool AdPaid { get; private set; } = false;
// 보상 수령 로직 저장용 대리자
Action _rewardCallback;
public void Init()
{
// 보상 수령용 오브젝트 생성
GameObject rewardReceiver = new GameObject() { name = "AdRewardReceiver" };
UnityEngine.Object.DontDestroyOnLoad(rewardReceiver);
rewardReceiver.AddComponent<RewardReceiver>();
MobileAds.Initialize(initStatus => { });
LoadRewardedAd();
}
// ------------------------------------------------------------------------------
_rewardedAd.OnAdPaid += HandleOnAdPaid;
public void HandleOnAdPaid(AdValue value)
{
Debug.Log("### AdPaid");
AdPaid = true;
}
// ------------------------------------------------------------------------------
public void ShowRewardedAds(Action rewardCallback, Action<Reward> rewardEarnedCallback = null)
{
if (_rewardedAd != null && _rewardedAd.CanShowAd())
{
// 보상 수령 내용을 _rewardCallback에 저장
_rewardCallback = rewardCallback;
_rewardedAd.Show(rewardEarnedCallback);
}
else
{
Managers.UI.OpenNotice(ConstValue.Notice_AdNotPrepared);
LoadRewardedAd();
}
}
public void GetReward()
{
// 보상 지급
_rewardCallback?.Invoke();
// 초기화
AdPaid = false;
_rewardCallback = null;
}
// 메인 스레드에서 광고 보상을 수령하기 위한 클래스
public class RewardReceiver : MonoBehaviour
{
void Update()
{
if(Managers.Ad.AdPaid == false)
return;
Managers.Ad.GetReward();
}
}
우선 AdManager에 보상 수령 플래그(bool)와 보상 수령 로직을 저장해둘 대리자(Action)를 추가했습니다. 그리고 AdManager는 싱글톤으로 설계한 매니저 클래스이므로, 대신 Update()를 돌며 플래그 체크 후 보상 수령 로직을 실행할 객체를 생성하도록 했습니다.
구글 애드몹의 이벤트 핸들러 중 OnAdPaid를 통해 보상 수령 플래그를 true로 설정하도록 했으며, 기존의 광고 시청 함수를 수정했습니다. 원래 보상 수령 로직을 Action<Reward> 타입의 대리자로 전달, RewardedAd.Show()의 매개변수로 사용하여 자동으로 보상 수령이 실행되도록 했는데, 해당 부분이 이슈를 발생시키고 있었습니다. 따라서 보상 수령 로직을 Action 타입 대리자로 받아 저장해두고(_rewardCallback), RewardedAd.Show()에는 별도로 콜백함수를 넘기지 않고 null을 넘겨주었습니다.
정리하자면 광고 시청 시, 1) 보상 수령 로직을 대리자로 저장 2) 보상 수령 시 플래그 true로 설정(OnAdPaid) 3) RewardReceiver의 Update()에서 플래그 체크 후 보상 수령 로직 실행(메인 스레드) 순으로 진행되게 수정하였습니다. 실제로 안드로이드 빌드를 뽑아 테스트해보니 보상 로직이 정상 실행됨을 확인할 수 있었습니다.
3. 결론
해당 포스팅은 다른 사이트에서 달린 링크를 통해 찾았는데, 그 사이트의 댓글에서 "거의 4년이 지났는데 아직도 발생한다"는 댓글이 있었습니다. 해당 댓글이 2021년에 달렸으니 거의 6년이 되어간다는 얘기인데, 유니티의 대처가 너무 늦는거 같아 아쉬운 마음이 듭니다. 언젠가 해결되어 애드몹의 핸들러를 통해 깔끔하게 구현할 수 있었으면 좋겠습니다.
'Unity' 카테고리의 다른 글
[Unity] 롤링 배너 구현 (1) | 2023.10.24 |
---|---|
[Unity][기타] 데이터 경로 관련 (0) | 2023.10.03 |
[Unity] 클릭/터치 인터페이스(IPointerClickHandler, IPointerDownHandler, IPointerUpHandler) (1) | 2023.09.06 |
[Unity] Addressable 기능 (0) | 2023.07.27 |
[Unity][포트폴리오] Rigidbody.AddForce()가 제대로 동작하지 않는 이슈 (0) | 2023.07.03 |