2014年11月29日土曜日

Androidのネットワーク通信に関するメモ

Androidのネットワーク通信


●パーミッション


□ネットワークの接続許可

<uses-permission android:name="android.permission.INTERNET" />

□ネットワークの接続状態を確認する

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

→ 接続不可能な場合のハンドリングに使用


●ネットワークの通信状態


ユースケース

・ネットワークに接続されていない場合は、ローカルにキャッシュされたデータを使用したい。
・ファイルサイズが大きい場合にwifiに接続時にのみダウンロードさせたい。

How


  • ContextからConnectivityManagerを取得する


ex)

public class MyActivity extends Activity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // do something...
        // Context 経由でインスタンスを取得する ←★ContextがあればConnectivityManagerを取得可能
        ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
        NetworkInfo info = cm.getActiveNetworkInfo();
        if (info.isConnected()) { ←★接続の確認
            Toast.makeText(this, info.getTypeName() + " connected", Toast.LENGTH_LONG).show();
        }
    }
}


●接続しているネットワークの種別


How


  • ConnectivityManagerからNetworkInfoを取得


ex)
ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo(); ←★ConnectivityManagerからNetworkInfoを取得
if (info.getType() == ConnectivityManager.TYPE_WIFI) {
    // wifi時のみ動作する処理を記述
}


●Http通信


Androidで用意されているライブラリ


  • HttpURLConnection:I/Oストリームを用いた通信の入出力処理を実装(Froyo以前ではコネクションプールの深刻なバグを含む)
  • Apache HttpClient:HttpURLConnectionより高いレイアの実装。抽象化された入出力を使うことができる(Froyo以前では使用が推奨される)



●非同期処理



  • ネットワーク通信の処理は、メインスレッド(UIスレッド)では実施しないこと。



●URLConnection:APIがシンプル、サイズが小さい



  1. URL#openConnection():接続の開始(レスポンスの型をHttpURLConnection型へキャスト)
  2. ヘッダーの設定
  3. HttpURLConnection#setDoOutput(true):bodyが存在する場合に明示する方法。接続の確率後に、getOutputStream()で取得したOutputStreamにbodyの内容を書き込む
  4. connect()で接続を確立する。setDoOutput(false)の場合、5にすすむ。setDoOutput(true)の場合、getOutputStreamで取得したOutputStreamに情報を書き込み、クローズするまでデータを送信する。closeを呼び出したら5にすすむ
  5. getResponseCode()でステータスコードを確認する
  6. getInputStream()でレスポンスを取得する



□ GET処理


ex)

URL url = new URL("http://mixi.jp");
HttpURLConnection connection = null;
try {
    connection = (HttpURLConnection) url.openConnection();
    connection.connect();
    InputStream is = connection.getInputStream();
    StringBuilder src = new StringBuilder();
    while (true) {
        byte[] line = new byte[1024];
        int size = is.read(line);
        if (size <= 0)
            break;
        src.append(new String(line, "euc-jp"));
    }
} catch (IOException e) {
    e.printStackTrace();
} finally{
    connection.disconnect();
}


□POST処理


ex)

URL url = new URL("http://mixi.jp");
HttpURLConnection connection = null;
try {
    connection = (HttpURLConnection) url.openConnection();
    connection.setRequestMethod("POST");
    connection.setDoOutput(true);
    String postData = "hoge=fuga&piyo=test";
    OutputStream os = connection.getOutputStream();
    os.write(postData.getBytes());
    os.flush();
    os.close();
    InputStream is = connection.getInputStream();
    StringBuilder src = new StringBuilder();
    while (true) {
        byte[] line = new byte[1024];
        int size = is.read(line);
        if (size <= 0)
            break;
        src.append(new String(line, "euc-jp"));
    }
} catch (IOException e) {
    e.printStackTrace();
} finally{
    connection.disconnect();
}


●ApacheHttpClient:Androidに組み込まれたライブラリはバージョンが古い、必要なら新しいものを別途組み込む


□GET処理

