使用异步任务加载网络上json数据并加载到ListView中

时间:2023-03-09 00:46:48
使用异步任务加载网络上json数据并加载到ListView中

  Android中使用网络访问来加载网上的内容,并将其解析出来加载到控件中,是一种很常见的操作。但是Android的UI线程(也就是主线程)中是不允许进行耗时操作的,因为耗时操作会阻塞主线程,影响用户体验。而访问网络同样是一个耗时操作,并且Android3.0以后是不允许在主线程中访问网络的,所以我们这里用Android封装好的AsyncTask类来完成这些耗时操作。

  项目的目录结构如下:

  使用异步任务加载网络上json数据并加载到ListView中

  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界面组件

  具体的AsyncTask的使用及源码分析请看这篇链接:http://blog.****.net/seu_calvin/article/details/52172248

  大概看完了这四个方法,下面我们开始看看这次的Demo:

  因为是想要从网络上获取json数据,所以要先准备一个接口,我的接口是时光网的:

 http://api.m.mtime.cn/News/NewsList.api?pageIndex=1
  为了得到这个接口中的Json格式的数据,我们先定义了一个HttpUtils类,在其中先定义了一个测试网络是否联通的类isNetConn(Context context)如下所示:
 
 /**
* 获取网络状态
*
* @param context 上下文
* @return 联通状态
*/
public static boolean isNetConn(Context context) {
//获取网络连接管理对象
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(context.CONNECTIVITY_SERVICE);
//获取活跃状态的网络信息对象
NetworkInfo info = manager.getActiveNetworkInfo();
if (info != null) {
return info.isConnected(); //返回是否链接
} else {
return false;
} }

