2014年11月16日日曜日

Android コレクション、直列化、永続化について

Android コレクション、直列化、永続化


●コレクション


Collectionインターフェースを実装したコレクションクラスのことで、
データセットの管理に重要。


●Bundle

コレクション実装の1つ。
Bundleはキー(String)、バリューの組を保持する。
バリューには、Androidに最適化されたオブジェクト郡を使うことができる。

IntentのExtrasは、内部でBundelオブジェクトを管理している。

□マッピングの追加・取得
・put~():追加
・get~():取得
※~は型が入る

ex) Intentの例(呼び出し元のActivity)

public void send(View v, int value) {
    Intent intent = new Intent(this, ResultActivity.class);
    Bundle bundle = new Bundle();
    bundle.putString("data","value"); ←★put(型はString)
    intent.putExtra("bundlePrams", parcelableData);
    startActivity(intent);
}


ex) Intentの例(呼び出し先のActivity)

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Bundle extras = getIntent().getExtras();←★get(型はString)
    Bundle bundlePrams = extras.getParcelable("bundlePrams");
    Toast.makeText(this, "data:" + bundlePrams.getString("data"), Toast.LENGTH_LONG).show();
}


●SparseArray


key(int)としたHashMapのようなもの。keyをint型に固定し、HashMapより高速に動作する。
Sparseという名の通り、keyに使用するint型が連続でなく(Sparse)とも使用できる。

AndroidのR.idで管理されるidはint型であるため、SparseArrayを用いることができるケースが
多くなります。(Android Listが警告を出してくれる。)

SparseArrayはvalueの型によってはvalueも最適化したSparseBooleanArray、SparseIntArrayを
使うことができる。

大規模である場合は、LongSparseArrayを使用することもできる。

ex)

SparseArray<String> array = new SparseArray<String>();
array.add(R.id.TextView1, "hoge");
array.add(R.id.TextView2, "fuga");
array.add(R.id.TextView3, "foo");
array.add(R.id.TextView4, "bar");


●直列化


Androidでは2種類の直列化フレームワークを利用可能。
Javaで提供されるものと、Androidで提供されるものである。

□Serializable(シリアライズ)



  • Javaフレームワーク
  • オブジェクトの保存と復旧の方法を決めたもの
  • 永続化


利用するI/Oの仕組み→データ化・データ復旧を保証する

  • ObjectInputStream
  • ObjectOutputStream


マーカーインターフェースである:メソッドの宣言のないインターフェース
クラスのフィールドがもつデータを扱う。

staticやtransientな変数、メソッドは直列化しない。

ex)

public class MyObject implements Serializable {
    public static final long serialVersionUID = -4324129709521L;
    private String mName;
}

【Notice】クラスのフィールドの互換性を管理する必要がある
※参考
http://d.hatena.ne.jp/asakichy/20120216/1329343931
http://d.hatena.ne.jp/Kazuhira/20130929/1380443535
→柔軟性、品質、安全性を熟考する必要がある

□Parcel、Parcelable



  • Androidフレームワーク
  • 永続化ではない
  • Serializableより軽量
  • プロセス間通信でハイパフォーマンスを得るために設計されたインターフェイス
  • IntentやBundleはParcelableを実装している


Parcelableのインターフェースに書き込み処理と復元処理を定義することにより、
メッセージングで型を限定されずにオブジェクトを渡すことが可能。


  • Parcelableの実装クラスの中にParcelableではないクラスを含めることが可能
  • 異なるアプリケーションへのメッセージングでも同一オブジェクトの復元を保証


Parcelableの実装手順


  1. Parcelable#writeToParcel()メソッド内でParcelに対してデータを書き込む
  2. CREATORという定数を定義し、Parcelable.Creator<T>を実装
  3. Parcelable.Creator#createFromParcel()でParcelからオブジェクトを生成する処理を実装


【Notive】Parcelから読み込む順序は、Parcelable#writeToParcel()で書き込んだ順序と同じにする

ex) ParcelableをIntentで受け渡しする


・受け渡すParcelableの実装

