本文是學習筆記,原視頻地址:異步加載。主要是從慕課網提供提供的接口(www.imooc.com/api/teacher?type=4&num=30)獲取數據進行Json解析,將解析得到的數據放到ListView中,觀察Json數據,我們主要用到的是name 作爲標題,description作爲內容,picSmall作爲ImageView獲取圖片的網址。其中用到的網絡請求,AsyncTask,Json解析,將在本文的末尾貼出詳細地址(如果還沒貼就是我還沒更完)
一、AsyncTask請求網絡獲取Jason數據解析後獲取數據源,建立適配器顯示在ListView中
(注:由於返回Json數據中picSmall爲圖片地址,所以第一步驟,先將ImageView中的圖片設置爲ic_launcher,在第二步驟中將聽過兩種方式來處理)
先新建佈局文件item.xml作爲ListView的Item
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:padding="4dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/item_pic"
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@mipmap/ic_launcher"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="4dp"
android:layout_gravity="center">
<TextView
android:id="@+id/item_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="This is a Title"
android:gravity="center"
android:ellipsize="end"
android:maxLines="1"/>
<TextView
android:id="@+id/item_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="3"
android:textSize="15sp"
android:text="This is Content"
/>
</LinearLayout>
</LinearLayout>
也就是左邊一個顯示圖片的ImageView,右邊有兩行,放兩個TextView分別顯示標題和內容
在MainActivity中:
1.先在MainActivity的佈局文件中加一個ListView,比較簡單,略去不寫
2.在MainActivity中,我們需要考慮到由於是網絡請求屬於費時操作,此處需要使用多線程和Handelr或者使用AsyncTask來處理,此處我們使用AsyncTask來處理
。開始之前,我們先搞清楚需要傳進去的參數,以及最終向要的結果是什麼?本程序中,我們傳進去一個url,想最終返回的是包含圖片地址的url(還要對圖片地址做進一步處理纔會達到最終想要的圖片)、題目、內容的多個對象,所以我們需要新建一個類來封裝這些屬性
2.1新建NewsBean:
package com.example.administrator.asyncapplication;
public class NewsBean {
public String newsPicUrl;
public String newsTitle;
public String newsContent;
}
2.2使用AsyncTask:
class NewsAsyncTask extends AsyncTask<String,Void,List<NewsBean>>{
@Override
protected List<NewsBean> doInBackground(String... params) {
return getJsonData (params[0]);
}
@Override
protected void onPostExecute(List<NewsBean> newsBeans) {
super.onPostExecute(newsBeans);
}
}
此時,我們需要一個getJsonData來返回我們所需要的List<NewsBean> 對象
2.2 獲取從網絡上返回的數據之後進行Json解析:
private List<NewsBean> getJasonData(String url){
List<NewsBean> news = new ArrayList<NewsBean>();
try {
String data = getDataFromInputStream(new URL(url).openStream());
// Log.i("xyz", data);
//Json解析
NewsBean bean;
JSONObject jsonObject;
try {
jsonObject = new JSONObject(data);
JSONArray jsonArray = jsonObject.getJSONArray("data");
for(int i=0;i<jsonArray.length();i++){
jsonObject = jsonArray.getJSONObject(i);
bean = new NewsBean();
bean.newsPicUrl=jsonObject.getString("picSmall");
bean.newsTitle=jsonObject.getString("name");
bean.newsContent=jsonObject.getString("description");
news.add(bean);
}
} catch (JSONException e) {
e.printStackTrace();
}
;
} catch (IOException e) {
e.printStackTrace();
}
//System.out.println(news.size());
return news;
}
2.3讀取服務器返回的InputStream數據 getDataFromInputStream
//讀取輸入流,返回字符
private String getDataFromInputStream(InputStream is){
InputStreamReader isr;
String result = "";//獲取讀取每一行之後的所有數據
try {
isr = new InputStreamReader(is, "utf-8");
BufferedReader bufferedReader = new BufferedReader(isr);
String line = "";
while ((line = bufferedReader.readLine()) != null) {
result += line;
}
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
以上幾個步驟,我們得到數據源List<NewsBean>,之後
3.1建立適配器NewAdapter(用BaseAdapter):
public class NewAdapter extends BaseAdapter {
List<NewsBean> list;
LayoutInflater inflater;
public NewAdapter(Context context,List<NewsBean> list){
this.list=list;
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder=null;
NewsBean bean = list.get(position);
if(convertView==null){
viewHolder = new ViewHolder();
convertView=inflater.inflate(R.layout.item,null);
viewHolder.pic = (ImageView)convertView.findViewById(R.id.item_pic);
viewHolder.title =(TextView)convertView.findViewById(R.id.item_title);
viewHolder.content=(TextView)convertView.findViewById(R.id.item_content);
convertView.setTag(viewHolder);
}
else{
viewHolder =(ViewHolder)convertView.getTag();
}
//先將ImageView的圖片設爲固定的,後面會更改
viewHolder.pic.setImageResource(R.mipmap.ic_launcher);
viewHolder.title.setText(bean.newsTitle);
viewHolder.content.setText(bean.newsContent);
return convertView;
}
class ViewHolder{
public TextView title,content;
public ImageView pic;
}
}
3.2需要在MainActivity中創建異步任務的onPostExecute方法中設置適配器:
protected void onPostExecute(List<NewsBean> newsBeans) {
super.onPostExecute(newsBeans);
listView.setAdapter(new NewAdapter(MainActivity.this,newsBeans));
}
3.3在MainActivity中的onCreate()方法中執行異步任務:
public class MainActivity extends ActionBarActivity {
ListView listView;
private static String URL ="http://www.imooc.com/api/teacher?type=4&num=30" ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView)findViewById(R.id.lv_main);
//請求網絡,異步加載
new NewsAsyncTask().execute(URL);
}
之後運行程序。
二、將得到的圖片url解析成圖片並設置給ImageView,並且做規避ListView的View複用的處理以及用Lrucache對圖片進行緩存
然後,我們來處理圖片,由於Jason數據中解析出來的是圖片地址,所以們需要連接網絡,獲取所需要的圖片。可以用多線程和異步加載:
1.採用多線程的方式給ImageView設置圖片:
1.1新建類ImageLoader,之後新建兩個成員變量。ImageView mImageView; String mUrl;之後:
//從內部類中訪問url需要被聲明爲最終類型
public void showImageByThread(ImageView imageView,final String url){
mUrl = url;
mImageView = imageView;
new Thread(){
@Override
public void run() {
super.run();
Bitmap bitmap = getbitMapFromUrl(url);
Message message = Message.obtain();
message.obj=bitmap;
//用Handler機制,將獲得的圖片通過消息發送出去
mHandler.sendMessage(message);
}
}.start();
}
1.2 getbitMapFromUrl :
//通過一個網絡url地址返回一個圖片
//得到拂去其返回的輸入流
//通過BitmapFactory的decodeStream()方法來獲取圖片
public Bitmap getbitMapFromUrl(String url){
InputStream in=null;
Bitmap bitMap;
URL url1 = null;
try {
url1 = new URL(url);
HttpURLConnection connection= (HttpURLConnection) url1.openConnection();
in = new BufferedInputStream(connection.getInputStream());//對InputStream進行封裝
bitMap = BitmapFactory.decodeStream(in);//根據InputStream得到BitMap
connection.disconnect();
return bitMap;
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
1.3 在Handler中進行消息處理 handleMessage
private Handler mHandler = new Handler(){
//收到並處理消息
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mImageView.setImageBitmap((Bitmap) msg.obj);
}
};
1.4在NewAdapter中在設置完默認圖片之後,加上一行
new ImageLoader(viewHolder.pic,uri);
(String url = list.get(position).newsPicUrl; url即爲newsBean的屬性之一)
1.5之後運行程序,可以看到現在已經完全能接收到圖片了,是不是已經完美了呢?遠遠不是!在快速滑動的過程中,可以看到圖片出現閃動的狀態,原因是刷新了多次,第一次是
ListView中的緩存,先從緩存池中拿出圖片,然後再從網絡上下載,等於顯示了兩次,有時候會造成圖片錯位等。如何解決,將ViewHolder.pic和url聯繫起來,只有是當前的url時,才設置圖片。在NewAdapter中 viewHolder.pic.setTag(url);在ImageLoader中的Handler處理中加上;if(mImageView.getTag().equals(mUrl)){mImageView.setBitmap((Bitmap)mas.obj)},之後再快速滑動,可以看到不會出現剛纔的情況。
2.用AsyncTask的方式加載圖片,不再詳解
2.1
//異步任務
class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{
//前期在設置ImageView和後期在避免ListView的緩存機制設置Tag時,需要用到url
private ImageView mImageView;
private String mUrl;
public NewsAsyncTask(ImageView imageView,String url){
mImageView = imageView;
mUrl = url;
}
@Override
protected Bitmap doInBackground(String... params) {
return <span style="font-family: Arial, Helvetica, sans-serif;">getbitMapFromUrl(url);</span>
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (mImageView.getTag().equals(mUrl)) {
mImageView.setImageBitmap(bitmap);
}
}
2.2 //通過異步任務將圖片設置給ImageView
public void showImageByAsyncTask(ImageView imageView,String url){
new NewsAsyncTask(imageView, url).execute(url);
}
之後再NewAdapter做相應的更改即可。現在程序上下滑動可以看到先出現默認圖標,然後才顯示了圖片,還不夠完美,因爲圖片沒有緩存,導致每次都得重新加載下面通過AsyncTask來介紹使用LruCache緩存
三、使用LruCache緩存
注意lruche對象的底層是用map來是實現的,所以很多map的方法,我們在這裏也可以使用,存儲也是通過鍵值對的方式,key在此處是圖像的url,value是Bitmap
1.創建cache對象
我們在ImageLoader的構造函數中來初始化LruCache
這樣當ImageLoader對象被創建時,也就初始化了cache
public class ImageLoader {
private LruCache<String,Bitmap> caches;
public ImageLoader(){
//獲得最大內存,從而設置緩存的空間大小,當款村的圖騙超過設置的空間大小時
//lru最近未被使用的圖片就會被拋出緩存
int maxMermory = (int)Runtime.getRuntime().maxMemory();
int cacheSize = maxMermory/8;
//匿名內部類 獲得cache實例
caches = new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
};
}
2.兩個方法,一個時從緩存中獲取圖片,另一個是添加圖片到緩存中:
//先看緩存中是否有圖片,沒有的話就加到緩存中去
public void addBitmapToCache(String url,Bitmap bitmap){
if(getBitmapFromCache(url)==null){
caches.put(url,bitmap);
}
}
//根據url(key)從緩存中取出Bitmao(value)
public Bitmap getBitmapFromCache(String url){
return caches.get(url);
}
3.之後,還需要對showImageByAsyncTask進行修改,給ImageView設置圖片之前,先看緩存中是否有對應圖片,沒有的話,從網絡上進行加載,有的話,直接設置
,但是,還有一點,從網絡上加載完了,應該添加到緩存中,以便下次備用
//通過異步任務將圖片設置給ImageView
public void showImageByAsyncTask(ImageView imageView,String url){
Bitmap bitMap = getBitmapFromCache(url);
//如果緩存中沒有圖片,就從網絡上下載
//如果緩存中有圖片了,直接設置
if(bitMap==null) {
new NewsAsyncTask(imageView, url).execute(url);
}else{
imageView.setImageBitmap(bitMap);
}
}
//異步任務
class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{
//前期在設置ImageView和後期在避免ListView的緩存機制設置Tag時,需要用到url
private ImageView mImageView;
private String mUrl;
public NewsAsyncTask(ImageView imageView,String url){
mImageView = imageView;
mUrl = url;
}
//先從網絡上下載圖片,下載下來之後,不需要重複下載,需要加入緩存
@Override
protected Bitmap doInBackground(String... params) {
String url = params[0];
Bitmap bitmap = getbitMapFromUrl(url);
if(bitmap!=null){
addBitmapToCache(url,bitmap);
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (mImageView.getTag().equals(mUrl)) {
mImageView.setImageBitmap(bitmap);
}
}
}
經過這幾個步驟,緩存已經差不多了,爲什麼說差不多呢?因爲我們在NewAdapter中還沒有修改的,回想一下我們如何調用的
new ImageLoader().showImageByAsyncTask(viewHolder.pic,url);
這樣,每次調用都重新生成了ImageLoader對象,哪裏有緩存了?解決辦法,將生成的ImageLoader對象保存起來
在NewAdapter中
private ImageLoader mImageLoader;
之後再NewAdapter的構造函數中,
mImageLoader = new ImageLoader();
之後在getView方法中更改:
mImageLoader.showImageByAsyncTask(viewHolder.pic,url);至此,緩存也已經完成,運行程序,可以看到當加載完圖片之後,再往上滑動,將會使用緩存,不會重複加載,這就省了流量
(待續。。。。)