Android学习之——自己搭建Http框架(2)——框架扩展

· 本文主要讲解的是Json指定转化成对象返回,下载进度更新,随时取消Request请求

一、Json指定转化成对象返回

上篇文章主要讲基础的框架搭建起来了,这次需要做一些些的扩展,这里Json转化用到了google的Gson。

上篇文章,我们直接返回了String的字符串,那么如果是请求返回回来的是Json格式的,我们能否在数据返回的时候将数据转化成需要的对象呢。答案当然是可以的。

我们可以在UI线程中创建Callback的时候将预处理的对象放入进去,还是直接代码描述比较清楚:

        1. 首先我们需要传递 实体类 的class 进去,该方法我们可以在抽象类 AbstractCallback 中定义:

	public AbstractCallback<T> setReturnClass(Class<T> clz) {
		this.mReturnClass = clz;
		return this;
	}
	public AbstractCallback<T> setReturnType(Type type) {
		this.mReturnType = type;
		return this;
	}

 
      2.  使用上述方法,在ui线程代码中,当使用的的时候调用方法如下, 注下面代码中的 new TypeToken<Entity>(){}.getType() 为GSON获取Type的方法:

private void requestJson() {
	Request request = new Request(UrlHelper.test_json_url, RequestMethod.GET);//UrlHelper.test_json_url是一个json地址
	request.setCallback(new JsonCallback<Entity>() {     // Entity 为 json 要转化的实体类 ,可以是 ArrayList<Entity>的形式等
		@Override
		public void onFilure(Exception result) {
		}
		@Override
		public void onSuccess(Entity result) {
			mTestResultLabel.setText(result.weatherinfo + "----");
		}
	}.setReturnType(new TypeToken<Entity>(){}.getType()));//.setReturnClass(Entity.class));
	request.execute();
}

 
      3. JsonCallback的实现方式如下所示:其中,我们需要将继承的类AbstractCallback改成AbstractCallback<T> 以及对应的接口也改成ICallback<T>,这里就不具体列出修改泛型的代码了

public abstract class JsonCallback<T> extends AbstractCallback<T> {
	public static Gson gson = new Gson();
	@Override
	protected T bindData(String content) {
		Log.i("bindData", content);
		if (TextUtil.isValidate(path)) {
			content = IOUtilities.readFromFile(path);
		}
		if (mReturnClass != null) {
			return gson.fromJson(content, mReturnClass);
		} else if (mReturnType != null) {
			return gson.fromJson(content, mReturnType);
		}
		return null;
	}
}

至此,转化成Json的对象已经处理完成。可能描述的不是太清楚,其实主要的步骤就是,跟上篇文章实现StringCallback.java一样,在UI线程的request.setcallback中new一个匿名内部类将ICallback以及他的抽象类,抽象子类实现泛型,使之可以传递需要的实体类的
cass,或者 type 进去。其实这个Json的转化并不是非常重要,在我阅读以及使用 android-async-http 框架的时候,直接的做法是,直接将HTTP返回的值预处理成JSON然后再onSuccess的时候进行JSON的解析,效率也并不会有太大的影响。

二、下载进度更新 

        处理思路:

在 AsyncTask 中doInBackground 里有一个方法publishProgress ,通过 AbstractCallback 的里的写入文件的进度,将进度实时更新到 onProgressUpdate 从而将更新进度更新到 主线程的功能,然后对进度进行相应的处理。如更新进度条等。

        1. 首先添加一个监听接口:

public interface IProgressListener {
	void onProgressUpdate(int curPos,int contentLength);
}

        2. 我们可以通过监听 写入到文件的循环中来进行监听,在AbstractCallback.java
中handle方法的写入文件的代码如下:

                    while ((read = in.read(b)) != -1) {
                        // TODO update progress
                        fos.write(b, 0, read);
                    }  

为了节省篇幅,具体的代码可以去上一篇文章阅读。在这里,我们可以通过当前写入的进度和总长度进行比较写入监听的接口方法中,具体代码如下:

            a. 修改 ICallback 接口:

Object handle(HttpResponse response, IProgressListener mProgressListener);

            b. 当然了同时要修改AbstractCallback
中的实现类

@Override
	public Object handle(HttpResponse response, IProgressListener mProgressListener){.........}

           c. 修改AbstractCallback 中的handle方法中写入文件的那段代码即上文代码的 //TODO update progress 段,具体代码如下:

