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

0 件のコメント:

コメントを投稿