diff --git a/NetworkConnect/Application/src/main/java/com/example/android/networkconnect/MainActivity.java b/NetworkConnect/Application/src/main/java/com/example/android/networkconnect/MainActivity.java index d490f5d1..d8096adf 100755 --- a/NetworkConnect/Application/src/main/java/com/example/android/networkconnect/MainActivity.java +++ b/NetworkConnect/Application/src/main/java/com/example/android/networkconnect/MainActivity.java @@ -87,11 +87,14 @@ private void startDownload() { @Override public void updateFromDownload(String result) { - if (result != null) { - mDataText.setText(result); - } else { - mDataText.setText(getString(R.string.connection_error)); - } + // Ensure that this is run from UI thread + runOnUiThread(() -> { + if (result != null) { + mDataText.setText(result); + } else { + mDataText.setText(getString(R.string.connection_error)); + } + }); } @Override @@ -105,26 +108,26 @@ public NetworkInfo getActiveNetworkInfo() { @Override public void finishDownloading() { mDownloading = false; - if (mNetworkFragment != null) { - mNetworkFragment.cancelDownload(); - } } @Override public void onProgressUpdate(int progressCode, int percentComplete) { - switch(progressCode) { - // You can add UI behavior for progress updates here. - case Progress.ERROR: - break; - case Progress.CONNECT_SUCCESS: - break; - case Progress.GET_INPUT_STREAM_SUCCESS: - break; - case Progress.PROCESS_INPUT_STREAM_IN_PROGRESS: - mDataText.setText("" + percentComplete + "%"); - break; - case Progress.PROCESS_INPUT_STREAM_SUCCESS: - break; - } + // Ensure that this is run from UI thread + runOnUiThread(() -> { + switch (progressCode) { + // You can add UI behavior for progress updates here. + case Progress.ERROR: + break; + case Progress.CONNECT_SUCCESS: + break; + case Progress.GET_INPUT_STREAM_SUCCESS: + break; + case Progress.PROCESS_INPUT_STREAM_IN_PROGRESS: + mDataText.setText("" + percentComplete + "%"); + break; + case Progress.PROCESS_INPUT_STREAM_SUCCESS: + break; + } + }); } -} +} \ No newline at end of file diff --git a/NetworkConnect/Application/src/main/java/com/example/android/networkconnect/NetworkFragment.java b/NetworkConnect/Application/src/main/java/com/example/android/networkconnect/NetworkFragment.java index cc41075a..722b438c 100644 --- a/NetworkConnect/Application/src/main/java/com/example/android/networkconnect/NetworkFragment.java +++ b/NetworkConnect/Application/src/main/java/com/example/android/networkconnect/NetworkFragment.java @@ -17,22 +17,12 @@ package com.example.android.networkconnect; import android.content.Context; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; +import android.os.HandlerThread; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; -import android.util.Log; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.net.URL; - -import javax.net.ssl.HttpsURLConnection; /** * Implementation of headless Fragment that runs an AsyncTask to fetch data from the network. @@ -43,8 +33,9 @@ public class NetworkFragment extends Fragment { private static final String URL_KEY = "UrlKey"; private DownloadCallback mCallback; - private DownloadTask mDownloadTask; private String mUrlString; + private HandlerThread mNetworkHandlerThread; + private NetworkHandler mNetworkHandler; /** * Static initializer for NetworkFragment that sets the URL of the host it will be downloading @@ -81,11 +72,22 @@ public void onAttach(Context context) { super.onAttach(context); // Host Activity will handle callbacks from task. mCallback = (DownloadCallback)context; + mNetworkHandlerThread = new HandlerThread("mNetworkHandlerThread"); + mNetworkHandlerThread.start(); + mNetworkHandler = new NetworkHandler(mNetworkHandlerThread.getLooper(), mCallback); } @Override public void onDetach() { super.onDetach(); + mNetworkHandler.removeMessages(NetworkHandler.DOWNLOAD_URL); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + mNetworkHandlerThread.quitSafely(); + } else { + mNetworkHandlerThread.quit(); + } + mNetworkHandler = null; + mNetworkHandlerThread = null; // Clear reference to host Activity. mCallback = null; } @@ -102,188 +104,13 @@ public void onDestroy() { */ public void startDownload() { cancelDownload(); - mDownloadTask = new DownloadTask(); - mDownloadTask.execute(mUrlString); + mNetworkHandler.createDownload(mUrlString).sendToTarget(); } /** - * Cancel (and interrupt if necessary) any ongoing DownloadTask execution. + * Cancel all pending download requests */ public void cancelDownload() { - if (mDownloadTask != null) { - mDownloadTask.cancel(true); - mDownloadTask = null; - } - } - - /** - * Implementation of AsyncTask that runs a network operation on a background thread. - */ - private class DownloadTask extends AsyncTask { - - /** - * Wrapper class that serves as a union of a result value and an exception. When the - * download task has completed, either the result value or exception can be a non-null - * value. This allows you to pass exceptions to the UI thread that were thrown during - * doInBackground(). - */ - class Result { - public String mResultValue; - public Exception mException; - public Result(String resultValue) { - mResultValue = resultValue; - } - public Result(Exception exception) { - mException = exception; - } - } - - /** - * Cancel background network operation if we do not have network connectivity. - */ - @Override - protected void onPreExecute() { - if (mCallback != null) { - NetworkInfo networkInfo = mCallback.getActiveNetworkInfo(); - if (networkInfo == null || !networkInfo.isConnected() || - (networkInfo.getType() != ConnectivityManager.TYPE_WIFI - && networkInfo.getType() != ConnectivityManager.TYPE_MOBILE)) { - // If no connectivity, cancel task and update Callback with null data. - mCallback.updateFromDownload(null); - cancel(true); - } - } - } - - /** - * Defines work to perform on the background thread. - */ - @Override - protected Result doInBackground(String... urls) { - Result result = null; - if (!isCancelled() && urls != null && urls.length > 0) { - String urlString = urls[0]; - try { - URL url = new URL(urlString); - String resultString = downloadUrl(url); - if (resultString != null) { - result = new Result(resultString); - } else { - throw new IOException("No response received."); - } - } catch(Exception e) { - result = new Result(e); - } - } - return result; - } - - /** - * Send DownloadCallback a progress update. - */ - @Override - protected void onProgressUpdate(Integer... values) { - super.onProgressUpdate(values); - if (values.length >= 2) { - mCallback.onProgressUpdate(values[0], values[1]); - } - } - - /** - * Updates the DownloadCallback with the result. - */ - @Override - protected void onPostExecute(Result result) { - if (result != null && mCallback != null) { - if (result.mException != null) { - mCallback.updateFromDownload(result.mException.getMessage()); - } else if (result.mResultValue != null) { - mCallback.updateFromDownload(result.mResultValue); - } - mCallback.finishDownloading(); - } - } - - /** - * Override to add special behavior for cancelled AsyncTask. - */ - @Override - protected void onCancelled(Result result) { - } - - /** - * Given a URL, sets up a connection and gets the HTTP response body from the server. - * If the network request is successful, it returns the response body in String form. Otherwise, - * it will throw an IOException. - */ - private String downloadUrl(URL url) throws IOException { - InputStream stream = null; - HttpsURLConnection connection = null; - String result = null; - try { - connection = (HttpsURLConnection) url.openConnection(); - // Timeout for reading InputStream arbitrarily set to 3000ms. - connection.setReadTimeout(3000); - // Timeout for connection.connect() arbitrarily set to 3000ms. - connection.setConnectTimeout(3000); - // For this use case, set HTTP method to GET. - connection.setRequestMethod("GET"); - // Already true by default but setting just in case; needs to be true since this request - // is carrying an input (response) body. - connection.setDoInput(true); - // Open communications link (network traffic occurs here). - connection.connect(); - publishProgress(DownloadCallback.Progress.CONNECT_SUCCESS); - int responseCode = connection.getResponseCode(); - if (responseCode != HttpsURLConnection.HTTP_OK) { - throw new IOException("HTTP error code: " + responseCode); - } - // Retrieve the response body as an InputStream. - stream = connection.getInputStream(); - publishProgress(DownloadCallback.Progress.GET_INPUT_STREAM_SUCCESS, 0); - if (stream != null) { - // Converts Stream to String with max length of 500. - result = readStream(stream, 500); - publishProgress(DownloadCallback.Progress.PROCESS_INPUT_STREAM_SUCCESS, 0); - } - } finally { - // Close Stream and disconnect HTTPS connection. - if (stream != null) { - stream.close(); - } - if (connection != null) { - connection.disconnect(); - } - } - return result; - } - - /** - * Converts the contents of an InputStream to a String. - */ - private String readStream(InputStream stream, int maxLength) throws IOException { - String result = null; - // Read InputStream using the UTF-8 charset. - InputStreamReader reader = new InputStreamReader(stream, "UTF-8"); - // Create temporary buffer to hold Stream data with specified max length. - char[] buffer = new char[maxLength]; - // Populate temporary buffer with Stream data. - int numChars = 0; - int readSize = 0; - while (numChars < maxLength && readSize != -1) { - numChars += readSize; - int pct = (100 * numChars) / maxLength; - publishProgress(DownloadCallback.Progress.PROCESS_INPUT_STREAM_IN_PROGRESS, pct); - readSize = reader.read(buffer, numChars, buffer.length - numChars); - } - if (numChars != -1) { - // The stream was not empty. - // Create String that is actual length of response body if actual length was less than - // max length. - numChars = Math.min(numChars, maxLength); - result = new String(buffer, 0, numChars); - } - return result; - } + mNetworkHandler.removeMessages(NetworkHandler.DOWNLOAD_URL); } } diff --git a/NetworkConnect/Application/src/main/java/com/example/android/networkconnect/NetworkHandler.java b/NetworkConnect/Application/src/main/java/com/example/android/networkconnect/NetworkHandler.java new file mode 100644 index 00000000..a5872618 --- /dev/null +++ b/NetworkConnect/Application/src/main/java/com/example/android/networkconnect/NetworkHandler.java @@ -0,0 +1,160 @@ +package com.example.android.networkconnect; + +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; + +import javax.net.ssl.HttpsURLConnection; + +public class NetworkHandler extends Handler { + public static int DOWNLOAD_URL = 0; + DownloadCallback mDcb; + public NetworkHandler(Looper looper, DownloadCallback dc) { + super(looper); + mDcb = dc; + } + @Override + public void handleMessage(Message msg) { + if (msg.what == 0 && msg.obj instanceof String) { + handleDownloadTask((String) msg.obj); + } + } + + public Message createDownload(String inUrl) { + return this.obtainMessage(DOWNLOAD_URL, inUrl); + } + + private void handleDownloadTask(String inUrl) { + if (mDcb != null) { + NetworkInfo networkInfo = mDcb.getActiveNetworkInfo(); + if (networkInfo == null || !networkInfo.isConnected() || + (networkInfo.getType() != ConnectivityManager.TYPE_WIFI + && networkInfo.getType() != ConnectivityManager.TYPE_MOBILE)) { + // If no connectivity, cancel task and update Callback with null data. + mDcb.updateFromDownload(null); + return; + } + } + Result result = null; + if (inUrl != null && inUrl.length() > 0) { + try { + URL url = new URL(inUrl); + String resultString = downloadUrl(url); + if (resultString != null) { + result = new Result(resultString); + } else { + throw new IOException("No response received."); + } + } catch(Exception e) { + result = new Result(e); + } + if (result.mException != null) { + mDcb.updateFromDownload(result.mException.getMessage()); + } else if (result.mResultValue != null) { + mDcb.updateFromDownload(result.mResultValue); + } + mDcb.finishDownloading(); + } + + } + + /** + * Wrapper class that serves as a union of a result value and an exception. When the + * download task has completed, either the result value or exception can be a non-null + * value. This allows you to pass exceptions to the UI thread that were thrown during + * doInBackground(). + */ + private static class Result { + public String mResultValue; + public Exception mException; + public Result(String resultValue) { + mResultValue = resultValue; + } + public Result(Exception exception) { + mException = exception; + } + } + + /** + * Given a URL, sets up a connection and gets the HTTP response body from the server. + * If the network request is successful, it returns the response body in String form. Otherwise, + * it will throw an IOException. + */ + private String downloadUrl(URL url) throws IOException { + InputStream stream = null; + HttpsURLConnection connection = null; + String result = null; + try { + connection = (HttpsURLConnection) url.openConnection(); + // Timeout for reading InputStream arbitrarily set to 3000ms. + connection.setReadTimeout(3000); + // Timeout for connection.connect() arbitrarily set to 3000ms. + connection.setConnectTimeout(3000); + // For this use case, set HTTP method to GET. + connection.setRequestMethod("GET"); + // Already true by default but setting just in case; needs to be true since this request + // is carrying an input (response) body. + connection.setDoInput(true); + // Open communications link (network traffic occurs here). + connection.connect(); + mDcb.onProgressUpdate(DownloadCallback.Progress.CONNECT_SUCCESS, 0); + int responseCode = connection.getResponseCode(); + if (responseCode != HttpsURLConnection.HTTP_OK) { + throw new IOException("HTTP error code: " + responseCode); + } + // Retrieve the response body as an InputStream. + stream = connection.getInputStream(); + mDcb.onProgressUpdate(DownloadCallback.Progress.GET_INPUT_STREAM_SUCCESS, 0); + if (stream != null) { + // Converts Stream to String with max length of 500. + result = readStream(stream, 500); + mDcb.onProgressUpdate(DownloadCallback.Progress.PROCESS_INPUT_STREAM_SUCCESS, 0); + } + } finally { + // Close Stream and disconnect HTTPS connection. + if (stream != null) { + stream.close(); + } + if (connection != null) { + connection.disconnect(); + } + } + return result; + } + + /** + * Converts the contents of an InputStream to a String. + */ + private String readStream(InputStream stream, int maxLength) throws IOException { + String result = null; + // Read InputStream using the UTF-8 charset. + InputStreamReader reader = new InputStreamReader(stream, "UTF-8"); + // Create temporary buffer to hold Stream data with specified max length. + char[] buffer = new char[maxLength]; + // Populate temporary buffer with Stream data. + int numChars = 0; + int readSize = 0; + while (numChars < maxLength && readSize != -1) { + numChars += readSize; + int pct = (100 * numChars) / maxLength; + mDcb.onProgressUpdate(DownloadCallback.Progress.PROCESS_INPUT_STREAM_IN_PROGRESS, pct); + readSize = reader.read(buffer, numChars, buffer.length - numChars); + } + if (numChars != -1) { + // The stream was not empty. + // Create String that is actual length of response body if actual length was less than + // max length. + numChars = Math.min(numChars, maxLength); + result = new String(buffer, 0, numChars); + } + return result; + } + +}