ex)

HttpClient client = new DefaultHttpClient();
try {
    client.execute(new HttpGet("http://mixi.jp"),
            new ResponseHandler<String>() {
                public String handleResponse(HttpResponse response)
                        throws ClientProtocolException, IOException {
                    return EntityUtils.toString(response.getEntity());
                }
            });
} catch (IOException e) {
    e.printStackTrace();
}

Java 非同期処理の同期をとる CountDownLatch

Java 非同期処理の同期をとる


java.util.concurrentパッケージにはJavaの同期・非同期処理を実装する上で便利なクラスある。

その中でも簡単に使えるのが CountDownLatch である。

□CountDownLatch


他スレッドでの操作完了を待機する同期支援クラス

CountDownLatchはON/OFF機能をもったLatch(ドア、掛け金)である。

ex) 待ち合せたい非同期処理の数Nの場合

// インスタンス生成
final CountDownLatch latch = new CountDownLatch(N);
// 待ち合せ実行(メインスレッドで実行しないこと)
try {
    latch.await();
} catch (InterruptedException e) {
    e.printStackTrace();
}

CountDownLatchインスタンスを非同期処理のスレッドに渡す。

非同期処理側

ex)
latch.countDown();

【Notice】
latch.countDown()とlatch.await()を呼び出すスレッドとタイミングに注意すること。
latch.countDown()を呼び出すスレッドが起動したあと、latch.await()を呼び出す方が安全。

Google:
http://developer.android.com/intl/ja/reference/java/util/concurrent/CountDownLatch.html

Androidでも共通して使用できる。というか個人的にはAndroidでしか使っていない。

2014年11月27日木曜日

Androidの非同期処理についてメモ

Androidの非同期処理


●Service


バックグラウンドで動作するAndroidのコンポーネント


●Serviceの状態


呼び出し方によって2種類の状態をとる。

□開始 Context#startService(Intent)による呼び出し:


  • 呼び出し元とは異なるライフサイクルをもつ。呼び出し元のコンポーネントが終了しても生きていることがある。
  • 主に結果を呼び出し元に返さない場合に用いる。
  • Service自身でライフサイクルを終了する。


終了のトリガー

  • 自身で終了を宣言
  • 誰かが終了を命令


ex)

public class StartedService extends Service {
    public static final String TAG = StartedService.class.getSimpleName();
    /**
     * {@link Service} のライフサイクルの開始。
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.v(TAG, "onCreate");
    }
    // バインド用のメソッド。今回は特に必要ないので null を返す。
    @Override
    public IBinder onBind(Intent intent) {
        Log.v(TAG, "onBind");
        return null;
    }
    /**
     * {@link Context#startService(Intent)} の呼び出しで呼ばれる。
     * このメソッドの処理は、{@link Context#startService(Intent)}を呼び出したスレッドと同じスレッドで実行されるので
     * メインスレッドで {@link Service} を起動した場合に、ここでネットワーク通信などスレッドをブロックする処理をしてしまうと
     * UI の処理がブロックされ AND となる。
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.v(TAG, "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }
    /**
     * {@link Service} のライフサイクルの終了。
     */
    @Override
    public void onDestroy() {
        Log.v(TAG, "onDestroy");
        super.onDestroy();
    }
}


□バインド Context#bindService(Intent)による呼び出し:



  • Serviceをbindする。
  • 呼び出し元のコンポーネントとServiceの間にクラサバの関係ができる
  • 連携、リクエスト送信、レスポンス受信などのプロセス間通信が可能
  • bindしたコンポーネントのライフサイクルに合わせることが可能
  • 1つのServiceに複数のコンポーネントをbind可能。bindされた全てのコンポーネントが終了すると、Serviceのライフサイクルも終了する。


終了のトリガー

  • バインドしている呼び出し元がすべて破棄される
  • バインドしている呼び出し元がすべてバインドの解除を行う


ex)

