一直用OkHttp感覺也沒什麼毛病,空閒時間看看Retrofit怎麼使用
本篇僅介紹最基本的Retrofit用法,如有錯誤請提出,我會虛心請教並改正,謝謝

一、介面

想必有所瞭解的同學都知道Retrofit是基於介面來進行請求,如下

  1. 開門見山貼程式碼

    interface RetrofitApi {
    
        /* 1. 僅請求伺服器資料 */
        @GET(".")
        fun register(): Call<String>
    
    
        /* 2. 上傳引數 */
        @GET("hi/register")
        fun register(@QueryMap map: Map<String, String>): Call<String>
    
    
        /* 3. ???上傳json[暫時處於僵硬狀態...] */
        @POST("postJson")
        fun postJson(@Body user: User): Call<String>
    
    
        /* 4. 上傳單個檔案 */
        @Multipart
        @POST(".")
        fun uploadFile(@Part("description") description: RequestBody, @Part file: MultipartBody.Part): Call<String>
    
    
        /* 5. 上傳多個檔案 & 多個引數 */
        @JvmSuppressWildcards   // [IllegalArgumentException: Parameter type must not include a type variable or wildcard]
        @Multipart
        @POST(".")
        fun uploadFiles(@PartMap fileMap: Map<String, RequestBody>, @QueryMap map: Map<String, String>): Call<String>
    
    
        /* 6. 下載檔案 */
        @Streaming  // 避免下載大檔案被Retrofit整個讀進記憶體
        @GET("downloadFile")
        fun download(): Call<ResponseBody>
    }

    javabean資料類User如下

    data class User(val password: String, val username: String)
  2. 做個解釋

    1. 上面的第三個上傳json(或者有需求是上傳xml的,這裡沒有成功實現,如有需要的同學聯絡我,然後我會盡快待補)

    2. 注意事項程式碼裡都加註釋了,再次說明幾點

      • 有@Part的地方基本都要加@Multipart

      • 有@PartMap的地方最好加@JvmSuppressWildcards,否則會出錯(註釋有)

      • 下載檔案時Retrofit會將檔案流整個讀進記憶體,避免OOM需新增@Streaming

二、具體的請求

