Android利用HttpURLConnection实现文件上传

普通Java应用

浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用Servlet获取上传文件的输入流然后再解析里面的请求参数是比较麻烦,所以Java中Web端可以用的上传组件有两种:

  • FileUpload【操作比较复杂】struts上传的功能就是基于这个实现的。


		try{
		
			//1.得到解析器工厂
			DiskFileItemFactory factory = new DiskFileItemFactory();
			
			//2.得到解析器
			ServletFileUpload upload = new ServletFileUpload(factory);
			
			//3.判断上传表单的类型
			if(!upload.isMultipartContent(request)){
				//上传表单为普通表单,则按照传统方式获取数据即可
				return;
			}
			
			//为上传表单,则调用解析器解析上传数据
			List<FileItem> list = upload.parseRequest(request);  //FileItem
			
			//遍历list,得到用于封装第一个上传输入项数据fileItem对象
			for(FileItem item : list){
				
				if(item.isFormField()){
					//得到的是普通输入项
					String name = item.getFieldName();  //得到输入项的名称
					String value = item.getString();
					System.out.println(name + "=" + value);
				}else{
					//得到上传输入项
					String filename = item.getName();  //得到上传文件名  C:\Documents and Settings\ThinkPad\桌面\1.txt
					filename = filename.substring(filename.lastIndexOf("\\")+1);
					InputStream in = item.getInputStream();   //得到上传数据
					int len = 0;
					byte buffer[]= new byte[1024];
					
					
					String savepath = this.getServletContext().getRealPath("/upload");
					FileOutputStream out = new FileOutputStream(savepath + "\\" + filename);  //向upload目录中写入文件
					while((len=in.read(buffer))>0){
						out.write(buffer, 0, len);
					}
					
					in.close();
					out.close();
				}
			}
		
		}catch (Exception e) {
			e.printStackTrace();
		}

 

  • SamrtUpload【操作比较简单】


        //实例化组件
        SmartUpload smartUpload = new SmartUpload();

        //初始化上传操作
        smartUpload.initialize(this.getServletConfig(), request, response);


        try {

            //上传准备
            smartUpload.upload();

            //对于普通数据,单纯到request对象是无法获取得到提交参数的。也是需要依赖smartUpload
            String password = smartUpload.getRequest().getParameter("password");
            System.out.println(password);

            //上传到uploadFile文件夹中
            smartUpload.save("uploadFile");


        } catch (SmartUploadException e) {
            e.printStackTrace();
        }

此时浏览器端只需要利用file组件和form就可以提交文件。

而如果不是webapp,而是Http客户端,可以利用Httpclient,其API操作也比较简单。

Android应用

Android 6中移除了HttpClient组件,推荐使用HttpURLConnection API, 需要手动按照格式去实现文件上传:上传文件数据是经过MIME协议进行分割的,表单进行了二进制封装。也就是说:getParameter()无法获取得到上传文件的数据。

下述代码的BOUNDARY可以是随机字符串。

