如何规范化的完成一个需要网络加载数据的页面?
先来看一下效果图
1、Json数据获取解析
查看Json数据,这里推荐使用chrome自带扩展程序JSONView:Validate and view JSON documents,但是可能需要翻墙,如下所示:
准备好工具了,我们接下来看我们如何实现对数据的json解析,假设我们需要的数据来源为:
public class UrlContainer {public static final String HOST_URL = "http://search./";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";}
通过将完整url输入到浏览器当中,我们会加载出如下格式化的数据(url后面的一连串参数不用管)
从返回的数据我们可以看出,result是一个Json数组,包含四个成员,每个成员的items又是一个json数组。
items中的每一个子项:
{ "cover_url": " http://img./img/595/t595_a24070_120*160.4.jpg", "sub_cover_url": " http://img./img/595/st595_a24070_272*145.1.jpg", "detail":{ "id": "24070", "title": "寒战", "type": "1", "year": "", "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": "-06-11 05:06:01", "merge_id": "0", "duration": "6621", "video_count": "3", "cover_url": " http://img./img/070/v24070_255*342.4.jpg", "cover_h_url": " http://img./img/070/hh24070_400*225_2.6.jpg", "update_at": "-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与线程的耦合性,比较好的方法就是使用接口回调的方式,
可以参考/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;}@Overridepublic 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的界面布局
<!-- 主体内容 --><RelativeLayoutandroid: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" ><GridViewandroid: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 stateor the visibility of views within the view tree changes.
其实就是当一个View的布局加载完成或者布局发生改变时,OnGlobalLayoutListener可以监听到,具体来看
@Overridepublic 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;@Overridepublic 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中进行使用了
@Overridepublic 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,这里就涉及到布局优化的问题,
可以参考/s003603u/article/details/46841267的大纲进行研究
<!-- 加载 --><ViewStubandroid: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" /><!-- 异常提示 --><!-- 无网络,服务器更新 --><ViewStubandroid: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" />
这里主要使用了一个netModeManager管理类进行管理,主要包括网络状态的检测,网络模式的刷新与切换,已经通过接口调用对不同网络模式的响应,这里就不贴代码了,大家可以下载demo自行研究,activity中相关
class MyNetModeStatusListener implements OnNetModeChangeListener {@Overridepublic void onUpdateData() {switchTargetView(0, OnTipsListener.TIPS_NONE);initData();}@Overridepublic void onShowNoNetView() {switchTargetView(2, OnTipsListener.TIPS_NO_NETWORK);}@Overridepublic void onShowNetModeView() {switchTargetView(3, OnTipsListener.TIPS_NONE);}@Overridepublic 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(mon_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);}@Overridepublic 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;}}}
网络数据页面的加载的内容暂时就到这了!欢迎点评!
demo地址:/feifei003603/LeftEyeDemo.git