インストールされているアプリのバージョンをチェックし、ストアに誘導する

#はじめに#
端末にインストールされているアプリのバージョンを取得し、サーバーで保持しているアプリのバージョンと比較、端末側のバージョンが低ければストアに誘導する等の処置を行いました。

#必要性#
外部データ（AssetBundle等）を扱っていると、端末側の処理と外部データとで整合性がとれなくなる場合がでてきてしまったからです。
ユーザーは必ずしも最新バージョンのアプリに更新している訳ではなく、古いバージョンのまま最新バージョンでしか動かない外部データをダウンロードした場合、予想外の挙動をする可能性があります。最悪の場合アプリはクラッシュするかもしれません。

#Android#

```csharp:AndroidVersionCheck.cs

#if UNITY_ANDROID

    /// <summary>
    /// バージョンネームを取得する
    /// PlayerSettings上では[ Bundle Version ]の値
    /// </summary>
    string GetAppVersionName_Android ()
    {
        AndroidJavaObject   pInfo       = this.GetPackageInfo();
        string versionName = pInfo.Get<string>( "versionName" );
        return versionName;
    }



    /// <summary>
    /// Version情報を保持しているPackageInfoクラスを取得する
    /// </summary>
    AndroidJavaObject GetPackageInfo()
    {
        AndroidJavaClass    unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        AndroidJavaObject   context     = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity").Call<AndroidJavaObject>("getApplicationContext");
        AndroidJavaObject   pManager    = context.Call<AndroidJavaObject>("getPackageManager");
        AndroidJavaObject   pInfo       = pManager.Call<AndroidJavaObject>( "getPackageInfo", context.Call<string>("getPackageName"), pManager.GetStatic<int>("GET_ACTIVITIES") );

        return pInfo;
    }

#endif //UNITY_ANDROID

```
Androidでしかビルドされないようにしている理由は、他のプラットフォームでビルドするとエラーになってしまうからです。


#iOS#

```objc++:BundleVersion.mm
char* MakeStringCopy ( const char* string )
{
    if (string == NULL) return NULL;

    char* res = (char*)malloc(strlen(string) + 1);
    strcpy(res, string);

    return res;
}


extern "C"
{

    char* GetVersionName_()
    {
        NSString *buildNo = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];

        return MakeStringCopy( [buildNo UTF8String] );
    }

}

```
上記のObjective-C++で書いたこのソースを「Plugins/iOS」フォルダ以下に保存します。


```csharp:IPhoneBundleVersion.cs
using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;



public class IPhoneBundleVersionManager
{

    [DllImport("__Internal")]
    private static extern string GetVersionName_();


    /// <summary>
    /// iOS版でのバージョンを取得する
    /// </summary>
    public static string GetAppVersionName ()
    {
        string versionName = GetVersionName_();
        return versionName;
    }

}

```
先ほどのObjective-C++で書いたネイティブコードを呼び出す上記のスクリプトを「Plugins/」以下に保存します。

これでUnity側でAndroid、iOS側のネイティブコードからインストールされているアプリのバージョンを取得できるようになりました。

#バージョン比較#
上記のコードでBundleVersionを「1.0.0」という「.」で区切られた3桁の文字列で取得することになりますが、このままでは比較ができないので、少し工夫する必要がありました。

```csharp:VersionCheck.cs

    public string GetAppVersion ()
    {
        string versionName = "1.0.0";

    #if UNITY_EDITOR
        //エディター側ではデフォルトで[ 1.0.0 ]を返す
    #elif UNITY_ANDROID
        versionName = GetAppVersionName_Android();
    #elif UNITY_IPHONE
        versionName = GetAppVersionName_iOS();
    #endif

        return versionName;
    }


    /// <summary>
    /// 最新バージョンと比較して、更新の必要があるか返す
    /// </summary>
    /// <returns>bool true="更新必要アリ"</returns>
    public bool CheckLatestVersion ( string storeVersion_in )
    {
        int userVersion     = ConvertCompareInteger( GetAppVersion() );
        int storeVersion    = ConvertCompareInteger( storeVersion_in );

        if( userVersion < storeVersion )
            return true;

        return false;
    }



    /// <summary>
    /// バージョン番号を比較用整数に変換
    /// 現在の実装だと3桁限定
    /// </summary>
    /// <example>
    /// before = 2.1.5
    /// after  = 215 -> int型として比較する
    /// </example>
    int ConvertCompareInteger( string bundleVersion )
    {
        char[] delimiterChar = { '.' };
        string[] splitString = bundleVersion.Split( delimiterChar );

        string compareString = splitString[ 0 ] + splitString[ 1 ] + splitString[ 2 ];

        return int.Parse( compareString );
    }


```
少し不安な実装です.....。
「こうしたらどう？」といった意見募集中です.....。


#サーバーと通信してバージョンを取得#
```csharp:VersionCheck.cs

    /// <summary>
    /// アプリのバージョンをサーバーと通信して取得します
    /// エラー時とチェック後のコールバックを設定すること
    /// </summary>
    IEnumerator CheckAppVersion( Action<string> errorCallback, Action<bool> checkedCallback )
    {
        WWWForm form    = new WWWForm();

        form.AddField( "Platform", platform );
        WWW www = new WWW ( checkVersionURL, form );

        yield return www;

        if( www.error != null )
        {
            errorCallback( "アプリのバージョンチェック中にエラーが発生しました" );
            return false;
        }

        //[1.0.0]といったバージョン番号を取得
        string storeVersion = www.text;
        checkedCallback( CheckLatestVersion( storeVersion ) );
    }

```
上記のスクリプト中の、
「platform」には、サーバー側でプラットフォームを識別できるような文字列なり、数字をいれており、サーバーからはプラットフォーム別にアプリのバージョン番号を返すようにしています。
引数には、エラー時に呼び出されるコールバックと、チェック後の処理を行うコールバックを渡します。

例えば、チェック後のコールバックは、

```csharp

void CheckApplicationUpdate ( bool needUpdate )
{
    if( needUpdate )
    {
        Application.OpenURL( storeURL );
    }
}
```
といった関数を渡します。
ゲーム中に使用しているダイアログ等と連携をとれればより良いと思います。

#問題点#
サーバーで保持しているアプリのバージョンは、実際のストアのバージョンではなく、自分で管理しているものです。ストアにパッケージが並んだところを見計らって手動で更新することになります。
今回はこの時差があっても問題は無い場合でしたが、本当のところは通知等を受け取った上で自動的に更新をしたいところです。
調べたところ、github等に上げられているライブラリを使用すれば可能でしたが、Unityとの連携ともなると障害が多そうだったので断念しました。

#最後に#
何から何まで説明する訳にもいかず、所々省いてしまっていますが、何か分からない点があればお気軽に聞いて頂ければと思います。
何かまずい点等あればご指摘頂けれると幸いです。