private static final String BOUNDARY = "----WebKitFormBoundaryT1HoybnYeFOGFlBR";
 
	/**
	 * 
	 * @param params
	 *            传递的普通参数
	 * @param uploadFile
	 *            需要上传的文件名
	 * @param fileFormName
	 *            需要上传文件表单中的名字
	 * @param newFileName
	 *            上传的文件名称,不填写将为uploadFile的名称
	 * @param urlStr
	 *            上传的服务器的路径
	 * @throws IOException
	 */
	public void uploadForm(Map<String, String> params, String fileFormName,
			File uploadFile, String newFileName, String urlStr)
			throws IOException {
		if (newFileName == null || newFileName.trim().equals("")) {
			newFileName = uploadFile.getName();
		}
 
		StringBuilder sb = new StringBuilder();
		/**
		 * 普通的表单数据
		 */
		for (String key : params.keySet()) {
			sb.append("--" + BOUNDARY + "\r\n");
			sb.append("Content-Disposition: form-data; name=\"" + key + "\""
					+ "\r\n");
			sb.append("\r\n");
			sb.append(params.get(key) + "\r\n");
		}
		/**
		 * 上传文件的头
		 */
		sb.append("--" + BOUNDARY + "\r\n");
		sb.append("Content-Disposition: form-data; name=\"" + fileFormName
				+ "\"; filename=\"" + newFileName + "\"" + "\r\n");
		sb.append("Content-Type: image/jpeg" + "\r\n");// 如果服务器端有文件类型的校验,必须明确指定ContentType
		sb.append("\r\n");
 
		byte[] headerInfo = sb.toString().getBytes("UTF-8");
		byte[] endInfo = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("UTF-8");
		System.out.println(sb.toString());
		URL url = new URL(urlStr);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setRequestMethod("POST");
		conn.setRequestProperty("Content-Type",
				"multipart/form-data; boundary=" + BOUNDARY);
		conn.setRequestProperty("Content-Length", String
				.valueOf(headerInfo.length + uploadFile.length()
						+ endInfo.length));
		conn.setDoOutput(true);
 
		OutputStream out = conn.getOutputStream();
		InputStream in = new FileInputStream(uploadFile);
		out.write(headerInfo);
 
		byte[] buf = new byte[1024];
		int len;
		while ((len = in.read(buf)) != -1)
			out.write(buf, 0, len);
 
		out.write(endInfo);
		in.close();
		out.close();
		if (conn.getResponseCode() == 200) {
			System.out.println("上传成功");
		}
 
	}

基本原理:

使用HttpUrlConnection上传有一个很致命的问题就是,当上传文件很大时,会发生内存溢出,手机分配给我们app的内存更小,所以就更需要解决这个问题,于是我们可以使用Socket模拟POST进行HTTP文件上传。 

	/**
	 * 
	 * @param params
	 *            传递的普通参数
	 * @param uploadFile
	 *            需要上传的文件名
	 * @param fileFormName
	 *            需要上传文件表单中的名字
	 * @param newFileName
	 *            上传的文件名称,不填写将为uploadFile的名称
	 * @param urlStr
	 *            上传的服务器的路径
	 * @throws IOException
	 */
	public void uploadFromBySocket(Map<String, String> params,
			String fileFormName, File uploadFile, String newFileName,
			String urlStr) throws IOException {
		if (newFileName == null || newFileName.trim().equals("")) {
			newFileName = uploadFile.getName();
		}
 
		StringBuilder sb = new StringBuilder();
		/**
		 * 普通的表单数据
		 */
 
		if (params != null)
			for (String key : params.keySet()) {
				sb.append("--" + BOUNDARY + "\r\n");
				sb.append("Content-Disposition: form-data; name=\"" + key
						+ "\"" + "\r\n");
				sb.append("\r\n");
				sb.append(params.get(key) + "\r\n");
			}                                                                                                                                                  else{ab.append("\r\n");}
		/**
		 * 上传文件的头
		 */
		sb.append("--" + BOUNDARY + "\r\n");
		sb.append("Content-Disposition: form-data; name=\"" + fileFormName
				+ "\"; filename=\"" + newFileName + "\"" + "\r\n");
		sb.append("Content-Type: image/jpeg" + "\r\n");// 如果服务器端有文件类型的校验,必须明确指定ContentType
		sb.append("\r\n");
 
		byte[] headerInfo = sb.toString().getBytes("UTF-8");
		byte[] endInfo = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("UTF-8");
 
		System.out.println(sb.toString());
 
		URL url = new URL(urlStr);
		Socket socket = new Socket(url.getHost(), url.getPort());
		OutputStream os = socket.getOutputStream();
		PrintStream ps = new PrintStream(os, true, "UTF-8");
 
		// 写出请求头
		ps.println("POST " + urlStr + " HTTP/1.1");
		ps.println("Content-Type: multipart/form-data; boundary=" + BOUNDARY);
		ps.println("Content-Length: "
				+ String.valueOf(headerInfo.length + uploadFile.length()
						+ endInfo.length));
		ps.println("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
 
		InputStream in = new FileInputStream(uploadFile);
		// 写出数据
		os.write(headerInfo);
 
		byte[] buf = new byte[1024];
		int len;
		while ((len = in.read(buf)) != -1)
			os.write(buf, 0, len);
 
		os.write(endInfo);
 
		in.close();
		os.close();
	}

如果觉得以上API过于复杂,可以使用okhttp,甚至是封装好的第三方框架: volleyRetrofit。 retrofit依赖于okhttp。

//通过“addFormDataPart”可以添加多个上传的文件。
public  class OkHttpCallBackWrap {
    public void post(String url) throws IOException{
                File file = new File("D:/app/dgm/3.mp4");
                RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
                RequestBody requestBody = new MultipartBody.Builder()
                        .setType(MultipartBody.FORM) 
                        .addFormDataPart("application/octet-stream", "1.mp4", fileBody)
                        .build();
                Request request = new Request.Builder()
                        .url(url)
                        .post(requestBody)
                        .build();

                final okhttp3.OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder();
                OkHttpClient okHttpClient  = httpBuilder
                        //设置超时
                        .connectTimeout(100, TimeUnit.SECONDS)
                        .writeTimeout(150, TimeUnit.SECONDS)
                        .build();
                okHttpClient.newCall(request).enqueue(new Callback() {

                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        System.out.println(response.body().string());
                    }

                    @Override
                    public void onFailure(Call arg0, IOException e) {
                        // TODO Auto-generated method stub
                        System.out.println(e.toString());

                    }

                });
            }
        }

