본문 바로가기
Android

WebView 연동, Multi file upload with retrofit

by 들풀민들레 2022. 8. 12.

 

 

수업시간의 질문에 도움을 주기 위해 작성된 글입니다.

 

WebView 연동으로 HTML 페이지에서 유저 이벤트 발생

Gallery App 목록화면 출력

유저가 사진 한장, 혹은 여러장 선택

서버 업로드

 

여러가지 방법이 있는데 어자피 native 와 연동을 해야 하는 방식임으로 파일 업로드도 native 에서 retrofit 으로 처리하는 것이 어떨까 생각했습니다. 

 

아래는 테스트한 코드입니다.

 

Server - Spring Controller

@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";
	}
	
}

 

테스트 html

단순 버튼으로 화면 처리

버튼 클릭시 native 함수 호출

 

<html>
<script>
    function upload() {
        window.android.upload()
    }
</script>
<body>
<button onclick="upload()">Click</button>
</body>
</html>

 

테스트 Native 코드

 

테스트했던 액티비티 코드입니다.

퍼미션 체크했으며

자바스크립트에 객체 공개 했습니다. 

자바스크립트에서 함수 호출시 Gallery App 목록 띄웠으며

되돌아 왔을때 선택한 파일을 업로드 한 것입니다.

 

성공 여부는 토스트로 출력했습니다.

 

class MainActivity : AppCompatActivity() {

    lateinit var permissionLauncher: ActivityResultLauncher<Array<String>>
    lateinit var requestGalleryLauncher: ActivityResultLauncher<Intent>

    val datas = mutableListOf<Uri>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        permissionLauncher = registerForActivityResult(
            ActivityResultContracts.RequestMultiplePermissions()
        ){
            for(entry in it.entries){
                if(entry.key =="android.permission.READ_EXTERNAL_STORAGE" && entry.value){
                    uploadImage()
                }else {
                    Toast.makeText(this, "required permission..", Toast.LENGTH_SHORT).show()
                }
            }
        }

        //gallery request launcher..................
        requestGalleryLauncher = registerForActivityResult(
            ActivityResultContracts.StartActivityForResult())
        {
            try {
                if (it.data!!.data != null) {
                    Log.d("kkang","select one..................")
                    datas.add(it.data!!.data!!)
                } 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)
                        }
                    }
                }
                uploadImage()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }

        binding.webView.run {
            settings.javaScriptEnabled = true

            webChromeClient = WebChromeClient()
            webViewClient = WebViewClient()

            addJavascriptInterface(JavascriptTest(), "android")

            loadUrl("file:///android_asset/test.html")
        }
    }

    fun uploadImage() {
        val fileList = mutableListOf<File>()

        val fileNameArray = Array<String>(datas.size, {""})

        //uri -> 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()
        }

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

        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")

                Toast.makeText(this@MainActivity, "upload ok", Toast.LENGTH_SHORT).show()
            }

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

    }

    inner class JavascriptTest {
        @JavascriptInterface
        fun upload() {
            Toast.makeText(this@MainActivity, "upload...", Toast.LENGTH_SHORT).show()
            if(ContextCompat.checkSelfPermission(
                    this@MainActivity,
                    "android.permission.READ_EXTERNAL_STORAGE"
                ) != PackageManager.PERMISSION_GRANTED){
                permissionLauncher.launch(arrayOf("android.permission.READ_EXTERNAL_STORAGE"))
            }else {
                //gallery app........................
                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)
            }

        }
    }
}

 

 

 

 

 

 

 

 

 

 

'Android' 카테고리의 다른 글

API Level 33 에서 Notification 이 안뜰때..  (0) 2023.03.20
API Level 33 외장 메모리 퍼미션 조정  (0) 2023.03.08
RecyclerView Swipe menu  (0) 2022.08.04
Progress Indicator  (0) 2022.08.01
Datastore  (0) 2022.07.29