Nobollel開発者ブログ

Nobollelのエンジニアが、UnityやCocos2d-xの旬な情報・技術を紹介します。

Unityでサーバーを使わずにアプリの最新バージョンがあるかどうかチェックし、ストアに誘導する

どうも、エンジニアの水津です
今回はUnityにおいて、アプリの更新がある場合にストアに誘導するポップアップを表示する実装になります

バージョンアップを促すポップアップの実装は、大規模なスマホゲームアプリの場合にサーバーとの整合性を図るためによく実装されています
ただ、そういったアプリはサーバー側でクライアントのバージョンも管理することが多く、その場合アプリの最新バージョンがストア上がったのを確認してからサーバー側のクライアントバージョンを書き換えるという手間がかかります
また、カジュアルゲームなどではそもそもサーバーを使用しないことがあるため、上記方法ではバージョンアップを通知することができません
今回はカジュアルゲームの実装でバージョンアップ通知をしたかったため、サーバーを使わないバージョンアップ通知を実装してみました

使用するライブラリ

以下の2つのライブラリを使用します
1.Html Agility Pack
htmlagilitypack.codeplex.com

2.Mobile Native PopUps
https://www.assetstore.unity3d.com/jp/#!/content/18479

1についてはAndroidにおいてHTMLのパースに使用します
SGMLReaderというライブラリも試してみたのですが、Play Storeのwebページのパースが正常に行えなかったので、こちらのライブラリを使うことを推奨します
サイトからダウンロードして解凍したら、Net20と書かれているディレクトリのHtmlAgilityPack.dllをUnityのスクリプトのあるディレクトリに入れてください

2のアセットについては更新を促すポップアップの表示に使用します
ポップアップの表示とボタン選択のコールバックが受け取れれば、なんでも構いません

ソースコード

全体のソースコードは以下のようになります

using UnityEngine;
using System;
using System.Collections;
using System.Xml;
using HtmlAgilityPack;

public class VersionChecker : MonoBehaviour
{
    string _storeUrl = "";

    void Start() {
        CurrentVersionCheck ();

        if (LoadInvalidVersionUpCheck ()) {
            return;
        }
       #if UNITY_IOS
        StartCoroutine(VersionCheckIOS());
       #elif UNITY_ANDROID
        StartCoroutine(VersionCheckAndroid());
       #endif
    }

    const string INVALID_VERSION_UP_CHECK_KEY = "InvalidVersionUpCheck";

    bool LoadInvalidVersionUpCheck() {
        return PlayerPrefs.GetInt (INVALID_VERSION_UP_CHECK_KEY, 0) != 0;
    }

    void SaveInvalidVersionUpCheck(bool invalid) {
        PlayerPrefs.SetInt(INVALID_VERSION_UP_CHECK_KEY, invalid ? 1 : 0);
    }

    const string CURRENT_VERSION_CHECK_KEY = "CurrentVersionCheck";

    void CurrentVersionCheck() {
        var version = PlayerPrefs.GetString(CURRENT_VERSION_CHECK_KEY, "");
        if (version != Application.version) {
            PlayerPrefs.SetString (CURRENT_VERSION_CHECK_KEY, Application.version);
            SaveInvalidVersionUpCheck (false);
        }
    }

    IEnumerator VersionCheckIOS() {
        var url = string.Format("https://itunes.apple.com/lookup?bundleId={0}", Application.bundleIdentifier);
        WWW www = new WWW(url);
        yield return www;

        if (string.IsNullOrEmpty(www.error) && !string.IsNullOrEmpty (www.text)) {
            var lookupData = JsonUtility.FromJson<AppLookupData> (www.text);
            if (lookupData.resultCount > 0 && lookupData.results.Length > 0) {
                var result = lookupData.results [0];
                if (VersionComparative (result.version)) {
                    ShowUpdatePopup (result.trackViewUrl);
                }
            }
        }
    }

