非同期処理、HTTP通信

(ブログ記事の一覧は「こちら」)

AndroidアプリでHTTP通信を行う方法を学びます。Androidアプリでは、非同期処理でHTTP通信を行う必要があります。例題を元に、非同期処理とHTTP通信のプログラミングを理解しましょう。

ここで学ぶこと

  • スレッド
  • 非同期処理(Thread, Handler による方法)
  • 非同期処理(AsyncTask による方法)
  • HTTP通信(HttpURLConnection, HttpResponse)

準備

スレッドとは

スレッドとは、プログラムの処理の一連の流れのことを言います。
今まで学んだAndroidアプリのプログラムは、1つの処理で実行していますので、同一のスレッドで動作しています。途中で重い処理があった場合は、その処理が終わるまで待たされることになります。そこで、重い処理を別のスレッドで処理する、つまり複数のスレッドを用意することで、そのような症状を改善することができます。

例えば、PCやスマートフォンのブラウザでサイトを開いたとき、画像が多く含まれている場合はそれらをダウンロードする必要がありますが、だからといって画像のダウンロード中にブラウザが重くなったり動かなくなることはないと思います。これは、ブラウザの動作と画像のダウンロードとでそれぞれ別のスレッドで処理する仕組みが働いています。

メインで処理するスレッドのことをメインスレッド(またはUIスレッド)と呼び、メインスレッドとは別に処理するスレッドのことをサブスレッドと呼ぶことが多いです。

非同期処理とは

非同期処理とは、メインスレッドがある処理を実行しているのに並行して、他のスレッドが別の処理を実行しているときに、メインスレッドの処理が中断されずに実行できる方式のことを言います。
同期処理とは、メインスレッドの処理の途中に、他のスレッドが別の処理を実行すると、メインスレッドの処理が中断される方式のことを言います。

これらからわかるように、HTTP通信を行う場合、非同期処理を行う必要であることがわかります。

例題1

非同期処理(Thread, Handler による方法)でのHTTP通信を行うサンプルを作成します。

作成するもの

私が管理しているサーバーにテキストファイルを置きましたので、AndroidアプリでHTTP通信を行って、書かれているテキストを読み出して画面に表示する、シンプルなサンプルアプリを作成します。

レイアウト

以下のコントロールを配置します。

  • TextView(R.id.textView)
  • Button(R.id.button)

プログラム

Androidアプリでインターネットに接続するためには、パーミッション(許可)を設定する必要があります。AndroidManifest.xmlXMLで開き、uses-permissionの行を追加します。(今回のプログラムは、これを追加しないとアプリの起動時に落ちてしまいます。)

<?xml version="1.0" encoding="utf-8"?>
<manifest package="[ This is the package name. ]"
          xmlns:android="http://schemas.android.com/apk/res/android">

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

    <application
        ...


まず準備として、MainActivityに以下の必要なコードを追記しましょう。

public class MainActivity extends AppCompatActivity {

    private final static String URL_SAMPLE = "https://www.loopsessions.co.jp/test/test.txt";

    private Handler mHandler = new Handler();

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

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                callThreadAndConnect();
            }
        });
    }

    private void callThreadAndConnect() {

    }
}


まずは非同期処理のプログラムです。
スレッドの生成にはThreadクラスを使用します。thread.start() を呼び出すと、threadの内部で定義した run() が非同期処理として実行されます。

private void callThreadAndConnect() {
    // スレッドの生成
    Thread thread = new Thread(new Runnable() {
        public void run() {
            String strData;
            try {
                // HTTP通信
                strData = new String(procHttp2Data(URL_SAMPLE));
            } catch (Exception e) {
                e.printStackTrace();
                strData = null;
            }

            // ハンドラの生成(スレッド内でUIを操作する場合は必要)
            final String strResult = strData;
            mHandler.post(new Runnable() {
                public void run() {
                    TextView textView = findViewById(R.id.textView);
                    if (strResult != null) {
                        textView.setText(strResult);
                    } else {
                        textView.setText("読み込みに失敗しました。");
                    }
                }
            });
        }
    });
    thread.start();
}

まず、サブスレッドでHTTP通信(procHttp2Data 関数は後ほど説明)を行います。その後、HTTP通信で受け取った結果の文字列をTextViewに表示する処理を行います。ここで注意が必要で、TextViewなどのコントロールはメインスレッドで処理しないといけません。(サブスレッドでコントロールで処理しようとすると、アプリは落ちてしまいます。)サブスレッド内でメインスレッドを呼び出すには、Handlerクラスを使用します。


