Problems caused by the implementation of a simple image list
I read some source code of Universal-Image-Loager a while ago. I think one of the reasons why I am tired of reading the source code is not only how it is implemented, but also why it is implemented. This speculation process is easy to look.
People are too lazy. In a word, they can lie down and never sit down. If they can sit, they will never squat. If they can squat, they will never stand. Sometimes the source code is the same. If you can understand the source code, you won't want to debug it. If you can understand the debug code, you are too lazy to write it.
Reading and writing are different. The result is written into the process.
The use of third-party libraries makes development very convenient. For the implementation of a large number of image requests, most of them do not mean the core of implementation, but directly refer to what third parties are used. Even so, it is important to know how others implement it. Universal-Image-Loader is a powerful open-source Image loading framework. It should all be said to be useless. This article mainly aims to identify the problem, starting from 0.
When I look at the code, why, why, and how can I think of these problems? How can I solve them in this way. To find the reason, start with the simplest implementation of an image list. No framework or cache is required. Simply write a network request to display the image list.
Create a PhotoListActivity. This class only displays one listview:
package com.aliao.learninguil.activity;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.widget.ListView;import com.aliao.learninguil.Constants;import com.aliao.learninguil.R;import com.aliao.learninguil.adapter.PhotoListAdapter;import com.aliao.learninguil.entity.ImageInfo;import java.util.ArrayList;import java.util.List;/** * Created by ALiao on 2015/7/13. */public class PhotoListActivity extends AppCompatActivity { private ListView mListView; private PhotoListAdapter mAdapter; private List<ImageInfo> mImageInfos = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_photolist); for (int i = 0; i< Constants.IMAGES.length; i++){ ImageInfo imageInfo = new ImageInfo(); imageInfo.setUrl(Constants.IMAGES[i]); imageInfo.setName("item-" + i); mImageInfos.add(imageInfo); } mListView = (ListView) findViewById(R.id.photoList); mAdapter = new PhotoListAdapter(mImageInfos, mListView); mListView.setAdapter(mAdapter); }}The layout of the items in listview is an ImageVeiw on the left and a textview on the right:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/iv_img" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/tv_imagename" android:layout_width="wrap_content" android:layout_height="wrap_content" /></LinearLayout>
To display a picture of a network request in each item of listview, start a thread in PhotoListAdapter to make a network request.
package com.aliao.learninguil.adapter;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.AsyncTask;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.ImageView;import android.widget.ListView;import android.widget.TextView;import com.aliao.learninguil.R;import com.aliao.learninguil.entity.ImageInfo;import com.aliao.learninguil.utils.L;import java.io.IOException;import java.net.HttpURLConnection;import java.net.MalformedURLException;import java.net.URL;import java.util.List;public class PhotoListAdapter extends BaseAdapter { private List<ImageInfo> imageInfos; private ListView mListView; public PhotoListAdapter(List<ImageInfo> imageInfos, ListView listView) { this.imageInfos = imageInfos; mListView = listView; } @Override public int getCount() { return imageInfos.size(); } @Override public Object getItem(int position) { return imageInfos.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null){ convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_photolist, parent, false); holder = new ViewHolder(); holder.imgView = (ImageView) convertView.findViewById(R.id.iv_img); holder.imgName = (TextView) convertView.findViewById(R.id.tv_imagename); convertView.setTag(holder); }else { holder = (ViewHolder) convertView.getTag(); } ImageInfo imageInfo = imageInfos.get(position); holder.imgName.setText(imageInfo.getName()); holder.imgView.setTag(imageInfo.getUrl()); loadAndSetImage(imageInfo.getUrl()); return convertView; } class ViewHolder{ ImageView imgView; TextView imgName; } private void loadAndSetImage(String url) { new LoadImageAsyncTask().execute(url); } class LoadImageAsyncTask extends AsyncTask<String, Void, Bitmap>{ private String mImageUrl; @Override protected Bitmap doInBackground(String... params) { mImageUrl = params[0]; Bitmap bitmap = loadBitmap(mImageUrl); return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); ImageView imageView = (ImageView) mListView.findViewWithTag(mImageUrl); if (imageView != null){ imageView.setImageBitmap(bitmap); } } } private Bitmap loadBitmap(String imageUrl) { HttpURLConnection connection = null; Bitmap bitmap = null; try { URL url = new URL(imageUrl); connection = (HttpURLConnection) url.openConnection(); bitmap = BitmapFactory.decodeStream(connection.getInputStream()); return bitmap; } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { if (connection != null){ connection.disconnect(); } } return bitmap; }}This is the most basic code for implementing an image list. The implementation is as follows:
It looks good, but when we slide up and down, we obviously don't feel smooth enough.
Return to the PhotoListAdapter class. Every time the getView method is called, the network request is initiated by the thread. The expected result is that when the page displays five items, five network requests are made, which is the most ideal status. However, in fact, getView calls are much more frequently than expected, which means redundant network requests are accompanied. Which situations will lead to redundant network requests? (in this case, in addition to the images displayed on the current screen, additional network requests for other images are made)
Case 1: the height of the item is not set.
The layout file of the listview item shows that the ImageView height is not set. The height of the item is automatically extended:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/iv_img" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/tv_imagename" android:layout_width="wrap_content" android:layout_height="wrap_content" /></LinearLayout>
Therefore, when you enter the image list page, because the image has not been loaded, the height of textview is by default. One screen can display 27 items, that is, 27 getviews are called, that is, 27 network requests must be performed when the thread is enabled. However, as the previous images are loaded one after another, five items are displayed on one screen, but the remaining 22 network requests continue. Generally, we want to request as many images as we want to display on a screen, and request as many images as we want. Since all these 22 network requests have been executed, can he directly display the loaded image as he slides down? The answer is no, because all items except the items displayed on the current screen are recycled, and the corresponding imageView (null) cannot be found through findViewWithTag (imageUrl ). Even if the image has been requested successfully, the image cannot be set because the current screen does not have the corresponding imageview.
@Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); ImageView imageView = (ImageView) mListView.findViewWithTag(mImageUrl); if (imageView != null){ imageView.setImageBitmap(bitmap); } }Therefore, when we continue to decline, we will repeatedly request images that we have previously requested.
If the display height of an item is known in advance, for example, setting the ImageView height to 100dp or setting the default image, the number of items displayed on a screen is determined, you can display the number of items on a screen and perform the corresponding number of network requests.
Case 2: If you want to see the picture list on the third screen, the first two screens have been scanned for network requests, but it is not necessary.
There is no need to waste resources to load images that we don't want to view and quickly slide over. However, because the image loading operation is placed in getView (), getView () is called as long as you slide the screen. We hope to load the image when the listview slide stops. The callback function of the listview rolling listening event can listen to the rolling status. Three scrollstates in onScrollStateChanged (AbsListView view, int scrollState) indicate the rolling status of listvuew, which are:
SCROLL_STATE_IDLE (= 0): Stop rolling
SCROLL_STATE_TOUCH_SCROLL (= 1): Rolling
SCROLL_STATE_FLING (= 2): the finger throws.
When the status of scrollState is SCROLL_STATE_IDLE, download the image. When the image download time is determined, How can I obtain the address of the visible image displayed on the current screen when the slide stops. The imageInfos object list stores all the image information. After knowing the position of the current visual image on the screen, you can obtain the image address through the index. The callback onScroll (AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) of another rolling listener helps us get the position information of the current screen image. Parameter
FirstVisibleItem: subscript of the first visible item on the current screen (starting from 0)
VisibleItemCount: Total number of all visible items on the current screen
TotalItemCount: Total number of items in the list
The Code is as follows:
Package com. aliao. learninguil. adapter; import android. graphics. bitmap; import android. graphics. bitmapFactory; import android. OS. asyncTask; import android. view. layoutInflater; import android. view. view; import android. view. viewGroup; import android. view. viewTreeObserver; import android. widget. absListView; import android. widget. baseAdapter; import android. widget. imageView; import android. widget. listView; import Ndroid. widget. textView; import com. aliao. learninguil. r; import com. aliao. learninguil. entity. imageInfo; import com. aliao. learninguil. utils. l; import java. io. IOException; import java.net. httpURLConnection; import java.net. malformedURLException; import java.net. URL; import java. util. hashSet; import java. util. list; import java. util. set; public class PhotoListAdapter extends BaseAdapter implements AbsListView. onS CrollListener {private List <ImageInfo> imageInfos; private ListView mListView; private int mFirstVisibleItem; private int mVisibleItemCount; private boolean mFirstEnter; private Set <LoadImageAsyncTask> taskCollection; public PhotoListAdapter (List <ImageInfo> imageInfos, ListView listView) {this. imageInfos = imageInfos; mListView = listView; mListView. setOnScrollListener (this); mFirstEnter = true; TaskCollection = new HashSet <> () ;}@ Override public int getCount () {return imageInfos. size () ;}@ Override public Object getItem (int position) {return imageInfos. get (position) ;}@ Override public long getItemId (int position) {return position ;}@ Override public View getView (int position, View convertView, ViewGroup parent) {ViewHolder holder; if (convertView = null) {convertView = LayoutInfla Ter. from (parent. getContext ()). inflate (R. layout. item_photolist, parent, false); holder = new ViewHolder (); holder. imgView = (ImageView) convertView. findViewById (R. id. iv_img); holder. imgName = (TextView) convertView. findViewById (R. id. TV _imagename); convertView. setTag (holder);} else {holder = (ViewHolder) convertView. getTag ();} ImageInfo imageInfo = imageInfos. get (position); holder. imgName. setText (ImageInfo. getName (); holder. imgView. setTag (imageInfo. getUrl (); // L. d ("position =" + position + ", url =" + imageInfo. getUrl (); // loadAndSetImage (imageInfo. getUrl (); return convertView;} class ViewHolder {ImageView imgView; TextView imgName;} @ Override public void onScrollStateChanged (AbsListView view, int scrollState) {/*** scrollState = SCROLL_STATE_IDLE (= 0) Stop mixing * scrollState = SCROLL_STAT E_TOUCH_SCROLL (= 1) is rolling * scrollState = SCROLL_STATE_FLING (= 2). the finger has done the throwing action */L. d ("-------" onScrollStateChanged scrollState = "+ scrollState); if (scrollState = SCROLL_STATE_IDLE) {loadAndSetImage (mFirstVisibleItem, mVisibleItemCount );} else {// when listview slides again, cancel all tasks being downloaded // cancelAllTasks () ;}} public void cancelAllTasks () {if (taskCollection! = Null) {for (LoadImageAsyncTask task: taskCollection) {task. cancel (false) ;}}@ Override public void onScroll (AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {L. d ("onScroll firstVisibleItem =" + firstVisibleItem + ", visibleItemCount =" + visibleItemCount + ", totalItemCount =" + totalItemCount); mFirstVisibleItem = firstVisibleItem; // The subscript mVisibleItemCount = v of the first visible image IsibleItemCount; // The total number of visible images on one screen // The onScrollStateChanged method is not called when the program is started for the first time, so when you enter the program for the first time, start the download task if (mFirstEnter & visibleItemCount> 0) {loadAndSetImage (dimensions, mVisibleItemCount); mFirstEnter = false;} private void loadAndSetImage, int visibleItemCount) {for (int I = firstVisibleItem; I <firstVisibleItem + visibleItemCount; I ++) {ImageInfo imageInfo = imageInfos. get (I ); L. d ("position =" + I + ", url =" + imageInfo. getUrl (); LoadImageAsyncTask task = new LoadImageAsyncTask (); task.exe cute (imageInfo. getUrl (); taskCollection. add (task) ;}} class LoadImageAsyncTask extends AsyncTask <String, Void, Bitmap> {private String mImageUrl; @ Override protected Bitmap doInBackground (String... params) {mImageUrl = params [0]; Bitmap bitmap = loadBitmap (mImageUrl); return bitmap;} @ Override protected void onPostExecute (Bitmap bitmap) {super. onPostExecute (bitmap); ImageView imageView = (ImageView) mListView. findViewWithTag (mImageUrl); if (imageView! = Null) {imageView. setImageBitmap (bitmap) ;}} private Bitmap loadBitmap (String imageUrl) {HttpURLConnection connection = null; Bitmap bitmap = null; try {URL url = new URL (imageUrl ); connection = (HttpURLConnection) url. openConnection (); bitmap = BitmapFactory. decodeStream (connection. getInputStream (); L. d ("------------------------------------ loadBitmap"); return bitmap;} catch (MalformedURL Exception e) {e. printStackTrace ();} catch (IOException e) {e. printStackTrace ();} finally {if (connection! = Null) {connection. disconnect () ;}return bitmap ;}}In addition to canceling all download tasks when the listview slides again, when the Activity is destroyed, you can call cancelAllTasks () in PhotoListAdapter in the onDestroy method to cancel all pending download tasks.
So far, by determining the height of the list item and modifying the time when the image download task is started, only the image to be viewed is downloaded to avoid unnecessary network requests, this reduces the load on network requests and reduces mobile phone traffic.
After the above problems are solved, the code is improved and robust, and you can think about what else will happen.
Refer:
Use memory cache to implement image Wall
Copyright Disclaimer: This article is an original article by the blogger and cannot be reproduced without the permission of the blogger.