This chapter mainly talks about how to use the network connection of android system, and introduces the use of formatted JSON and multithreaded programming AsyncTask. In addition, the use of Gson library is integrated into the challenge exercises.
GitHub address:
23 Chapters Completed but Challenges Not Completed
Complete Chapter 23 Chapter Challenge 1: Using Gson
Complete Chapter 23 Chapter Challenge 2: Adding Pages
Complete Chapter 23 Chapter Challenge 3: Dynamic Adjustment of Grid Columns
1. Basic Network Connection
The first step is to request network privileges in the Manifest file
<uses-permission android:name="android.permission.INTERNET" />
Then we set up a function for network requests:
// FlickrFetchr.java
// The parameter is a url string, and IO errors need to be thrown
public byte[] getUrlBytes(String urlSpec) throws IOException {
URL url = new URL(urlSpec);
// open a connection
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
try {
// Establishing Two Flow Objects
ByteArrayOutputStream out = new ByteArrayOutputStream();
// GET requests are not actually sent until the getInputStream() method is used
// If you want to use a POST request, you need to call getOutputStream()
InputStream in = connection.getInputStream();
// Throw an error if the connection fails
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
throw new IOException(connection.getResponseMessage() +
": with" +
urlSpec);
}
// Establish a counter
int bytesRead = 0;
// Create a cache buffer
byte[] buffer = new byte[1024];
// Read the data into the buffer with InputStream.read.
// Then write it to Output Stream
while ((bytesRead = in.read(buffer)) > 0) {
out.write(buffer, 0, bytesRead);
}
// After that, be sure to close OutputStream
out.close();
return out.toByteArray();
} finally {
// Finally, close the connection
connection.disconnect();
}
}
public String getUrlString(String urlSpec) throws IOException {
// Converting the results to String
return new String(getUrlBytes(urlSpec));
}
2. Threads and main threads
Network connections take time, Web servers may take 1 to 2 seconds to respond to access requests, and file downloads take longer. Considering this, Android prohibits any main thread network connection behavior. Android throws a NetworkOnMainThreadException exception even if a network connection is forced in the main thread.
Why? To know, first of all, we need to understand what is a thread, what is the main thread and what is the purpose of the main thread.
Threads are a single execution sequence. Code in a single thread is executed step by step. All Android applications run from the main thread. However, the main thread is not a predefined execution sequence like a thread. Instead, it runs in an infinite loop, waiting for the user or system to trigger events. When events are triggered, the main thread is responsible for executing the code in response to these events.
The main thread runs all the code that updates the UI, including the code that responds to different UI-related events such as the start of activity, the click of buttons, etc. (Because the response events are basically related to the user interface, the main thread is sometimes called the UI thread. )
Event processing loops allow UI code to be executed sequentially. This ensures that no event handling conflicts occur and that the code can respond quickly to execution.
The network connection is more time-consuming than other tasks. While waiting for a response, the user interface does not respond, which may lead to the application not responding (ANR) phenomenon, that is, a bullet-box that requires you to close the application.
How is it easiest to use background threads? The answer is to use the AsyncTask class
3. AsyncTask
3.1 AsyncTask's Life
The method that the AsyncTask class can override corresponds to the life process of a process:
- Before onPreExecute() is executed
- onProgressUpdate() Update Progress
- What doInBackground() really wants to do in a thread
- What to do after onPostExecute() is completed (executed in the UI thread)
- After onCancelled() exits
3.2 Three parameters of AsyncTask
The three types of template parameters (not basic types) are input, progress and result.
3.2.1 First parameter: input
The first type parameter specifies the type of the input parameter. This parameter can be used with reference to the following examples:
AsyncTask<String,Void,Void> task = new AsyncTask<String,Void,Void>() {
public Void doInBackground(String... params) {
for (String parameter : params) {
Log.i(TAG, "Received parameter: " + parameter);
}
return null;
}
};
The input parameter is passed into execute(... ) Method (acceptance of one or more parameters): task.execute("first parameter", "second parameter", "..." );
Then, these variable parameters are passed to doInBackground(... Method.
3.2.2 Second parameter: progress
The second type parameter specifies the type required to send progress updates. The following is an example code:
final ProgressBar gestationProgressBar = /* A specific progress bar */;
gestationProgressBar.setMax(42); /* Maximum progress */
AsyncTask<Void,Integer,Void> haveABaby = new AsyncTask<Void,Integer,Void>() {
public Void doInBackground(Void... params) {
while (!babyIsBorn()) {
Integer weeksPassed = getNumberOfWeeksPassed();
publishProgress(weeksPassed); // The key is to send parameters to onProgressUpdate
patientlyWaitForBaby();
}
}
public void onProgressUpdate(Integer... params) {
int progress = params[0];
gestationProgressBar.setProgress(progress);
}
};
/* call when you want to execute the AsyncTask */
haveABaby.execute();
Progress updates usually occur in the background process of execution. The problem is that the necessary UI updates cannot be completed in the background process. So AsyncTask provides publishProgress(... ) And onProgressUpdate(... Method.
It works like this: in the background thread, from doInBackground(... ) Method calls publishProgress(... Method. So onProgressUpdate(... ) Method can be invoked on the UI thread. Therefore, in onProgressUpdate(... ) It is possible to perform UI updates in the method, but it must be doInBackground(... ) The method uses publishProgress(... ) Methods To control them.
3.2.3 Third parameter: results
The third type parameter is the type parameter returned by the processing result. Here's the sample code for this chapter
// PhotoGalleryFragment.java
private class FetchItemsTask extends AsyncTask<Integer, Void, List<GalleryItem>> {
@Override
protected List<GalleryItem> doInBackground(Integer... params) {
return new FlickrFetchr().fetchItems(params[0]);
}
@Override
protected void onPostExecute(List<GalleryItem> galleryItems) {
mItems = galleryItems;
setAdapter();
}
}
The third parameter is the result returned in doInBackground. We need to request the JSON data returned from the API in the background, and format it to return the data we need.
4. JSON data parsing
What is JSON data? JSON(JavaScript Object Notation) is a lightweight data exchange format. Easy to read and write. It is also easy for machine parsing and generation. It is based on a subset of JavaScript. JSON uses a completely language-independent text format, but also uses habits similar to those of the C language family (including C, C++, C#, Java, JavaScript, Perl, Python, etc.). These features make JSON an ideal data exchange language.
JSON objects are a series of name-value pairs contained in {}. JSON arrays are lists of JSON objects contained in [] separated by commas. Objects nest with each other to form hierarchical relationships. Detailed grammar can be viewed JSON official website.
JSON is a data format that can be easily exchanged between programming languages based on the same structure. As a result, more and more network servers are using JSON to exchange data. The same is true of the API s we use in this chapter.
An example
// To save layout, remove irrelevant attributes
{
"photos": {
"page": 1,
"pages": 10,
"photo": [
{
"id": "31987348504",
"title": "Penny",
"url_s": "https://farm3.staticflickr.com/2915/31987348504_9a949c482d_m.jpg",
},
{
"id": "31987352214",
"title": "",
"url_s": "https://farm1.staticflickr.com/455/31987352214_58428f3a9d_m.jpg",
}
]
},
"stat": "ok"
}
The corresponding parsing code:
// When parsing, try... catch, throw JSONException to prevent program crash
// After the JSONObject construction method parses the incoming JSON data
// The object tree corresponding to the original JSON data is generated
JSONObject jsonBody = new JSONObject(jsonString);
// The top-level JSONObject corresponds to the outermost {} of the original data. It contains a nested JSONObject called photos
JSONObject photosJsonObject = jsonBody.getJSONObject("photos");
// This nested object also contains a JSONArray called photo.
JSONArray photoJsonArray = photosJsonObject.getJSONArray("photo");
// This nested array also contains a set of JSONObject s
// These JSONObeject s are metadata for each picture to be retrieved
for (int i = 0; i < photoJsonArray.length(); i++) {
JSONObject photoJsonObject = photoJsonArray.getJSONObject(i);
GalleryItem item = new GalleryItem();
item.setId(photoJsonObject.getString("id"));
item.setCaption(photoJsonObject.getString("title"));
if (!photoJsonObject.has("url_s")) {
continue;
}
item.setUrl(photoJsonObject.getString("url_s"));
items.add(item);
}
Once the parsing is complete, the UI can be updated in AsyncTask's onPost Execute.
5. Challenge exercises
Challenge exercises in this chapter are increasingly difficult, testing a lot of our knowledge.
5.1 Use Gson library to parse JSON data
Gson is the JSON parsing library recommended by Google. It can automatically map JSON data to Java objects without writing any parsing code.
5.1.1 Adding Gson dependencies
Add gson dependencies in File - > Project Structure - > Dependencies
5.1.2 Building corresponding POJO classes
Because I didn't want to change the original GalleryItem class and wanted the name of member variables to conform to the java Naming convention, I used the @SerializedName() annotation, which indicates the key name corresponding to Gson's transformation. A new class is constructed to match the corresponding API structure:
// PhotoBean.java
public class PhotoBean {
public static final String STATUS_OK = "ok"
, STATUS_FAILED = "fail";
@SerializedName("photos")
private PhotosInfo mPhotoInfo;
@SerializedName("stat")
private String mStatus;
@SerializedName("message")
private String mMessage;
public class PhotosInfo {
@SerializedName("photo")
List<GalleryItem> mPhoto;
public List<GalleryItem> getPhoto() {
return mPhoto;
}
}
// Omit getter and setter
}
5.1.3 Use Gson
The use of Gson is simpler than that of the above code.
PhotoBean photoBean = (PhotoBean) new Gson()
.fromJson(jsonString, PhotoBean.class);
But remember to throw JsonSyntaxException.
5.2 Paging Display
The challenge is to add the next page to the bottom of the slide.
So we need to add the page parameter to the url generation. I added a member variable mNextPage to record the next page to be requested, and then added a constant MAX_PAGES to control the maximum number of pages requested.
5.2.1 RecyclerView.onScrollListener
onScrollStateChanged() and onScrolled are two rewritable methods for onScrollListener. Obviously onScrollStateChanged is suitable for our needs. There are also three kinds of ScrollStateChanged:
- SCROLL_STATE_IDLE: The view is not dragged and is still
- SCROLL_STATE_DRAGGING: View is dragging
- SCROLL_STATE_SETTLING: Views scroll in inertia
The key to this challenge is to determine how to slide to the bottom. The first two states are actually all right when you first slide to the bottom, but only LayoutManager knows the information when you slide to the bottom. We can look directly at code analysis:
private RecyclerView.OnScrollListener onButtomListener =
new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
// Get LayoutManager first
GridLayoutManager layoutManager = (GridLayoutManager) recyclerView.getLayoutManager();
// Then you can find the final display location, which you can get once you scroll.
mLastPosition = layoutManager.findLastCompletelyVisibleItemPosition();
// If the last position at rest is greater than or equal to the number of data
// And when the previous task is completed (to prevent multiple repetitions)
if (newState == RecyclerView.SCROLL_STATE_IDLE
&& mLastPosition >= mPhotoAdapter.getItemCount() - 1) {
if (mFetchItemsTask.getStatus() == AsyncTask.Status.FINISHED) {
// Add one to the next page, less than the maximum number of pages
// Pop-up Toast indicates loading
// Then open a new task and load the next page
mNextPage++;
if (mNextPage <= MAX_PAGES) {
Toast.makeText(getActivity(), "waiting to load ……", Toast.LENGTH_SHORT).show();
// AsyncTask can only be executed once, so you need to create a new one
mFetchItemsTask = new FetchItemsTask();
mFetchItemsTask.execute(mNextPage);
} else {
// The bottom tip is over.
Toast.makeText(getActivity(), "This is the end!", Toast.LENGTH_SHORT).show();
}
}
}
}
};
5.2.2 Add data and display
I added an addData method to the Adapter, added new data to the data set, and then updated the view using the notify DataSetChanged method.
Then the setAdapter method is modified:
private void setAdapter() {
if (isAdded()) {
if (mPhotoAdapter == null) {
mPhotoAdapter = new PhotoAdapter(mItems);
mPhotoRecyclerView.setAdapter(mPhotoAdapter);
mPhotoRecyclerView.addOnScrollListener(onButtomListener);
} else {
mPhotoAdapter.addData(mItems);
}
}
}
5.3 Dynamic Adjustment of Grid Columns
Use OnGlobalLayoutListener:
mPhotoRecyclerView.getViewTreeObserver()
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// Calculate the number of columns, using 1080p screen display 3 as the benchmark
int columns = mPhotoRecyclerView.getWidth() / 350;
// Reset LayoutManager, Adapter and Listener
mPhotoRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), columns));
mPhotoRecyclerView.setAdapter(mPhotoAdapter);
mPhotoRecyclerView.addOnScrollListener(onButtomListener);
// Scroll to the position you saw before
mPhotoRecyclerView.getLayoutManager().scrollToPosition(mLastPosition);
//Remove Global LayoutListener to avoid multiple triggers
mPhotoRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});