Android中使用網路訪問來載入網上的內容,並將其解析出來載入到控制項中,是一種很常見的操作。但是Android的UI線程(也就是主線程)中是不允許進行耗時操作的,因為耗時操作會阻塞主線程,影響用戶體驗。而訪問網路同樣是一個耗時操作,並且Android3.0以後是不允許在主線程中訪問網路的,所以我們 ...
Android中使用網路訪問來載入網上的內容,並將其解析出來載入到控制項中,是一種很常見的操作。但是Android的UI線程(也就是主線程)中是不允許進行耗時操作的,因為耗時操作會阻塞主線程,影響用戶體驗。而訪問網路同樣是一個耗時操作,並且Android3.0以後是不允許在主線程中訪問網路的,所以我們這裡用Android封裝好的AsyncTask類來完成這些耗時操作。
項目的目錄結構如下:
AsyncTask是一個抽象類,實際上他是封裝好的一個類,底層也是用handler和thread來實現的,我們首先應該定義一個類來繼承它。AsyncTask的繼承是包含三個泛型參數的,這點官方文檔上有詳細說明,第一個參數是要操作的數據的類型,我們一般傳入一個String字元串來表示網址;第二個參數是想要展示進度條的時候,進度條的參數類型,一般指定為Integer;第三個參數是doInBackground()方法操作返回的數據類型,這裡根據你想操作什麼樣的數據類型,就返回什麼樣的數據類型。註意:這三個參數不是一定要設置,用到了哪個設置哪個,不需要的參數可以設置為Void。
然後我們來看一下AsyncTask中的四個重要方法,基本上使用的時候我們都要重寫這幾個方法:
- onPreExecute():這個方法是在UI線程中調用,當我們調用AsyncTask的execute()方法的時候,此方法會首先執行,主要是完成一些初始化的操作,比如多用於初始化並顯示進度條
- doInBackground(Params...):該方法在onPreExecute()方法調用結束之後調用,他的形參是一個可變參數,此方法在WorkThread也就是工作線程中完成,所有的耗時操作都要放在這個方法中執行,他可以將計算的結果返回到onPostExecute()方法中,同時在這裡也可以調用publishProgress()方法來跳到onProgressUpdate()方法完成進度條刻度的更新,需要主要的是publishProgress()方法在WorkThread中完成,而onProgressUpdate()方法在UI線程中完成
- onProgressUpdate(Progress...):當調用了publishProgress()方法的時候,在UI線程被調用此方法,實現進度條的更新
- onPostExecute(Result):此方法在後臺任務執行完後調用,在UI線程中執行,後臺執行計算出的Result可以返回到此方法中,在此方法中可以更新UI界面組件
大概看完了這四個方法,下麵我們開始看看這次的Demo:
因為是想要從網路上獲取json數據,所以要先準備一個介面,我的介面是時光網的:
http://api.m.mtime.cn/News/NewsList.api?pageIndex=1
為了得到這個介面中的Json格式的數據,我們先定義了一個HttpUtils類,在其中先定義了一個測試網路是否聯通的類isNetConn(Context context)如下所示:
1 /** 2 * 獲取網路狀態 3 * 4 * @param context 上下文 5 * @return 聯通狀態 6 */ 7 public static boolean isNetConn(Context context) { 8 //獲取網路連接管理對象 9 ConnectivityManager manager = (ConnectivityManager) context.getSystemService(context.CONNECTIVITY_SERVICE); 10 //獲取活躍狀態的網路信息對象 11 NetworkInfo info = manager.getActiveNetworkInfo(); 12 if (info != null) { 13 return info.isConnected(); //返回是否鏈接 14 } else { 15 return false; 16 } 17 18 }
又定義了一個返回byte數組downloadFromNet()方法,來獲取數據的byte[]數組:
1 /** 2 * 獲取網路上下載下來的數據的byte數組 3 * 4 * @param urlPath 網路URL路徑 5 * @return 網路上獲取的json字元串的byte數組形式 6 */ 7 public static byte[] downloadFromNet(String urlPath) { 8 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 9 URL url = null; 10 try { 11 url = new URL(urlPath); 12 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 13 conn.setRequestMethod("GET"); 14 conn.setConnectTimeout(5000); 15 conn.setReadTimeout(5000); 16 conn.setDoInput(true); 17 conn.connect(); 18 if (conn.getResponseCode() == 200) { 19 InputStream is = conn.getInputStream(); 20 int len; 21 byte b[] = new byte[1024]; 22 //註意這裡:is.read(b) 中的b數組一定要寫,不然讀取的數據不對 23 while ((len = is.read(b)) != -1) { 24 baos.write(b, 0, len); 25 baos.flush(); 26 } 27 return baos.toByteArray(); 28 } 29 return baos.toByteArray(); 30 } catch (IOException e) { 31 e.printStackTrace(); 32 } 33 return baos.toByteArray(); 34 }
之所以返回byte[]數組類型,是方便我們下載其他東西的時候也可以使用。
獲取到了json字元串,下一步就是將其解析出來,定義一個ParserJson方法,json字元串的解析相信大家應該都是瞭解的,因為這是Android中非常重要的一部分知識,這裡就不再贅述,直接上代碼:
json串對應的實體類:
1 package com.yztc.lx.asynctasklistview.com.yztc.lx.bean; 2 3 import java.util.List; 4 5 /** 6 * 外層JsonObject對象 7 * Created by Lx on 2016/8/10. 8 */ 9 10 public class ShiGuang { 11 12 private int totalCount; 13 private int pageCount; 14 private List<News> newsList; 15 }
1 package com.yztc.lx.asynctasklistview.com.yztc.lx.bean; 2 3 /** 4 * Created by Lx on 2016/8/10. 5 */ 6 7 public class News { 8 private int id; 9 private int type; 10 private String image; 11 private String title; 12 private String title2; 13 private String summary; 14 private String summaryInfo; 15 private String tag; 16 private int commentCount; 17 18 @Override 19 public String toString() { 20 return "News{" + 21 "id=" + id + 22 ", type=" + type + 23 ", image='" + image + '\'' + 24 ", title='" + title + '\'' + 25 ", title2='" + title2 + '\'' + 26 ", summary='" + summary + '\'' + 27 ", summaryInfo='" + summaryInfo + '\'' + 28 ", tag='" + tag + '\'' + 29 ", commmentCount=" + commentCount + 30 '}'; 31 } 32 33 public int getId() { 34 return id; 35 } 36 37 public void setId(int id) { 38 this.id = id; 39 } 40 41 public int getType() { 42 return type; 43 } 44 45 public void setType(int type) { 46 this.type = type; 47 } 48 49 public String getTitle() { 50 return title; 51 } 52 53 public void setTitle(String title) { 54 this.title = title; 55 } 56 57 public String getImage() { 58 return image; 59 } 60 61 public void setImage(String image) { 62 this.image = image; 63 } 64 65 public String getTitle2() { 66 return title2; 67 } 68 69 public void setTitle2(String title2) { 70 this.title2 = title2; 71 } 72 73 public String getSummary() { 74 return summary; 75 } 76 77 public void setSummary(String summary) { 78 this.summary = summary; 79 } 80 81 public String getSummaryInfo() { 82 return summaryInfo; 83 } 84 85 public void setSummaryInfo(String summaryInfo) { 86 this.summaryInfo = summaryInfo; 87 } 88 89 public String getTag() { 90 return tag; 91 } 92 93 public void setTag(String tag) { 94 this.tag = tag; 95 } 96 97 public int getCommmentCount() { 98 return commentCount; 99 } 100 101 public void setCommmentCount(int commmentCount) { 102 this.commentCount = commmentCount; 103 } 104 }
下麵是ParserJson類:
1 package com.yztc.lx.asynctasklistview.com.yztc.lx.utils; 2 3 import com.yztc.lx.asynctasklistview.com.yztc.lx.bean.News; 4 5 import org.json.JSONArray; 6 import org.json.JSONException; 7 import org.json.JSONObject; 8 9 import java.util.ArrayList; 10 import java.util.List; 11 12 /** 13 * Created by Lx on 2016/8/10. 14 */ 15 16 public class ParserJson { 17 public static List<News> parserJsonToNews(String jsonString){ 18 List<News> list=null; 19 try { 20 list=new ArrayList<>(); 21 JSONObject obj=new JSONObject(jsonString); 22 JSONArray arr=obj.getJSONArray("newsList"); 23 for(int i=0;i<arr.length();i++){ 24 JSONObject obj1=arr.getJSONObject(i); 25 News news=new News(); 26 news.setId(obj1.getInt("id")); 27 news.setTitle(obj1.getString("title")); 28 news.setSummary(obj1.getString("summary")); 29 list.add(news); 30 } 31 } catch (JSONException e) { 32 e.printStackTrace(); 33 } 34 return list; 35 } 36 }
json串格式化後的一部分如下所示:
本Demo中只解析了newsList中的部分內容,包括id,title,summary這三部分,但是實體類中基本上都定義了。
定義完了這些工具類之後,我們在主頁面的佈局中加入一個ListView控制項,再定義一個item.xml用來作為ListView的自佈局,主界面就不上代碼了,下麵看一下item的佈局:
1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="wrap_content"> 5 6 <TextView 7 android:id="@+id/tv_title" 8 android:layout_width="wrap_content" 9 android:layout_height="wrap_content" 10 android:padding="10dp" 11 android:text="Title" 12 android:textSize="20sp" /> 13 14 <TextView 15 android:id="@+id/tv_summary" 16 android:layout_width="wrap_content" 17 android:layout_height="wrap_content" 18 android:layout_below="@+id/tv_title" 19 android:padding="10dp" 20 android:text="Summary" 21 android:textSize="12sp" /> 22 23 <TextView 24 android:id="@+id/tv_id" 25 android:layout_width="wrap_content" 26 android:layout_height="wrap_content" 27 android:layout_alignParentRight="true" 28 android:layout_alignTop="@+id/tv_summary" 29 android:padding="10dp" 30 android:text="id" 31 android:textSize="12sp" /> 32 33 </RelativeLayout>
基本工作都完成了,下麵完成非同步任務類DownloadAsyncTask中的內容,因為在進入後臺線程前沒有什麼準備工作,並且也不需要進度條,所以就只重寫了doInBackground()方法和onPostExecute()方法,代碼如下:
1 package com.yztc.lx.asynctasklistview.com.yztc.lx.async; 2 3 import android.content.Context; 4 import android.os.AsyncTask; 5 import android.util.Log; 6 import android.widget.ListView; 7 import android.widget.Spinner; 8 import android.widget.Toast; 9 10 import com.yztc.lx.asynctasklistview.com.yztc.lx.adapter.MyBaseAdapter; 11 import com.yztc.lx.asynctasklistview.com.yztc.lx.bean.News; 12 import com.yztc.lx.asynctasklistview.com.yztc.lx.utils.HttpUtils; 13 import com.yztc.lx.asynctasklistview.com.yztc.lx.utils.ParserJson; 14 15 import java.util.List; 16 17 /** 18 * Created by Lx on 2016/8/10. 19 */ 20 21 public class DownloadAsyncTask extends AsyncTask<String, Void, List<News>> { 22 private Context mContext; 23 private ListView lv; 24 private Spinner sp; 25 26 public DownloadAsyncTask(Context mContext, ListView lv) { 27 this.mContext = mContext; 28 this.lv = lv; 29 } 30 31 32 @Override 33 protected List<News> doInBackground(String... params) { 34 List<News> list = null; 35 if(HttpUtils.isNetConn(mContext)){ 36 byte[] b=HttpUtils.downloadFromNet(params[0]); //可變參數params當成一個數組使用,其中的params[0]就是我們傳遞過來的參數 37 String jsonString=new String(b); 38 Log.d("Tag",jsonString); 39 list=ParserJson.parserJsonToNews(jsonString); 40 Log.d("List",list.toString()); 41 } 42 return list; 43 } 44 45 @Override 46 protected void onPostExecute(List<News> newses) { 47 if(newses!=null&&newses.size()!=0){ 48 MyBaseAdapter adapter=new MyBaseAdapter(mContext,newses); 49 lv.setAdapter(adapter); 50 }else { 51 Toast.makeText(mContext,"數據載入失敗", Toast.LENGTH_SHORT).show(); 52 } 53 } 54 }
因為要更新UI中的ListView,所以在DownloadAsyncTask的構造函數中傳入了ListView和Context兩個形參。在doInBackground()方法中完成了數據計算操作後,將返回一個List<News>類型的變數,會接著執行onPostExecute()方法,變數會傳到他的形參中。通過這個List集合,我們來完成ListView的數據的填充。填充ListView首先需要自定義一個適配器繼承自BaseAdapter,我們取名為MyBaseAdapter。為其傳入Context和list兩個參數,至於ListView的填充請看我的另一篇博客,下麵直接上代碼:
1 package com.yztc.lx.asynctasklistview.com.yztc.lx.adapter; 2 3 import android.content.Context; 4 import android.view.LayoutInflater; 5 import android.view.View; 6 import android.view.ViewGroup; 7 import android.widget.BaseAdapter; 8 import android.widget.TextView; 9 10 import com.yztc.lx.asynctasklistview.R; 11 import com.yztc.lx.asynctasklistview.com.yztc.lx.bean.News; 12 13 import java.util.List; 14 15 /** 16 * Created by Lx on 2016/8/10. 17 */ 18 19 public class MyBaseAdapter extends BaseAdapter { 20 private Context mContext; 21 private List<News> list; 22 private LayoutInflater inflater; 23 24 public MyBaseAdapter(Context context, List<News> list) { 25 this.mContext = context; 26 this.list = list; 27 this.inflater=LayoutInflater.from(mContext); 28 } 29 30 /** 31 * 32 * @return 要填充的集合的長度 33 */ 34 @Override 35 public int getCount() { 36 return list.size(); 37 } 38 39 40 @Override 41 public Object getItem(int position) { 42 return list.get(position); 43 } 44 45 @Override 46 public long getItemId(int position) { 47 return position; 48 } 49 50 /** 51 * 52 * @param position 在適配器數據集合中的item的位置 53 * @param convertView 已經填充過的View,可以用來重用來提高載入速度 54 * @param parent 這個view將要展示的父容器 55 * @return 56 */ 57 @Override 58 public View getView(int position, View convertView, ViewGroup parent) { 59 News news=list.get(position); 60 ViewHolder holder; 61 if(convertView==null){ 62 holder=new ViewHolder(); 63 convertView=inflater.inflate(R.layout.item,null); 64 holder.tv_id= (TextView) convertView.findViewById(R.id.tv_id); 65 holder.tv_summary= (TextView) convertView.findViewById(R.id.tv_summary); 66 holder.tv_title= (TextView) convertView.findViewById(R.id.tv_title); 67 convertView.setTag(holder); 68 }else{ 69 holder= (ViewHolder) convertView.getTag(); 70 } 71 holder.tv_id.setText(""+news.getId()); 72 holder.tv_title.setText(news.getTitle()); 73 holder.tv_summary.setText(news.getSummary()); 74 return convertView; 75 } 76 77 class ViewHolder{ 78 private TextView tv_title,tv_summary,tv_id; 79 } 80 }
這裡需要註意的是,不要忘了為ListView設置適配器setAdapter(adapter).。還有就是setText()方法有一個重載形式是:setText(int i)傳入了一個int類型的形參的話,系統會根據這個int類型的參數去查找資源ID,所以如果要為TextView設置一個整型的形參的話,需要將其轉換為字元串的格式,不然會報錯。
接下來要做的就是在主函數中調用我們的非同步任務了:
1 package com.yztc.lx.asynctasklistview; 2 3 import android.os.Bundle; 4 import android.support.v7.app.AppCompatActivity; 5 import android.widget.ListView; 6 import android.widget.Spinner; 7 8 import com.yztc.lx.asynctasklistview.com.yztc.lx.async.DownloadAsyncTask; 9 10 public class MainActivity extends AppCompatActivity { 11 12 private ListView lv; 13 private String urlPath = "http://api.m.mtime.cn/News/NewsList.api?pageIndex=1"; 14 15 @Override 16 protected void onCreate(Bundle savedInstanceState) { 17 super.onCreate(savedInstanceState); 18 setContentView(R.layout.activity_main); 19 lv = (ListView) findViewById(R.id.listview); 20 new DownloadAsyncTask(MainActivity.this,lv).execute(urlPath); 21 } 22 }
註意:不要忘了寫execute()方法。
至此,整個Demo就完成了,沒有什麼難得邏輯,就是一些小的問題需要註意一下,通過這個小Demo提高了我對Android中非同步任務的理解,也加深了訪問網路和自定義適配器的使用。
最後的截圖如下: