volley源码分析之自定义multipartrequest(文件上传)

新根 新根     2022-08-13     246

关键词:

本篇内容目录:

  • 使用HttpURLConnection上传文件到服务器案例

  • 自定义支持文件上传的MultiPartRequest

  • Web后台接收文件的部分代码

先来看下HttpURLConnection来文件上传的案例:

1.传送数据到服务器,必定是使用POST请求:

    //设置请求方式为post
    httpURLConnection.setDoOutput(true);
    httpURLConnection.setRequestMethod("POST");

2.上传文件的HTTP请求中的Content-Type:

在HTML中文件上传:

<form method="POST" enctype="multipart/form-data" action="fup.cgi">
  File to upload: <input type="file" name="upfile"><br/>
  Notes about the file: <input type="text" name="note"><br/>
  <br/>
  <input type="submit" value="Press"> to upload the file!
</form>

从上面可知,Content-Type的格式为multipart/form-data。无论是网页还是android app都是客户端,使用HttpURLConnection上传文件都是一致的。故,这里的Content-Type应该设置为:

multipart/form-data

3.来了解下multipart/form-data格式的数据:

这里,案例:一个名为file1的text文件和一个名为file2的gif文件,同时带有一段字符串(”Joe Blow”)共同在HTML中上传 。而在Http请求中显示的数据:

      Content-type: multipart/form-data, boundary=AaB03x 

        //普通数据
        --AaB03x
        content-disposition: form-data; name="field1"
        Joe Blow 

       //多个文件数据的格式
        --AaB03x
        content-disposition: form-data; name="pics"
        Content-type: multipart/mixed, boundary=BbC04y

        //file1.text的数据
        --BbC04y
        Content-disposition: attachment; filename="file1.txt"
        Content-Type: text/plain
        ... file1.txt 的内容...   

        //file2.gif的数据结构
        --BbC04y 
        Content-disposition: attachment; filename="file2.gif"
        Content-type: image/gif
        Content-Transfer-Encoding: binary
        ... file2.gif的内容... 

        --BbC04y-- 

        --AaB03x--    

从以上资料可知:multipart/form-data由多个部分组成,每一部分都有一个content-disposition标题头,它的值是”form-data”,它的属性指明了其在表单内的字段名。

举例来说,’content-disposition: form-data; name=”xxxxx”’,这里的xxxxx就是对应于该字段的字段名。

对所有的多部分MIME类型来说,每一部分有一个可选的Content-Type,默认的值是text/plain。如果知道是什么类型的话,就定义为相应的媒体类型。否则的话,就标识为application/octet-stream。

文件名可以由标题头”content-disposition”中的filename参数所指定。

总之,multipart/form-data的媒体内容遵从RFC 1521所规定的多部分的数据流规则。

以上关于multipart/form-data的资料来源于RFC文档目录, HTML中基于表单的文件上传

