你的位置:首页 > 操作系统

[操作系统]使用异步任务加载网络上json数据并加载到ListView中


  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.

 1 <? 2 <RelativeLayout  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   <TextView15     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   <TextView24     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   @Override33   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   @Override46   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   @Override35   public int getCount() {36     return list.size();37   }38 39 40   @Override41   public Object getItem(int position) {42     return list.get(position);43   }44 45   @Override46   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    * @return56   */57   @Override58   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   @Override16   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中异步任务的理解,也加深了访问网络和自定义适配器的使用。
  最后的截图如下: