Android OkHttp(一)初识,这篇文章最后提供了一个封装Okhttp请求的类,今天就来看看在项目中具体的使用情况。
一、简单接口请求。
接口请求,需要有一个服务端,这里就使用之前用SpringMVC做的一个接口服务,接口有关的详细开发步骤,请参考这篇文章,SpringMVC 开发接口。
1.启动接口服务后,运行后的效果截图如下,
可以看到 ,接口返回的是json格式的数据,json数据定义了返回状态、返回码以及返回数据等,有关接口定义,可以看这篇文章,java web开发(二) 接口开发。
2.客户端程序开发。
客户端使用Android小程序去调用接口。这个更加详细的描述,请参考, java web开发(三) 接口使用。记得加入网络访问请求权限
<uses-permission android:name="android.permission.INTERNET"/>
下面主要展示具体的调用,
public class MainActivity extends AppCompatActivity {
private Button btn;
private TextView tv;
private ImageView iv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.btn);
tv = (TextView) findViewById(R.id.tv);
iv = (ImageView) findViewById(R.id.iv);
Log.e("Thread.currentThread()--->", Thread.currentThread().getId() + "");//打印当前线程的id
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
getAllStudent();
}
});
}
}
MainActivity界面有Button和TextView,点击Button去调用接口,TextView则用来显示接口数据。下面是具体调用方法,
/**
* 获取接口数据`
*/
private void getAllStudent() {
//接口返回的json
TypeToken<ListResponse<Students>> typeToken = new TypeToken<ListResponse<Students>>() {
};
OkHttpUtils.getInstance().getAsyn(Constant.GET_ALL_STUDENT, null, typeToken, new BaseResponseCallback<ListResponse<Students>>() {
@Override
public void onCompleted(final Throwable e, ListResponse<Students> result) {
if (e != null) {
tv.post(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
}
});
} else {
Log.e("Thread.currentThread()--->", Thread.currentThread().getId() + "");//打印当前线程的id
//获取接口返回的列表数据
List<Students> list = result.getItems();
final StringBuffer sb = new StringBuffer();
for (Students students : list) {
sb.append("姓名:" + students.getName() + ", 年龄" + students.getAge() + ", 电话" + students.getMobile()).append("\n");
}
//更新UI,在子线程中,不能直接更新UI
tv.post(new Runnable() {
@Override
public void run() {
tv.setText(sb.toString());
}
});
}
}
});
}
在回调方法中要加入判断请求是否成功,最后记得显示接口数据时,不能直接在回调方法中更新UI,否则会报异常,如下图所示,
再看下面这种截图,
可以看到打印了两个线程的id是不一样的,一个是主线程,一个是子线程。程序运行后的效果截图,
PS: 我们还可以在点击Button时,显示一个进度条(模态对话框),当请求结束后,关闭该进度条。这样在请求时,有一个比较好的用户体验,明确告诉用户,程序现在在做什么!
更新UI,有多种方式,还可以使用handler发消息到主线程中,让主线程更新UI!
二、文件下载。
1.服务端。
下载一张图片。该图片位于Tomact服务器路径底下。
启动在Tomact服务器,然后在浏览器中输入图片地址,截图如下所示,
2.客户端程序开发。
MainActivity界面有Button和ImageView,点击Button去调用接口,ImageView则用来显示下载的图片。
首先记得加入读写sd卡权限,
<!-- 在SD卡中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!-- 向SD卡写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
下面看具体调用,
/**
* 下载图片
*/
private void downLoadPic() {
OkHttpUtils.getInstance().downloadFileAsyn(Constant.PIC_URL, null, FileUtils.createFile(MainActivity.this, "pic", "1.png"), new BaseResponseCallback() {
@Override
public void onCompleted(final Throwable e, final Object result) {
if (e != null) {
tv.post(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
}
});
} else {
if (result != null) {
iv.post(new Runnable() {
@Override
public void run() {
String path = (String) result;
Log.e("path--->", path);
Bitmap bitmap = BitmapFactory.decodeFile(path);
iv.setImageBitmap(bitmap);
}
});
}
}
}
});
}
FileUtils.createFile(MainActivity.this,"pic","1.png") //是创建一个文件
点击Button去调用下载图片,下载完成后在ImageView中加载图片,点击运行后的效果如下,
打印出下载文件的路径,我们在手机上去该目录底下看看文件是否存在,
在目录下,已经存在下载的文件了,下面显示手机运行后的效果截图,
PS: 图片下载需要和图片缓存结合使用!
三、文件上传。
在调用接口上传文件前,先做了一个JSP网页上传一个文件。
1. 使用form 上传一个文件。
(1). 首先需要创建一个上传的JSP网页,具体代码
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Insert title here</title>
</head>
<body>
<form action="./upLoadFile.dbo" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="file"/>
<input type="submit" value="提交"/>
</form>
</body>
</html>
该JSP网页功能很简单,选择文件,然后点击提交。
(2).创建两个JSP网页,一个是成功,另外一个是失败,用于上传后跳转,内容自己可以定制,我这边只显示文字。具体代码就不列举了!具体代码详解Demo工程。
(3).创建一个上传接口,具体代码如下,
public interface UpLoadFileService {
public boolean uploadFile(String destinationDir, MultipartFile file);
}
该接口只有一个上传方法,下面在看该接口的实现类,
public class UploadFileServiceImpl implements UpLoadFileService {
public boolean upload(String destinationDir, MultipartFile file) {
return uploadFile(destinationDir, file);
}
/**
* 保存文件
*
* @param stream
* @param path
* @param filename
* @throws IOException
*/
private void SaveFileFromInputStream(InputStream stream, String path, String filename) throws IOException {
FileOutputStream outputStream = new FileOutputStream(path + "/" + filename);
int byteCount = 0;
byte[] bytes = new byte[1024];
while ((byteCount = stream.read(bytes)) != -1) {
outputStream.write(bytes, 0, byteCount);
}
outputStream.close();
stream.close();
}
@Override
public boolean uploadFile(String destinationDir, MultipartFile file) {
StringBuffer sb = new StringBuffer();
sb.append("文件长度: " + file.getSize());
sb.append("文件类型: " + file.getContentType());
sb.append("文件名称: " + file.getName());
sb.append("文件原名: " + file.getOriginalFilename());
System.out.println(sb.toString());
try {
SaveFileFromInputStream(file.getInputStream(), destinationDir, file.getOriginalFilename());
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
}
该实现类,将文件流写入到具体目录中,和我们第二部的下载文件很类似。
(4).接着定义个Action,提供外部调用接口,
@Controller
public class UpLoadFileServlet {
@RequestMapping(value = "/upLoadFile.dbo", method = RequestMethod.POST)
public ModelAndView getAllStudent(HttpServletRequest request, HttpServletResponse response,
ModelMap modelMap) {
// 转型为MultipartHttpRequest:
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
// 获得文件:
MultipartFile file = multipartRequest.getFile("file");
String realPath = "D:/java_web/upload";//文件存放路径,需要首先创建
boolean result = false;
result = UploadFileBusiness.upload(realPath, file);
String resultJsp = "";
SingleObject singleObject = new SingleObject();
if (result) {
resultJsp = "uploadFileSuccess";
singleObject.setCode(StatusCode.CODE_SUCCESS);
singleObject.setMsg("上传成功");
} else {
resultJsp = "uploadFileFail";
singleObject.setCode(StatusCode.CODE_ERROR);
singleObject.setMsg("上传失败");
}
singleObject.setObject("");
modelMap.addAttribute("result", singleObject);
return new ModelAndView(resultJsp, modelMap);
}
}
上传文件调用了UploadFileBusiness.upload()方法,
public class UploadFileBusiness {
/**
* 上传文件
* @param destinationDir
* @param file
* @return
*/
public static boolean upload(String destinationDir, MultipartFile file) {
return new UploadFileServiceImpl().upload(destinationDir, file);
}
}
(5).最后记得要在springmvc.xml中配置过滤器,
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<!--配置自动解析的包 -->
<context:component-scan base-package="cn.springmvc"></context:component-scan>
<!--配置视图解析器:如何把handler 方法返回值解析为实际的物理视图 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- SpringMVC上传文件时,需要配置MultipartResolver处理器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"/>
<!-- 指定所上传文件的总大小不能超过200KB。注意maxUploadSize属性的限制不是针对单个文件,而是所有文件的容量之和 -->
<property name="maxUploadSize" value="200000"/>
</bean>
...
<!-- SpringMVC在超出上传文件限制时,会抛出org.springframework.web.multipart.MaxUploadSizeExceededException -->
<!-- 该异常是SpringMVC在检查上传的文件信息时抛出来的,而且此时还没有进入到Controller方法中 -->
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!-- 遇到MaxUploadSizeExceededException异常时,自动跳转到/WEB-INF/jsp/error_fileupload.jsp页面 -->
<prop key="org.springframework.web.multipart.MaxUploadSizeExceededException">error_fileupload</prop>
</props>
</property>
</bean>
...
</beans>
因为需要上传文件,所以还要导入几个jar包才可以,commons-fileupload-*.jar和commons-io-*.jar等包。
此致,上传功能已经完成了!下面展示一张,项目截图,
点击提交后,如果上传成功,会跳转至上传成功(uploadFileSuccess.jsp),并且在管理端打印了该文件的信息,
我们去上传文件的保存目录(D:/java_web/upload)看看是否有上传文件,
点击提交后,如果上传失败,会跳转至上传失败(uploadFileFail.jsp),在控制台会输出上传失败原因,便于大家分析出错原因。
2.客户端调用接口上传文件。
在Android设备上,选择一张图片,上传至后台。
(1). 选择图片,
/**
* 选择图片
*/
private void selectPic() {
Intent intent = new Intent();
if (Build.VERSION.SDK_INT < 19) {//因为Android SDK在4.4版本后图片action变化了 所以在这里先判断一下
intent.setAction(Intent.ACTION_GET_CONTENT);
} else {
intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
}
intent.setType("image/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, PICTURE);
}
回调处理,
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (data == null) {
return;
}
Uri uri = data.getData();
switch (requestCode) {
case PICTURE:
String imagePath = FileUtils.getUriPath(this, uri); //(因为4.4以后图片uri发生了变化)通过文件工具类 对uri进行解析得到图片路径
if (!TextUtils.isEmpty(imagePath)) {
uploadFile(imagePath);
}
break;
default:
break;
}
}
上传文件,
/**
* 文件上传
*
* @param filePath
*/
private void uploadFile(String filePath) {
File file = new File(filePath);
TypeToken<EntityResponse<Image>> typeToken = new TypeToken<EntityResponse<Image>>() {
};
OkHttpUtils.getInstance().postAsyn(Constant.UPLOAD_PIC_URL, null, file,"file",typeToken, new BaseResponseCallback<EntityResponse<Image>>() {
@Override
public void onCompleted(final Throwable e, EntityResponse<Image> result) {
if (e != null) {
tv.post(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
}
});
} else {
//获取接口返回的列表数据
Image image = result.getObject();
final StringBuffer sb = new StringBuffer();
sb.append("文件名称:" + image.getName() + ", 下载地址:" + image.getUrl() );
//更新UI,在子线程中,不能直接更新UI
tv.post(new Runnable() {
@Override
public void run() {
tv.setText(sb.toString());
}
});
}
}
});
}
客户端运行截图,首先选择图片,
选择完成后,上传成功返回json数据截图,
上传成功截图,
再看看服务端截图,控制台输入日志截图,
然后打开上传文件目录,
上传文件也OK了!
四、总结
本文主要是基于封装Okhttp请求的类调用后台服务接口,实现不同的功能!经测试,该类基本满足功能!本文对上篇文章封装的Okhttp请求类有所优化,更加详细的代码,请看本篇的例子工程源代码!本文需要有点Okhttp和SpringMVC方面的知识!
下篇文章将结合OKHttp的源码,详细解析OKHttp的整个流程,详情请看, Android OkHttp(三)源码解析。