byte[] b = new byte[IO_BUFFER_SIZE];
int read;
long curPos = 0;
long length = entity.getContentLength();
while ((read = in.read(b)) != -1) {
	checkIfCanceled();
	if (mProgressListener != null) {
		curPos += read;
		//将当前进度和总进度返回到具体的实现层,
		//我们在 RequestTask 的 doInBackground 中去实现 IProgressLinstener 接口中的的该方法,将值传到onProgressUpdate中
		mProgressListener.onProgressUpdate((int) (curPos / 1024), (int) (length / 1024));
	}
	fos.write(b, 0, read);
}

        3. 在RequestTask.java 中的doInBackground 中我们来实现上述内容:

@Override
protected Object doInBackground(Object... params) {
	try {
		HttpResponse response = HttpClientUtil.excute(request);
		//response 解析代码放到对应的类中,对应handle中的bindData方法
		Log.i("doInBackground", response.toString());
		if (request.mProgressListener != null) {
			return request.callback.handle(response, new IProgressListener() {
				@Override
				public void onProgressUpdate(int curPos, int contentLength) {
					//这里的参数类型是 AsyncTask<Object, Integer, Object>中的Integer决定的,在onProgressUpdate中可以得到这个值去更新UI主线程
					publishProgress(curPos,contentLength);
				}
			});
		}else {
			return request.callback.handle(response, null);
		}
	} catch (Exception e) {
		return e;
	}
}

   
    4. 写到这里,突然忘记最重要的一点,我们需要在主线程中设置它的监听才能真正实现监听,在Request.java中我们加入如下方法,使主线程可以调用:

	public IProgressListener mProgressListener;
	public void setProgressListener(IProgressListener iProgressListener) {
		this.mProgressListener = iProgressListener;
	}

        5. 继续上面第3点的话题,在RequestTask.java 中我们实现 AsyncTask 的onProgressUpdate
方法, 将在doInBackground 中调用的 publishProgress(..., ...) 方法得到的值在该方法中中传给IProgressListener的onProgressUpdate。不知道我这样的描述是否准确。表达不是很理想。

@Override
protected void onProgressUpdate(Integer... values) {
	super.onProgressUpdate(values);
	if (request.mProgressListener != null) {
		request.mProgressListener.onProgressUpdate(values[0], values[1]);
	}
}

        6. 最后,我们在主线程中设置setProgressListener 并实现匿名内部类 IProgressListener ,通过重写 onProgressUpdate 方法得到当前进度值和总进度值,根据该进度值,进行实时的进度条更新等操作,主线程调用方法如下:

private void requestString() {
	//设置保存路径
	String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "mrfu_http.txt";
	Request request = new Request(UrlHelper.test_string_url, RequestMethod.GET);
	request.setCallback(new StringCallback() {
		@Override
		public void onSuccess(String result) {
			mTestResultLabel.setText((String)result);
		}
		@Override
		public void onFilure(Exception result) {
			result.printStackTrace();
		}
	}.setPath(path));
	request.setProgressListener(new IProgressListener() {
		//在这里实现 IProgressListener 的onProgressUpdate 将子线程中得到的进度值获取到。
		//这里,我们只是显示到LogCat中,实际我们可以根据需要实现进度条更新等操作
		@Override
		public void onProgressUpdate(int curPos, int contentLength) {
			System.err.println("curPost:"+curPos +",contentLength:" + contentLength);
		}
	});
	request.execute();
}

三、 如何随时取消 Request 请求

        1. 我们需要取消 Request 请求,那么,在代码中,我们在哪些地方可以取消请求呢?我们先来分析框架的基本内容:

            a. 在主线程我们执行 request.execute(); 在 Request.java 中开启了一个 RequestTask,它继承自 AsyncTask

            b. doInBackground 是异步执行的,在这个子线程中 我们执行 HttpResponse response = HttpClientUtil.excute(request); 代码段 正式调用HTTP的get或者set方法,得到类型为 HttpResponse
的返回值 ,然后执行 AbstractCallback 的 handle 方法

            c. 在AbstractCallback 的 public T handle(HttpResponse response, IProgressListener mProgressListener) 方法中我们处理返回回来的 HttpResponse
