본문 바로가기
Android

Multiple File upload, download with retrofit, Glide, spring boot

by 들풀민들레 2022. 7. 25.

 


 

 

수업시간에 활용하기 위해 만든 자료를 올립니다.

 

 

 

 

Back-End : Spring Boot

 

Upload :

l  여러 파일 동시 업로드

l  C:/upload 에 파일 write

l  파일 명은 upload 되는 파일명을 그대로 이용

 

Download :

l  클라이언트 request parameter download 파일명으로 이용

 

 

@RestController
public class UploadController {
	@PostMapping("/mobile/upload.do")
	public String upload(@RequestParam("multipartFiles") List<MultipartFile> multipartFiles) throws IOException {
		System.out.println("multipartFiles.size():"+multipartFiles.size());

        for (MultipartFile multipartFile : multipartFiles) {
            BufferedImage image = ImageIO.read(multipartFile.getInputStream());

            String[] array = multipartFile.getContentType().split("/");
            for(String data: array) {
            	System.out.println("array : "+data);
            }
            String filename = multipartFile.getOriginalFilename();
            System.out.println("filename:"+filename);
            String path = "C:/upload/" + filename;
            File outputFile = new File(path);
            ImageIO.write(image,"jpg",outputFile);
        }
		return "success";
	}
	@GetMapping(value="/mobile/download.do")
	public ResponseEntity<ByteArrayResource> download(@RequestParam("filename") String filename) throws IOException {
		System.out.println("download:"+filename);

        Path path = Paths.get("C:/upload/"+filename);
        byte[] data = Files.readAllBytes(path);
        ByteArrayResource resource = new ByteArrayResource(data);

        return ResponseEntity.ok()
                // Content-Disposition
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + path.getFileName().toString())
                // Content-Type
                .contentType(MediaType.parseMediaType("image/jpeg")) //
                // Content-Lengh
                .contentLength(data.length) //
                .body(resource);
	}
}

 

Front-End : Android App

 

라이브러리 dependency

 

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.google.code.gson:gson:2.8.9'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.1'

implementation 'com.github.bumptech.glide:glide:4.12.0'

 

AndroidManifest.xml 설정

 

 

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

 

Multiple Picker

 

Gallery 목록 화면 출력

 

val intent = Intent(
    Intent.ACTION_GET_CONTENT,//extra_allow_multiple 이려면 action 문자열이 action_get_content
    MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
intent.type = "image/*"
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
requestGalleryLauncher.launch(intent)

선택한 이미지 화면 출력

 

선택한 이미지를 RecyclerView 를 이용해 가로 방향으로 나열

 

try {
    if (it.data!!.data != null) {
        Log.d("kkang","select one..................")
        datas.add(it.data!!.data!!)
        adapter.notifyDataSetChanged()
    } else {
        if (it.data!!.clipData != null) {
            val mClipData: ClipData = it.data!!.clipData!!
            Log.d("kkang","select count: ${mClipData.itemCount}")
            for (i in 0 until mClipData.itemCount) {
                val item = mClipData.getItemAt(i)
                datas.add(item.uri)
            }
            adapter.notifyDataSetChanged()
        }
    }
} catch (e: Exception) {
    e.printStackTrace()
}

 

이미지를 출력한 RecyclerView.Adapter onBindViewHolder()

 

override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
    try {
        val option = BitmapFactory.Options()
        option.inSampleSize = 10
        // 이미지 로딩
        var inputStream = context.contentResolver.openInputStream(datas[position])
        val bitmap = BitmapFactory.decodeStream(inputStream, null, option)
        inputStream!!.close()
        bitmap?.let {
            holder.binding.itemImageView.setImageBitmap(bitmap)
        } ?: let {
            Log.d("kkang", "${datas[position]} ... bitmap null")
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

 

Upload

 

이미지 Uri 값을 이용, File Path 획득, File 객체 준비

 

datas.forEachIndexed { index, uri ->
    var filePath = ""
    val wholeID = DocumentsContract.getDocumentId(uri)
    val id = wholeID.split(":").toTypedArray()[1]

    val column = arrayOf(MediaStore.Images.Media.DATA)
    val sel = MediaStore.Images.Media._ID + "=?"

    val cursor: Cursor = contentResolver.query(
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        column, sel, arrayOf(id), null
    )!!

    val columnIndex = cursor.getColumnIndex(column[0])

    if (cursor.moveToFirst()) {
        filePath = cursor.getString(columnIndex)
    }
    Log.d("kkang","filePath:$filePath")
    fileList.add(File(filePath))

    val segment = filePath.split("/")
    fileNameArray[index] = segment.last()

    cursor.close()
}

 

이미지 업로드를 위한 MultipartBody.Part 준비

 

val listPart = mutableListOf<MultipartBody.Part>()
fileList.forEach {
    listPart.add(MultipartBody.Part.createFormData(
        name = "multipartFiles",
        filename = it.name,
        body = it.asRequestBody("image/jpg".toMediaType())
    ))
}

Retrofit 을 이용한 Upload

 

interface UploadService {
    @Multipart
    @POST("upload.do")
    fun uploadMultipleFiles(
        @Part multipartFiles: List<MultipartBody.Part>
    ): Call<String>
}
val call = (applicationContext as MyApplication).apiService.uploadMultipleFiles(listPart)
call.enqueue(object : Callback<String> {
    override fun onResponse(call: Call<String>, response: Response<String>) {
        val result = response.body()!!
        Log.d("kkang", "result................$result")

    }

    override fun onFailure(call: Call<String>, t: Throwable) {
        t.printStackTrace()
        call.cancel()
    }
})

Download

 

Glide.with(context)
    .load("http://10.0.2.2:8080/mobile/download.do?filename=${datas[position]}")
    .into(holder.binding.itemImageView)