想必大家都在android中或多或少的使用过XUtils框架了吧,今天我们通过他来实现一个照片上传的Demo,希望能够对大家有帮助,下一篇再从源码角度来分析下XUtils的HttpUtils是怎么一个执行流程的;
先上执行效果图:
客户端实现:
首先来看布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<LinearLayout
android:id="@+id/top"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:id="@+id/upload_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="上传图片" />
</LinearLayout>
<ImageView
android:id="@+id/imageView"
android:layout_below="@id/top"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
很简单吧,就是一个按钮和一个用于显示图片的ImageView;
接下来是MainActivity,直接看onCreate方法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
httpUtils = new HttpUtils(100000);
httpUtils.configCurrentHttpCacheExpiry(5000);
}
这个方法首先会调用initView来初始化界面,接着创建了一个HttpUtils对象,并且设置他的连接超时时间是100s,设置他的缓存有效时间是5s,来看看initView方法:
/**
* 初始化view控件
*/
public void initView()
{
uploadImageBt = (Button) findViewById(R.id.upload_image);
imageView = (ImageView)findViewById(R.id.imageView);
uploadImageBt.setOnClickListener(this);
progressDialog = getProgressDialog();//获得进度条
dialogListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case 0:
tempFile = new File(Environment.getExternalStorageDirectory(),getPhotoFileName());
//调用系统拍照
startCamera(dialog);
break;
case 1:
//打开系统图库
startWall(dialog);
break;
default:
break;
}
}
};
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.arg1 > 0)
progressDialog.setProgress(msg.arg1);//更新进度条
}
};
}
首先第9行获得一个ProgressDialog对象,第10行为选择对话框绑定点击监听事件,用来提示用户是通过拍照获得照片还是从图库获得,这个对象的定义如下:
/**
* 显示选择图片来源的dialog(来自拍照还是本地图库)
* @param title
* @param items
*/
public void showDialog(String title,String[] items)
{
AlertDialog.Builder dialog = new AlertDialog.Builder(this).setTitle(title).setItems(items, dialogListener);
//显示dialog
dialog.show();
}
如果选择拍照,则通过startCamera方法来调用系统照相机:
/**
* 调用相机来照相
* @param dialog
*/
public void startCamera(DialogInterface dialog)
{
dialog.dismiss();//首先隐藏选择照片来源的dialog
//调用系统的拍照功能
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra("camerasensortype", 2);//调用前置摄像头
intent.putExtra("autofocus", true);//进行自动对焦操作
intent.putExtra("fullScreen", false);//设置全屏
intent.putExtra("showActionIcons", false);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(tempFile));//指定调用相机之后所拍照存储到的位置
startActivityForResult(intent, PHOTO_CAMERA);
}
该方法首先会将选择对话框隐藏,接着开启系统摄像头并且进行相应的设置,并且指定保存照片的位置,最后在返回上一个Activity之前将照片结果封装在intent中返回,并且设置返回标志是PHOTO_CAMERA;
如果选择的是相册的话,则通过调用startWall方法来获得SD上面照片:
/**
* 打开系统图库
* @param dialog
*/
public void startWall(DialogInterface dialog)
{
dialog.dismiss();//设置隐藏dialog
Intent intent = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
startActivityForResult(intent, PHOTO_WALL);
}
该方法同样首先将选择对话框隐藏,接着调用系统服务显示出SD卡中所有存在的图片,并且通过intent返回图片信息,同时设置返回标志为PHOTO_WALL;
接下来我们看看不同的返回标志各自所执行的到底是什么内容呢?
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case PHOTO_CAMERA:
//表示从相机获得的照片,需要进行裁剪
startPhotoCut(Uri.fromFile(tempFile), 300,true);
break;
case PHOTO_WALL:
if(null != data)
startPhotoCut(data.getData(),300,false);
break;
case PHOTO_STORE:
if(null != data)
{
setPictureToImageView(data,true);
}
break;
case PHOTO_NOT_STORE:
if(null != data)
{
setPictureToImageView(data,false);
}
break;
default:
break;
}
}
该方法是在调用startActivityForResult之后由系统调用的,看到了我们刚刚见到的PHOTO_CAMREA以及PHOTO_WALL标志,首选来看看PHOTO_CAMREA,他会调用startPhotoCut对照片进行裁剪,来看看startPhotoCut方法:
/**
* 将图片裁剪到指定大小
* @param uri
* @param size
* @param flag
*/
public void startPhotoCut(Uri uri,int size,boolean flag)
{
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, "image/*");
intent.putExtra("crop", true);//设置Intent中的view是可以裁剪的
//设置宽高比
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
//设置裁剪图片的宽高
intent.putExtra("outputX", size);
intent.putExtra("outputY", size);
//设置是否返回数据
intent.putExtra("return-data", true);
if(flag == true)
startActivityForResult(intent, PHOTO_STORE);
else
{
tempIntent = intent;
try {
startActivityForResult(tempIntent, PHOTO_NOT_STORE);
System.out.println("haha");
} catch (Exception e) {
System.out.println(e.toString());
}
}
}
这个方法前面的几行都是对照片进行裁剪的一些设置,接着通过flag标志来判断是否将照片存储到SD卡上面,因为如果flag为false的话,表示这张照片是我们通过图库获取的,他来自于SD卡,因此没什么必要再存一次了,因而返回标志PHOTO_NOT_STORE,如果flag为true的话,才需要存储一遍,返回标志PHOTO_STORE,同样的调用的是startActivityForResult方法,那么他也会执行onActivityResult方法;
那么对于PHOTO_WALL标志,如果选择的图片不为空的话,则执行startPhotoCut方法,同样也进行裁剪;
对于PHOTO_NOT_STORE和PHOTO_STORE标志,他们都会执行setPictureToImageView方法,所以我们直接看他的代码就可以了:
/**
* 将图片显示到ImageView上面
* @param data
* @param flag 表示如果是拍照获得的照片的话则是true,如果是从系统选择的照片的话就是false
*/
public void setPictureToImageView(Intent data,boolean flag)
{
Bundle bundle = data.getExtras();
if(null != bundle)
{
Bitmap bitmap = bundle.getParcelable("data");
imageView.setImageBitmap(bitmap);//将图片显示到ImageView上面
//上传图片到服务器
if(flag == false)
{
//需要首先修改tempFile的值
String path = getSelectPhotoPath(tempIntent);
System.out.println("path: "+path);
tempFile = new File(path);
//uploadPicture();
//上传图片
UploadThread thread = new UploadThread();
thread.start();
}else
{
//uploadPicture();
//上传图片
UploadThread thread = new UploadThread();
thread.start();
}
if(flag == true)
savePictureToSD(bitmap);//保存图片到sd卡上面
}
}
这个方法做的事比较多,首先呢,他会从intent中获取到获取到图片并且显示到ImageView上面,接着会调用UploadThread线程将图片上传到服务器上面,最如果flag为true的话表示需要将图片存储到本地,那么我们需要调用savePictureToSD来存储图片,先来看看UploadThread线程:
class UploadThread extends Thread
{
@Override
public void run() {
uploadPicture();
}
}
很简单,就只有一个uploadPicture方法了,自然我们需要查看uploadPicture的代码:
/**
* 上传图片到数据库
*/
public void uploadPicture()
{
RequestParams params = new RequestParams();
params.addBodyParameter("msg",tempFile.getAbsolutePath());
params.addBodyParameter(tempFile.getPath().replace("/", ""), tempFile);
httpUtils.send(HttpMethod.POST, url,params,new RequestCallBack<String>() {
@Override
public void onStart() {
progressDialog.show();//显示进度条
}
@Override
public void onFailure(HttpException arg0, String arg1) {
System.out.println("上传失败");
System.out.println(arg0.toString());
//上传失败之后隐藏进度条
progressDialog.dismiss();
}
@Override
public void onLoading(long total, long current, boolean isUploading) {
System.out.println("current/total: "+current+"/"+total);
int process = 0;
if(total != 0)
{
process = (int)(current/(total/100));
}
Message message = new Message();
message.arg1 = process;
mHandler.sendMessage(message);
super.onLoading(total, current, isUploading);
}
@Override
public void onSuccess(ResponseInfo<String> arg0) {
System.out.println("上传成功");
//上传成功之后隐藏进度条
progressDialog.dismiss();
}
});
}
这部分就是用到XUtils的HttpUtils的部分啦,首先设置一些请求参数,接着调用HttpUtils的send方法进行请求,为了能够对上传结果更加直观,我们添加了进度条,onStart方法是上传执行开始调用的方法,我们在此显示出进度条,onLoading是上传过程中执行的方法,大约每一秒钟会执行一次,我们在此通过Handler的消息机制将进度封装成Message对象将其发送给Handler处理,具体的更新进度条的代码是在我们上面的initView方法出现的,因为他只能在主线程中更新,最后在电泳失败或者成功之后都要调用ProgressDialog的dismiss方法来隐藏进度条;
最后就只剩下保存图片到SD卡的操作了,这个比较简单,就只是简单的文件存储操作了,只不过路径是SD卡而已:
/**
* 将图片保存到SD卡上面
* @param bitmap
*/
public void savePictureToSD(Bitmap bitmap)
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
FileOutputStream fos = null;
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);//第2个参数表示压缩率,100表示不压缩
try {
fos = new FileOutputStream(tempFile);
fos.write(baos.toByteArray());
fos.flush();
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if(null != baos)
{
baos.close();
baos = null;
}
if(null != fos)
{
fos.close();
fos = null;
}
} catch (Exception e2) {
}
}
}
至此,客户端代码讲解完毕,接下来是服务器端代码:
服务器端:
代码比较少,直接copy出来了:
public class UploadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
response.setContentType("text/html,charset=utf-8");
SmartUpload smartUpload = new SmartUpload();
String msg= null;
try {
smartUpload.initialize(this.getServletConfig(), request, response);
smartUpload.upload();
msg = smartUpload.getRequest().getParameter("msg");
System.out.println(smartUpload.getFiles().getCount());
com.jspsmart.upload.File smartFile = smartUpload.getFiles().getFile(0);
if (!smartFile.isMissing()) {
String saveFileName = getServletContext().getRealPath("/")+"images\\" + smartFile.getFileName();
System.out.println(saveFileName);
smartFile.saveAs(saveFileName, SmartUpload.SAVE_PHYSICAL);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
最主要是doPost方法了,本实例采用的是SmartUpload的方式来实现文件上传操作的,当然你也可以采用别的方法,至于jar包等会源码下载链接的工程里面就有啦,第18行首先对SmartUpload进行初始化,接着调用upload方法准备上传,第22行获得客户端上传文件的第一个文件,从这里我们也可以看出来SmartUpload是支持多文件上传的,接着第23行判断这个文件是否存在,24行生成存放文件的路径,26行进行文件的存储操作,注意SmartUpload.SAVE_PHYSICAL的意思指的是绝对路径,因此你的文件存储路径必须是全路径,这样服务器端代码讲解结束,是不是很简单呀,提醒一下web.xml的配置,我的配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<servlet>
<servlet-name>UploadServlet</servlet-name>
<servlet-class>com.hzw.servlet.UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/upload</url-pattern>
</servlet-mapping>
</web-app>
基本上讲解结束啦,记得在客户端里面别忘记添加网络访问和SD卡访问权限哈:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
点击这里下载源码!!!!!