public class BoundService extends Service {
    public static final String TAG = BoundService.class.getSimpleName();
    private final IBinder mBinder = new ServiceBinder();
    @Override
    public void onCreate() {
        super.onCreate();
        Log.v(TAG, "onCreate");
    }
    // 最初にバインドした時のコールバック
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    // 再度バインドした時のコールバック
    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
        Log.v(TAG, "onRebind");
    }
    // バインドを解除された時のコールバック
    @Override
    public boolean onUnbind(Intent intent) {
        Log.v(TAG, "onUnbind");
        return super.onUnbind(intent);
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.v(TAG, "onDestroy");
    }
    // サービスをバインドした後、バインドしたサービスのインスタンスそのものを得るためのインタフェース
    public class ServiceBinder extends Binder {
        public BoundService getService() {
            return BoundService.this;
        }
    }
}


●Serviceを構築する


□Service



  • バックグラウンドで動作(実動作はプロセスのメインスレッド)
  • Service内でブロック処理をする場合、自ら別スレッドの実行を記述する必要がある



□AndroidManufestの記述


ex)

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <service
        android:name="jp.mixi.sample.service.BoundService"/> ←★起動方法によらず、Manufestに記載
    <service
        android:name="jp.mixi.sample.service.StartedService"/> ←★起動方法によらず、Manufestに記載
</application>


●IntentService


  • Intentによる呼び出しで開始するService
  • Serviceの開始要求を1つずづ順に処理するワーカスレッド上で動作する特別なService
  • 一度に複数の処理を並列して行う必要が無い場合、IntentServiceを利用すると実装コストが小さい
  • AndroidManufestへの記述が必要


ex)

public class MyIntentService extends IntentService {
    public static final String TAG = MyIntentService.class.getSimpleName();
    public MyIntentService() {
        this(MyIntentService.class.getSimpleName());
    }
    public MyIntentService(String name) {
        super(name);
    }
    /**
     * {@link Service} のライフサイクルの開始。
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.v(TAG, "onCreate");
    }
    /**
     * 親クラスで必要な処理がひと通り揃っているため、通常は Override の必要はない。
     */
    @Override
    public IBinder onBind(Intent intent) {
        Log.v(TAG, "onBind");
        return super.onBind(intent);
    }
    /**
     * 親クラスで必要な処理がひと通り揃っているため、通常は Override の必要はない。
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.v(TAG, "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }
    /**
     * {@link Context#startService(Intent)} によって呼び出される。
     * ワーカスレッド上で実行されるため、ネットワーク通信等のスレッドをブロックする処理を直接記述しても問題ない。←★ワーカスレッドで実行される
     */
    @Override
    protected void onHandleIntent(Intent intent) {
        Log.v(TAG, "onHandleIntent");
    }
    /**
     * {@link Service} のライフサイクルの終了。
     * {@link IntentService} では、1 回の {@link Context#startService(Intent)} の呼び出しで
     * 1 つのライフサイクルが回るように作られている。
     */
    @Override
    public void onDestroy() {
        Log.v(TAG, "onDestroy");
        super.onDestroy();
    }
}


●Serviceの呼び出し


□開始サービス、IntentService



  • 開始サービスは、サービス自身で終了の命令を実行しない場合、サービスを止めるメソッドを呼び出す
  • IntentServiceは自身で終了するので必要ない


ex)

public class MyActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // do something ...
        Intent intent = new Intent(this, StartedService.class);
        startService(intent);
    }
    @Override
    protected void onStop() {
        Intent intent = new Intent(this, StartedService.class);
        stopService(intent);
        super.onStop();
    }
}


□バインドするサービス



  • 呼び出しの後、バインドしたサービスのインスタンスを受け取り、直接操作が可能
  • バインドするコンポーネントにしたがってライフサイクルを管理する。コンポーネントの終了時にサービスのバインドを解除する。


ex)