剛開始,多寫幾遍最基本的請求步驟,加強記憶,不急著封裝啥的

  1. 程式碼貼上

    object RetrofitRequest {
    
        @JvmStatic
        fun main(args: Array<String>) {
    //        register()
    //        registerWithParams()
    //        postJson()
    //        uploadFile()
    //        uploadFiles()
            download()
        }
    
    
        private fun register() {
            // 先來個模子 -> 給模子加花 -> 通過模子刻個例項出來
            val retrofit = Retrofit.Builder()   // 建立客戶端建造器
                    .baseUrl("http://127.0.0.1:8080/hi/register/")   // 新增主機地址
                    .addConverterFactory(ScalarsConverterFactory.create()) // 制定資料解析器[可用gson、jackson等或自定製]
                    .build()    // 建立客戶端例項
    
            val api = retrofit.create(RetrofitApi::class.java)  // 獲取Api例項
    
            val call = api.register()   // 通過Api例項獲取一個Call物件
    
            call.enqueue(object : Callback<String> {  // 開始請求[enqueue(非同步) | execute(同步)]
                override fun onFailure(call: Call<String>?, t: Throwable?) {
                    println("請求失敗: ${t.toString()}")
                }
    
                override fun onResponse(call: Call<String>?, response: Response<String>?) {
                    println("請求成功: ${response?.body()}")
                }
            })
        }
    
    
        private fun registerWithParams() {
            val retrofit = Retrofit.Builder()
                    .baseUrl("http://127.0.0.1:8080/")
                    .addConverterFactory(ScalarsConverterFactory.create())
                    .build()
    
            val api = retrofit.create(RetrofitApi::class.java)
    
            val call = api.register(mapOf("username" to "catface", "password" to "root"))   // 新增請求引數map
    
            call.enqueue(object : Callback<String> {
                override fun onResponse(call: Call<String>?, response: Response<String>?) {
                    println("請求成功: ${response?.body()}")
                }
    
                override fun onFailure(call: Call<String>?, t: Throwable?) {
                    println("請求失敗: ${t.toString()}")
                }
    
            })
        }
    
    
        // 僵硬中...
        private fun postJson() {
            val retrofit = Retrofit.Builder()
                    .baseUrl("http://127.0.0.1:8080/")
                    .addConverterFactory(GsonConverterFactory.create())
                    .build()
    
            val api = retrofit.create(RetrofitApi::class.java)
    
            val call = api.postJson(User("root", "catface"))
    
            call.enqueue(object : Callback<String> {
                override fun onFailure(call: Call<String>?, t: Throwable?) {
                    println("請求失敗: ${t.toString()}")
                }
    
                override fun onResponse(call: Call<String>?, response: Response<String>?) {
                    println("請求成功: ${response?.body()}")
                }
            })
        }
    
    
        private fun uploadFile() {
            val retrofit = Retrofit.Builder()
                    .addConverterFactory(ScalarsConverterFactory.create())
                    .baseUrl("http://127.0.0.1:8080/hi/uploadFileByJava/")
                    .build()
    
            val api = retrofit.create(RetrofitApi::class.java)
    
            val file = File("D:/下載/圖片/ic_launcher.png")
            val requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file)
            val fileBody = MultipartBody.Part.createFormData("file", file.name, requestBody)
    
            val description = RequestBody.create(MediaType.parse("multipart/form-data"), "This is a description")
    
            val call = api.uploadFile(description, fileBody)
    
            call.enqueue(object : Callback<String> {
                override fun onResponse(call: Call<String>, response: Response<String>) {
                    println("請求成功: ${response.body().toString()}")
                }
    
                override fun onFailure(call: Call<String>, t: Throwable) {
                    println("請求失敗: ${t.toString()}")
                }
            })
        }
    
    
        private fun uploadFiles() {
            val retrofit = Retrofit.Builder()
                    .baseUrl("http://127.0.0.1:8080/hi/uploadFileBySpringAndFileName/")
                    .addConverterFactory(ScalarsConverterFactory.create())
                    .build()
    
            val api = retrofit.create(RetrofitApi::class.java)
    
            /* 檔案 */
            val fileMap = HashMap<String, RequestBody>()
            val file1 = File("D:/下載/圖片/ic_launcher.png")
            val file2 = File("D:/下載/圖片/girl01.jpg")
            fileMap.put("file1\"; filename=\"" + file1.name, RequestBody.create(MediaType.parse("multipart/form-data"), file1))
            fileMap.put("file2\"; filename=\"" + file2.name, RequestBody.create(MediaType.parse("multipart/form-data"), file2))
    
            /* 引數 */
            val map = HashMap<String, String>()
            map.put("username", "catface")
            map.put("password", "root")
    
            val call = api.uploadFiles(fileMap, map)
    
            call.enqueue(object : Callback<String> {
                override fun onResponse(call: Call<String>?, response: Response<String>?) {
                    println("請求成功: ${response?.body()}")
                }
    
                override fun onFailure(call: Call<String>?, t: Throwable?) {
                    println("請求失敗: ${t.toString()}")
                }
    
            })
        }
    
    
        private fun download() {
            val retrofit = Retrofit.Builder()
                    .baseUrl("http://127.0.0.1:8080/hi/")
                    .addConverterFactory(ScalarsConverterFactory.create())
                    .build()
    
            val api = retrofit.create(RetrofitApi::class.java)
    
            val call = api.download()
    
            call.enqueue(object : Callback<ResponseBody> {
                override fun onResponse(call: Call<ResponseBody>?, response: Response<ResponseBody>?) {
                    println("請求成功")
                    if (response!!.isSuccessful) {
                        writeResponseBodyToDisk(response.body()!!)
                    }
                }
    
                override fun onFailure(call: Call<ResponseBody>?, t: Throwable?) {
                    println("請求失敗: ${t.toString()}")
                }
    
            })
        }
    
    
        /* 將流寫到本地的工具 */
        fun writeResponseBodyToDisk(body: ResponseBody): Boolean {
            try {
                val futureStudioIconFile = File("d:/a_test_dir/Future Studio Icon.jpg")
    
                var inputStream: InputStream? = null
                var outputStream: OutputStream? = null
    
                try {
                    val fileReader = ByteArray(4096)
    
                    val fileSize = body.contentLength()
                    var fileSizeDownloaded: Long = 0
    
                    inputStream = body.byteStream()
                    outputStream = FileOutputStream(futureStudioIconFile)
    
                    while (true) {
                        val read = inputStream!!.read(fileReader)
    
                        if (read == -1) {
                            break
                        }
    
                        outputStream.write(fileReader, 0, read)
    
                        fileSizeDownloaded += read.toLong()
    
                        println("file download: $fileSizeDownloaded of $fileSize")
                    }
    
                    outputStream.flush()
    
                    return true
                } catch (e: IOException) {
                    return false
                } finally {
                    if (inputStream != null) {
                        inputStream.close()
                    }
    
                    if (outputStream != null) {
                        outputStream.close()
                    }
                }
            } catch (e: IOException) {
                return false
            }
        }
    }
  2. 做個解釋

    1. 在資料解析器方面,我覺得直接返回json串後自己使用gson或者fastjson解析,又清楚也不麻煩,不太喜歡在構造Retrofit例項的時候就固定了解析方式,當然如果為了方便專案需求需要加也沒問題

    2. 步驟大致就是:建立Retrofit例項,然後通過介面定義的請求得到Api例項,通過這個例項得到對應的Call物件,最後進行具體的請求

