marunomaruno-memo

marunomaruno-memo

[Android] 位置情報 (3) / ファイル・システム (1) メモリー内のファイルに位置情報を書き込む

2012年01月10日 | Android
[Android] 位置情報 (3) / ファイル・システム (1) メモリー内のファイルに位置情報を書き込む
================================================================================

Android で、永続化をはかる方法としては、以下の方法がある。
・SharedPreferences
・ファイル・システム
・SQLite データベース
・ContentProvider

今回は、ファイル・システムを利用する。
ファイルシステムも、メメリー内とメモリーの外(SD カードなど)にファイルを構築す
る方法で違いがある。

このサンプルは、メモリー内のファイルへの入出力を行う。


■ メモリー内のファイルに位置情報を書き込む

このアプリケーションは、つぎつぎと変化する位置情報を、メモリー内のファイル(ロー
カル・ファイル)に書き込む。また、利用者の指定によって、ファイルの内容を表示したり、
削除したりする。

また、位置情報は、コンソールに表示するように、画面にスクロールして表示される。

位置情報の取得は、「開始/終了」ボタンのクリックによる。このボタンはトグルボタン
になっていて、そのときの状態を保存する必要がある。

単なるファイルの扱いだけになっていないので、コードはある程度複雑になっているが、
ファイルの扱いの部分は、LogDao クラスのコードを参照すればよい。
トグルボタンを扱う部分と、コンソール表示コは、つぎの既出のドキュメントを参照され
たい。
    位置情報 (2) トグルボタンによる位置情報の取得の開始・終了
    http://blog.goo.ne.jp/marunomarunogoo/d/20120109

    標準出力 - あて先に画面を追加する
    http://blog.goo.ne.jp/marunomarunogoo/d/20111222


コード
---
GpsLogger01Activity.java  アクティビティ
GpsLocationListener.java  ロケーションのリスナー
TextViewPrintStream.java  System.out オブジェクトの置き換え(SyystemOut01 と同じ)
LogDao.java               ログの入出力を行うユーティリティのクラス
---


◆ アクティビティのクラス

採ったログを表示するための「ログ表示」ボタン、ログファイルをクリアする「ログクリ
ア」ボタンが追加になっているので、これに対するハンドラー・メソッド
    onClickLogPrintButton()
    onClickLogClearButton()
を追加している。メソッドは、単に LogDao のメソッドを呼び出しているだけである。


□ GpsLogger01Activity.java
---
package jp.marunomaruno.android.sample;

import java.io.FileNotFoundException;
import java.io.IOException;

import android.app.Activity;
import android.location.Criteria;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

/**
 * @author marunomaruno
 * @version 1.0, 2011-12-14
 * @since 1.0
 */
public class GpsLogger01Activity extends Activity {

    private boolean isRunning = false;    // 現在起動中かどうか
    private static final String IS_RUNNING = "isRunning";

    private LocationManager locationManager;
    private LocationListener sensorEventListener; 
                // onPause()が呼ばれたときに、すべてのリスナーを解除するため
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.d(getString(R.string.logTag), String.format("onCreate() "
                + getString(R.string.sensor_value_format_for_timestamp),
                getString(R.string.time), System.currentTimeMillis()));

        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // 標準出力を置き換え(標準出力(LogCat)+テキストビュー)
        TextView sysout = (TextView) findViewById(R.id.sysout);
        System.setOut(new TextViewPrintStream(System.out, sysout));
        System.setErr(new TextViewPrintStream(System.err, sysout));

        locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
    }

    @Override
    protected void onResume() {
        Log.d(getString(R.string.logTag), String.format("onResume() "
                + getString(R.string.sensor_value_format_for_timestamp),
                getString(R.string.time), System.currentTimeMillis()));

        super.onResume();

        Button startStopButton = (Button) findViewById(R.id.startStopButton);

        Log.d(getString(R.string.logTag), "起動中 = " + isRunning);
        
        if (isRunning) {    // 起動中のとき、終了ボタンを表示する
            startStopButton.setText(R.string.stopButton);

        } else {
            startStopButton.setText(R.string.startButton);
        }
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        Log.d(getString(R.string.logTag), String.format("onRestoreInstanceState() "
                + getString(R.string.sensor_value_format_for_timestamp),
                getString(R.string.time), System.currentTimeMillis()));

        super.onRestoreInstanceState(savedInstanceState);
        isRunning = savedInstanceState.getBoolean(IS_RUNNING);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        Log.d(getString(R.string.logTag), String.format("onSaveInstanceState() "
                + getString(R.string.sensor_value_format_for_timestamp),
                getString(R.string.time), System.currentTimeMillis()));

        super.onSaveInstanceState(outState);
        outState.putBoolean(IS_RUNNING, isRunning);
    }

    /**
     * 開始/終了ボタン押下時
     * @param view
     */
    public void onClickStartStopButton(View view) {
        Log.d(getString(R.string.logTag), String.format("onClickStartButton() "
                + getString(R.string.sensor_value_format_for_timestamp),
                getString(R.string.time), System.currentTimeMillis()));

        Button startStopButton = (Button) view;
        
        if (isRunning) {    // 起動中のとき、とめる
            // すべてのリスナーを解除する
            removeListeners();                     

            // 開始ボタンにする
            startStopButton.setText(R.string.startButton);

            System.out.println(getString(R.string.stopButton));
            System.out.println(String.format(
                        getString(R.string.sensor_value_format_for_timestamp),
                        getString(R.string.stopButton), System
                                .currentTimeMillis()));

        } else {
            // 起動していないとき、
            // GPSロケーションを取得して登録する
            setProviderAndListener();
            
            // 停止ボタンにする
            startStopButton.setText(R.string.stopButton);
            System.out.printf(getString(R.string.startMessageFormat), LogDao
                    .getFile(this));    // (1)
            System.out.println();
        }
        isRunning = !isRunning;    // 起動中状態を逆にする
        Log.d(getString(R.string.logTag), String.format(
                "起動中 = %b, 開始時刻  = %tT, ファイル名 = %s", isRunning, System
                        .currentTimeMillis(), LogDao.getFile(this)));
    }

    private void setProviderAndListener() {
        // プロバイダーを取得する条件を作成する
        Criteria fineAndLowPower = new Criteria();
        fineAndLowPower.setAccuracy(Criteria.ACCURACY_FINE);
        fineAndLowPower.setPowerRequirement(Criteria.POWER_LOW);
        
        // プロバイダーを取得する
        String provider = locationManager.getBestProvider(fineAndLowPower, true);
        Log.d(getString(R.string.logTag), "provider = "
                + provider);
        TextView providerNameTextView = (TextView) findViewById(R.id.provider_name);
        providerNameTextView.setText(provider);
        
        // プロバイダーがみつからなければ、NETWORK_PROVIDER を設定しておく
        if (provider == null) {
            provider = LocationManager.NETWORK_PROVIDER;
            providerNameTextView.setText(R.string.no_provider);
        }
        
        // 位置情報のリスナーを取得して登録する
        sensorEventListener = GpsLocationListener.getInstance(this);
        locationManager.requestLocationUpdates(
                provider, 
                0, 
                0, 
                sensorEventListener);
    }

    /**
     * ログをクリアする。
     * @param view
     */
    public void onClickLogClearButton(View view) {
        LogDao.delete(this);                                   // (2)
    }

    /**
     * ログを表示する
     * @param view
     */
    public void onClickLogPrintButton(View view) { 
        try {
            LogDao.print(this);                                // (3)
        } catch (FileNotFoundException e) {
            System.out.println(getString(R.string.logNotFound));

        } catch (IOException e) {
            e.printStackTrace();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void removeListeners() {
        if (sensorEventListener != null) {
            locationManager.removeUpdates(sensorEventListener);
        }
    }
}
---

(1) ログのファイル名を取得する

    System.out.printf(getString(R.string.startMessageFormat), LogDao
            .getFile(this));    // (1)

LogDao は、ユーティリティのクラスとして、すべてのメソッドは static になっている。
基本的には、アクティビティのオブジェクトを渡して、処理する。


(2) ログをクリアする

    LogDao.delete(this);                                   // (2)


(3) ログを表示する

    LogDao.print(this);                                    // (3)


◆ センサーのリスナーのクラス。

Gps01 プロジェクトからみると、変更している箇所は、位置情報を標準出力に表示すると
ころを、ログにも書き込む 1 箇所だけ。

□ GpsLocationListener.java
---
package jp.marunomaruno.android.sample;

import java.io.FileNotFoundException;

import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationProvider;
import android.os.Bundle;
import android.util.Log;

/**
 * GPSによるロケーションのリスナー
 * @author maruno
 * @version 1.0, 2011-12-14
 * @since 1.0
 */
public class GpsLocationListener implements LocationListener {

    protected Context context;
    private static LocationListener instance;
    
    private GpsLocationListener(Context context) {
        this.context = context;
    }
    
    /**
     * このリスナーの唯一のインスタンスを取得する。
     * @param context
     * @return このインスタンス
     */
    public static LocationListener getInstance(Context context) {
        if (instance == null) {
            instance = new GpsLocationListener(context);
        }
        return instance;
    }

    @Override
    public void onLocationChanged(Location location) {
        Log.d(context.getString(R.string.logTag),
                String.format("onLocationChanged() " +  context.getString(
                        R.string.sensor_value_format_for_timestamp), 
                        context.getString(R.string.time), 
                        System.currentTimeMillis()));
        
        System.out.println("---");

        System.out.println(String.format(context.getString(
                R.string.sensor_value_format_for_timestamp), 
                context.getString(R.string.time), 
                location.getTime()));                                    

        System.out.println(String.format(context.getString(
                R.string.sensor_value_format_for_real), 
                context.getString(R.string.latitude), 
                location.getLatitude()));                    

        System.out.println(String.format(context.getString(
                R.string.sensor_value_format_for_real), 
                context.getString(R.string.longitude), 
                location.getLongitude()));                    
        
        System.out.println(String.format(context.getString(
                R.string.sensor_value_format_for_real), 
                context.getString(R.string.accuracy), 
                location.getAccuracy()));                    

        System.out.println(String.format(context.getString(
                R.string.sensor_value_format_for_real), 
                context.getString(R.string.altitude), 
                location.getAltitude()));                    
        
        System.out.println(String.format(context.getString(
                R.string.sensor_value_format_for_real), 
                context.getString(R.string.speed), 
                location.getSpeed()));                
        
        System.out.println(String.format(context.getString(
                R.string.sensor_value_format_for_real), 
                context.getString(R.string.bearing), 
                location.getBearing()));                        

        try {
            LogDao.write(context, location);                    // (1)

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onProviderDisabled(String provider) {
        System.out.println(context.getString(R.string.providerDisable));
    }

    @Override
    public void onProviderEnabled(String provider) {
        System.out.println(context.getString(R.string.providerEnabled));
    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {
        Log.d(context.getString(R.string.logTag),
                String.format("onStatusChanged() " +  context.getString(
                        R.string.sensor_value_format_for_timestamp), 
                        context.getString(R.string.time), 
                        System.currentTimeMillis()));

        switch (status) {
        case LocationProvider.AVAILABLE:
            Log.d(context.getString(R.string.logTag),
                    "onStatusChanged() - AVAILABLE");
            break;
        case LocationProvider.OUT_OF_SERVICE:
            Log.d(context.getString(R.string.logTag),
                    "onStatusChanged() - OUT_OF_SERVICE");
            break;
        case LocationProvider.TEMPORARILY_UNAVAILABLE:
            Log.d(context.getString(R.string.logTag),
                    "onStatusChanged() - TEMPORARILY_UNAVAILABLE");
            break;
        default:
            Log.d(context.getString(R.string.logTag),
                    "onStatusChanged() - Other");
            break;
        }
    }
}
---

(1) 位置情報をログに書き込む

    LogDao.write(context, location);                    // (1)


◆ ログを管理するクラス

位置情報ログにアクセスするクラス。すべてのメソッドは static となっているユーティ
リティのクラス。

つぎのメソッドがある。
---
static void delete(Context context) 
          ログファイルを削除する。 

static File getFile(Context context) 
          ログファイルを取得する。 

static void print(Context context) 
          ログファイルの内容を標準出力にプリントする。 

static void write(Context context, Location location) 
          location のデータをログファイルに CSV 形式で書き出す。 
---

□ LogDao.java
---
package jp.marunomaruno.android.sample;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;

import android.content.Context;
import android.location.Location;

/**
 * 位置情報ログにアクセスするクラス。
 * すべてのメソッドはstatic。
 * @author marunomaruno
 * @version 1.0, 2011-12-14
 * @since 1.0
 */
public class LogDao {

    private LogDao() {
    }

    /**
     * ログファイルを取得する。
     * @param context コンテキスト
     * @return ログファイル
     */
    public static File getFile(Context context) {                    // (1)
        File file = new File(context.getString(R.string.logFilePrefix) + "."
                + context.getString(R.string.logFileExtension));    // (2)
        return file;
    }

    /**
     * locationのデータをログファイルにCSV形式で書き出す。
     * @param context
     * @param location
     * @throws UnsupportedEncodingException
     * @throws FileNotFoundException
     */
    public static void write(Context context, Location location)    // (3)
            throws UnsupportedEncodingException, FileNotFoundException {
        PrintWriter out = new PrintWriter(new OutputStreamWriter(context
                .openFileOutput(getFile(context).getName(), Context.MODE_APPEND
                        | Context.MODE_WORLD_READABLE), context.getResources()
                .getString(R.string.logFileEncoding)));                // (4)

        out.printf("¥"%1$tF %1$tT¥"", location.getTime()); 
                                // ファイルをわかりやすくするため    // (5)
        out.print(context.getString(R.string.csvSeparateCharacter));
        out.print(location.getTime());
        out.print(context.getString(R.string.csvSeparateCharacter));
        out.print(location.getLatitude());
        out.print(context.getString(R.string.csvSeparateCharacter));
        out.print(location.getLongitude());
        out.print(context.getString(R.string.csvSeparateCharacter));
        out.print(location.getAccuracy());
        out.print(context.getString(R.string.csvSeparateCharacter));
        out.print(location.getAltitude());
        out.print(context.getString(R.string.csvSeparateCharacter));
        out.print(location.getSpeed());
        out.print(context.getString(R.string.csvSeparateCharacter));
        out.print(location.getBearing());
        out.println();
        out.close();
    }

    /**
     * ログファイルの内容を標準出力にプリントする。
     * @param context
     * @throws UnsupportedEncodingException
     * @throws FileNotFoundException
     * @throws IOException
     */
    public static void print(Context context)                        // (6)
            throws UnsupportedEncodingException, FileNotFoundException,
            IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(context
                .openFileInput(getFile(context).getName()), context
                .getString(R.string.logFileEncoding)));                // (7)
        String line;
        while ((line = in.readLine()) != null) {
            System.out.println(line);
        }
        in.close();
    }

    /**
     * ログファイルを削除する。
     * @param context
     */
    public static void delete(Context context) {                    // (8)
        context.deleteFile(getFile(context).getName());                // (9)
    }
}
---

(1)(2) ファイルを取得する

    public static File getFile(Context context) {                    // (1)

引数のコンテキストから、リソースで指定してある名前を基に、ファイル・オブジェクト
を取得する。JavaSE と同じ方法で取得できる。

    File file = new File(context.getString(R.string.logFilePrefix) + "."
            + context.getString(R.string.logFileExtension));    // (2)


(3)(4)(5) location のデータをログファイルに CSV 形式で書き出す

    public static void write(Context context, Location location)    // (3)
            throws UnsupportedEncodingException, FileNotFoundException {

CSV ファイル(テキスト・ファイル)なので、扱いやすい PrintWriter オブジェクトとす
る。

        PrintWriter out = new PrintWriter(new OutputStreamWriter(context
                .openFileOutput(getFile(context).getName(), Context.MODE_APPEND
                        | Context.MODE_WORLD_READABLE), context.getResources()
                .getString(R.string.logFileEncoding)));                // (4)


JavaSE と同じ部分として、つぎのように、指定された文字セットを使う 
OutputStreamWriter を作成する部分がある。

    PrintWriter out = new PrintWriter(new OutputStreamWriter(OutputStream out, 
										String charsetName));

上記の out オブジェクトは、Context クラスの openFileOutput() メソッドによる。

    context.openFileOutput(getFile(context).getName(), Context.MODE_APPEND
                        | Context.MODE_WORLD_READABLE), 


Context クラスの FileOutputStream オブジェクトを開くメソッド
---
abstract FileOutputStream     openFileOutput(String name, int mode)
---

ファイル名 name は、この LogDao クラスの getFile() メソッドを使えばよい。

モード mode は、ファイル操作のモードで、次のものがある。
---
MODE_PRIVATE          他のアプリからアクセス不可(デフォルト)  0x00000000
MODE_WORLD_READABLE   他のアプリから読み込み可                0x00000001
MODE_WORLD_WRITEABLE  他のアプリから書き込み可                0x00000002
MODE_APPEND           既存ファイルに追加可                    0x00008000
---

これらのモードは、ビットに対応しているので、論理和を使って組み合わせが可能。


文字セット charsetName は、リソースファイルに指定してあるものを使う。
    context.getResources().getString(R.string.logFileEncoding)


PrintWriter オブジェクトができれば、あとは、通常の out オブジェクトを書くのと一
緒。

    out.printf("¥"%1$tF %1$tT¥"", location.getTime()); // ファイルをわかりやすく
するため    // (5)


(6)(7) ログファイルの内容を標準出力にプリントする。

    public static void print(Context context)                        // (6)


BufferedReader オブジェクトを作って扱う。作り方は、PrintWriter オブジェクトを作
るのと同様。ローカルのファイルを入力モードで開くのは、openFileInput() メソッドを
使う。

    BufferedReader in = new BufferedReader(new InputStreamReader(context
            .openFileInput(getFile(context).getName()), context
            .getString(R.string.logFileEncoding)));                // (7)


Context クラスの FileInputStream オブジェクトを開くメソッド
---
abstract FileInputStream     openFileInput(String name)
---


(8)(9) ログファイルを削除する

    public static void delete(Context context) {                    // (8)

ローカルのファイルを削除するのは、deleteFile() メソッドを使う。

        context.deleteFile(getFile(context).getName());                // (9)

---
abstract boolean     deleteFile(String name)
---


・Context クラスのファイル関係と思われるメソッド
---
abstract boolean   deleteFile(String name)
abstract String[]  fileList()
abstract File      getCacheDir()
abstract File      getDir(String name, int mode)
abstract File      getExternalCacheDir()
abstract File      getExternalFilesDir(String type)
abstract File      getFileStreamPath(String name)
abstract File      getFilesDir()
abstract String    getPackageCodePath()
abstract String    getPackageResourcePath()
abstract FileInputStream   openFileInput(String name)
abstract FileOutputStream  openFileOutput(String name, int mode)
---

詳細は API 参照。


◆ レイアウト

以下の Gps02 プロジェクトと同じ。
位置情報 (2) トグルボタンによる位置情報の取得の開始・終了
http://blog.goo.ne.jp/marunomarunogoo/d/20120109


◆ 文字列の定数

□ res/values/string.xml

センサーの名前と合わせて、そのセンサーで取得する値の単位も定義した。

□ res/values/string.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">GpsLogger01</string>
    <string name="logTag">TEST</string>
    <string name="no_provider">プロバイダーは見つかりませんでした。</string>
    <string name="sensor_value_format_for_real">%1$s = %2.2f</string>
    <string name="sensor_value_format_for_timestamp">%1$s = %2$tF %2$tT</string>
    <string name="latitude">緯度</string>
    <string name="longitude">経度</string>
    <string name="accuracy">精度</string>
    <string name="altitude">標高</string>
    <string name="time">時間</string>
    <string name="speed">速度</string>
    <string name="bearing">ベアリング</string>
    <string name="logFilePrefix">location</string> <!-- (1) ファイル名 -->
    <string name="logFileExtension">txt</string>   <!-- (2) 拡張子 -->
    <string name="logFileEncoding">UTF-8</string>  <!-- (3) 文字コード -->
    <string name="csvSeparateCharacter">,</string> <!-- (4) CSVファイルの分離符 -->
    <string name="startButton">開始</string>
    <string name="stopButton">終了</string>
    <string name="logPrintButton">ログ表示</string>
    <string name="sendMailButton">メール送信</string>
    <string name="logClearButton">ログクリア</string>
    <string name="logNotFound">ログはありません。</string>
    <string name="startMessageFormat">開始します。ファイル名 = %s</string>
    <string name="providerDisable">無線ネットワークを使用できるようにしてください。
	</string>
    <string name="providerEnabled">無線ネットワークを使用します。</string>
</resources>
---


◆マニフェスト

以下の Gps01 プロジェクトと同じ。
位置情報 (1) 無線ネットワークを使った位置情報
http://blog.goo.ne.jp/marunomarunogoo/d/20111218