又定义了一个返回byte数组downloadFromNet()方法,来获取数据的byte[]数组:
  /**
* 获取网络上下载下来的数据的byte数组
*
* @param urlPath 网络URL路径
* @return 网络上获取的json字符串的byte数组形式
*/
public static byte[] downloadFromNet(String urlPath) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
URL url = null;
try {
url = new URL(urlPath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
conn.setDoInput(true);
conn.connect();
if (conn.getResponseCode() == 200) {
InputStream is = conn.getInputStream();
int len;
byte b[] = new byte[1024];
//注意这里:is.read(b) 中的b数组一定要写,不然读取的数据不对
while ((len = is.read(b)) != -1) {
baos.write(b, 0, len);
baos.flush();
}
return baos.toByteArray();
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return baos.toByteArray();
}

之所以返回byte[]数组类型,是方便我们下载其他东西的时候也可以使用。

  获取到了json字符串,下一步就是将其解析出来,定义一个ParserJson方法,json字符串的解析相信大家应该都是了解的,因为这是Android中非常重要的一部分知识,这里就不再赘述,直接上代码:

json串对应的实体类:

 package com.yztc.lx.asynctasklistview.com.yztc.lx.bean;

 import java.util.List;

 /**
* 外层JsonObject对象
* Created by Lx on 2016/8/10.
*/ public class ShiGuang { private int totalCount;
private int pageCount;
private List<News> newsList;
}
 package com.yztc.lx.asynctasklistview.com.yztc.lx.bean;

 /**
* Created by Lx on 2016/8/10.
*/ public class News {
private int id;
private int type;
private String image;
private String title;
private String title2;
private String summary;
private String summaryInfo;
private String tag;
private int commentCount; @Override
public String toString() {
return "News{" +
"id=" + id +
", type=" + type +
", image='" + image + '\'' +
", title='" + title + '\'' +
", title2='" + title2 + '\'' +
", summary='" + summary + '\'' +
", summaryInfo='" + summaryInfo + '\'' +
", tag='" + tag + '\'' +
", commmentCount=" + commentCount +
'}';
} public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public int getType() {
return type;
} public void setType(int type) {
this.type = type;
} public String getTitle() {
return title;
} public void setTitle(String title) {
this.title = title;
} public String getImage() {
return image;
} public void setImage(String image) {
this.image = image;
} public String getTitle2() {
return title2;
} public void setTitle2(String title2) {
this.title2 = title2;
} public String getSummary() {
return summary;
} public void setSummary(String summary) {
this.summary = summary;
} public String getSummaryInfo() {
return summaryInfo;
} public void setSummaryInfo(String summaryInfo) {
this.summaryInfo = summaryInfo;
} public String getTag() {
return tag;
} public void setTag(String tag) {
this.tag = tag;
} public int getCommmentCount() {
return commentCount;
} public void setCommmentCount(int commmentCount) {
this.commentCount = commmentCount;
}
}

下面是ParserJson类:

 package com.yztc.lx.asynctasklistview.com.yztc.lx.utils;

 import com.yztc.lx.asynctasklistview.com.yztc.lx.bean.News;

 import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import java.util.ArrayList;
import java.util.List; /**
* Created by Lx on 2016/8/10.
*/ public class ParserJson {
public static List<News> parserJsonToNews(String jsonString){
List<News> list=null;
try {
list=new ArrayList<>();
JSONObject obj=new JSONObject(jsonString);
JSONArray arr=obj.getJSONArray("newsList");
for(int i=0;i<arr.length();i++){
JSONObject obj1=arr.getJSONObject(i);
News news=new News();
news.setId(obj1.getInt("id"));
news.setTitle(obj1.getString("title"));
news.setSummary(obj1.getString("summary"));
list.add(news);
}
} catch (JSONException e) {
e.printStackTrace();
}
return list;
}
}

json串格式化后的一部分如下所示:

使用异步任务加载网络上json数据并加载到ListView中

本Demo中只解析了newsList中的部分内容,包括id,title,summary这三部分,但是实体类中基本上都定义了。

  定义完了这些工具类之后,我们在主页面的布局中加入一个ListView控件,再定义一个item.xml用来作为ListView的自布局,主界面就不上代码了,下面看一下item的布局:

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"> <TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="Title"
android:textSize="20sp" /> <TextView
android:id="@+id/tv_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_title"
android:padding="10dp"
android:text="Summary"
android:textSize="12sp" /> <TextView
android:id="@+id/tv_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignTop="@+id/tv_summary"
android:padding="10dp"
android:text="id"
android:textSize="12sp" /> </RelativeLayout>

   基本工作都完成了,下面完成异步任务类DownloadAsyncTask中的内容,因为在进入后台线程前没有什么准备工作,并且也不需要进度条,所以就只重写了doInBackground()方法和onPostExecute()方法,代码如下:

 package com.yztc.lx.asynctasklistview.com.yztc.lx.async;

 import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.Toast; import com.yztc.lx.asynctasklistview.com.yztc.lx.adapter.MyBaseAdapter;
import com.yztc.lx.asynctasklistview.com.yztc.lx.bean.News;
import com.yztc.lx.asynctasklistview.com.yztc.lx.utils.HttpUtils;
import com.yztc.lx.asynctasklistview.com.yztc.lx.utils.ParserJson; import java.util.List; /**
* Created by Lx on 2016/8/10.
*/ public class DownloadAsyncTask extends AsyncTask<String, Void, List<News>> {
private Context mContext;
private ListView lv;
private Spinner sp; public DownloadAsyncTask(Context mContext, ListView lv) {
this.mContext = mContext;
this.lv = lv;
} @Override
protected List<News> doInBackground(String... params) {
List<News> list = null;
if(HttpUtils.isNetConn(mContext)){
byte[] b=HttpUtils.downloadFromNet(params[0]); //可变参数params当成一个数组使用,其中的params[0]就是我们传递过来的参数
String jsonString=new String(b);
Log.d("Tag",jsonString);
list=ParserJson.parserJsonToNews(jsonString);
Log.d("List",list.toString());
}
return list;
} @Override
protected void onPostExecute(List<News> newses) {
if(newses!=null&&newses.size()!=0){
MyBaseAdapter adapter=new MyBaseAdapter(mContext,newses);
lv.setAdapter(adapter);
}else {
Toast.makeText(mContext,"数据加载失败", Toast.LENGTH_SHORT).show();
}
}
}

  因为要更新UI中的ListView,所以在DownloadAsyncTask的构造函数中传入了ListView和Context两个形参。在doInBackground()方法中完成了数据计算操作后,将返回一个List<News>类型的变量,会接着执行onPostExecute()方法,变量会传到他的形参中。通过这个List集合,我们来完成ListView的数据的填充。填充ListView首先需要自定义一个适配器继承自BaseAdapter,我们取名为MyBaseAdapter。为其传入Context和list两个参数,至于ListView的填充请看我的另一篇博客,下面直接上代码:

 package com.yztc.lx.asynctasklistview.com.yztc.lx.adapter;

 import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView; import com.yztc.lx.asynctasklistview.R;
import com.yztc.lx.asynctasklistview.com.yztc.lx.bean.News; import java.util.List; /**
* Created by Lx on 2016/8/10.
*/ public class MyBaseAdapter extends BaseAdapter {
private Context mContext;
private List<News> list;
private LayoutInflater inflater; public MyBaseAdapter(Context context, List<News> list) {
this.mContext = context;
this.list = list;
this.inflater=LayoutInflater.from(mContext);
} /**
*
* @return 要填充的集合的长度
*/
@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;
} /**
*
* @param position 在适配器数据集合中的item的位置
* @param convertView 已经填充过的View,可以用来重用来提高加载速度
* @param parent 这个view将要展示的父容器
* @return
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
News news=list.get(position);
ViewHolder holder;
if(convertView==null){
holder=new ViewHolder();
convertView=inflater.inflate(R.layout.item,null);
holder.tv_id= (TextView) convertView.findViewById(R.id.tv_id);
holder.tv_summary= (TextView) convertView.findViewById(R.id.tv_summary);
holder.tv_title= (TextView) convertView.findViewById(R.id.tv_title);
convertView.setTag(holder);
}else{
holder= (ViewHolder) convertView.getTag();
}
holder.tv_id.setText(""+news.getId());
holder.tv_title.setText(news.getTitle());
holder.tv_summary.setText(news.getSummary());
return convertView;
} class ViewHolder{
private TextView tv_title,tv_summary,tv_id;
}
}

  这里需要注意的是,不要忘了为ListView设置适配器setAdapter(adapter).。还有就是setText()方法有一个重载形式是:setText(int i)传入了一个int类型的形参的话,系统会根据这个int类型的参数去查找资源ID,所以如果要为TextView设置一个整型的形参的话,需要将其转换为字符串的格式,不然会报错。

  接下来要做的就是在主函数中调用我们的异步任务了:

 package com.yztc.lx.asynctasklistview;

 import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ListView;
import android.widget.Spinner; import com.yztc.lx.asynctasklistview.com.yztc.lx.async.DownloadAsyncTask; public class MainActivity extends AppCompatActivity { private ListView lv;
private String urlPath = "http://api.m.mtime.cn/News/NewsList.api?pageIndex=1"; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv = (ListView) findViewById(R.id.listview);
new DownloadAsyncTask(MainActivity.this,lv).execute(urlPath);
}
}

  注意:不要忘了写execute()方法。

  至此,整个Demo就完成了,没有什么难得逻辑,就是一些小的问题需要注意一下,通过这个小Demo提高了我对Android中异步任务的理解,也加深了访问网络和自定义适配器的使用。
  最后的截图如下:
使用异步任务加载网络上json数据并加载到ListView中