public class MyActivity extends Activity {
    private BoundService mBoundService;
    // BoundService を扱う時のインタフェース
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.v(TAG, "onServiceDisconnected");
            mBoundService = null;
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.v(TAG, "onServiceConnected");
            mBoundService = ((BoundService.ServiceBinder) service).getService();
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // do something ...
        // サービスのバインド
        // バインド時の、バインド元とバインド先サービスの橋渡しをするための ServiceConnection インスタンスを一緒に渡す
        // Context.BIND_AUTO_CREATE によって、バインド時に自動で Service のライフサイクルが始まるようになる
        bindService(new Intent(MainActivity.this, BoundService.class), mConnection, Context.BIND_AUTO_CREATE);
    }
    @Override
    protected void onStop() {
        unbindService(mConnection);
        super.onStop();
    }
}


●Loader



  • 非同期で処理を行う新しいフレームワーク
  • ネットワークやファイルI/Oを介してデータを呼び出すためのフレームワークとして設計されている。
  • ActivityやFragmentのライフサイクルと、非同期処理を分類する目的で作られた。AsyncTaskの改良版


□AsyncTaskLoader

  • ネットワークや通信、ファイルI/Oでデータを読み出す場合、クラスを拡張して非同期処理を行う


ex)

// Support Package のものを利用する ←★2.xの対応が必要な場合
import android.support.v4.content.AsyncTaskLoader;
public class MyAsyncTaskLoader extends AsyncTaskLoader<String> {
    public static final String TAG = MyAsyncTaskLoader.class.getSimpleName();
    private String mCachedData;
    public MyAsyncTaskLoader(Context context) {
        super(context);
    }
    // 非同期処理の中身
    @Override
    public String loadInBackground() {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            Log.e(TAG, "interrupted!: ", e);
        }
        return "hogehoge";
    }
    @Override
    public void deliverResult(String data) {
        // ローダがリセットされ、そのローダのライフサイクルが終了となる場合
        if (isReset()) {
            // キャッシュデータがある場合は、キャッシュを削除して、メモリから破棄可能にする
            if (mCachedData != null) {
                mCachedData = null;
            }
            return;
        }
        // 得られたデータをキャッシュする
        mCachedData = data;
        // ローダが開始されている場合、親にデータが得られたことを通知する
        if (isStarted()) {
            super.deliverResult(data);
        }
    }
    @Override
    protected void onStartLoading() {
        // キャッシュがある場合はそちらを返す
        if (mCachedData != null) {
            deliverResult(mCachedData);
            return;
        }
        // データソースに変更があったり、キャッシュデータがない場合は loadInBackground() に行くようにする
        if (takeContentChanged() || mCachedData == null) {
            forceLoad();
        }
    }
    // ローダの非同期処理がストップする時のコールバック
    @Override
    protected void onStopLoading() {
        cancelLoad();
        super.onStopLoading();
    }
    // ローダがリセットされる時のコールバック
    @Override
    protected void onReset() {
        onStopLoading();
        super.onReset();
    }
}


●Loaderの呼び出しとコールバック



  • LoaderManagerとLoaderCallbacksを用いる


ex)

public class MainActivity extends FragmentActivity implements LoaderCallbacks<String> {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // ローダの管理をするオブジェクト
        LoaderManager manager = getSupportLoaderManager();
        Bundle argsForLoader = new Bundle();
        // ローダを初期化して非同期処理を開始する
        manager.initLoader(0, argsForLoader, MainActivity.this);
    }
    // id に対応した Loader のインスタンスを作って返す
    // args は Loader に渡したい引数を Bundle に詰めたもの
    @Override
    public Loader<String> onCreateLoader(int id, Bundle args) {
        switch (id) {
            case 0:
                return new MyAsyncTaskLoader(this);
            default:
                return null;
        }
    }
    // 結果を受け取るコールバック
    // メインスレッドで動作する
    @Override
    public void onLoadFinished(Loader<String> loader, String result) {
        Toast.makeText(this, result, Toast.LENGTH_LONG).show();
    }
    // ローダがリセットされる時のコールバック
    @Override
    public void onLoaderReset(Loader<String> loader) {}
}