次に、HTTP通信のプログラムです。
AndroidでHTTP通信を行うには、HttpURLConnectionクラスを使用します。(try構文の内部で使用する必要があります。)

private byte[] procHttp2Data(String strUrl) throws Exception {
    byte[] data = new byte[1024];
    HttpURLConnection conn = null;
    InputStream input = null;
    ByteArrayOutputStream output = null;

    try {
        URL url = new URL(strUrl);
        conn = (HttpURLConnection)url.openConnection();
        conn.setRequestMethod("GET");
        conn.connect();

        input = conn.getInputStream();

        // バイト配列の読み込み
        output = new ByteArrayOutputStream();
        while (true) {
            int size = input.read(data);
            if (size <= 0) break;
            output.write(data, 0, size);
        }

        output.close();
        input.close();
        conn.disconnect();

        return output.toByteArray();

    } catch (Exception e) {
        // 例外によるエラー
        e.printStackTrace();
        try {
            if (conn != null) conn.disconnect();
            if (input != null) input.close();
            if (output != null) output.close();
        } catch (Exception e2) {
            e2.printStackTrace();
        }
        // 呼び出し元に例外処理を投げる
        throw e;
    }
}

実行、確認

ボタンをクリックすると、 非同期処理でHTTP通信が始まり、TextViewに結果が表示されます。(「こんにちは」と表示されれば、HTTP通信の処理が成功しています。)

例題2

非同期処理(AsyncTask による方法)でのHTTP通信を行うサンプルを作成します。
Androidアプリでは、こちらのAsyncTaskクラスの方法を使用するのが一般的ですので、今後HTTP通信のプログラムを説明する場合はこちらの方法を使用します。

作成するもの

例題1と全く同じ結果となるアプリを作成します。
違いは、非同期処理のプログラムの記述方法です。

プログラム

AsyncTaskクラスによる非同期処理のプログラムです。
MainActivity内に以下のクラスを追加します。

/*
class AsyncTask<Params, Progress, Result> {
   Result doInBackground(Params... var1);
   void onProgressUpdate(Progress... values);
   void onPostExecute(Result result);
}
*/

private class MyAsyncTask extends AsyncTask<String, Integer, String> {
    @Override
    protected String doInBackground(String... params) {
        String strData;
        try {
            // myAsyncTask.execute の引数から取得(必要に応じて使用)
            String strParam0 = params[0];
            String strParam1 = params[1];
                
            // HTTP通信
            strData = new String(procHttp2Data(strParam0));
        } catch (Exception e) {
            e.printStackTrace();
            strData = null;
        }
        return strData;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
    }

    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);

        TextView textView = findViewById(R.id.textView);
        textView.setText(result);
    }
}

AsyncTaskクラスを継承したMyAsyncTaskクラスを定義することで、例題1で行っているプログラムと同じことが実現できます。doInBackground関数にて非同期処理がサブスレッドで行われ、そこでの結果をonPostExecute関数にてメインスレッドで処理されます。


呼び出し方法の例は以下になります。
例題1の callThreadAndConnect関数と差し替えてください。

private void callThreadAndConnect_AsyncTask() {
    // AsyncTaskの呼び出し
    MyAsyncTask myAsyncTask = new MyAsyncTask();
    myAsyncTask.execute(URL_SAMPLE, "dummy");
}

実行、確認

例題1と同じ結果になります。

課題1

例題で設定している URL_SAMPLE について、別のURLに変更して実行すると、どのような結果になるか確認してください。

private final static String URL_SAMPLE = "https://www.loopsessions.co.jp/test/test.txt";

課題2

(課題2はエミュレータで行ってください。)

まず準備として、 「PHPからデータベースを操作(データの表示、検索)」から、「準備」のデータベースと「例題」のPHPプログラムを構築して、以下のURLが実行できるようにしてください。

http://localhost/sample/food/getFoodName_receive_pdo.php

上記URLに対してAndroidアプリでHTTP通信を行い、データが取得できることを確認してください。

注意: AndroidからHTTP通信で呼び出す場合は、「localhost」をIPアドレスに置き換える必要があります。 「コマンドプロンプト」で以下のコマンドを実行するとIPアドレスが確認できます。

> ipconfig