public class MyParcelable implements Parcelable { ←★Parcelbleをimplements
    public static final Parcelable.Creator<MyParcelable> CREATOR = new Parcelable.Creator<MyParcelable>() { ←★CREATORの実装
        public MyParcelable createFromParcel(Parcel in) { ←★Parcelからオブジェクトを生成する処理
            return new MyParcelable(in);
        }
        public MyParcelable[] newArray(int size) {
            return new MyParcelable[size];
        }
    };
    private int mData;
    public MyParcelable() {}
    private MyParcelable(Parcel in) {
        mData = in.readInt();
    }
    @Override
    public int describeContents() {
        return 0;
    }
    @Override
    public void writeToParcel(Parcel out, int flags) { ←★writeToParcelでParcelにデータを書き込む
        out.writeInt(mData);
    }
    public void setData(int data) {
        mData = data;
    }
    public int getData(){
        return mData;
    }
}

・Parcelの送信元(呼び出し元Activity)

public void send(View v, int value) {
        Intent intent = new Intent(this, ResultActivity.class);
        MyParcelable parcelableData = new MyParcelable();
        parcelableData.setData(value);
        intent.putExtra("parcelableData", parcelableData);
        startActivity(intent);
    }

・Parcelの受信先(呼び出し先Activity)

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Bundle extras = getIntent().getExtras();
        MyParcelable parcelData = extras.getParcelable("parcelableData");
        Toast.makeText(this, "data:" + parcelData.getData(), Toast.LENGTH_LONG).show();
    }


□JSONObjectとJSONArray



  • JSONはオブジェクトのシリアライズ・デシリアライズ形式の1つ
  • JSONを用いたフレームワーク
  • JSONRPCなど主にネットワーウ経由でのデータの転送に利用する


JSONObject:JSONにおけるオブジェクトに対応
JSONArray:JSONにおける配列に対応

JSONObjectからの値の取得方法は2種類

1. JSONObject#get~():~は型


  • JSON文字列上の型と異なる型を取得するメソッドを用いた場合、適宜型が変換される(安全ではない)
  • 存在しないkeyの場合、JSONExceptionがスローされる


2. JSONObject#opt~():~は型


  • 存在しないkeyを指定した場合、フォールバックの値が使用される。


【Notive】値がnullかどうか判定するには、JavaのNULLを使用できない。
JSONObject#isNull() を使うこと。

□Gsonフレームワーク



  • JSON文字列からJavaのオブジェクトへマッピングするフレームワーク


【Notice】端末依存に注意
http://alpha.mixi.co.jp/entry/2013/11572/


●永続化


Androidで用意された永続化手法

□ここで扱うもの


  • Shared Preferences
  • Internal Storage
  • External Storage


□別で扱うもの


  • SQLite Databases(データベース)
  • Network Connection(ネットワーク通信)



●SharedPreferences



  • Android標準で用意されている永続化の方法の1つ
  • keyとvalueの組み合わせでプリミティブ型やString型のデータを永続化する
  • ファイルの読み書き、状態管理・監視を請け負うので、自前でI/Oを準備するより簡単


□ファイルの実体



  • ファイルはアプリのもつ内部ストレージに保存される
  • アプリのアンインストール時は、すべてのデータが削除される
  • アプリが再インストールされてもなおデータを永続化したいユースケースには適用できない
  •  →そのような場合は適宜サーバにデータを保存するようにする


【Notive】保存場所が違うことに起因する問題
http://alpha.mixi.co.jp/entry/2013/11572/

→ Android-Device-Compatibilityライブラリの使用の検討
https://github.com/mixi-inc/Android-Device-Compatibility

□ファイルへのアクセス権


  • モード:SharedPreferencesのファイル作成時に設定するアクセス権限。アプリの領域内のアクセス権をファイル単位でコントロールする。
  • 他のアプリからのアクセスを許可することは推奨されない。
  •  →アプリ間連携のための仕組みを使用する。
  • 複数のプロセスをもつアプリケーションで同じファイルを参照する場合、モード:MODE_MULTI_PROCESSが存在するが、API Level 11以降となる。


□SharedPreferencesの使用


・インスタンスの取得

Context#getSharedPreferences(String, int)
:第1引数(ファイルの名前)
:第2引数(モード、デフォルトはMODE_PRIVATE)


□データの保存

SharedPreferences.Editorを介して行う


  1.  SharedPreferences.Editorオブジェクトの取得:SharedPreferences#edit()
  2.  値の設定:SharedPreferences.Editor#putString(String,String)
  3.  保存:SharedPreferences.Editor#commit()、SharedPreferences.Editor#apply()


【Notice】
SharedPreferences.Editor#apply()は保存の完了を待たない、かつAPI Level9以降となる。