●AsyncTask



  • 非同期処理のためのクラス
  • 非同期に実行したい処理、処理前、処理後のメインスレッド上での処理を記述できる
  • 内部でスレッドプールを保持(バージョンによって異なる)
  • 最大プール数は128
  • インスタンスの使い回しはできない



/**
 * 非同期処理を実行するためのネストクラス。
 *
 * ジェネリクスの仕組みを用いて、非同期処理に渡す引数の型、進捗を監視するコールバック用の型、非同期処理の結果を表す型を指定する。
 *
 * Activity や Fragment のライフサイクルに合わせて、自分で AsyncTask をコントロールする必要があり、これを行わないと
 * 特に {@link AsyncTask#onPostExecute()} で、参照するオブジェクトが既にメモリから破棄されていて NullPointerException となることが起こりえる。
 */
public class MyAsyncTask extends AsyncTask<Void, Void, Void> { ←★ジェネリクスによる引数の型などの宣言
    private Context mApplicationContext;
    public MyAsyncTask(Context applicationContext) {
        super();
        mApplicationContext = applicationContext;
    }
    /**
     * ★非同期処理を実行する前に UI スレッドで実行する処理を書く
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        Toast.makeText(mContext, "onPreExecute", Toast.LENGTH_SHORT).show();
    }
    /**
     * ★非同期処理の進捗を受け取るコールバック。
     */
    @Override
    protected void onProgressUpdate(Void... values) {
        super.onProgressUpdate(values);
        Toast.makeText(mContext, "onProgressUpdate", Toast.LENGTH_SHORT).show();
    }
    /**
     * ★非同期処理の本体
     * 引数は非同期処理内容に渡すためのパラメータ配列。
     */
    @Override
    protected Void doInBackground(Void... params) {
        // 2 秒おきに進捗を通知する
        try {
            publishProgress();
            Thread.sleep(2000L);
            publishProgress();
            Thread.sleep(2000L);
            publishProgress();
            Thread.sleep(2000L);
            publishProgress();
            Thread.sleep(2000L);
            publishProgress();
            Thread.sleep(2000L);
            publishProgress();
        } catch (InterruptedException e) {
            Log.e(MyAsyncTask.class.getSimpleName(), "thread interrupted: ", e);
        }
        return null;
    }
    /**
     * ★非同期処理の実行後に、UI スレッドで実行する処理。
     * 引数は {@link AsyncTask#execute(Object...)} の返り値。
     */
    @Override
    protected void onPostExecute(Void result) {
        super.onPostExecute(result);
        Toast.makeText(mContext, "onPostExecute", Toast.LENGTH_SHORT).show();
    }
}

□呼び出し元のコンポーネント


public class MyActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // do something...
        // 非同期処理の開始
        new MyAsyncTask(getApplicationContext()).execute(); ←★executeで実行
    }
}


※参考:mixi Android
https://github.com/mixi-inc/AndroidTraining/wiki/2.08.-%E9%9D%9E%E5%90%8C%E6%9C%9F%E5%87%A6%E7%90%86

2014年11月17日月曜日

Android in-App Billing 注意点

Android の in-App Billing については近いうちまとめようと思うが、検証中にいくつかハマったことをメモ

1. AndroidManifest.xmlの記述

基本的なことなのだが、何度もプロジェクトを作っていると忘れることがあった。疑わないので質がわるい。

<uses-permission android:name="com.android.vending.BILLING" />


2. アプリケーションのバージョン

アプリケーションがGoogle Developer Console でアップロードしたものをより低いと、課金対象にならない。注意が必要。

上記の場合、「このバージョンのアプリではアプリ内課金が許可されません」となる。


3. アプリケーションのパッケージ構成

Google Developer Consoleに登録したパッケージの配下に「プロダクトID」がひもづく。

そのため、アプリケーションのパッケージ構造が違う場合、プロダクトIDが見つからないエラーがでる。

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

2014年11月15日土曜日

Android SDK Tools をバージョンアップして問題がおこったときに試すこと