    IEnumerator VersionCheckAndroid() {
        var url = string.Format("https://play.google.com/store/apps/details?id={0}", Application.bundleIdentifier);

        WWW www = new WWW(url);
        yield return www;
        if (string.IsNullOrEmpty(www.error) && !string.IsNullOrEmpty (www.text)) {
            var htmlDoc = new HtmlDocument ();
            htmlDoc.LoadHtml (www.text);
            var node = htmlDoc.DocumentNode.SelectSingleNode ("//div[@itemprop=\"softwareVersion\"]");
            if (node != null) {
                if (VersionComparative (node.InnerText)) {
                    ShowUpdatePopup (url);
                }
            }
        }
    }

    bool VersionComparative(string storeVersionText) {
        if (string.IsNullOrEmpty (storeVersionText)) {
            return false;
        }
        try {
            var storeVersion = new Version (storeVersionText);
            var currentVersion = new Version (Application.version);

            if (storeVersion.CompareTo(currentVersion) > 0) {
                return true;
            }
        } catch (Exception e) {
            Debug.LogErrorFormat("{0} VersionComparative Exception caught.", e);
        }

        return false;
    }

    void ShowUpdatePopup(string url) {
        if (string.IsNullOrEmpty (url)) {
            return;
        }
        _storeUrl = url;
       #if !UNITY_EDITOR
        if (Application.systemLanguage == SystemLanguage.Japanese)
        {
            string title = "アプリの更新があります";
            string message = "更新しますか?";
            string yes = "はい";
            string no = "いいえ";
            MobileNativeDialog dialog = new MobileNativeDialog(title, message, yes, no);
            dialog.OnComplete += OnPopUpClose;
        }
        else {
            string title = "There is an update of the application";
            string message = "Do you want to update the application?";
            string yes = "Yes";
            string no = "No";
            MobileNativeDialog dialog = new MobileNativeDialog(title, message, yes, no);

            dialog.OnComplete += OnPopUpClose;
        }
       #endif
    }


    private void OnPopUpClose(MNDialogResult result)
    {
        switch (result)
        {
        case MNDialogResult.YES:
            Application.OpenURL (_storeUrl);
            break;
        case MNDialogResult.NO:
            SaveInvalidVersionUpCheck (true);
            break;
        }
    }
}


[Serializable]
public class AppLookupData {
    public int resultCount;
    public AppLookupResult[] results;

}

[Serializable]
public class AppLookupResult {
    public string version;
    public string trackViewUrl;
}

これをシーン上のオブジェクトにAddComponentすれば自動的にバージョンチェックが行われます
ただ、通信中やポップアップ表示中にシーンが遷移すると処理が中断したり挙動がおかしくなるので注意してください

各部分解説

    void Start() {
        CurrentVersionCheck ();

        if (LoadInvalidVersionUpCheck ()) {
            return;
        }
       #if UNITY_IOS
        StartCoroutine(VersionCheckIOS());
       #elif UNITY_ANDROID
        StartCoroutine(VersionCheckAndroid());
       #endif

    }

    const string INVALID_VERSION_UP_CHECK_KEY = "InvalidVersionUpCheck";

    bool LoadInvalidVersionUpCheck() {
        return PlayerPrefs.GetInt (INVALID_VERSION_UP_CHECK_KEY, 0) != 0;
    }

    void SaveInvalidVersionUpCheck(bool invalid) {
        PlayerPrefs.SetInt(INVALID_VERSION_UP_CHECK_KEY, invalid ? 1 : 0);
    }

    const string CURRENT_VERSION_CHECK_KEY = "CurrentVersionCheck";

    void CurrentVersionCheck() {
        var version = PlayerPrefs.GetString(CURRENT_VERSION_CHECK_KEY, "");
        if (version != Application.version) {
            PlayerPrefs.SetString (CURRENT_VERSION_CHECK_KEY, Application.version);
            SaveInvalidVersionUpCheck (false);
        }
    }

