第三方开源库Okhttp——自己动手写表单提交和文件上传

 1.文件上传,post提交表单

   1.1表单提交需要用到 post,put方法,以方法体的方式组件表单信息。

             RequestBody requestBody = new RequestBody()
                .type(RequestBody.FORM)
                .addParam("file", RequestBody.create(file))
                .addParam("file2", RequestBody.create(file))
                .addParam("pageSize", 1 + "");

    1.2   然后在RequestBody 中将添加的参数添加到一个集合之中

   public RequestBody addParam(String key, String value) {
        params.put(key,value);
        return this;
    }

    public RequestBody addParam(String key, Bindry value) {
        params.put(key,value);
        return this;
    }

      1.3 确定文件传输的类型

public RequestBody type(String type) {
        this.type = type;
        return this;
    }

     1.4 创建文件,获取创建的文件的长度,类型,文件名,

//获取创建的文件的长度,类型,文件名
public static Bindry create(final File file) {

        return new Bindry() {
            @Override
            public long fileLength() {
                return file.length();
            }

            @Override
            public String mimType() {
                FileNameMap fileNameMap = URLConnection.getFileNameMap();

                String mimType = fileNameMap.getContentTypeFor(file.getAbsolutePath());

                if(TextUtils.isEmpty(mimType)){
                    return "application/octet-stream";
                }

                return mimType;
            }

            @Override
            public String fileName() {
                return file.getName();
            }

            @Override
            public void onWrite(OutputStream outputStream) throws IOException {
                InputStream inputStream = new FileInputStream(file);
                byte[] buffer = new byte[2048];
                int len = 0;
                while ((len=inputStream.read(buffer))!=-1){
                    outputStream.write(buffer,0,len);
                }
                inputStream.close();
            }
        };
    }

1.5将RequestBody 存到Request中,

                Request request = new Request.Builder()
                .url("https://api.saiwuquan.com/api/appv2/sceneModel")
                .post(requestBody).build();

1.6封装成一个请求之后返回给MainActivity(调用者)

        public Request build(){
            return new Request(this);
        }

1.7  通过 OkhttpCliet的newInstance(request) 这个方法,将将请求 封装成一个Call在返回给客户端(MainActivity)

Call call = client.newCall(request);

1.8 Call 的 newInstance(request) 方法实际上是调用RealCall的newInstance(request,this) ,RealCall是接口Call的实现类,所以返回值可以用以个Call接收,并同时将封装好的request和网络引擎OkhttpCliet客户端传递给RealCall。

    public Call newCall(Request request) {
        return RealCall.newCall(request,this);
    }

1.9 RealCall 内部对Call的封装

   public static Call newCall(Request request, OkHttpClient client) {
        return new RealCall(request,client);
    }


   public RealCall(Request request, OkHttpClient client) {
        orignalRequest = request;
        this.client = client;
    }

1.10 通过Call的enqueue这个方法去异步的执行网络请求

扫描二维码关注公众号,回复: 4986264 查看本文章
     call.enqueue(new Callback() {

            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.e("TAG", response.string());
                //Log.e("TAG",response.string());
            }
        });

 1.11但是因为Call是一个接口,所以当调用enqueue这个方法的时候,就会执行Call的实现类RealCall的enqueue方法

   @Override
    public void enqueue(Callback callback) {
        // 异步的
        AsyncCall asyncCall = new AsyncCall(callback);
        // 交给线程池
        client.dispatcher.enqueue(asyncCall);
    }

1.12  AsyncCall 是RealCall的一个内部类,它是NamedRunnable的子类,覆写NamedRunnable的抽象方法exectue , 而NamedRunable是接口Runnable的实现类,并在run方法内调用了exectue,最终会调到AsyncCall的exectue这个方法

public abstract class NamedRunnable implements Runnable{
    @Override
    public void run() {
        execute();
    }

    protected abstract void execute();
}
final class AsyncCall extends NamedRunnable {
        Callback callback;
        public AsyncCall(Callback callback) {
            this.callback = callback;
        }