文件上传的注意点

  (1)、为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,比如放于WEB-INF目录下。

  (2)、为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名。

  (3)、为防止一个目录下面出现太多文件,要使用hash算法打散存储。

  (4)、要限制上传文件的最大值。

  (5)、要限制上传文件的类型,在收到上传文件名时,判断后缀名是否合法。

       (6)、大文件的上传也可以实现为断点上传,需要服务端配合实现,和断点下载实现类似,也采用RandomAccessFile来做文件移动。

文件上传的方式:1.multipart/from-data方式上传。2.binary方式上传。multipart上传方式html的上传方式代码这中上传方式是我们最常用的上传方式。比如我们使用网页上传文件,其中html代码大致为这样:<formmethod="post"enctype="multipart/form-data"action="/upload/single">。本文讲的也是这种方式。

multipart/form-data方式的一个重要组成部分请求头Content-Type必须为: Content-Type:multipart/form-data;boundarty=一个32字节的随机数,用来分割每个part。 然后每个Part之间用“双横杠”加bundary来分割,最后一个Part分割符末尾也要加“双横杠”。

每个Part中必须包含Content-Disposition字段来注明字段文件名等信息,也可以包含Content-Type来说明文件的MeidaType。

文件下载API

最后提供一个文件下载的Servlet实现:直接设置响应response的响应头和输出流即可。Content-Disposition (以附件形式传输)