SharedPreferencesを扱っていて、保存の成功・失敗を意識しなくていい場合は、
非同期処理の中でSharedPreferences.Editor#commit()を呼ぶ。


  • 値の削除:SharedPreferences.Editor#remove(String)
  • すべての値の削除:SharedPreferences.Editor#clear()


【Notive】
値の削除も、SharedPreferences.Editor#commit()またはapply()を呼び出すまで完了しない。

ex)

// データの保存
public boolean savePerson(String name, int age) {
    SharedPreferences sp = getSharedPreferences("person", MODE_PRIVATE);
    Editor editor = sp.edit();
    editor.putString("name", name);
    editor.putInt("age", age);
    return editor.commit();
}

□データの取得



  • SharedPreferences#get~():~は型


【Notive】
保存時の型と取得時の型が一致していること

ex)

// データの取得
private String mName;
private int mAge;
public void readPerson() {
    SharedPreferences sp = getContext().getSharedPreferences("person", MODE_PRIVATE);
    mName = sp.getString("name", "no name"); ←★String型を取得(第2引数はフォールバック)
    mAge = sp.getInt("age", 0);
}

□データ変更の監視

保存されている値に変更が合った場合、UIに反映する処理などに使用する。


  • SharedPreferences.OnSharedPreferenceChangeListener:データ変更監視のリスナーオブジェクト
  • SharedPreferences#registerOnSharedPreferenceChangeListenerでリスナーオブジェクトを登録


【Notive】
このリスナーオブジェクトのライフサイクル管理は実装で行う必要がある。
すなわち、SharedPreferences#unregisterOnSharedPreferenceChangeListener()をContextのライフサイクルの終わりに行う。

ex)

public class MainActivity extends Activity implements OnSharedPreferenceChangeListener {
    private SharedPreferences mSharedPreferences;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mSharedPreferences = getSharedPreferences("sample", MODE_PRIVATE);
        mSharedPreferences.registerOnSharedPreferenceChangeListener(this);
    }
    protected void onDestroy(){
        mSharedPreferences.unregisterOnSharedPreferenceChangeListener(this); ←★リスナーの解除
        super.onDestroy();
    }
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        String value = sharedPreferences.getString(key, null);
        if (value != null) {
            TextView tv = (TextView) findViewById(R.id.PreferencesValue);
            tv.setText(value);
        }
    }
}


●Internal Storage


  • 内部ストレージのこと。
  • モードによってアクセス制御が可能
  • 容量の制限があることが多いので大量データの保存はできない


□Contextが提供する内部ストレージ利用のインターフェイス



・ファイルを保存



  1.  FileOutputStreamの取得:Context#openFileOutput()
  2.  バイト列をストリームに格納:FileOutputStream#write()
  3.  ストリームを閉じる:FileOutputStream#close()


【Notice】
FileOutputStream#close()処理はfinallyで行うこと

ex)

String FILENAME = "hello_file";
String string = "hello world!";
FileOutputStream fos = null;
try {
  openFileOutput(FILENAME, MODE_PRIVATE);
  fos.write(string.getBytes());
} catch (IOException e) {
  // 例外処理
} finally {
  try {
    if (fos != null) {
      fos.close();
    }
  } catch (IOException e) {
  }
}

・ファイルの内容を取得


  1.  FileInputStreamを取得:Context#openFileInput()
  2.  ファイル内容を読み出し:FileInputStream#read()
  3.  ストリームを閉じる:FileIntputStream#close() 【Notice】finallyで呼び出すこと



●CacheDirectory



  • 一時ファイルとしてキャッシュする特別なディレクトリ
  • 内蔵ディレクトリにキャッシュ用ディレクトリが作成される



●External Storage



  • 外部ストレージ
  • SDカード
  • 端末によっては、内部とは別の内蔵メモリが存在する。(ハイエンド端末に多い。Galaxy、Nexus)
  • アクセス制御できない
  • 全てのアプリケーションからアクセスが可能
  • ルートディレクトリの取得:getExternalFilesDir()
  • 取得するディレクトリのtype

   DIRECTORY_MUSIC
   DIRECTORY_PODCASTS
   DIRECTORY_RINGTONES
   DIRECTORY_ALARMS
   DIRECTORY_NOTIFICATIONS
   DIRECTORY_PICTURES
   DIRECTORY_MOVIES

0 件のコメント:

コメントを投稿