        @Override
        protected void execute() {
            // 来这里,开始访问网络 Request -> Response
            Log.e("TAG", "execute");
            // Volley xUtils Afinal AsyHttpClient
            // 基于 HttpUrlConnection , OkHttp = Socket + okio(IO)
            final Request request = orignalRequest;
            try {
                URL url = new URL(request.url);

                HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();

                if (urlConnection instanceof HttpsURLConnection) {
                    HttpsURLConnection httpsURLConnection = (HttpsURLConnection) urlConnection;
                    // https 的一些操作
                    // httpsURLConnection.setHostnameVerifier();
                    // httpsURLConnection.setSSLSocketFactory();
                }
                // urlConnection.setReadTimeout();

                // 写东西
                urlConnection.setRequestMethod(request.method.name);
                urlConnection.setDoOutput(request.method.doOutput());

                RequestBody requestBody = request.requestBody;
                if (requestBody != null) {
                    // 头信息
                    //设置传输文件的格式
                    urlConnection.setRequestProperty("Content-Type", requestBody.getContentType());
                    //设置我们将传输多少长度的信息
                    urlConnection.setRequestProperty("Content-Length", Long.toString(requestBody.getContentLength()));
                }

                urlConnection.connect();

                // 写内容
                if (requestBody != null) {
                    requestBody.onWriteBody(urlConnection.getOutputStream());
                }

                int statusCode = urlConnection.getResponseCode();
                if (statusCode == 200) {
                    InputStream inputStream = urlConnection.getInputStream();
                    Response response = new Response(inputStream);
                    callback.onResponse(RealCall.this, response);
                }
                // 进行一些列操作,状态码 200
            }catch (IOException e){

                callback.onFailure(RealCall.this, e);
            }
        }
    }

1.13 至于Dispatcher这个方法 这个类在Okhttpcliet中被实例化,通过这行代码将线程AsyncCall这个任务添加到线程池当中去。

client.dispatcher.enqueue(asyncCall);

1.14用dispatcher 的 enqueue这个方法将AsyncCall这个任务添加到线程池中

public void enqueue(RealCall.AsyncCall call) {
        executorService().execute(call);
    }

1.15 当调用dispatcher 的 enqueue这个方法将AsyncCall这个任务添加到线程池中的时候,在这个方法内部会调用executorService这个方法,开启线程池,

public synchronized ExecutorService executorService() {
        if (executorService == null) {
            executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(), new ThreadFactory() {
                @Override
                public Thread newThread(@NonNull Runnable r) {
                    Thread thread = new Thread(r,"OkHttp");
                    thread.setDaemon(false);
                    return thread;
                }
            });
        }
        return executorService;
    }

1.16 然后线程池就回执行NamedRunnable的run方法,run方法会去执行AsyncCall的execute这个方法,在这个方法中开始真正的请求网络的操作,由于AsyncCall是RealCall的内部类,而RealCall又封装了request,request中封装了 url,requestBody,等这些东西。

final class AsyncCall extends NamedRunnable {
        Callback callback;
        public AsyncCall(Callback callback) {
            this.callback = callback;
        }