Android SDK Tools をバージョンアップして問題がおこったときに試すこと


Andrid SDK Toolsをバージョンアップして問題が起こるケースは珍しくない。

support libraryのgen配下が生成されないなどの障害が発生する。

そのたびに解決策を考えるのだが、まずは以下のような対策を行うのでメモ。


1.ビルドターゲットバージョンのAndroid SDK Build-toolsがインストールされているか


Android SDK Build-toolsにもたくさんのバージョンが存在する。

project.propertiesで指定しているビルドターゲットに対応したSDK Build-toolsをいれておこう。



2.最新のJDKに入れ替える


新しいAndroid SDKが、最新のJDKに依存しているケースもある。最新のJDKに変更しよう。


3.project.propertiesの見直し


そもそもターゲットが間違っていないだろうか。最新のBuild-toolsを導入した場合、targetも最新に切り替えておこう。


4.EclipseのUpdateを試す

新しいアップデートが促されていないかどうか確認しよう。

Eclipse → 「Help」 → 「Check for Update」


5.Eclipseの再起動


変更した項目によっては、反映に再起動を必要とする場合がある。


6.SDK Managerから以下をインストール、アップデートする


Android Support Library

Google Play services
Goole Repository


7.Android L のライブラリを認識できない場合


SDK Managerを使って、L(API 21)のアンインストール、インストールを行う




これらで解決しない場合、ADTを丸ごと入れ替えるなど、力技を使う。

2014年11月14日金曜日

Android ListViewとViewPager

ListViewとViewPager


●2つの特殊なView


□ListView

縦にスクロールするView。ListView自身には、一覧の中身を管理する機能が無い。
Adapterを用いて、データソースの管理とデータのViewへのBindを行い、スクロール位置に合わせたデータをAdapterから取得する仕組み。この仕組みは、UI作成の際の汎用的な手法である。

リストデータは、Listインターふぇおーすを実装したデータソースや、DBへ問い合わせ結果のデータソースも可能。

□ListAdapter

データをbindして表示する際に、ListAdapterインターフェースを実装したAdapterを使用する。
Adapterにはいくつかの種類が提供されている。


  • BaseAdapter:共通基底クラス。ListViewとSpinnerで使用可能。ListViewでは、ListAdapter用のinterfaceを実装する。
  • ArrayAdapter:配列やリストデータソースをBindする。
  • CursorAdapter:データベースへの問い合わせ結果をBindする。
  • SimpleCursorAdapter:データベースへ問い合わせた結果をViewにBindするための簡易Adapter



●ListViewの表示

以下の2つだけで、ListViewへの表示ができる。


  • ListViewをレイアウトxmlに配置
  • ListViewにAdapterをセットする


レイアウトのex)

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <!-- ListViewを配置 -->
    <ListView ←★これ
        android:id="@+id/ListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" /
</RelativeLayout>

□レイアウトxmlを使うActivity側では、以下の様にAdapterを用意してBindする。


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main); ←★ListViewを定義したレイアウトxml
    mActivity = this;
    // ListViewに表示するデータを作成する ←★ListViewに表示するデータ
    ArrayList<String> list = new ArrayList<String>();
    for (int i = 0; i < 20; i++) {
        list.add("hoge" + i);
    }
    ListView listView = (ListView) findViewById(R.id.ListView);
    // android.R.layout.simple_list_item_1はAndroidで既に定義されているリストアイテムのレイアウトです
    ArrayAdapter<String> adapter = new ArrayAdapter<String>(mActivity,
            android.R.layout.simple_list_item_1, list); ←★Adapterの生成
    listView.setAdapter(adapter); ←★Bind
   
   
    // タップした時の動作を定義する
    listView.setOnItemClickListener(new OnItemClickListener() { ←★タップのリスナーを設定
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            // Adapterからタップした位置のデータを取得する
            String str = (String) parent.getItemAtPosition(position); ←★タップしたPositionなどの情報を取得可能
            Toast.makeText(mActivity, str, Toast.LENGTH_SHORT).show();
        }
    });
}