package me.gacl.web.controller;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DownLoadServlet extends HttpServlet {

    
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //得到要下载的文件名
        String fileName = request.getParameter("filename");  //23239283-92489-阿凡达.avi
        fileName = new String(fileName.getBytes("iso8859-1"),"UTF-8");
        //上传的文件都是保存在/WEB-INF/upload目录下的子目录当中
        String fileSaveRootPath=this.getServletContext().getRealPath("/WEB-INF/upload");
        //通过文件名找出文件的所在目录
        String path = findFileSavePathByFileName(fileName,fileSaveRootPath);
        //得到要下载的文件
        File file = new File(path + "\\" + fileName);
        //如果文件不存在
        if(!file.exists()){
            request.setAttribute("message", "您要下载的资源已被删除!!");
            request.getRequestDispatcher("/message.jsp").forward(request, response);
            return;
        }
        //处理文件名
        String realname = fileName.substring(fileName.indexOf("_")+1);
        //设置响应头,控制浏览器下载该文件
        response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(realname, "UTF-8"));
        //读取要下载的文件,保存到文件输入流
        FileInputStream in = new FileInputStream(path + "\\" + fileName);
        //创建输出流
        OutputStream out = response.getOutputStream();
        //创建缓冲区
        byte buffer[] = new byte[1024];
        int len = 0;
        //循环将输入流中的内容读取到缓冲区当中
        while((len=in.read(buffer))>0){
            //输出缓冲区的内容到浏览器,实现文件下载
            out.write(buffer, 0, len);
        }
        //关闭文件输入流
        in.close();
        //关闭输出流
        out.close();
    }
    
    /**
    * @Method: findFileSavePathByFileName
    * @Description: 通过文件名和存储上传文件根目录找出要下载的文件的所在路径
    * @Anthor:孤傲苍狼
    * @param filename 要下载的文件名
    * @param saveRootPath 上传文件保存的根目录,也就是/WEB-INF/upload目录
    * @return 要下载的文件的存储目录
    */ 
    public String findFileSavePathByFileName(String filename,String saveRootPath){
        int hashcode = filename.hashCode();
        int dir1 = hashcode&0xf;  //0--15
        int dir2 = (hashcode&0xf0)>>4;  //0-15
        String dir = saveRootPath + "\\" + dir1 + "\\" + dir2;  //upload\2\3  upload\3\5
        File file = new File(dir);
        if(!file.exists()){
            //创建目录
            file.mkdirs();
        }
        return dir;
    }
    
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个使用HttpURLConnectionAndroid Studio中实现注册功能的示例代码: ```java public class RegisterActivity extends AppCompatActivity { private EditText mUsernameEditText; private EditText mPasswordEditText; private EditText mEmailEditText; private Button mRegisterButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_register); mUsernameEditText = findViewById(R.id.edit_text_username); mPasswordEditText = findViewById(R.id.edit_text_password); mEmailEditText = findViewById(R.id.edit_text_email); mRegisterButton = findViewById(R.id.button_register); mRegisterButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String username = mUsernameEditText.getText().toString(); String password = mPasswordEditText.getText().toString(); String email = mEmailEditText.getText().toString(); // 创建一个新的线程来执行注册请求 new RegisterTask().execute(username, password, email); } }); } private class RegisterTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... params) { String username = params[0]; String password = params[1]; String email = params[2]; // 创建一个URL对象 URL url; try { url = new URL("http://your-domain.com/register.php"); } catch (MalformedURLException e) { e.printStackTrace(); return "URL错误"; } // 创建一个HttpURLConnection对象 HttpURLConnection connection; try { connection = (HttpURLConnection) url.openConnection(); } catch (IOException e) { e.printStackTrace(); return "连接错误"; } // 设置请求参数 connection.setDoOutput(true); connection.setRequestProperty("Content-Type", "application/json"); // 创建一个JSON对象来存储请求数据 JSONObject data = new JSONObject(); try { data.put("username", username); data.put("password", password); data.put("email", email); } catch (JSONException e) { e.printStackTrace(); return "JSON错误"; } // 将JSON对象转换为字符串并写入输出流 OutputStream outputStream; try { outputStream = connection.getOutputStream(); outputStream.write(data.toString().getBytes()); outputStream.flush(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); return "输出错误"; } // 获取服务器响应 StringBuilder response = new StringBuilder(); InputStream inputStream; try { inputStream = connection.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = reader.readLine()) != null) { response.append(line); } reader.close(); } catch (IOException e) { e.printStackTrace(); return "输入错误"; } return response.toString(); } @Override protected void onPostExecute(String result) { Toast.makeText(RegisterActivity.this, result, Toast.LENGTH_SHORT).show(); } } } ``` 在上面的代码中,我们使用了AsyncTask来在新的线程中执行HTTP请求。在doInBackground()方法中,我们首先创建了一个URL对象,并使用它来创建一个HttpURLConnection对象。然后,我们设置了请求参数,包括设置请求方法为POST、设置请求头部为JSON格式等。接着,我们创建了一个JSON对象来存储请求数据,将其转换为字符串并写入输出流,最后读取服务器响应并返回。在onPostExecute()方法中,我们展示了一个Toast来显示请求结果。 当然,在实际应用中,我们需要根据具体需求来修改代码。例如,我们可能需要对请求数据进行加密,或者使用HTTPS来保证安全性等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值