[Unity] Android版だけAdMobの広告のリワードが受け取れなくてハマった話

# 起きたこと
Unity製のアプリにAdMobの広告を組み込み、Rewarded型の広告を視聴した後にゲーム内の特典を与えるような実装をしていました。実装が完了し、Unityで動かしてOK、iOS実機で動かしてOKだったのでヨシヨシと思っていたのですが、最後に確認したAndroid実機でNGでした。ググってもなかなか答えに辿りつかず困ったのでここに吐き出しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296554/c299ac3e-9c3f-76e7-113d-715a4dad641d.png)

ちなみに広告の実装に関しては以下の公式サイトの説明を読みながら進めていました。
https://developers.google.com/admob/unity/rewarded?hl=ja

# NGだった時の実装
以下のような形で、rewardedAd.Showの中に処理を書き、PlayerPrefsを更新することでアプリ全体からspecial_item_countの値にアクセスできるようにしていました。
``` C#
// 広告の初期化処理・読み込み処理
割愛

// 広告の表示処理
public void ShowRewardedAd()
{
    if (rewardedAd != null && rewardedAd.CanShowAd())
    {
        rewardedAd.Show((Reward reward) =>
        {
            // 表示終了：特典を与える
            Debug.Log("The ad was successfully shown.");
            PlayerPrefs.SetInt("special_item_count", 1);
        });
    }
}
```

# OKな実装
Showの中の処理は非同期で呼び出されるため、ここからPlayerPrefsにアクセスすることはAndroidではできないことが原因でした。MainThreadからPlayersPrefsにアクセスするように変更したところOKになりました。
``` C#
private bool isRewarded = false;

void Update() {
    if (isRewarded) {
        PlayerPrefs.SetInt("special_item_count", 1);
        isRewarded = false;
    }
}

// 広告の表示処理
public void ShowRewardedAd()
{
    if (rewardedAd != null && rewardedAd.CanShowAd())
    {
        rewardedAd.Show((Reward reward) =>
        {
            // 表示終了：特典を与える
            Debug.Log("The ad was successfully shown.");
            isRewarded = true;
        });
    }
}
```

# 有効だった解析方法
android実機をPCに繋いだ上で、adbコマンドを叩いてlogcatを見ました。
ちなみに素のlogcatだとかなりたくさんログが出てしまうので、以下のような形でfilterするのがおすすめ。
```
$ adb logcat -s Unity:*
```

すると、以下のようなエラーが広告終了直後に出ていました。
一番上の「The ad was successfully shown.」は上記の実装例にも書いてある自分で仕組んだログです。
```
Unity   : The ad was successfully shown.
Unity   : UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
Unity   : UnityEngine.Logger:Log(LogType, Object)
Unity   : UnityEngine.Debug:Log(Object)
Unity   : <>c:<ShowRewardedAd>b__8_0(Reward) (at /Users/xxxx/xxxx/xxxx.cs:xx)
Unity   : GoogleMobileAds.Api.<RegisterAdEvents>c__AnonStorey4:<>m__0()
Unity   : GoogleMobileAds.Api.MobileAds:RaiseAction(Action)
Unity   : GoogleMobileAds.Api.RewardedInterstitialAd:<RegisterAdEvents>m__6(Object, Reward)
Unity   : GoogleMobileAds.Android.RewardedInterstitialAdClient:onUserEarnedReward(String, Single)
Unity   : System.Reflection.RuntimeMethodInfo:Invoke(Object, BindingFlags, Binder, Object[], CultureInfo)
Unity   : System.Reflection.MethodBase:Invoke(Object, Object[])
Unity   : UnityEngine.AndroidJavaProxy:Invoke(String, Object[]) (at /home/bokken/build/output/unity/unity/Modules/AndroidJNI/AndroidJava.cs:174)
Unity   : UnityEngine.AndroidJavaProxy:Invoke(String, AndroidJavaObject[]) (at /home/bokken/build/output/unity/unity/Modules/AndroidJNI/AndroidJava.cs:222)
Unity   : UnityEngine.Android
Unity   : Uploading Crash Report
Unity   : UnityException: TrySetInt can only be called from the main thread.
```
この最後に書かれている「TrySetInt can only be called from the main thread.」が決定的なヒントとなり、その後すぐに解決できましたとさ。

# 反省点
Unityはクロスプラットフォーム開発の一種とみなせると思っていますが、iOSでOKだからと言ってAndroidでもOKとは必ずしも限らない、というのがわかりました。こういうの頭から抜けがちなので注意したい。。。
あとは、急がば回れでちゃんとlogcatと睨めっこするフェーズを早めにやればよかった。。。