●カスタマイズしたリストアイテムの表示


以下が必要な作業。


  • リストアイテムのレイアウトxmlに配置
  • 独自アダプタの作成


□リストアイテムのレイアウトxml


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" ←★ここではRelative(横)レイアウトを使う
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="4dp" >
    <ImageView ←★各行に表示する画像
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:src="@drawable/ic_launcher" />
    <TextView ←★各行に表示するテキスト
        android:id="@+id/TitleText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_toRightOf="@+id/imageView1" ←★imageViw1の右に配置
        android:textSize="18sp" />
    <TextView ←★
        android:id="@+id/SubTitleText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/TitleText"←★TitleTextと左端をあわせる
        android:layout_alignParentRight="true"
        android:layout_below="@+id/TitleText" /> ←★TitleTextの下に配置
</RelativeLayout>

□独自Adapterの作成


public class CustomListItemAdapter extends ArrayAdapter<String> { ←★ArrayAdapterを継承
    private LayoutInflater mLayoutInflater;
    public CustomListItemAdapter(Context context, List<String> objects) {
        // 第2引数はtextViewResourceIdとされていますが、カスタムリストアイテムを使用する場合は特に意識する必要のない引数です
        super(context, 0, objects); ←★表示するデータobjectsの設定
        // レイアウト生成に使用するインフレーター
        mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = null;
        // ListViewに表示する分のレイアウトが生成されていない場合レイアウトを作成する
        if (convertView == null) {
            // レイアウトファイルからViewを生成する
            view = mLayoutInflater.inflate(R.layout.custom_list_item, parent, false);
        } else {
            // レイアウトが存在する場合は再利用する
            view = convertView;
        }
        // リストアイテムに対応するデータを取得する
        String item = getItem(position);
        // 各Viewに表示する情報を設定
        TextView text1 = (TextView) view.findViewById(R.id.TitleText);
        text1.setText("Title:" + item);
        TextView text2 = (TextView) view.findViewById(R.id.SubTitleText);
        text2.setText("SubTitle:" + item);
        return view;
    }
}


●パフォーマンス view holder デザインパターン


findViewByIdメソッドを頻繁に呼び出すとパフォーマンスが低下する。その場合、view holderデザインパターンを利用する。

□Adapter パターン

【参考】少し古い資料ですが、丁寧でわかりやすいのでおすすめ。
http://www.slideshare.net/yanzm/adapter-listview-expandalbelistview
Adapterもデザインパターンの1つである。
メリット
・既存のクラスに修正不要
・インターフェースを変更可能
・ListViewのadapterは委譲のパターン

□view holder パターン

【参考】使い方などの丁寧な説明。
http://y-anz-m.blogspot.jp/2010_08_01_archive.html

findViewByIdでviewを探す処理を削減することで、パフォーマンスを改善することができる。


●ViewPager

横にフリックしてViewを切り替えるためのView

複数のFragmentを切り替える用途にも使用可能
SupportPackageで提供される機能

□ViewPagerの管理

Adapterを使用する。

以下が必要な作業。


  • ①ViewPagerをレイアウトxmlに配置
  • ②独自PagerAdapterを実装
  • ③ViewPagerにAdapterをセットする


①レイアウトxml

レイアウトxmlの例)

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <android.support.v4.view.ViewPager ←★SupportPackageで提供されるViewPager
        android:id="@+id/Pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

②PaerAdapterの実装

PagerAdapterにimplementsが必要なメソッドは以下のとおり。

Object instatiateItem(ViewGroup container, int position); ← ★ページの生成
void destroyItem(ViewGroup container, int position, Object object); ← ★ページの削除
int getCount(); ← ★ページ数を返却
boolean isViewFromObject(View view, Object object); ← ★instantiateItemで返却されたキーObjectとページが関連づいているかどうかを確認。


