2011年1月10日月曜日

アプリのデータを保存する(基本編)

Androidは、永続的なアプリケーションのデータを保存するため、以下の記憶媒体に関するAPIを端末に提供しています。

1. 内部記憶媒体(デバイスメモリ)
Androidがインストールされている場所です。 一般には端末に内蔵されています。
2. 外部記憶媒体
SDカードなどの共有された外部ストレージです。
3. ネットワークサーバ
ネットワーク内のホストコンピュータです。 インターネット経由でWebサービスを読み込んだりなどの用途で利用します。

1 と 2 は、OSのファイルシステムにより一元化されますが、プログラム上は区別して扱います。
3 は、サーバの有無や都合に左右されますので、一般にアプリのデータ保存として用いられる記憶媒体としては 1 と 2 でしょう。

注意したいのは、Androidはアプリごとにユーザ登録することで、アプリのシステムに対するアクセスを制御しており、
1 の「内部記憶媒体」にアプリがデータを保存できる場所は、特定の場所に限られるということです。

間違って、システムにとって重要なファイルを破壊してしまったら大変ですから、セキュリティ上そのような仕組みになっているようです。

そのため、1 の「内部記憶媒体」にデータを保存する場合、安全にシステム領域にアクセスするために android.context.Context#openFileOutput(String, int) を使います。
このメソッドの戻り値は、java.io.FileOnputStream オブジェクトとなっています。
以下に引数を示します。

第1引数は、ファイル名を指定します。
パスではないのでセパレータは含みません。

第2引数は、ファイルのパーミッションになっています。
複数の値を合わせて指定したい場合は、値の論理和を指定します。

定数 説明
MODE_APPEND 追記モードで開く
MODE_PRIVATE 作成したアプリのみ読み書きできる
MODE_WORLD_READABLE 他アプリに読み込み権を与える
MODE_WORLD_WRITEABLE 他アプリに書み込み権を与える

ファイルに保存したデータを読み込む場合、android.context.Context#openFileInput(String) を使います。
第1引数は、ファイル名を指定します。こちらもパス名ではないのでセパレータを含めないようにしてください。

package com.blogspot.androlab.example;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Date;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends Activity {
 final String FILE_NAME = "app_data.txt";

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  /* データの書き込み */
  try {
   // MODE_PRIVATE と MODE_APPEND を指定
   FileOutputStream fos = openFileOutput(FILE_NAME, MODE_PRIVATE|MODE_APPEND);
   PrintWriter pw = new PrintWriter(fos);
   pw.println(new Date());
   pw.flush();
   fos.close();
   pw.close();
  } catch (FileNotFoundException e) {
   // ファイルが開けなかった -> openFileOutput(String, int)
   e.printStackTrace();
  } catch (IOException e) {
   // ストリームの切断に失敗した -> fos.close()
   e.printStackTrace();
  }

  /* データの読み込み */
  try {
   FileInputStream fis = openFileInput(FILE_NAME);
   BufferedReader br = new BufferedReader(new InputStreamReader(fis));
   String line;
   while ((line = br.readLine()) != null)
    Log.e("Debug", line);
   fis.close();
   br.close();
  } catch (FileNotFoundException e) {
   // ファイルが開けなかった -> openFileInput(String)
   e.printStackTrace();
  } catch (IOException e) {
   // テキスト行が読み込めなかった -> br.readLine()
   // ストリームの切断に失敗した -> fis.close(), br.close()
   e.printStackTrace();
  }
 }
}

2 の外部記憶媒体にデータを保存する場合、下準備として AndroidManifest.xml にパーミッションを記述します。
以下を参考にしてください。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      android:versionCode="1"
      android:versionName="1.0" package="com.blogspot.androlab.example">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".MainActivity" android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    <uses-sdk android:minSdkVersion="4" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>

ファイルを開くときは、直接パスを指定して出力ストリームを開きます。
このときに指定するパスは、バージョンによって変わる場合がありますので、
Environment#getExternalStorageDirectory() など、可能な限りAPIとして提供されたものを使うようにします。

package com.blogspot.androlab.example;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;

public class MainActivity extends Activity {
 final String FILE_NAME = "app_data.txt";

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  // SDカードのディレクトリオブジェクトを取得
  File dir = Environment.getExternalStorageDirectory();

  // 新規のファイルオブジェクトを作成
  File file = new File(dir, FILE_NAME);

  /* データの書き込み */
  try {
   // 追記モードでストリームを開く
   FileWriter fw = new FileWriter(file, true);
   PrintWriter pw = new PrintWriter(fw);
   pw.println(new Date());
   pw.flush();
   fw.close();
   pw.close();
  } catch (IOException e) {
   // ファイルが開けなかった -> new FileWriter(File, boolean)
   // ストリームの切断に失敗した -> fw.close()
   e.printStackTrace();
  }

  /* データの読み込み */
  try {
   FileReader fr = new FileReader(file);
   BufferedReader br = new BufferedReader(fr);
   String line;
   while ((line = br.readLine()) != null)
    Log.e("Debug", line);
   fr.close();
   br.close();
  } catch (FileNotFoundException e) {
   // ファイルが開けなかった -> new FileReader(File)
   e.printStackTrace();
  } catch (IOException e) {
   // テキスト行が読み込めなかった -> br.readLine()
   // ストリームの切断に失敗した -> fr.close(), br.close()
   e.printStackTrace();
  }
 }
}

3 のネットワークサーバにデータを保存する場合は、サーバとの都合がありますので今回は省略しますが、
サーバ上にある、データを保存してくれるなんらかのファイル(LLスクリプトなど)」にアクセスして、データを渡すというのが一般的な方法でしょう。

読込みの場合は、「CSVやXML、JSON、YAMLなどにフォーマットされたデータ列を返してくれるなんらかのファイル」にアクセスしてデータを取得(いわゆるアプリとWebのマッシュアップ)という具合です。

参考
Android Developers Data Strage
1. Using the Internal Storage
2. Using the External Storage

0 件のコメント:

コメントを投稿