読者です 読者をやめる 読者になる 読者になる

【Android】AsyncTaskの各種イベントを全部クロージャでフックする

Android snippet

昨日のやつは別にTwitterとの通信に限らず、もっと汎用的に出来るんじゃないの?と思ったので試し書き。30分ぐらいでサクラエディタで一気に書いたのでコンパイルが通るかも怪しいです。

コード

public class ReactiveAsyncTask<Param, Progress, Result> extends AsyncTask<Param, Progress, ReactiveAsyncTaskResult<Result>> {
    
    private V1<Void> _onPreExecute = null;
    private R1<Result, Param> _onBackground = null;
    private R1<Progress, Void> _progressArg = null;
    private V1<Progress> _onProgress = null;
    private V1<Result> _onPostExecute = null;
    private V1<RuntimeException> _onError = null;
    
    public ReactiveAsyncTask(R1<Result, Param> onBackground) {
        _onBackground = onBackground;
    }
    
    public ReactiveAsyncTask<Param, Progress, Result> setOnPreExecute(V1<Void> action) {
        _onPreExecute = action;
        return this;
    }
    
    public ReactiveAsyncTask<Param, Progress, Result> setOnProgress(R1<Progress, Void> progressArg, V1<Progress> action) {
        _progressArg = progressArg;
        _onProgress = action;
        return this;
    }
    
    public ReactiveAsyncTask<Param, Progress, Result> setOnPostExecute(V1<Result> action) {
        _onPostExecute = action;
        return this;
    }
    
    public ReactiveAsyncTask<Param, Progress, Result> setOnError(V1<Exception> action) {
        _onError = action;
        return this;
    }
    
    @Override
    protected void onPreExecute() {
        if(_onPreExecute != null) _onPreExecute.call(null);
    }
    
    @Override
    protected ReactiveAsyncTaskResult<Result> doInBackground(Param... arg0) {
        
        ReactiveAsyncTaskResult<Result> r = new ReactiveAsyncTaskResult<Result>();
        
        if(_onProgress == null) {
            try {
                r.setResult(_onBackground.call(arg0[0]);
                if(_progressArg != null) publishProgress(_progressArg.call(null));
            } catch(RuntimeException e) {
                r.setError(e);
            }
            
            return r;
        }
    }
    
    @Override
    protected void onProgressUpdate(Progress... progress) {
        _onProgress.call(progress[0]);
    }

    @Override
    protected void onPostExecute(ReactiveAsyncTaskResult<Result> r) {
        if(!r.hasError() && _onPostExecute != null) {
            _onPostExecute.call(r.getResult());
        } else {
            if(_onError != null) {
                _onError.call(r.getError());
            }
        }
    }
    
}

public class ReactiveAsyncTaskResult<T> {
    private T result;
    private RuntimeException error;
    
    public T getResult() { return this.result; }
    public void setResult(T result) { this.result = result; }
    public RuntimeException getError() { return this.error; }
    public void setError(RuntimeException error) { this.error = error; }
    public boolean hasResult() { return this.error != null; }
    
}

説明

基本的な考え方(Maybeモナドちっくなものを使うとか)は昨日説明した通りなので割愛します。イベントをフックする方法は[Android]Twitter4JのUserStreamのイベント時の動作をクロージャで指定するで書いたので割愛します。説明することがなくなりました。お疲れ様でした。

コンストラクタで渡してるのが_onBackgroundだけなのは、明示的に実装が必要なのはそれだけだからです。

補足

二つほど気に入らないことがあります。エラー処理とonProgressUpdateです。後者から説明しましょう。

onProgressUpdateを呼ぶにはdoInBackgroundからpublishProgressってメソッドを呼んでやる必要があるんですが、肝心のdoInBackgroundに設定するクロージャ(_onBackground)からはこいつを呼び出すことが出来ません。ですので、publishProgressに渡す引数を作るクロージャ(_progressArg)とonProgressUpdateが呼び出された時に使うクロージャ(_onProgress)の二つを設定してあげなければなりません。

まぁどうせ呼び出し元はActivityで、各イベントを一気に設定するのがほとんどでしょうから、呼び出し元でメンバ変数なりなんなりを上手く設定してやればさほど問題はないと思います。(プログレスバーに値を設定する、とかのレベルなら。)単に気に入らないだけです。

問題はエラー処理です。前回のは、言ってみれば_onBackground内でReactiveAsyncTaskResultを作ってしまってたんですが、汎用的って意味だとなんとなく違う雰囲気がするのでやめました。

それでも_onBackground内で検査例外をやり過ごして_onErrorの処理に向かわせるには

try {
    //検査例外が発生する処理
} catch(Exception e) {
    throw new RuntimeException(e);
}

としなければなりません。(自信ないけどあってるよね?)そう考えると、やっぱり_onBackgroundの返り値はReactiveAsyncTaskResult<Result>がベターなのかなぁ。うむむ。