如何规范化的完成一个需要网络加载数据的页面?
先来看一下效果图
1、Json数据获取解析
查看Json数据,这里推荐使用chrome自带扩展程序JSONView:Validate and view JSON documents,但是可能需要翻墙,如下所示:
准备好工具了,我们接下来看我们如何实现对数据的json解析,假设我们需要的数据来源为:
public class UrlContainer { public static final String HOST_URL = "http://search.shouji.baofeng.com/"; public static final String LEFT_EYE_CINEMA = HOST_URL + "lefteye.php?platf=android&mtype=normal&g=23&ver=5.2.10&td=0&s=14D910696A44EC703DBDF57F8486114EAF1AE125"; }
从返回的数据我们可以看出,result是一个Json数组,包含四个成员,每个成员的items又是一个json数组。
items中的每一个子项:
{ "cover_url": " http://img.shouji.baofeng.com/img/595/t595_a24070_120*160.4.jpg", "sub_cover_url": " http://img.shouji.baofeng.com/img/595/st595_a24070_272*145.1.jpg", "detail":{ "id": "24070", "title": "寒战", "type": "1", "year": "2012", "total": "1", "has": "[1]", "sites": "[\"qiyi\",\"m1905\",\"tencent\"]", "cover_v": "{\"file_id\":\"33c3d7fc4f15efa2a128cc9e5eebee80\",\"size\":21822,\"version\":4,\"manual\":1}", "cover_h": "{\"file_id\":\"141baa8258d57316ee92219f84108bb0\",\"size\":302456,\"version\":6,\"manual\":1}", "score": "8.5", "clicks": 2414594, "finish": "1", "3d": "0", "status": "7", "update_time": "2015-06-11 05:06:01", "merge_id": "0", "duration": "6621", "video_count": "3", "cover_url": " http://img.shouji.baofeng.com/img/070/v24070_255*342.4.jpg", "cover_h_url": " http://img.shouji.baofeng.com/img/070/hh24070_400*225_2.6.jpg", "update_at": "2015-06-11", "area_name": [ "港台" ], "actors_name": [ "刘德华", "郭富城", "梁家辉", "杨采妮" ], "directors_name": [ "陆剑青", "梁乐民" ], "max_site": "qiyi", "last_seq": "1", "ending": 0, "danmaku": "0" }
通过继续查看items,我们会发现也是层层嵌套,对于这种比较复杂的json格式,我们应该如何解析那?其实我们会发现,对于返回的数据,我们并不一定全部获取,我们只要按照我们的需求进行获取就行
接下来按需构建实体类LeftEyeItem和LeftEyeItems
public class LeftEyeItem implements Cloneable, Serializable { private static final long serialVersionUID = 1L; private String cover_url; /** 每期的id */ private String periodId; /** 每期里面视频项的id */ private String id; private String title; private String desc; private String titleDate; private String site; private String threeD; private String has; ... }
public class LeftEyeItems implements Cloneable, Serializable { private static final long serialVersionUID = 1L; private ArrayList<LeftEyeItem> items = new ArrayList<LeftEyeItem>(); public ArrayList<LeftEyeItem> getItems() { return items; } public void setItems(ArrayList<LeftEyeItem> items) { this.items = items; } public void addItem(LeftEyeItem item) { items.add(item); } }这里我们启用了一个线程进行数据的异步加载,具体实现如下:
这里需要注意两点:
1、json数据的按需解析具体如何对应的,大家可以仔细对比一下(根据代码中key值跟json数据的具体字段做对比)
2、就是该段代码的实现将Activity中定义的Handler作为线程构造函数的参数,其实这样并不是很好,
增加了activity中Handler与线程的耦合性,比较好的方法就是使用接口回调的方式,
可以参考http://blog.csdn.net/s003603u/article/details/46813509
public class LeftEyeLoadThread extends Thread { private Context context; private Handler handler; private String url; public static final int LOADING_NORMAL = 0; public static final int LOADING_FIRT = 1; public static final int LOADING_SORT = 2; public LeftEyeLoadThread(Context context, Handler handler, String url) { super(); this.context = context; this.handler = handler; this.url = url; } @Override public void run() { LeftEyeItems showItems; try { showItems = doGet(); if (showItems == null) { HandlerMsgUtils.sendMsg(handler, LeftEyeCinemaActivity.MSG_ID_LOADING_FAILED); } else { HandlerMsgUtils.sendMsg(handler, LeftEyeCinemaActivity.MSG_ID_LOADING_SUCCESS, showItems); } } catch (Exception e) { HandlerMsgUtils.sendMsg(handler, LeftEyeCinemaActivity.MSG_ID_LOADING_FAILED); } } private LeftEyeItems doGet() throws MalformedURLException, ProtocolException, SocketException, IOException, CustomException, JSONException { LeftEyeItems items = new LeftEyeItems(); String jsonString = NetUtils.getJsonStringFrUrl(context, url); if (jsonString == null || "".equals(jsonString.trim()) || "[]".equals(jsonString.trim())) { throw new JSONException("josn is null"); } JSONObject jsonObj; JSONArray result; String periodId; try { jsonObj = new JSONObject(jsonString); result = jsonObj.getJSONArray("result"); for (int i = 0; i < result.length(); i++) { JSONObject obj1 = result.getJSONObject(i); periodId = obj1.getString("id"); JSONArray arr2 = obj1.getJSONArray("items"); for (int j = 0; j < arr2.length(); j++) { LeftEyeItem item = new LeftEyeItem(); JSONObject obj3 = arr2.getJSONObject(j); item.setperiodId(periodId); item.setCover_url(obj3.getString("sub_cover_url")); item.setDesc(obj3.getString("desc")); item.setId(obj3.getString("id")); item.setTitle(obj3.getString("title")); item.setSite(obj3.getJSONObject("detail").getString("max_site")); item.setThreeD(obj3.getJSONObject("detail").getString("3d")); item.setHas(obj3.getJSONObject("detail").getString("has")); items.addItem(item); } } } catch (Exception e) { return null; } return items; } }
2、构建adapter
这次实现我们准备使用自适应的gridview,首先看activity的界面布局
<!-- 主体内容 --> <RelativeLayout android:id="@+id/activity_left_eye_cinema_root" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@+id/activity_left_eye_cinema_title_layout" > <GridView android:id="@+id/activity_left_eye_cinema_gridview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/web_img_spacing" android:layout_marginRight="@dimen/web_img_spacing" android:layout_marginTop="@dimen/web_img_spacing" android:cacheColorHint="#00000000" android:fadingEdge="none" android:horizontalSpacing="@dimen/web_img_spacing" android:listSelector="@drawable/hide_gridview_yellow_selector" android:numColumns="auto_fit" android:stretchMode="columnWidth" android:verticalSpacing="@dimen/web_img_spacing" > </GridView> </RelativeLayout>注意:这里我们设置GridView的numColumns为atuo_fit
LeftEyeAdapter中gridview的子布局其实很简单,就只有一张影片图片和电影名字,就不赘述了,其实自适应最主要的就是根据设置的图片宽度,图片之间的间隔,手机屏幕的宽度来计算gridview将显示几列 ,这里我们让LeftEyeCinemaActivity实现ViewTreeObserver.OnGlobalLayoutListener接口,这个接口的作用是:
Interface definition for a callback to be invoked when the global layout state or the visibility of views within the view tree changes.
其实就是当一个View的布局加载完成或者布局发生改变时,OnGlobalLayoutListener可以监听到,具体来看
@Override public void onGlobalLayout() { if (mAdapter != null && mAdapter.getNumColumns() <= 0) { getScreenWidth(); final int numColumns = (int) Math .floor(screenWidth / (mImageThumbWidth + mImageThumbSpacing)); int columnWidth; if (numColumns > 0) { columnWidth= (screenWidth - (numColumns + 1) * mImageThumbSpacing) / numColumns; mAdapter.setNumColumns(numColumns); } else { columnWidth = screenWidth - 2 * mImageThumbSpacing; mAdapter.setNumColumns(1); } mAdapter.setItemHeight(columnWidth); gridView.setColumnWidth(columnWidth); } }我们首先会获取到手机屏幕的宽度,然后基于事先设置好的图片缩略图宽度和图片间距来计算列数,然后根据列数来计算每一列的宽度,如果我们设置的图片宽度过大的话,我们会基于屏幕宽度只显示一列,在adapter中我们基于imgWidthParam计算出图片的高度
/** * Sets the item height. Useful for when we know the column width so the * height can be set to match. * * @param height */ public void setItemHeight(int width) { mImageWidth = width; mItemHeight = (int) (mImageWidth * imgWidthParam); mImageViewLayoutParams = new RelativeLayout.LayoutParams(mImageWidth, mItemHeight); notifyDataSetChanged(); }3、图片加载
这里面关于图片加载的管理,我们使用的是图片处理开源库Universal-image-loader,这个大家可以去github上去找,这里我只是介绍一下在我们这个项目中怎么简单地使用,首先你要导入jar包,然后就是初始化,这里我们使用Application进行全局统一管理
public class LeftEyeApplication extends Application { private static LeftEyeApplication instance; @Override public void onCreate() { instance = this; super.onCreate(); ImageUtil.initImageLoader(this); } public static LeftEyeApplication getInstance() { return instance; } }初始化(具体相关的细节和参数设置我就不细说了)
public class ImageUtil { /** * 初始化图片加载相关 * * @param context */ public static void initImageLoader(Context context) { File cacheDir = StorageUtils.getCacheDirectory(context); // int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // // 使用最大可用内存值的1/8作为缓存的大小。 // int cacheSize = maxMemory / 8; ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context) // max width, max height,即保存的每个缓存文件的最大长宽 .diskCacheExtraOptions(720, 1280, null).threadPriority(Thread.NORM_PRIORITY - 2) .diskCache(new UnlimitedDiscCache(cacheDir)).denyCacheImageMultipleSizesInMemory() .diskCacheSize(50 * 1024 * 1024).diskCacheFileCount(1000) .diskCacheFileNameGenerator(new Md5FileNameGenerator()) .tasksProcessingOrder(QueueProcessingType.LIFO).memoryCacheSizePercentage(15) .build(); ImageLoader.getInstance().init(config); } }OK!,现在我们就可以在adapter中进行使用了
@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { convertView = mInflater.inflate(R.layout.left_eye_cinema_item, null); holder = new ViewHolder(); holder.image = (ImageView) convertView.findViewById(R.id.web_normal_view_item_img); holder.title = (TextView) convertView.findViewById(R.id.web_normal_view_item_title); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } if (holder.image.getLayoutParams().width != mImageWidth) { holder.image.setLayoutParams(mImageViewLayoutParams); } ImageLoader.getInstance().displayImage(items.get(position).getCover_url(), holder.image, options); holder.title.setText(items.get(position).getTitle()); return convertView; }如上使用了displayImage方法,其余用法,朋友们可以自己研究
4、异常处理响应
这里主要就是当页面加载时的loading界面,页面加载异常的异常界面,以及通过广播监听网络状态,加载失败后自动加载的处理
,而对于这些不是经常出现的界面,我们使用了轻量级控件ViewStub,这里就涉及到布局优化的问题,
可以参考http://blog.csdn.net/s003603u/article/details/46841267的大纲进行研究
<!-- 加载 --> <ViewStub android:id="@+id/viewstub_left_eye_cinema_loading" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerInParent="true" android:layout_below="@id/activity_left_eye_cinema_title_layout" android:inflatedId="@+id/viewstub_inflate_left_eye_cinema_loading" android:layout="@layout/common_loading" /> <!-- 异常提示 --> <!-- 无网络,服务器更新 --> <ViewStub android:id="@+id/viewstub_left_eye_cinema_tips" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@id/activity_left_eye_cinema_title_layout" android:inflatedId="@+id/viewstub_inflate_left_eye_cinema_tips" android:layout="@layout/common_tips_layout" />
class MyNetModeStatusListener implements OnNetModeChangeListener { @Override public void onUpdateData() { switchTargetView(0, OnTipsListener.TIPS_NONE); initData(); } @Override public void onShowNoNetView() { switchTargetView(2, OnTipsListener.TIPS_NO_NETWORK); } @Override public void onShowNetModeView() { switchTargetView(3, OnTipsListener.TIPS_NONE); } @Override public void onHideNetModeView() { initData(); } }
/** * 切换显示页面 * * @param pageType * @param exceptionType */ private void switchTargetView(int pageType, int exceptionType) { View loadingView = (View) findViewById(R.id.viewstub_inflate_left_eye_cinema_loading); View tipsView = (View) findViewById(R.id.viewstub_inflate_left_eye_cinema_tips); switch (pageType) { case 0: { if (tipsView != null) { tipsView.setVisibility(View.INVISIBLE); } if (loadingView != null) { loadingView.setVisibility(View.INVISIBLE); } if (netModeManager != null) { netModeManager.dismissZeroFlowView(); } break; } case 1: { // 加载页面 View view = inflateSubView(R.id.viewstub_left_eye_cinema_loading, R.id.viewstub_inflate_left_eye_cinema_loading); if (view != null) { String[] list = getResources().getStringArray(R.array.common_loading_text); int index = (int) (Math.random() * list.length); TextView txt = (TextView) view.findViewById(R.id.lay_progressbar_text); txt.setText(list[index]); view.setVisibility(View.VISIBLE); } if (tipsView != null) { tipsView.setVisibility(View.INVISIBLE); } if (netModeManager != null) { netModeManager.dismissZeroFlowView(); } break; } case 2: { // 异常页面 View view = inflateExceptionSubView(R.id.viewstub_left_eye_cinema_tips, R.id.viewstub_inflate_left_eye_cinema_tips, exceptionType, this); if (view != null) { view.setVisibility(View.VISIBLE); } if (loadingView != null) { loadingView.setVisibility(View.INVISIBLE); } if (netModeManager != null) { netModeManager.dismissZeroFlowView(); } break; } default: break; } }
5、补充
我们再来说说其它一些细节:
1)在使用Handler时避免activity内存泄露的方法
不使用非静态内部类,继承Handler时,要么是放在单独的类文件中,要么就是使用静态内部类。因为静态的内部类不会持有外部类的引用,所以不会导致外部类实例的内存泄露。当你需要在静态内部类中调用外部的Activity时,我们可以使用弱引用来处理
/** * Handler的处理事件 */ private static class MyHandler extends Handler { WeakReference<LeftEyeCinemaActivity> thisLayout; MyHandler(LeftEyeCinemaActivity layout) { thisLayout = new WeakReference<LeftEyeCinemaActivity>(layout); } @Override public void handleMessage(Message msg) { final LeftEyeCinemaActivity theLayout = thisLayout.get(); if (theLayout == null) { return; } switch (msg.what) { case MSG_ID_LOADING_SUCCESS: theLayout.loadingSuccess((LeftEyeItems) msg.obj); break; case MSG_ID_LOADING_FAILED: theLayout.loadingFail(); break; default: break; } } }
网络数据页面的加载的内容暂时就到这了!欢迎点评!
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/s003603u/article/details/46931101