的内容,如果返回的code是200,则成功,那么我们根据是否设置了下载路径选择是否下载,或者是直接返回数值。

            d.  根据主线程设置的 StringCallback 或者JsonCallback 或者其他解析类型,通过调用 bindData(....); 去具体的解析内容,并返回到 UI 线程。

        2. 设计思路:

            在主线程,我们接到了取消请求的需求,通过 调用 Requset 的 cancel() 方法,去调用 callback 中的 cancel(); 方法,将其AbstractCallback 中的取消标志设置为true,如果为true 我们就在checkIfCanceled()方法中抛出异常,结束该次请求。我们可以将 checkIfCanceled()
方法放在handle(..., ...)刚开始的时候,放在while ((read = in.read(b)) != -1){ fos.write(b, 0, read); } 写入文件的时候 以及返回数据放入不同callback中进行处理的时候。下面我们会给出具体的实现方法,还有http请求的 get 和 post 的时候。

        3. 实现代码:

            a. ICallback 接口中定义如下方法:

void checkIfCanceled() throws AppException;
void cancel();

           b. 主线程调用cancel请求:

public void testCancel(){
	String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "stay_training_http.txt";
	final Request request = new Request("http://h.hiphotos.baidu.com/image/w%3D2048/sign=432fca00369b033b2c88fbda21f636d3/a2cc7cd98d1001e9ae04c30bba0e7bec54e797fe.jpg",RequestMethod.GET);
	request.setCallback(new PathCallback() {

		@Override
		public void onSuccess(String result) {
		}

		@Override
		public void onFilure(Exception result) {
			result.printStackTrace();
		}
	}.setPath(path));
	request.setProgressListener(new IProgressListener() {
		@Override
		public void onProgressUpdate(int curPos, int contentLength) {
			System.err.println("curPost:"+curPos +",contentLength:" + contentLength);
			if (curPos > 8) {
				request.cancel();//当下载进度为8的时候我们取消了该请求
				}
			}
		});
		request.execute();
	}
}

            c. 在 Request 中实现 cancel() 方法,方法内调用 callback 的 cancel() 方法:

public ICallback callback;
public  void cancel(){
	if (callback != null) {
		this.callback.cancel();
	}
}

            d. 在 AbstractCallback  中实现ICallback 里定义的 cancel() 的方法,如果主线程调用了cancel方法,我们就将标志设置为 true

protected boolean isCancelled;
@Override
public void cancel() {
	isCancelled = true;
}

            e. 实现 ICallback 中的 checkIfCanceled() 方法,如果 isCancelled 为 true 则抛出异常,即可中断请求操作,代码中出现了 AppException 自定义异常类 这个我们后面再讲

@Override
public void checkIfCanceled() throws AppException {
	if (isCancelled) {
		throw new AppException(EnumException.CancelException, "request has been cancelled");
	}
}

            f. 在 handle 开开始放入 checkIfCanceled() 的判断,在将下载的文件写入到文件的时候我们也做判断,还有在进行数据处理的时候也进行判断

	@Override
	public T handle(HttpResponse response, IProgressListener mProgressListener) throws AppException{
		// file, json, xml, image, string
		checkIfCanceled();//在这里我们调用检查是否取消请求的方法
		int statusCode = -1;
		InputStream in = null;
		try {
			HttpEntity entity = response.getEntity();
			statusCode = response.getStatusLine().getStatusCode();
			switch (statusCode) {
			case HttpStatus.SC_OK:
				if (TextUtil.isValidate(path)) {
					//将服务器返回的数据写入到文件当中
					FileOutputStream fos = new FileOutputStream(path);
					if (entity.getContentEncoding() != null) {
						String encoding = entity.getContentEncoding().getValue();
						if (encoding != null && "gzip".equalsIgnoreCase(encoding)) {
							in = new GZIPInputStream(entity.getContent());
						} if (encoding != null && "deflate".equalsIgnoreCase(encoding)) {
							in = new InflaterInputStream(entity.getContent());
						}
					} else {
						in = entity.getContent();
					}
					byte[] b = new byte[IO_BUFFER_SIZE];
					int read;
					long curPos = 0;
					long length = entity.getContentLength();
					while ((read = in.read(b)) != -1) {
						checkIfCanceled(); //<span style="font-family: Arial, Helvetica, sans-serif;">在这里我们调用检查是否取消请求的方法</span>
						if (mProgressListener != null) {
							curPos += read;
							//将当前进度和总进度返回到具体的实现层,
							//我们在 RequestTask 的 doInBackground 中去实现 IProgressLinstener 接口中的的该方法,将值传到onProgressUpdate中
							mProgressListener.onProgressUpdate((int) (curPos / 1024), (int) (length / 1024));
						}
						fos.write(b, 0, read);
					}
					fos.flush();
					fos.close();
					in.close();
					//写入文件之后,再从文件当中将数据读取出来,直接返回对象
					return bindData(path);
				} else {
					// 需要返回的是对象,而不是数据流,所以需要去解析服务器返回的数据
					// 对应StringCallback 中的return content;
					//2. 调用binData
					return bindData(EntityUtils.toString(entity));
				}
			default:
				break;
			}
			return null;
		} catch (ParseException e) {
			throw new AppException(EnumException.ParseException, e.getMessage());
		} catch (IOException e) {
			throw new AppException(EnumException.IOException, e.getMessage());
		}
	}
	/**
	 * 数据放入到不同的Callback中处理,StringCallback 等方法中实现了该方法
	 * @throws AppException
	 */
	protected T bindData(String content) throws AppException{
		checkIfCanceled();//在这里我们检查是否取消请求的方法
		return null;
	}

           g. 我们在 RequestTask 中重写 onCancelled  判断 是否有做了 task.cancel(true); 的操作,当然,我们并没有实现该操作,那是因为,我们需要不管Request 的请求结果如果,我都需要返回到主线程,如果我这个时候取消掉了 AsyncTask ,那么AsyncTask 就永远不继续执行了,也就无法回调回来了。

