アプリ開発の注意点

この文書ではPSMアプリを開発する上での注意点について説明します。

ネットワーク環境の差異について

HTTP や Socket などの API を使用して、携帯電話ネットワークにアクセスする場合は、以下の点にご注意ください。

  • 20MB を超えるファイルをダウンロードしない。

現在のところ、デバイスが Wifi と 携帯電話ネットワークのどちらを使用しているのかを取得する API が提供されていません。

この API については、今後の SDK で追加する予定です。

ネットワークのプロキシ設定について

PlayStation(R)Mobile シミュレータでネットワークに接続するアプリケーションは、必要に応じてプロキシの設定を行って下さい。

設定方法は、コントロールパネルにある「インターネットオプション」から「接続」タブを選択し、「LANの設定」から「プロキシ サーバー」の設定を行って下さい。

また、PlayStation(R)Mobile Simulatorは「自動構成」で設定された値は利用しません。プロキシを利用の際は「プロキシ サーバー」へ設定を行って下さい。

ファイル名の大文字小文字について

PSM アプリは様々なプラットフォーム上で動作するため、ファイル名の大文字小文字を区別する環境 (case-sensitive) と、大文字小文字を区別しない環境 (case-insensitive) の両方で動作するよう配慮してください。

両方の環境で動作させるには、ソースコード中で記述するファイル名と、ストレージに存在するファイル名の大文字小文字が一致するようにコーディングします。

例:

ストレージに存在する foo.txt ファイルを読む。

// A
StreamReader r = new StreamReader("Foo.txt");

// B
StreamReader r = new StreamReader("foo.txt");

case-insensitive な環境では、どちらもファイルの読み込みに成功しますが、case-sensitive な環境では A の記述だと読み込みに失敗します。そのため、B のように大文字小文字を一致させて記述してください。

プログラム終了に関する注意

PSMアプリは、システムから通知を受けることなく終了する場合があります。

例: PS Vitaで、PSボタンを押した後に、アプリを「はがす」動作を行った場合。

そのため、アプリケーションの実行が突然中断しても安全性を確保できるように、アプリケーションを実装してください。

データを保存する際の工夫

データを保存するとき、以下のいずれかの方法をとると、安全性を高めることができます。

Write() 一回でファイルを作成する

StreamWriterやBinaryWriterのWrite()は一度呼び出されると、アプリケーションの終了処理によって中断されることはありません。そのデータの書き出しを完全に完了するか、データの書き出しを全く行わないかの、いずれかの結果になります。

そのため、Write()を複数回呼び出してデータを書きだすのではなく、バッファにデータを確保した後で一度にデータを書きだす方法をとると、中途半端なデータを作成してしまう危険性を低くすることができます。

保存データ作成時、前回の保存データを一時的にとっておく

以下のような手順で、保存データを作成すると、安全性を高めることができます。

  1. 保存データを一時的なファイルとして作成する。
  2. 作成が完了してから、File.Move()で一時的なファイルを正式な保存データに置き換える。

PersistentMemory クラスを使用する

PersistentMemory は、アプリケーションが使用できる不揮発性のメモリ領域です。

PersistentMemory に保存されたメモリー内容は、次回アプリケーション起動時に自動的に復元されるため、アプリケーション動作中に逐一 PersistentMemory にアプリケーションの状態を保存しておき、次回アプリケーション起動時にその内容から前回アプリケーション終了時の状態を復元する、などの使い方ができます。

しかし、アプリケーション実行中にシステムの予期せぬ異常終了が発生した時など、特定の場合において PersistentMemory の内容は初期化されることがあります。 そのため、アプリケーションの進行状況など、失われることでユーザーに大きな損失となるような情報は、セーブデータとして Documents/* 配下に保存してください。

PersistentMemory にセーブデータと依存のある情報を配置する場合は、PersistentMemory が失われてしまう場合の対策として、同じ情報をセーブデータ側でも持つようにし、アプリケーション起動時や動作中の適切なタイミングで Persistent Memory とセーブデータの情報の同期をとるようにすることをおすすめします。

ハッシュ値を使って、破損を検出する

保存データにハッシュ値を保持しておくと、ロード時に破損を検出することができます。

以下は実装例です。3つのランキングデータとハッシュ値のWrite/Readを行っています。

using System.IO;
...
const string SAVE_FILE = "/Documents/savedata.dat";
const string TEMP_FILE = "/Documents/temp.dat";

const Int32 numOfRanking=3;
Int32[] ranking=new Int32[numOfRanking];

void SaveData()
{
    Console.WriteLine("==SaveData()==");

    ranking[0]=10000;
    ranking[1]=9000;
    ranking[2]=8000;

    int bufferSize=sizeof(Int32)* (numOfRanking+1);
    byte[] buffer = new byte[bufferSize];

    Int32 sum=0;
    for(int i=0; i<numOfRanking; ++i)
    {
        Console.WriteLine("ranking[i]="+ranking[i]);
        Buffer.BlockCopy(ranking, sizeof(Int32)*i, buffer, sizeof(Int32)*i, sizeof(Int32));
        sum+=ranking[i];
    }

    Int32 hash=sum.GetHashCode();
    Console.WriteLine("sum={0},hash={1}",sum,hash);

    Buffer.BlockCopy(BitConverter.GetBytes(hash), 0, buffer, numOfRanking * sizeof(Int32), sizeof(Int32));

    using (System.IO.FileStream hStream = System.IO.File.Open(@TEMP_FILE, FileMode.Create))
    {
        hStream.SetLength((int)bufferSize);
        hStream.Write(buffer, 0, (int)bufferSize);
        hStream.Close();
    }

    if(File.Exists(@SAVE_FILE))
        File.Delete(@SAVE_FILE);

    File.Move(@TEMP_FILE, @SAVE_FILE);
}

bool LoadData()
{
    Console.WriteLine("==LoadData()==");

    if ( System.IO.File.Exists(@SAVE_FILE)==false &&  System.IO.File.Exists(@TEMP_FILE)==true)
    {
        File.Move(@TEMP_FILE, @SAVE_FILE);
    }

    if ( System.IO.File.Exists(@SAVE_FILE)==true )
    {
        using (System.IO.FileStream hStream = System.IO.File.Open(@SAVE_FILE, FileMode.Open))
        {
            if (hStream != null)
            {
                long size = hStream.Length;
                byte[] buffer = new byte[size];
                hStream.Read(buffer, 0, (int)size);


                Int32 sum=0;
                for(int i=0; i<numOfRanking; ++i)
                {
                    Buffer.BlockCopy(buffer, sizeof(Int32)*i, ranking, sizeof(Int32)*i,  sizeof(Int32));
                    Console.WriteLine("ranking[i]="+ranking[i]);
                    sum+=ranking[i];
                }

                Int32 hash=BitConverter.ToInt32( buffer, numOfRanking * sizeof(Int32) );

                hStream.Close();

                if(sum.GetHashCode()==hash)
                {
                    Console.WriteLine("Correct Data.");
                    return true;
                }
                else
                {
                    Console.WriteLine("Incorrect Data.");
                    return false;
                }
            }
        }
    }

    return false;
}