三、服務端

為了方便同學們瞭解服務端是怎麼接收檔案和引數的,在此貼出我寫的完整的服務端小demo,可做大家入門案例,灰常簡單,且功能很全,能接收客戶端傳來的各種引數、檔案,而且能返回給客戶端字串、json串、還能提供檔案給客戶端進行下載,直接看,不用害怕

  1. Controller程式碼貼上

    @Controller
    @RequestMapping("hi")
    public class HiController {
    
        /******************************************** 與客戶端進行入參出參聯絡 ********************************************/
        /**
         * 1. 接收引數 --> 返回字串
         */
        @RequestMapping(value = "/register", produces = "application/json; charset=utf-8")
        @ResponseBody
        public String register(HttpServletRequest request) {
            System.out.println("介面開始執行-->register");
    
            Map<String, String[]> map = request.getParameterMap();
    
            if (null == map || map.size() < 1) return "本次請求未上傳任何引數";
    
            String result = "";
            for (String key : map.keySet()) {
                System.out.println(key + "-" + map.get(key)[0]);
    
                result += "\r\n" + key + "-" + map.get(key)[0];
            }
    
            return result;
        }
    
    
        /**
         * 接收json串
         */
        @RequestMapping(value = "/postJson", method = RequestMethod.POST, produces = "application/json; charset=utf-8")
        @ResponseBody
        public String postJson(@RequestBody Map<String, String> map) throws IOException {
            System.out.println("介面開始執行-->postJson");
    
            if (null == map || map.size() < 1) return "本次請求未上傳任何引數";
    
            String result = "";
            for (String key : map.keySet()) {
                System.out.println(key + "-" + map.get(key));
    
                result += "\r\n" + key + "-" + map.get(key);
            }
    
            return result;
        }
    
        /**
         * 2. 接收引數 --> 返回json
         */
        @RequestMapping(value = "/login", produces = "application/json; charset=utf-8")
        @ResponseBody
        public String login(@RequestParam("username") String username, @RequestParam("password") String password) throws IOException {
            System.out.println("介面開始執行-->login");
    
            /* 接收入參 */
            System.out.println(username + " || " + password);
    
    
            /* 提供出參[json格式] */
            Map<String, String> map = new HashMap<String, String>();
            map.put("username", "測試中文 your username is: " + username);
            map.put("password", "測試中文 your password is: " + password);
    
            /* 返回json給客戶端 */
            return JSON.toJSONString(map);
        }
    
    
        @RequestMapping(value = "/receiveArr")
        public void receiveArr(@RequestParam("ids[]") String[] ids) {
            System.out.println("介面開始執行-->receiveArr");
    
            for (String id : ids) {
                System.out.println(id);
            }
        }
    
        /******************************************** 客戶端上傳檔案 ********************************************/
        /**
         * ↓通過流儲存上傳的檔案(耗時較長)
         *
         * @param file 將檔案封裝成CommonsMultipartFile
         */
        @RequestMapping("/uploadFileByStream")
        @ResponseBody
        public String uploadFileByStream(@RequestParam("file") CommonsMultipartFile file) throws IOException {
            System.out.println("介面開始執行-->uploadFileByStream");
    
            OutputStream os = new FileOutputStream("d:/" + file.getOriginalFilename());
    
            InputStream is = file.getInputStream();
    
            int temp;
            while ((temp = is.read()) != -1) {
                os.write(temp);
            }
    
            os.flush();
            os.close();
            is.close();
    
            return "uploadFileByStream suc...";
        }
    
    
        /**
         * ↓通過Java API: file.transferTo()儲存上傳的單個檔案
         */
        @RequestMapping("/uploadFileByJava")
        @ResponseBody
        public String uploadFileByJava(@RequestParam("file") CommonsMultipartFile file) throws IOException {
            System.out.println("介面開始執行-->uploadFileByJava");
    
            file.transferTo(new File("d:/" + file.getOriginalFilename()));
    
            return "uploadFileByJava suc... ";
        }
    
    
        /**
         * ↓通過Spring API儲存上傳的多個檔案
         */
        @RequestMapping("/uploadFilesBySpring")
        @ResponseBody
        public String uploadFilesBySpring(HttpServletRequest request) throws IOException {
            System.out.println("介面開始執行-->uploadFilesBySpring");
    
            String desPath;
    
            CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext());
            if (multipartResolver.isMultipart(request)) {
                MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
    
                Iterator<String> iterator = multiRequest.getFileNames();
    
                while (iterator.hasNext()) {
                    MultipartFile file = multiRequest.getFile(iterator.next());
    
                    if (null != file) {
                        desPath = "d:/tt/" + file.getOriginalFilename();
                        file.transferTo(new File(desPath));
                    }
                }
            }
    
            return "uploadFilesBySpring suc...";
        }
    
    
        /**
         * →接收檔案map & 請求引數map←
         */
        @RequestMapping(value = "/uploadFileBySpringAndFileName", method = RequestMethod.POST, produces = "application/json; charset=utf-8")
        @ResponseBody
        public String uploadFileBySpringAndFileName(HttpServletRequest request) throws IOException {
            System.out.println("介面開始執行-->uploadFileBySpringAndFileName");
    
            /* 1. 獲取所有引數 */
            Map<String, String[]> parameterMap = request.getParameterMap();
    
            System.out.println("引數數量: " + parameterMap.size()); // 入引數量
    
            for (String key : parameterMap.keySet()) {
                System.out.println(key + ":" + parameterMap.get(key)[0]);
            }
    
             /* 2. 獲取所有檔案 */
            CommonsMultipartResolver multiResolver = new CommonsMultipartResolver(request.getSession().getServletContext());
    
            if (multiResolver.isMultipart(request)) {
                MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
    
                MultiValueMap<String, MultipartFile> multiFileMap = multiRequest.getMultiFileMap();
                System.out.println("檔案數量: " + multiFileMap.size()); // 入引數量
                for (String key : multiFileMap.keySet()) {
                    System.out.println(key + " : " + multiFileMap.get(key).get(0).getOriginalFilename());
                    multiFileMap.get(key).get(0).transferTo(new File("d:/a_test_dir/" + multiFileMap.get(key).get(0).getOriginalFilename()));
                }
    
    
                /*Iterator<String> iterator = multiRequest.getFileNames();
    
                while (iterator.hasNext()) {
                    Map<String, MultipartFile> fileMap = multiRequest.getFileMap();
    
                    MultipartFile file = fileMap.get("file");
    
                    System.out.println(file.getOriginalFilename());
                }*/
            }
    
            return "uploadFileBySpringAndFileName suc...";
        }
    
    
        /* 新增一個網上的小demo,沒啥用,隨便看看 */
        @RequestMapping("/uploadFiles")
        @ResponseBody
        public String uploadFiles(HttpServletRequest request) throws IOException {
            System.out.println("介面開始執行-->uploadFiles");
    
            //建立一個通用的多部分解析器
            CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext());
            //判斷 request 是否有檔案上傳,即多部分請求
            if (multipartResolver.isMultipart(request)) {
                //轉換成多部分request
                MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
                //取得request中的所有檔名
                Iterator<String> iter = multiRequest.getFileNames();
                while (iter.hasNext()) {
                    //記錄上傳過程起始時的時間,用來計算上傳時間
                    int pre = (int) System.currentTimeMillis();
                    //取得上傳檔案
                    MultipartFile file = multiRequest.getFile(iter.next());
                    if (file != null) {
                        //取得當前上傳檔案的檔名稱
                        String myFileName = file.getOriginalFilename();
                        //如果名稱不"",說明該檔案存在,否則說明該檔案不存在
                        if (myFileName.trim() != "") {
                            System.out.println(myFileName);
                            //重新命名上傳後的檔名
                            String fileName = "demoUpload" + file.getOriginalFilename();
                            //定義上傳路徑
                            String path = "D:/a_test_dir/" + fileName;
                            File localFile = new File(path);
                            file.transferTo(localFile);
                        }
                    }
                    //記錄上傳該檔案後的時間
                    int finaltime = (int) System.currentTimeMillis();
                    System.out.println(finaltime - pre);
                }
            }
    
            return "/success";
        }
    
        /******************************************* 向頁面輸出驗證碼png ********************************************/
        @RequestMapping("/verificationCode")
        public void verificationCode(HttpServletResponse response, HttpSession session) throws IOException {
            System.out.println("介面開始執行-->verificationCode");
    
            Object[] objs = ImageUtil.createImage();
            session.setAttribute("imgCode", objs[0]);
    
            BufferedImage img = (BufferedImage) objs[1];
            response.setContentType("image/png");
            OutputStream os = response.getOutputStream();
            ImageIO.write(img, "png", os);
        }
    
    
        /******************************************* 向客戶端提供檔案下載 ********************************************/
        @RequestMapping("/downloadFile")
        public ResponseEntity<byte[]> downloadFile() throws IOException {
            System.out.println("介面開始執行-->downloadFile");
    
            File file = new File("C:\\Users\\Administrator\\Desktop\\temp.jpg");
    
            HttpHeaders headers = new HttpHeaders();
            headers.setContentDispositionFormData("attachment", "temp.jpg");
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setContentLength(file.length());
    
            System.out.println(file.length() + " is length,..");
    
            return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file), headers, HttpStatus.CREATED);
        }
    }
  2. 做個解釋

    1. 伺服器就基於SSM(SpringMVC+Spring+Mybatis)

    2. 新手學習中,本篇未涉及難點和概念部分,即看完能夠在專案中使用

    3. 如有關於Retrofit和服務端的問題,請留下回復,一起交流