@Override
protected void onCancelled() {
	super.onCancelled();
	if (request.callback != null) {
		request.callback.cancel();
	}
}

 
         h. HttpClientUtil.java 中的 post 和 get 代码中加入如下代码,具体内容请看注释:

private static HttpResponse get(Request request) throws AppException {
	try {
		//如果在代码已经执行到这里的时候,AbstractCallback中的isCancelled被置为了 true
		//这时我们就要再一次进行检查是否取消。 post方法同理,不再赘述
		if (request.callback != null) {
			request.callback.checkIfCanceled();
		}
		HttpClient client = new DefaultHttpClient();
		HttpGet get = new HttpGet(request.url);
		addHeader(get, request.headers);
		//返回的结果放到上一层进行处理
		HttpResponse response = client.execute(get);
		return response;
	} catch (ClientProtocolException e) {
		throw new AppException(EnumException.ClientProtocolException, e.getMessage());
	} catch (IOException e) {
		throw new AppException(EnumException.IOException, e.getMessage());
	}
}

上文提到的 AppException 就放到下篇文章再讲吧,还有 预处理返回的对象,即将返回回来的数据解析成对象以后,对该对象进行预处理操作,如写入数据库之类的操作,也一起放入下篇文章讲解,因为这块我也还不是啃的很透,需要再磨练磨练,再看看 stay 老师的视频,

一不小心一点半了,今天就写到这里吧,要写好一篇博客真心难,从晚上 9 点开始一边回顾视频,一边整理思路,一边再重新实现一遍,然后一点点写上来。

ps 本来写了一大段抒情性的话的,等写完了又觉得不太好意思,技术博客就单纯一点吧。

特别感谢 stay 老师在这当中的帮助。让我在框架学习这块实打实的迈出了第一步! 他的个人网站:Stay技术分享技术生活 ,有兴趣的可以去看看他的视频,讲解的相当到位。——纯技术推荐...

欢迎转载,转载注明出处,谢谢

Mr.傅:Stay

Android学习之——自己搭建Http框架(2)——框架扩展

时间: 2024-10-19 00:16:55

Android学习之——自己搭建Http框架(2)——框架扩展的相关文章

Android学习之——自己搭建Http框架

一.前言 最近学习http框架. 目前写的这个框架暂时只适用于学习之用,实际用于项目之中还需要不断的优化. 要从服务器或者网络获取数据,显示到UI上面,网络请求的操作不能放在UI线程中进行,android为我们封装了AsyncTask类来进行异步的请求操作,所以这个Http框架基于AsyncTask. 二.框架主要类 定义Request类,定义url,服务器返回数据,post的请求params,下载进度等参数. 定义HttpUtil类来封装http请求代码.在里面定义execute()方法,该方

Android学习之环境搭建

