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

うさがにっき

読書感想文とプログラムのこと書いてきます

Androidの非同期処理まとめ

概要

以下3種類のAndroidの非同期処理についてまとめる

  • Handler
  • AsyncTask
  • AsyncTaskLoader

詳細

Handler

Androidではメインスレッド以外からUIを書き換えようとしたら例外が発生する
そのため、別スレッドからUI(メイン)スレッドを書き換えたいときの処理をLooperというQueueにためて、順番に処理していく機構をHandlerという
基本的な使い方

    public static class PlaceholderFragment extends Fragment {
        private Handler handler = new Handler();

        public PlaceholderFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);
            final TextView text = (TextView)rootView.findViewById(R.id.text);
            Thread thread = new Thread(new Runnable() {
                
                // ネットワーク処理などの時間のかかる処理
                
                @Override
                public void run() {
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            text.setText("test");
                        }
                    });
                }
            });

            return rootView;
        }

    }

Handler#postは指定された処理(Runnableオブジェクト)をLooperにpostする処理
なお、Handlerオブジェクトを作成するのはUIスレッドで行うこと、他スレッドで行うとエラーとなる

別スレッドからUI操作の別解としてHandler#sendMessage, Activity#runOnUiThreadがある

Handler#sendMessage

メッセージの送信と処理方法を明確に分離する方法
複数箇所からメッセージを送信し、その処理が共通している場合にはよく使われる

    public static class PlaceholderFragment extends Fragment {
        private Handler handler;
        public PlaceholderFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);
            final TextView text = (TextView)rootView.findViewById(R.id.text);

            handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    if(msg.what == 1) {
                        text.setText((String)msg.obj);
                    }
                }
            };

            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {

                    // ネットワーク処理などの時間のかかる処理

                    Message msg = Message.obtain(handler, 1, "test");
                    handler.sendMessage(msg);

                }
            });

            return rootView;
        }

    }

前述した通り、sendMessageでは送信処理と結果処理を分けて記述する
結果処理を司るのがhandleMessage
引数として送信されたMessageを受け取り、処理結果を記述する

messageクラスには以下のフィールドがある

int what ユーザ定義のメッセージコード
int arg1, arg2 任意の整数値
Object obj 任意のオブジェクト

whatの種類によって処理を分岐したり、より細かな指定が必要な場合にはarg1, arg2を用いることができる
また、メッセージインスタンスを作成する際にはnewを使うよりMessage.obtainを使ったほうが効率的
なお、メッセージ本体が必要なく、メッセージコードだけで処理をしたいときはsendEmptyMessageメソッドを使うのもあり

Activity#runOnUiThread

runOnUiThreadでは現在のスレッドがメインスレッドかどうか判定し、別スレッドである場合には内部的にpostメソッドを呼び出す

    public static class PlaceholderFragment extends Fragment {
        Activity activity;
        
        public PlaceholderFragment() {
        }

        @Override
        public void onAttach(Activity activity) {
            super.onAttach(activity);
            
            this.activity = activity; 
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);
            final TextView text = (TextView)rootView.findViewById(R.id.text);

            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {

                    // ネットワーク処理などの時間のかかる処理

                    activity.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            text.setText("test");
                        }
                    });

                }
            });

            return rootView;
        }

    }

AsyncTask

非同期処理を実施するための専用クラス
非同期処理とメインスレッド処理をまとめて記述できるためソースの見通しがよくなる

/*
 * 型パラメータは
 * 1.非同期処理の実行時にメインスレッド側から与えられる情報
 * 2.進捗状況を管理するための情報
 * 3.非同期処理の結果
 */
public class HttpTestGet extends AsyncTask<Void, Integer, String> {
    TextView textView;
    private static final String BR = System.getProperty("line.separator");

    public HttpTestGet(TextView text) {
        super();
        textView = text;
    }

    // doInBackgroundの事前準備処理(UIスレッド)
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    // 別スレッド処理
    @Override
    protected String doInBackground(Void... param) {
        try {
            publishProgress(10);
            URL url = new URL("http://www.wings.msn.to/tmp/books.json");
            URLConnection connection = url.openConnection();

            BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));

            StringBuilder sb = new StringBuilder();
            StringBuilder result = new StringBuilder();

            String line;

            publishProgress(50);
            while ((line = in.readLine()) != null) {
                sb.append(line);
            }

            // 取得した文字列からjsonobjectを作成
            JSONObject jsonObject = new JSONObject(sb.toString());
            // booksキー配下の要素を配列として取得
            JSONArray jsonArray = jsonObject.getJSONArray("books");
            // 配列の内容を順に取得し、titleキーを読み込み
            for(int i=0; i<jsonArray.length(); i++) {
                JSONObject book = jsonArray.getJSONObject(i);
                result.append(book.getString("title") + BR);
            }

            publishProgress(100);
            return result.toString();

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


        return "";
    }

    // doInBackgroundの事後処理(UIスレッド)
    @Override
    protected void onPostExecute(String result) {
        textView.setText(result);
    }

    // 進捗状況をUIに反映するための処理(UIスレッド)
    @Override
    protected void onProgressUpdate(Integer... values) {
    // progressDialogなどで進捗表示したりする
    }

    // 非同期処理がキャンセルされた場合の処理
    @Override
    protected void onCancelled(String s) {
        super.onCancelled(s);
    }
}

AsyncTaskの型パラメータは以下の意味

1つめ 非同期処理の実行時にメインスレッド側から与えられる情報 doInBackgroundの引数と同じ型になる
2つめ 進捗状況を管理するための情報 onProgressUpdateの引数と同じ型になる
3つめ 非同期処理の結果 doInBackgroundの返り値、onPostExecuteの引数と同じ型になる

実行処理

            HttpTestGet get = new HttpTestGet(text);
            get.execute();
            
//            get.cancel(true);

executeメソッドではAsyncTaskの一つ目に指定した型パラメータの型を引数として指定する
cancelメソッドでは現在行っている処理を中断するかどうかを引数に指定する

AsyncTaskLoader

位置づけはほぼAsyncTaskと同じだが、メインスレッドと非同期処理を別々に記述するのがAsyncTaskLoader

public class MyLoader extends AsyncTaskLoader<String> {
    private static final String BR = System.getProperty("line.separator");

    private String urlStr;

    public MyLoader(Context context, String url) {
        super(context);
        this.urlStr = url;
    }

    // データの非同期読み込み
    @Override
    public String loadInBackground() {
        try {
            URL url = new URL(urlStr);
            URLConnection connection = url.openConnection();

            BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));

            StringBuilder sb = new StringBuilder();
            StringBuilder result = new StringBuilder();

            String line;

            while ((line = in.readLine()) != null) {
                sb.append(line);
            }

            // 取得した文字列からjsonobjectを作成
            JSONObject jsonObject = new JSONObject(sb.toString());
            // booksキー配下の要素を配列として取得
            JSONArray jsonArray = jsonObject.getJSONArray("books");
            // 配列の内容を順に取得し、titleキーを読み込み
            for(int i=0; i<jsonArray.length(); i++) {
                JSONObject book = jsonArray.getJSONObject(i);
                result.append(book.getString("title") + BR);
            }

            return result.toString();

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


        return "";
    }
}

バックグラウンドでの処理をAsyncTaskを継承したクラスを作成し、loadInBackgroundに記述する
AsyncTaskLoaderの型変数はAsyncTaskのメインスレッドに返す型に該当する

public class MainActivity extends ActionBarActivity implements LoaderManager.LoaderCallbacks<String> {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Bundle bundle = new Bundle();
        bundle.putString("url", "http://www.wings.msn.to/tmp/books.json");
        getLoaderManager().initLoader(1, bundle, this);
    }


    @Override
    public Loader<String> onCreateLoader(int id, Bundle args) {
        if(id == 1) {
            MyLoader loader = new MyLoader(this, args.getString("url"));
            loader.forceLoad();
            return loader;
        }
        return null;
    }

    @Override
    public void onLoadFinished(Loader<String> loader, String data) {

    }

    @Override
    public void onLoaderReset(Loader<String> loader) {

    }

定義したAsyncTaskLoaderをActivityで利用する
LoaderManager#initLoaderを使ってAsyncTaskLoaderオブジェクトを利用する
LoaderManagerはLoaderを管理するためのクラスでActivity#getLoaderManagerで取得できる
ActivityにはLoaderCallbackを実装させる
以下のメソッドを実装する

  • Loader onCreateLoader (int id, Bundle args)

Loaderの生成時

  • void onLoadFinished (Loader loader, D data)

Loaderでの処理終了時

  • void onLoaderReset (Loader loader)

Loaderのリセット時

参考

AndroidエンジニアのためのモダンJava

AndroidエンジニアのためのモダンJava