• 【Android】图片(文件)上传的请求分析结构


      怎么在android中上传文件,即怎么用Java向服务器上传文件、上传图片,这是个老问题了,在网上能搜到现成的代码,很多朋友用起来也比较熟了,但是为什么这么写,可能很多朋友并不清楚,这篇文章就来分析一下Web中文件上传的请求包内容。如有问题请联系zhfch.class@gmail.com。

      当然说,这篇文章被我冠了一个“Android”的前缀,因为我们在平常的非移动开发中,很少需要用最原始的方法来构造请求内容,但实际上,这种上传文件的方法和android是没有必然关系的,做一个C/S模式的客户端文件上传工具,也可以用这样的方法。这篇文章分为Web应用中文件上传的请求结构和用Java(在android中)上传文件/图片的方法这两部分。如果只想看怎么上传文件,推荐直接看第三部分,用开源项目提供的类库上传。

    1. Web中文件上传的请求包结构分析

    2. Java(Android)中的文件/图片上传代码

    3. 开源库推荐

    1. Web中文件上传的请求包结构分析  

      首先写了简单的Web工程,index.html中写一个表单,主要的两个表单项,一个是文本框,输入一个用户名,另一个是一个文件上传的控件,表单提交的url随便写,运行这个工程并打开这个页面:

    <form action="index.jsp" method="post">
        <input type="text" name="username" />
        <input type="file" name="mfile" />
        <input type="submit" />
    </form>

      另外我写了一个简单的Web代理服务器,监听8033端口,把请求数据拦截下来并全部打印出来(这是为了看请求包的完整结构,图省事的话可以参考浏览器开发人员工具中的数据包分析),然后修改浏览器中代理服务器的设置,改成localhost的8033端口,此时在文本框中输入username为tjutester,然后选择一个桌面上的本地文件test.txt,里面只有一行字:“这里是测试文字”,点击提交,会看到代理服务器打印出的POST请求内容:

    POST http://localhost:8080/ServerDemo/index.jsp HTTP/1.1
    Host: localhost:8080
    ......(一堆其他属性,这里略过,下文还会贴上) Cookie: JSESSIONID=4FA349F284EEE82AD5E15D571A0E9869 username=tjutester
    &mfile=test.txt

      这是这一次POST请求的内容,发现并没有文件的内容,当然在服务器上也是拿不到的了,因为form表单没有设置enctype属性值,浏览器将使用其默认值"application/x-www-form-urlencoded",根据结果可以看到,对于这种编码方式,所有字段按URL参数的形式排成一行,在服务端可以直接用getParameter方法来处理,这个时候mfile域跟username域在性质上没有什么区别,都是简单的文本,现在在<form>标签内添加enctype属性:

    <form action="getnews" method="post" enctype="multipart/form-data">

      刷新网页后重新发送请求,可以得到下面的头部信息:

    POST http://localhost:8080/ServerDemo/index.jsp HTTP/1.1
    Host: localhost:8080
    Proxy-Connection: keep-alive
    Content-Length: 311
    Cache-Control: max-age=0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Origin: http://localhost:8080
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundary22uii9i7wXAwzBvK
    Referer: http://localhost:8080/ServerDemo/
    Accept-Encoding: gzip,deflate,sdch
    Accept-Language: zh-CN,zh;q=0.8
    Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3
    Cookie: JSESSIONID=4FA349F284EEE82AD5E15D571A0E9869
    
    ------WebKitFormBoundary22uii9i7wXAwzBvK
    Content-Disposition: form-data; name="username"
    
    tjutester
    ------WebKitFormBoundary22uii9i7wXAwzBvK
    Content-Disposition: form-data; name="mfile"; filename="test.txt"
    Content-Type: text/plain
    
    这里是测试文字
    ------WebKitFormBoundary22uii9i7wXAwzBvK--

      抽取其精华,也就是如下的结构:

    Content-Type: multipart/form-data; boundary=----分隔符字符串
    
    ------分隔符字符串
    Content-Disposition: form-data; name="字段名"
    
    属性值
    ------分隔符字符串
    Content-Disposition: form-data; name="字段名"; filename="本地的文件名"
    Content-Type: 文件类型
    
    文件内容(在网络上传输就是二进制流了)
    ------分隔符字符串--

      服务器会在这样的请求中拿到文件内容,那么我们就在Java程序中人工构造这样的请求内容交给服务器,就完成了文件或图片的上传。那么,这个分隔符字符串和“-”有什么讲究呢,分隔符字符串是没什么讲究的,前后保持一致就可以了,Chrome生成的请求,都是WebKitFormBoundaryxxxxxxx这样的形式,用IE的话就是一个普通的数字字母串,如7dd2029110746,每个上传的参数前面有一个“--boundary”,最后是一个“--boundary--”表示终止。

    2. Java(Android)中的文件/图片上传代码

       这一部分的代码具体含义我就不多做说明了,就是在拼上面的请求串,核心类的内容:

    package org.fletcher.android.net;
    
    import java.io.ByteArrayOutputStream;
    import java.io.DataOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.UnsupportedEncodingException;
    import java.net.HttpURLConnection;
    import java.net.MalformedURLException;
    import java.net.ProtocolException;
    import java.net.URL;
    import java.net.URLEncoder;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    import org.apache.http.HttpResponse;
    import org.apache.http.NameValuePair;
    import org.apache.http.client.ClientProtocolException;
    import org.apache.http.client.HttpClient;
    import org.apache.http.client.entity.UrlEncodedFormEntity;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.message.BasicNameValuePair;
    
    public class HttpRequester {
        private static final String BOUNDARY = "-------45962402127348";
        private static final String FILE_ENCTYPE = "multipart/form-data";
    
        public static InputStream post(String urlStr, Map<String, String> params,
                Map<String, File> images) {
            InputStream is = null;
            
            try {
                URL url = new URL(urlStr);
                HttpURLConnection con = (HttpURLConnection) url.openConnection();
    
                con.setConnectTimeout(5000);
                con.setDoInput(true);
                con.setDoOutput(true);
                con.setUseCaches(false);
                con.setRequestMethod("POST");
                con.setRequestProperty("Connection", "Keep-Alive");
                con.setRequestProperty("Charset", "UTF-8");
                con.setRequestProperty("Content-Type", FILE_ENCTYPE + "; boundary="
                        + BOUNDARY);
                
                StringBuilder sb = null;
                DataOutputStream dos = new DataOutputStream(con.getOutputStream());;
                if (params != null) {
                    sb = new StringBuilder();
                    for (String s : params.keySet()) {
                        sb.append("--");
                        sb.append(BOUNDARY);
                        sb.append("
    ");
                        sb.append("Content-Disposition: form-data; name="");
                        sb.append(s);
                        sb.append(""
    
    ");
                        sb.append(params.get(s));
                        sb.append("
    ");
                    }
        
                    dos.write(sb.toString().getBytes());
                }
    
                if (images != null) {
                    for (String s : images.keySet()) {
                        File f = images.get(s);
                        sb = new StringBuilder();
                        sb.append("--");
                        sb.append(BOUNDARY);
                        sb.append("
    ");
                        sb.append("Content-Disposition: form-data; name="");
                        sb.append(s);
                        sb.append(""; filename="");
                        sb.append(f.getName());
                        sb.append(""
    ");
                        sb.append("Content-Type: image/jpeg");  //这里注意!如果上传的不是图片,要在这里改文件格式,比如txt文件,这里应该是text/plain
                        sb.append("
    
    ");
                        dos.write(sb.toString().getBytes());
        
                        FileInputStream fis = new FileInputStream(f);
                        byte[] buffer = new byte[1024];
                        int len;
                        while ((len = fis.read(buffer)) != -1) {
                            dos.write(buffer, 0, len);
                        }
                        dos.write("
    ".getBytes());
                        fis.close();
                    }
        
                    sb = new StringBuilder();
                    sb.append("--");
                    sb.append(BOUNDARY);
                    sb.append("--
    ");
                    dos.write(sb.toString().getBytes());
                }
                dos.flush();
    
                if (con.getResponseCode() == 200)
                    is = con.getInputStream();
                
                dos.close();
    //            con.disconnect();
            } catch (MalformedURLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ProtocolException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return is;
        }
        
        public static byte[] read(InputStream inStream) throws Exception{
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = 0;
            while( (len = inStream.read(buffer)) != -1){
                outputStream.write(buffer, 0, len);
            }
            inStream.close();
            return outputStream.toByteArray();
        }
    }

      在Android界面当中,点击按钮选择图片,然后调用这个类上传,在Android中怎么选择图片,直接copy了这位仁兄的代码(http://www.oschina.net/question/157182_53236):

    private static final int RESULT_LOAD_IMAGE = 0;
    TextView tv;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button b = (Button) findViewById(R.id.button1);
        tv = (TextView) findViewById(R.id.tv1);
        b.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Intent i = new Intent(
                Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                startActivityForResult(i, RESULT_LOAD_IMAGE);
            }
        });
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && null != data) {
            Uri selectedImage = data.getData();
            String[] filePathColumn = { MediaStore.Images.Media.DATA };
            Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);
            cursor.moveToFirst();
            int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
            final String picturePath = cursor.getString(columnIndex);
            cursor.close();
            
            new AsyncTask<Void, Void, String>() {
                @Override
                protected String doInBackground(Void... params) {
                    // TODO Auto-generated method stub
                    Map<String, File> maps = new HashMap<String, File>();
                    maps.put("image", new File(picturePath));
                    InputStream is = HttpRequester.post("http://192.168.199.2/Test/Upload/upload", null, maps);
                    try {
                        return new String(HttpRequester.read(is));
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    return "";
                }
                @Override
                protected void onPostExecute(String result) {
                    // TODO Auto-generated method stub
                    super.onPostExecute(result);
                    tv.setText(result);
                }
            }.execute((Void)null);
        }
    }

    3. 推荐开源项目:android-async-http

      实际开发当中,在理解原理之后,当然还是应该站在巨人的肩膀上写程序了,我做URL请求,都是用的android-async-http这个开源库:

      http://loopj.com/android-async-http/

      用起来很简单,网站上也有一些建议的用法和API,上传文件就简单地用

    RequestParams params = new RequestParams();
    params.put("image", new File(path));

      来指定参数就可以了。

      

  • 相关阅读:
    Linux下安装jdk
    hadoop下载
    Java:xxx is not an enclosing class
    Android:Gradle报错——No resource found that matches the given name (at 'dialogCornerRadius' with value '?android:attr/dialogCornerRadius')
    TensorFlow:在PyCharm中配置TensorFlow
    Android:屏幕旋转
    Android:onActivityResult详解
    Android:Bundle类
    Android:ConstraintLayout完全解析
    Android:Android Studio生成签名文件,自动签名,以及获取SHA1和MD5值
  • 原文地址:https://www.cnblogs.com/smarterplanet/p/3471426.html
Copyright © 2020-2023  润新知