ex)
class SamplePagerAdapter extends PagerAdapter {
private static final int PAGE_COUNT = 5; ← ページ数
private Context mContext;
public SamplePagerAdapter(Context context) {
    super();
    mContext = context;
}

@Override
public Object instantiateItem(ViewGroup container, int position) { ←★ページの生成
    // TextViewを生成
    TextView textView = new TextView(mContext);
    textView.setText("Position:" + position);
    //コンテナに追加
    container.addView(textView);
    // ここではTextView自体をキーとして返しています←★Adapter内のuniqueな識別子として利用
    return textView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) { ←★ページ削除
    // viewの削除
    // objectはinstantiateItemで返却したオブジェクトです
    container.removeView((View) object);
}
@Override
public int getCount() { ← ★ページ数
    // ページ数を返します。今回は固定値としています。
    return PAGE_COUNT;
}
@Override
public boolean isViewFromObject(View view, Object object) { ←★instantiateItemのレスうポンスObjectとページが等しいかどうか?
    // キーが正しいかを判定
    return view == (TextView) object;
}

③ViewPagerにAdapterを設定する


MainActivityでViewPagerを生成し、Adapterを設定する。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ViewPager pager = (ViewPager) findViewById(R.id.Pager);
    pager.setAdapter(new SamplePagerAdapter(this));
}


●FragmentPagerAdapter

Fragmentの切り替えで使うケースが多い。全てのページをメモリで保持する。

メモリで保持しきれない数のページがある場合、FragmentStatePagerAdapter を使用する。

以下が必要な作業

  • ①ViewPagerをレイアウトxmlに配置
  • ②Fragmentを作成
  • ③独自FratmentPagerAdapterを実装
  • ④ViewPagerにAdapterをセットする



①Fratmentのレイアウト(fragment_main.xml)


ex)

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black" >
    <TextView
        android:id="@+id/TextView1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="4dp"
        android:background="@android:color/white"
        android:gravity="center"
        android:textSize="90sp"
        android:textStyle="bold" />
</FrameLayout>


②SampleFragment(fragmentクラス)


ex)

public class SampleFragment extends Fragment {
    public static SampleFragment newInstance(int position) { ←★ページのpositionをいれておく
        SampleFragment sampleFragment = new SampleFragment();
        Bundle bundle = new Bundle();
        bundle.putInt("position", position);
        sampleFragment.setArguments(bundle);
        return sampleFragment;
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Bundle bundle = getArguments();
        int position = 0;
        if (bundle != null) {
            position = bundle.getInt("position");
        }
        View view = inflater.inflate(R.layout.fragment_main, container, false); ←★Viewにfragmentのレイアウトを設定
        TextView text = (TextView) view.findViewById(R.id.TextView1);
        text.setText(String.valueOf(position));
        return view;
    }
}


③独自FragmentPagerAdapter SampleFragmentPagerAdapter


implementsが必要なデータ

  • Fragment getItem(int position); ←★Fragmentの生成
  • int getCount(); ←★ページ数を返す


ex)

public class SampleFragmentPagerAdapter extends FragmentPagerAdapter {
    private static final int PAGE_COUNT = 5; ←★ページ数を返す
    public SampleFragmentPagerAdapter(FragmentManager fm) {←★ FragmentPagerAdapterを受け取る
        super(fm);
    }
    @Override
    public Fragment getItem(int position) { ←★Fragmentを新しく生成する
        return SampleFragment.newInstance(position);
    }
    @Override
    public int getCount() { ←★ページ数を返す
        return PAGE_COUNT;
    }
}


④ViewPagerにAdapterをセットする


FragmentManagerを独自のFragmentPagerAdapterに渡す。
FragmentPagerAdapterをViewPagerに設定する。


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ViewPager pager = (ViewPager) findViewById(R.id.Pager);
    FragmentManager fm = getSupportFragmentManager();
    SampleFragmentPagerAdapter sampleFragmentPagerAdapter = new SampleFragmentPagerAdapter(fm);
    pager.setAdapter(sampleFragmentPagerAdapter);
}