4.设置multipart/form-data格式的数据:

    private static final String BOUNDARY = "----------" + System.currentTimeMillis();
    /**
     * 请求的内容类型
     */
    private static final String PROTOCOL_CONTENT_TYPE = "multipart/form-data; boundary=" + BOUNDARY; 

    /**
     * 多个文件间的间隔
     */
    private static final String FILEINTERVAL = "\r\n";  

    /**
     * 获取到文件的head
     *
     * @return
     */
    public byte[] getFileHead(String fileName) {
        try {
            StringBuffer buffer = new StringBuffer();
            buffer.append("--");
            buffer.append(BOUNDARY);
            buffer.append("\r\n");
            buffer.append("Content-Disposition: form-data;name=\"media\";filename=\"");
            buffer.append(fileName);
            buffer.append("\"\r\n");
            buffer.append("Content-Type:application/octet-stream\r\n\r\n");
            String s = buffer.toString();
            return s.getBytes("utf-8");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取文件的foot
     *
     * @return
     */
    public byte[] getFileFoot() {
        try {
            StringBuffer buffer = new StringBuffer();
            buffer.append("\r\n--");
            buffer.append(BOUNDARY);
            buffer.append("--\r\n");
            String s = buffer.toString();
            return s.getBytes("utf-8");
        } catch (Exception e) {
            return null;
        }
    }

5.支持文件上传的HttpURLConnection的完整代码如下:

/**
 * Created by ${新根} on 2016/11/6.
 * 博客:http://blog.csdn.net/hexingen
 * <p/>
 * 用途:
 * 使用httpUrlConnection上传文件到服务器
*/
public class HttpUrlConnectionOpts {

    /**
     * 字符编码格式
     */
    private static final String PROTOCOL_CHARSET = "utf-8";
    private static final String BOUNDARY = "----------" + System.currentTimeMillis();
    /**
     * 请求的内容类型
     */
    private static final String PROTOCOL_CONTENT_TYPE = "multipart/form-data; boundary=" + BOUNDARY;

    /**
     * 多个文件间的间隔
     */
    private static final String FILEINTERVAL = "\r\n";

    public HttpUrlConnectionOpts() {

    }
    public void fileUpLoad(String url, Map<String, File> files) {

            HttpURLConnection connection = createMultiPartConnection(url);
            addIfParameter(connection, files);
            String responeContent = getResponeFromService(connection);
    }
    /**
     * 获取从服务器相应的数据
     *
     * @param connection
     * @return
     */
    public String getResponeFromService(HttpURLConnection connection) {
        String responeContent = null;
        BufferedReader bufferedReader = null;
        try {
            if (connection != null) {
                connection.connect();
                int responeCode = connection.getResponseCode();
                if (responeCode == 200) {
                    bufferedReader = new BufferedReader( 
                           new InputStreamReader(connection.getInputStream()));
                    String line;
                    StringBuffer stringBuffer = new StringBuffer();
                    while ((line = bufferedReader.readLine()) != null) {
                        stringBuffer.append(line);
                    }
                    responeContent = stringBuffer.toString();
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
            responeContent = null;
        } finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
                if (connection != null) {
                    connection.disconnect();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        return responeContent;
    }

    /**
     * 若是文件列表不为空,则将文件列表上传。
     *
     * @param connection
     * @param files
     */
    public void addIfParameter(HttpURLConnection connection, Map<String, File> files) {
        if (files != null && connection != null) {
            DataOutputStream dataOutputStream = null;
            try {
                dataOutputStream = new DataOutputStream(connection.getOutputStream());
                int i = 1;
                Set<Map.Entry<String, File>> set = files.entrySet();
                for (Map.Entry<String, File> fileEntry : set) {
                    byte[] contentHeader = getFileHead(fileEntry.getKey());
                    //添加文件的头部格式
                    dataOutputStream.write(contentHeader, 0, contentHeader.length);
                    //添加文件数据
                    readFileData(fileEntry.getValue(), dataOutputStream);
                    //添加文件间的间隔,若是一个文件则不用添加间隔。若是多个文件时,最后一个文件不用添加间隔。
                    if (set.size() > 1 && i < set.size()) {
                        i++;
                        dataOutputStream.write(FILEINTERVAL.getBytes(PROTOCOL_CHARSET));
                    }
                }
                //写入文件的尾部格式
                byte[] contentFoot = getFileFoot();
                dataOutputStream.write(contentFoot, 0, contentFoot.length);
                //刷新数据到流中
                dataOutputStream.flush();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (dataOutputStream != null) {
                        dataOutputStream.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 将file数据写入流中
     *
     * @param file
     * @param outputStream
     */
    public void readFileData(File file, OutputStream outputStream) {
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(file);
            byte[] bytes = new byte[1024];
            int length;
            while ((length = fileInputStream.read(bytes)) > 0) {
                outputStream.write(bytes, 0, length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 创建和设置HttpUrlConnection的内容格式为文件上传格式
     *
     * @param url
     * @return
     */
    public HttpURLConnection createMultiPartConnection(String url) {
        HttpURLConnection httpURLConnection = null;
        try {
            httpURLConnection = (HttpURLConnection) new URL(url).openConnection();
            //设置请求方式为post
            httpURLConnection.setDoOutput(true);
            httpURLConnection.setRequestMethod("POST");
            //设置不使用缓存
            httpURLConnection.setUseCaches(false);
            //设置数据字符编码格式
            httpURLConnection.setRequestProperty("Charsert", PROTOCOL_CHARSET);
            //设置内容上传类型(multipart/form-data),这步是关键
            httpURLConnection.setRequestProperty("Content-Type", PROTOCOL_CONTENT_TYPE);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return httpURLConnection;
    }


    /**
     * 获取到文件的head
     *
     * @return
     */
    public byte[] getFileHead(String fileName) {
        try {
            StringBuffer buffer = new StringBuffer();
            buffer.append("--");
            buffer.append(BOUNDARY);
            buffer.append("\r\n");
            buffer.append("Content-Disposition: form-data;name=\"media\";filename=\"");
            buffer.append(fileName);
            buffer.append("\"\r\n");
            buffer.append("Content-Type:application/octet-stream\r\n\r\n");
            String s = buffer.toString();
            return s.getBytes("utf-8");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取文件的foot
     *
     * @return
     */
    public byte[] getFileFoot() {
        try {
            StringBuffer buffer = new StringBuffer();
            buffer.append("\r\n--");
            buffer.append(BOUNDARY);
            buffer.append("--\r\n");
            String s = buffer.toString();
            return s.getBytes("utf-8");
        } catch (Exception e) {
            return null;
        }
    }
}

自定义支持文件上传的MultiPartRequest

这里不再详细讲述怎么自定义Request,如何设置Header,Body等,可以阅读 Volley源码分析之自定义GsonRequest(带header,coockie,Json参数,Gson解析)

在不修改HurlStack 源码的前提下,将file文件数据转成byte[],传入MultiPartRequest中。完整代码如下:

/**
 * Created by 新根 on 2016/8/9.
 * 用途:
 * 各种数据上传到服务器的内容格式:
 * <p/>
 * 文件上传(内容格式):multipart/form-data
 * String字符串传送(内容格式):application/x-www-form-urlencoded
 * json传递(内容格式):application/json
 */
public class MultiPartRequest<T> extends Request<T> {
    private  static  final  String TAG=MultiPartRequest.class.getSimpleName();
    /**
     * 解析后的实体类
     */
    private final Class<T> clazz;

    private final Response.Listener<T> listener;

    /**
     * 自定义header:
     */
    private Map<String, String> headers;
    private final Gson gson = new Gson();
    /**
     * 字符编码格式
     */
    private static final String PROTOCOL_CHARSET = "utf-8";

    private static final String BOUNDARY = "----------" + System.currentTimeMillis();
    /**
     * Content type for request.
     */
    private static final String PROTOCOL_CONTENT_TYPE = "multipart/form-data; boundary=" + BOUNDARY;

    /**
     * 文件列表。参数1是文件名,参数2是文件编码成的byte[]
     */
    private Map<String, byte[]> fileList;
    /**
     * 多个文件间的间隔
     */
    private static final String FILEINTERVAL = "\r\n";

    public MultiPartRequest(int method, String url,
                            Class<T> clazz,
                            Response.Listener<T> listener, Response.ErrorListener errorListenerr) {
        super(method, url, errorListenerr);
        this.clazz = clazz;
        this.listener = listener;
        headers = new HashMap<>();
        fileList = new HashMap<String, byte[]>();
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            String json = new String(
                    response.data,
                    HttpHeaderParser.parseCharset(response.headers));

            T t = gson.fromJson(json, clazz);
            return Response.success(t, HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JsonSyntaxException e) {
            return Response.error(new ParseError(e));
        }
    }

    @Override
    protected void deliverResponse(T t) {
        listener.onResponse(t);
    }


    /**
     * 重写getHeaders(),添加自定义的header
     *
     * @return
     * @throws AuthFailureError
     */
    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        return headers;
    }

    public Map<String, String> setHeader(String key, String content) {
        if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(content)) {
            headers.put(key, content);
        }
        return headers;
    }

    /**
     * 默认添加图片数据
     *
     * @param file
     */
    public void addFile(byte[] file) {
        if (file != null) {
            addFile(getFileName(), file);
        }
    }

    /**
     * 添加文件名和文件数据
     *
     * @param fileName
     * @param file
     */
    public void addFile(String fileName, byte[] file) {
        if (!TextUtils.isEmpty(fileName) && file != null) {
            Log.i(TAG,fileName+" fileName");
            fileList.put(fileName, file);
        }
    }


    /**
     * 重写Content-Type:设置为json
     */
    @Override
    public String getBodyContentType() {
        return PROTOCOL_CONTENT_TYPE;
    }

    /**
     * post参数类型
     */
    @Override
    public String getPostBodyContentType() {
        return getBodyContentType();
    }

    /**
     * post参数
     */
    @Override
    public byte[] getPostBody() throws AuthFailureError {

        return getBody();
    }

    /**
     * 将string编码成byte
     *
     * @return
     * @throws AuthFailureError
     */
    @Override
    public byte[] getBody() throws AuthFailureError {
        byte[] body;
        ByteArrayOutputStream outputStream = null;
        try {
            outputStream = new ByteArrayOutputStream();
            Set<Map.Entry<String, byte[]>> set = fileList.entrySet();
            Log.i(TAG,set.size()+"filesize");
            int i=1;
            for (Map.Entry entry : set) {
                //添加文件的头部格式
                writeByte(outputStream, getFileHead((String) entry.getKey()));
                //添加文件数据
                writeByte(outputStream, (byte[]) entry.getValue());
                //添加文件间的间隔
                if (set.size() > 1&&i<set.size()) {
                    i++;
                    Log.i(TAG,"添加文件间隔");
                    writeByte(outputStream, FILEINTERVAL.getBytes(PROTOCOL_CHARSET));
                }
            }
            writeByte(outputStream, getFileFoot());
            outputStream.flush();
            body = outputStream.toByteArray();
            return body == null ? null : body;
        } catch (Exception e) {
            return null;
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (Exception e) {

            }
        }
    }

    public void writeByte(ByteArrayOutputStream outputStream, byte[] bytes) {
        Log.i(TAG,bytes.length+"byte长度");
        outputStream.write(bytes, 0, bytes.length);
    }

    /**
     * 以当前时间为文件名,
     * 文件后缀".png"
     *
     * @return
     */
    private int fileEnd=1;
    public String getFileName() {
        ++fileEnd;
        StringBuilder stringBuilder=new StringBuilder();
        stringBuilder.append(new Date().getTime());
        stringBuilder.append(fileEnd);
        stringBuilder.append(".png");
        return stringBuilder.toString();
    }


    /**
     * 获取到文件的head
     *
     * @return
     */
    public byte[] getFileHead(String fileName) {
        try {
            StringBuffer buffer = new StringBuffer();
            buffer.append("--");
            buffer.append(BOUNDARY);
            buffer.append("\r\n");
            buffer.append("Content-Disposition: form-data;name=\"media\";filename=\"");
            buffer.append(fileName);
            buffer.append("\"\r\n");
            buffer.append("Content-Type:application/octet-stream\r\n\r\n");
            String s = buffer.toString();
            return s.getBytes("utf-8");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取文件的foot
     *
     * @return
     */
    public byte[] getFileFoot() {
        try {
            StringBuffer buffer = new StringBuffer();
            buffer.append("\r\n--");
            buffer.append(BOUNDARY);
            buffer.append("--\r\n");
            String s = buffer.toString();
            return s.getBytes("utf-8");
        } catch (Exception e) {
            return null;
        }
    }
}

这里存在一个比较大的问题,文件过大转成byte[]会导致内存溢出,推荐采用hook方式,让Request不走内存流,走磁盘文件流,详情请阅读,Android开发一个VolleyHelper库,Hook Volley方式,无入侵实现(Form表单、JSON、文件上传、文件下载)

Web后台接口部分接收文件上传的代码:将文件写入F盘中,然后返回文件路径给客户端。

/**
     * Commons FileUpload 方式:当个或者多个文件上传
     * @param request
     * @param response
     */
    @RequestMapping(value="/fileUpload",method=RequestMethod.POST)
    public void fileUpLoad(HttpServletRequest request,HttpServletResponse response){
                // Check that we have a file upload request
                boolean isFile=ServletFileUpload.isMultipartContent(request);
                JSONObject jsonObject=new JSONObject();
                if(isFile){
                  String result=    writeFile(request);
                  jsonObject.put("path", result);
                }else{
                      jsonObject.put("path", "这不是文件");
                }
                setResponse(response, jsonObject);
    }
    public static final String CACHEFILE="F:"+File.separator 
                       +"WebProject"+File.separator+"fileUploadBitmap"; 

    public String writeFile(HttpServletRequest request){ 

        DiskFileItemFactory diskFileItemFactory=new DiskFileItemFactory();
        //设置储存器的最大值(内存缓存值)
        diskFileItemFactory.setSizeThreshold(1000*1024);
        //设置超出部分的存储位置(临时存储位置)
        diskFileItemFactory.setRepository(new File("E:/"));
        // Create a new file upload handler
        ServletFileUpload  upload=new ServletFileUpload(diskFileItemFactory);
        upload.setSizeMax(1000*1024);
        String result=null;
        try {
            List<FileItem> items=   upload.parseRequest(request);
            System.out.println(items.size()+"个文件");

            for (FileItem  fileItem: items) {
                System.out.println(fileItem.getName());
                if(!fileItem.isFormField()){
                   String fileName=fileItem.getName();
                   File file=new File(CACHEFILE);
                   if(file!=null&&!file.exists()){
                       file.mkdir();
                   }
                    //将数据写入文件
                    if(fileName.lastIndexOf("\\")>=0){
                         file=new File(file.getAbsoluteFile()+File.separator 
                            +fileName.substring(fileName.lastIndexOf("\\")));
                    }else{
                        file=new File(file.getAbsoluteFile()+File.separator 
                               +fileName.substring(fileName.lastIndexOf("\\")+1));
                    }
                    //FileUpload 提供两种方式:一种是直接将内容写入文件中,一种是将内容写入IO流中
                    fileItem.write(file);
                     result +=file.getAbsolutePath();
                }else{
                    result= "这不是文件,是一个表单";
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
           result=  "发生异常";
        }
        return  result;
    }


    /**
     * 设置客户端返回值
     * @param response
     * @param jsonObject
     */
    public void setResponse(HttpServletResponse response,JSONObject jsonObject){
        try {
            response.setCharacterEncoding("utf-8");
            response.setContentType("charset=UTF-8");
            String result=jsonObject.toString();
            response.getWriter().write(result, 0, result.length());;
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

在Activity中使用图片上传功能:一些Volley的配置,阅读 Volley源码分析之自定义GsonRequest(带header,coockie,Json参数,Gson解析)

    /**
     * 将bitmap编码成byte[],然后上传到服务器
     *
     * @param bitmap
     */
    public void sendFileUploadRequest(byte[] bitmap) {
        System.out.print("开始上传");
        MultiPartRequest<JsonBean> request = new MultiPartRequest<>(Request.Method.POST,  
              "http://192.168.1.101:8080/SSMProject/file/fileUpload", JsonBean.class 
               , new Response.Listener<JsonBean>() { 

            @Override
            public void onResponse(JsonBean jsonBean) {
               path_tv.setText("bitmap存储在:"+jsonBean.path);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                Toast.makeText(MainActivity.this,  
                        volleyError.getMessage(), Toast.LENGTH_LONG).show();
            }
        });
        request.addFile(bitmap);
        request.setRetryPolicy(new DefaultRetryPolicy(50000,
                DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        request.setTag(TAG);
        VolleySingleton.getInstance().addToRequestQueue(request);
    }

项目运行结果:

1.先进行拍照操作,效果图如下:

这里写图片描述

2.然后点击文件上传,web后台接收到文件,将文件写入F盘中文件夹里。

这里写图片描述

3.app上获取到服务器返回文件路径,效果图如下:

这里写图片描述

项目代码:http://download.csdn.net/detail/hexingen/9681762

PS : 推荐使用 , Android开发一个VolleyHelper库,Hook Volley方式,无入侵实现(Form表单、JSON、文件上传、文件下载)

相关知识点阅读:

volley--图片处理方式源码分析(代码片段)

简介本篇文章是关于对Volley的图片加载做相应的分析,分析Volley的ImageRequest、ImageLoader、NetworkImageView类对图片加载的策略,同样,本文是多多少少基于前面两篇文章Volley–基本用法和Volley–源码分析的分析,比如... 查看详情

volley--源码分析(代码片段)

简介关于Volley封装性跟实用性是毋庸置疑的,本篇文章是争对上一篇文章Volley–基本用法做出比较详细的过程分析,分析Volley请求的流程,缓存的策略,工作线程的执行分配,接口回调的机制,代码的封装... 查看详情

volley--网络请求源码分析

简介本篇文章分析Volley的网络请求的过程,以及获取缓存数据时是如何判断缓存是否过期,是否需要刷新。RequestQueue再分析从之前的文章Volley–基本用法中知道,每一个请求都添加到RequestQueue中,有其分配管理... 查看详情

帝国cms之自定义系统模型

...扩展各种系统模块。自定义系统模型一般步骤:1、系统分析;2、建立数据表;3、建立字段;4、建立系统模型;5、完成。·举例说明:制作“作品管理系统”1、系统分析:经过分析我们需要的字段如下:(字段标识:... 查看详情

qgis开发之自定义符号

...etails/50428179,实现自定义的右键菜单类,具体代码可参考源码中的QgsAppLayerTreeViewMenuProvider类。源码里有一个edi 查看详情

volley源码解读(上)

Volley框架的使用Volley网络框架的使用方式绝大部分人都已经很熟悉了。最简单的就是通过Volley提供的静态方法newRequestQueue(Contextcontext)来返回一个消息队列MessageQueue,然后在需要使用时将构造的网络请求消息添加到队列中去,这... 查看详情

qt之自定义检索框

...伙伴可以看下步骤3的思路讲解。图1自定义搜索框2、功能分析   这个自定义搜索框支持输入 查看详情

requests库之自定义request

 阅读requests源码会有更清楚的理解。tcp/ip的三次握手,使用requests每次请求会占用更多资源,使用session则可以重复使用一个request。自定义requests:首先定义session(proxy,timeout,verify……),定义request(body,headers,author……... 查看详情

volley源码看这一篇就够了

Volley是Android中一个经典的网络框架,所以现如今面试的时候问Volley的源码分析有很多,那我们就有很有必要细致的研究一下它的源码了,让我们走进它的内心世界里吧!一些错误的理解在分析Volley源码之前,... 查看详情

volley源码解析

很早之前就想写下关于Volley的源码解析。一开始学android网络访问都是使用HttpClient,刚接触么Volley的时候就瞬间爱不释手,虽说现在项目中使用OkHttp多些(Volley更新慢),但是作为google自家推出的网络框架࿰... 查看详情

mybatis进阶之自定义mybatis框架(代码片段)

MyBatis进阶之自定义MyBatis框架1.自定义MyBatis框架流程分析2.自定义框架原理介绍3.准备工作:1)创建maven工程Groupid:com.itheimaArtifactId:custom-mybatisPacking:jar2)添加pom依赖:<dependency><groupId>mysql</groupId&g 查看详情

webpack配置之自定义loader

...文档:https://webpack.js.org/api/loaders/简单案例1.创建一个替换源码中字符串的loader2.在配置文件中使用loader,这里用的绝对路径其他引入自定义loader方式,可参考另外一篇文章--webpack中resolveLoader的使用方法4.this.callback:如何返回多个信... 查看详情

源码解析volley框架(代码片段)

Volley是Google在2013年I/O大会上发布的一个网络异步请求和图片加载框架。框架设计的非常好,可扩展性极强,很值得我们去学习。在这篇文章中重点去分析一下它的源码,Volley的使用在这里就不多加赘述了,如果有... 查看详情

jvm进阶之自定义类加载器(代码片段)

...以从数据库、网络、甚至是电视机机顶盒进行加载。防止源码泄露可以对Java源码进行编译时的加密,还原时的解密。2.场景当实现类似进程内隔 查看详情

jvm进阶之自定义类加载器(代码片段)

...以从数据库、网络、甚至是电视机机顶盒进行加载。防止源码泄露可以对Java源码进行编译时的加密,还原时的解密。2.场景当实现类似进程内隔 查看详情

flinksql实战演练之自定义clickhouseconnector

...目前想要实现flinksql数据落地到ck,可以修改jdbcconnector的源码,增加ck方言,或者采用阿里提供的ckconnector包,为了更好的理解flinkconnector的原理,这里自定义connector实现。目前支持Flink写入Clickhouse的依赖哭比较多,如果数据格式... 查看详情

编译原理语法分析之自底向上分析之算符优先分析法

语法分析之自顶向下分析说明:以老师PPT为标准,借鉴部分教材内容,AlvinZH学习笔记。先看看PPT吧!引用说明-邵老师课堂PDF-《编译原理级编译程序构造》 查看详情

android天气app城市切换之自定义弹窗与使用(代码片段)

...、城市数据操作四、切换城市五、切换城市处理六、文章源码旧版-------------------自定义弹窗新版-------------------  在上一篇文章中,完成了风力风向的显 查看详情