この部分はバージョンアップチェックを行うかどうかを確認しています
バージョンアップ確認ポップアップで一度でもいいえを選択している場合、以後バージョンアップチェックを行わないようにしています
ただ、いいえを選択した後にアプリのバージョンが変わっている場合、再度バージョンアップチェックを有効にしています
バージョンアップチェックを行う場合、IOSAndroidで分岐します

   IEnumerator VersionCheckIOS() {
        var url = string.Format("https://itunes.apple.com/lookup?bundleId={0}", Application.bundleIdentifier);
        WWW www = new WWW(url);
        yield return www;

        if (string.IsNullOrEmpty(www.error) && !string.IsNullOrEmpty (www.text)) {
            var lookupData = JsonUtility.FromJson<AppLookupData> (www.text);
            if (lookupData.resultCount > 0 && lookupData.results.Length > 0) {
                var result = lookupData.results [0];
                if (VersionComparative (result.version)) {
                    ShowUpdatePopup (result.trackViewUrl);
                }
            }
        }
    }

iOSにおいてApp Storeのアプリのバージョンを取得しています
https://itunes.apple.com/lookup?bundleId=バンドル名"でアプリの詳細なデータをJSONで取得できるので、JSONをデシリアライズしアプリバージョンを取得しています
ストアのURLもJSONに含まれているので、もしバージョンアップしていたらそのストアのURLをポップアップに渡しています

   IEnumerator VersionCheckAndroid() {
        var url = string.Format("https://play.google.com/store/apps/details?id={0}", Application.bundleIdentifier);

        WWW www = new WWW(url);
        yield return www;
        if (string.IsNullOrEmpty(www.error) && !string.IsNullOrEmpty (www.text)) {
            var htmlDoc = new HtmlDocument ();
            htmlDoc.LoadHtml (www.text);
            var node = htmlDoc.DocumentNode.SelectSingleNode ("//div[@itemprop=\"softwareVersion\"]");
            if (node != null) {
                if (VersionComparative (node.InnerText)) {
                    ShowUpdatePopup (url);
                }
            }
        }
    }

AndroidにおいてPlay Storeのアプリのバージョンを取得しています
Androidはストア上の情報を取得する手段がないため、ストアのwebページのHTMLをパースしてバージョンを取得しています
ページのHTMLを全探索してitemprop属性がsoftwareVersionのものを探しているので、処理的には結構重い処理になっていると思います
もしPlay Storeのアプリページの構造が変わった場合、上記の方法では動かなくなるので注意してください

   bool VersionComparative(string storeVersionText) {
        if (string.IsNullOrEmpty (storeVersionText)) {
            return false;
        }
        try {
            var storeVersion = new Version (storeVersionText);
            var currentVersion = new Version (Application.version);

            if (storeVersion.CompareTo(currentVersion) > 0) {
                return true;
            }
        } catch (Exception e) {
            Debug.LogErrorFormat("{0} VersionComparative Exception caught.", e);
        }

        return false;
    }

ストアのバージョンとアプリのバージョンを比較して、ストアのバージョンが新しければtrueを返します

   void ShowUpdatePopup(string url) {
        if (string.IsNullOrEmpty (url)) {
            return;
        }
        _storeUrl = url;
       #if !UNITY_EDITOR
        if (Application.systemLanguage == SystemLanguage.Japanese)
        {
            string title = "アプリの更新があります";
            string message = "更新しますか?";
            string yes = "はい";
            string no = "いいえ";
            MobileNativeDialog dialog = new MobileNativeDialog(title, message, yes, no);
            dialog.OnComplete += OnPopUpClose;
        }
        else {
            string title = "There is an update of the application";
            string message = "Do you want to update the application?";
            string yes = "Yes";
            string no = "No";
            MobileNativeDialog dialog = new MobileNativeDialog(title, message, yes, no);

            dialog.OnComplete += OnPopUpClose;
        }
       #endif
    }


    private void OnPopUpClose(MNDialogResult result)
    {
        switch (result)
        {
        case MNDialogResult.YES:
            Application.OpenURL (_storeUrl);
            break;
        case MNDialogResult.NO:
            SaveInvalidVersionUpCheck (true);
            break;
        }
    }

ポップアップを表示して、YESが押されたらアプリのURLへ飛ばしています
文言などは適時適当に変更してください