        @Override
        protected void execute() {
            // 来这里,开始访问网络 Request -> Response
            Log.e("TAG", "execute");
            // Volley xUtils Afinal AsyHttpClient
            // 基于 HttpUrlConnection , OkHttp = Socket + okio(IO)
            final Request request = orignalRequest;
            try {
                URL url = new URL(request.url);

                HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();

                if (urlConnection instanceof HttpsURLConnection) {
                    HttpsURLConnection httpsURLConnection = (HttpsURLConnection) urlConnection;
                    // https 的一些操作
                    // httpsURLConnection.setHostnameVerifier();
                    // httpsURLConnection.setSSLSocketFactory();
                }
                // urlConnection.setReadTimeout();

                // 写东西
                urlConnection.setRequestMethod(request.method.name);
                urlConnection.setDoOutput(request.method.doOutput());

                RequestBody requestBody = request.requestBody;
                if (requestBody != null) {
                    // 头信息
                    //设置传输文件的格式
                    urlConnection.setRequestProperty("Content-Type", requestBody.getContentType());
                    //设置我们将传输多少长度的信息
                    urlConnection.setRequestProperty("Content-Length", Long.toString(requestBody.getContentLength()));
                }

                urlConnection.connect();

                // 写内容
                if (requestBody != null) {
                    requestBody.onWriteBody(urlConnection.getOutputStream());
                }

                int statusCode = urlConnection.getResponseCode();
                if (statusCode == 200) {
                    InputStream inputStream = urlConnection.getInputStream();
                    Response response = new Response(inputStream);
                    callback.onResponse(RealCall.this, response);
                }
                // 进行一些列操作,状态码 200
            }catch (IOException e){

                callback.onFailure(RealCall.this, e);
            }
        }
    }

1.17 需要将请求头里面的东西写道HttpUrlConnection中,首先设置请求方式 urlConnection.setRequestMethod(request.method.name)再开启输出流urlConnection.setSoOutput(request.method.doOutput),判断requestBody不为空的话,就设置请求的请求参数(传输文本的类型和传输文本的长度),再次判断请求头是否为空调用请求头内部封装的方法onWriteBody并将HttpUrlConnettion的输出流传进去

 // 写东西
                urlConnection.setRequestMethod(request.method.name);
                urlConnection.setDoOutput(request.method.doOutput());

                RequestBody requestBody = request.requestBody;
                if (requestBody != null) {
                    // 头信息
                    //设置传输文件的格式
                    urlConnection.setRequestProperty("Content-Type", requestBody.getContentType());
                    //设置我们将传输多少长度的信息
                    urlConnection.setRequestProperty("Content-Length", Long.toString(requestBody.getContentLength()));
                }

                urlConnection.connect();

                // 写内容
                if (requestBody != null) {
                    requestBody.onWriteBody(urlConnection.getOutputStream());
                }

1.18 再onWriteBody这个方法遍历装有请求头参数的集合,并且按照一定的格式拼接成字符串,然后写到urlConnection的输出流里面,如果是Bindry这个类型的,就将文本输入,再将输出流传入到bindry的onWrite中将文件中的内容写入到输出流当中去。

 public void onWriteBody(OutputStream outputStream) throws IOException{
        for(Map.Entry<String,Object> entry:params.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if(value instanceof String){
                String postTextStr = getText(key,(String)value);
                outputStream.write(postTextStr.getBytes());
            }
            if(value instanceof Bindry){
                Bindry bindry = (Bindry) value;
                String postTextStr = getText(key,bindry);
                outputStream.write(postTextStr.getBytes());
                bindry.onWrite(outputStream);
                outputStream.write("\r\n".getBytes());
            }
        }

        if(params.size()!=0){
            outputStream.write(endBoundary.getBytes());
        }
    }
            @Override
            public void onWrite(OutputStream outputStream) throws IOException {
                InputStream inputStream = new FileInputStream(file);
                byte[] buffer = new byte[2048];
                int len = 0;
                while ((len=inputStream.read(buffer))!=-1){
                    outputStream.write(buffer,0,len);
                }
                inputStream.close();
            }

1.19 首先再addParam(key, bindry)这个方法中 通过RequestBody.create这个方法将文件的长度,类型,名称封装成bindry这个是实体类,存放在集合当中,所以当在onWriteBody这个方法当中取出bindry之后,就可以访问他的属性了。

public interface Bindry {
    long fileLength();

    String mimType();

    String fileName();

    void onWrite(OutputStream outputStream)throws IOException;
}

2.文件提交

 2.1 文件提交格式

            startBoundary+"\r\n"+
                "Content-Disposition: form-data; name = \""+key+"\" filename = \""+value.fileName()+"\""+
                "Context-Type: "+value.mimType()+"\r\n"+
                "\r\n";

猜你喜欢

转载自blog.csdn.net/lyfxh1314/article/details/86530813