Android学习之环境搭建 园里有很多关于Android开发的环境搭建的资料,本人是安卓开发初学者,这里记录一下个人搭建Android环境的总结. 1.准备Eclipse IDE for Java Developers 网上可以下载的版本还是比较多的,本人选择了eclipse-java-luna-SR2-win32. 网址:http://www.eclipse.org/downloads/packages/eclipse-ide-java-developers/lunasr2 Note:官网点

Android学习之——自己搭建Http框架(1)

一.前言 ? ? ? ? 近期学习http框架. ? ? ? ??眼下写的这个框架临时仅仅适用于学习之用,实际用于项目之中还须要不断的优化. ? ? ? ??要从server或者网络获取数据.显示到UI上面,网络请求的操作不能放在UI线程中进行,android为我们封装了AsyncTask类来进行异步的请求操作.所以这个Http框架基于AsyncTask. 二.框架主要类 ? ? ? ? 定义Request类,定义url.server返回数据,post的请求params,下载进度等參数. ? ?

【转】Android开发学习总结(一)——搭建最新版本的Android开发环境

最近由于工作中要负责开发一款Android的App,之前都是做JavaWeb的开发,Android开发虽然有所了解,但是一直没有搭建开发环境去学习,Android的更新速度比较快了,Android1.0是2008年发布的,截止到目前为止Android已经更新Android5.0.1,学习Android开发的第一步就是搭建Android的开发环境,博客园里面有不少人也写了关于如何搭建Android开发环境的文章,我也看了一下,但是感觉都比较旧了,对照着做不一定能够搭建成功,但是有些搭建步骤是还是可

Android学习系列--App工程结构搭建

本文算是一篇漫谈,谈一谈关于android开发中工程初始化的时候如何在初期我们就能搭建一个好的架构.      关于android架构,因为手机的限制,目前我觉得也确实没什么大谈特谈的,但是从开发的角度,看到整齐的代码,优美的分层总是一种舒服的享受的.      从艺术的角度看,其实我们是在追求一种美. 本文先分析几个当今比较流行的android软件包,最后我们汲取其中觉得优秀的部分,搭建我们自己的通用android工程模板.      1. 微盘      2. 久忆日记      3.网易新

Android开发学习总结(一)——搭建最新版本的Android开发环境【转】

最近由于工作中要负责开发一款Android的App,之前都是做JavaWeb的开发,Android开发虽然有所了解,但是一直没有搭建开发环境去学习,Android的更新速度比较快了,Android1.0是2008年发布的,截止到目前为止Android已经更新Android5.0.1,学习Android开发的第一步就是搭建Android的开发环境,博客园里面有不少人也写了关于如何搭建Android开发环境的文章,我也看了一下,但是感觉都比较旧了,对照着做不一定能够搭建成功,但是有些搭建步骤是还是可

一、Android学习第一天——环境搭建(转)

(转自:http://wenku.baidu.com/view/af39b3164431b90d6c85c72f.html) 一. Android学习第一天——环境搭建 Android 开发环境的搭建 环境搭建需要①Android SDK ②JDK ③eclipse 环境搭建开始: ㈠将Android SDK与JDK解压,最好路径中不要出现汉字,然后配置环境变量,方便命令行操作 ㈡为eclipse(3.4.1)安装开发Android插件——Android ADT Help-->Install N

Android学习总结之WIN上搭建环境

引言 本系列适合0基础的人员,因为我就是从0开始的,此系列记录我步入Android开发的一些经验分享,望与君共勉!作为Android队伍中的一个新人的我,如果有什么不对的地方,还望不吝赐教. 在开始Android开发之旅启动之前,首先要搭建环境,然后创建一个简单的HelloWorld.本文的主题如下: 1.环境搭建 1.1.JDK安装 1.2.Eclipse安装 1.3.Android SDK安装 1.4.ADT安装 1.5.创建AVD 2.HelloWorld 1.环境搭建 1.1.JDK安装

C#程序员学习Android开发系列之搭建开发环境

接触Android好久了,记得09年刚在中国大陆有点苗头的时候,我就知道了google有个Android,它是智能机操作系统.后来在Android出1.5版本之后,我第一时间下载了eclipse开发工具.adt以及android sdk,体验了一把android开发,记得当时搭建开发环境相当麻烦.由于android开发是基于Java的,所以除了上述的工具之外,首先需要安装jdk,记得当时流行的是jdk1.5(后来是1.6,现在是1.7,未来是1.8),安装好之后需要设置环境变量. 时隔4年之后,