새소식

mobile/🦖 Android

[Kotlin] webview에서 input file 동작하도록 만들기 (파일 업로드)

  • -
728x90

안녕하세요. 팀드모네입니다.

오늘은 webview에서 input file이 동작하도록 만드는 방법을 공유드립니다.

이 포스팅은 카메라 촬영 기능은 아니고, 웹앱에서 사진 갤러리에서 사진을 선택 후 웹에 올리는 것을 의미합니다.

작업이 복잡하여 소스코드 전문을 공유드립니다. 작업에 도움이 되길 바랍니다.

1. 권한 부여

1.1 AndroidManifest.xml에 권한 부여

EXTERNAL_STORAGE 등의 권한을 부여합니다.

    <!-- webView input file을 위한 permission -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

 

1.2 소스코드 전문 공유

혹시 몰라 제가 쓰는 AndroidManifest.xml을 공유드립니다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

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

    <!-- webView input file을 위한 permission -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        
        ... 생략

 

2. 소스코드 작업

2.1 MainActivity에 아래 코드를 작성

    var cameraPath = ""
    var webViewImageUpload: ValueCallback<Array<Uri>>? = null
    val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == RESULT_OK) {
            val intent = result.data
            if(intent == null){ //사진을 찍어서 올리는 경우
                val results = arrayOf(Uri.parse(cameraPath))
                webViewImageUpload!!.onReceiveValue(results!!)
            }
            else{ //갤러리를 통해 사진을 가져온 경우
                val results = intent!!.data!!
                webViewImageUpload!!.onReceiveValue(arrayOf(results!!))
            }
        }
        else{ //취소 한 경우 초기화 수행
            webViewImageUpload!!.onReceiveValue(null)
            webViewImageUpload = null
        }
    }
    
    fun createImageFile(): File? {
        @SuppressLint("SimpleDateFormat")
        val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
        val imageFileName = "img_" + timeStamp + "_"
        val storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
        return File.createTempFile(imageFileName, ".jpg", storageDir)
    }

 

그리고 웹뷰 변수에 webChromeClient를 부여합니다.

당신의 웹뷰 변수.webChromeClient = object : WebChromeClient(){
    override fun onShowFileChooser(webView: WebView?, filePathCallback: ValueCallback<Array<Uri>>?, fileChooserParams: FileChooserParams?): Boolean {
        try{
            webViewImageUpload = filePathCallback!!
            var takePictureIntent : Intent?
            takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
            if(takePictureIntent.resolveActivity(packageManager) != null){
                var photoFile : File?

                photoFile = createImageFile()
                takePictureIntent.putExtra("PhotoPath",cameraPath)

                if(photoFile != null){
                    cameraPath = "file:${photoFile.absolutePath}"
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(photoFile))
                }
                else takePictureIntent = null
            }
            val contentSelectionIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
            contentSelectionIntent.type = "image/*"

            var intentArray: Array<Intent?>

            if(takePictureIntent != null) intentArray = arrayOf(takePictureIntent)
            else intentArray = takePictureIntent?.get(0)!!

            val chooserIntent = Intent(Intent.ACTION_CHOOSER)
            chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent)
            chooserIntent.putExtra(Intent.EXTRA_TITLE,"사용할 앱을 선택해주세요.")
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray)
            launcher.launch(chooserIntent)
        }
        catch (e : Exception){ }
        return true
    }
}

 

2.2 소스코드 전문 공유

혹시 몰라 제가 쓰는 MainActivity를 공유드립니다. 작업에 도움이 되길 바랍니다.

class MainActivity : AppCompatActivity(), AndroidBridge.BridgeListener{

    private val bridge = AndroidBridge()
    private lateinit var webView: WebView

    var cameraPath = ""
    var webViewImageUpload: ValueCallback<Array<Uri>>? = null
    val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == RESULT_OK) {
            val intent = result.data
            if(intent == null){ //사진을 찍어서 올리는 경우
                val results = arrayOf(Uri.parse(cameraPath))
                webViewImageUpload!!.onReceiveValue(results!!)
            }
            else{ //갤러리를 통해 사진을 가져온 경우
                val results = intent!!.data!!
                webViewImageUpload!!.onReceiveValue(arrayOf(results!!))
            }
        }
        else{ //취소 한 경우 초기화 수행
            webViewImageUpload!!.onReceiveValue(null)
            webViewImageUpload = null
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {

        // 와이파이 & 데이터 연결되어 있으면 웹뷰 생성
        if(getNetworkConnected(applicationContext) ) {

            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)

            webView = findViewById<WebView>(R.id.main_web_view)
            webView.webViewClient = WebViewClient()
            webView.webChromeClient = object : WebChromeClient(){
                override fun onShowFileChooser(webView: WebView?, filePathCallback: ValueCallback<Array<Uri>>?, fileChooserParams: FileChooserParams?): Boolean {
                    try{
                        webViewImageUpload = filePathCallback!!
                        var takePictureIntent : Intent?
                        takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
                        if(takePictureIntent.resolveActivity(packageManager) != null){
                            var photoFile : File?

                            photoFile = createImageFile()
                            takePictureIntent.putExtra("PhotoPath",cameraPath)

                            if(photoFile != null){
                                cameraPath = "file:${photoFile.absolutePath}"
                                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(photoFile))
                            }
                            else takePictureIntent = null
                        }
                        val contentSelectionIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
                        contentSelectionIntent.type = "image/*"

                        var intentArray: Array<Intent?>

                        if(takePictureIntent != null) intentArray = arrayOf(takePictureIntent)
                        else intentArray = takePictureIntent?.get(0)!!

                        val chooserIntent = Intent(Intent.ACTION_CHOOSER)
                        chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent)
                        chooserIntent.putExtra(Intent.EXTRA_TITLE,"사용할 앱을 선택해주세요.")
                        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray)
                        launcher.launch(chooserIntent)
                    }
                    catch (e : Exception){ }
                    return true
                }
            }

            webView.settings.javaScriptEnabled = true
            webView.addJavascriptInterface(bridge, "AndroidBridge")
            webView.getSettings().setUserAgentString("Chrome/56.0.0.0 Mobile");
            bridge.setListener(this)
            webView.loadUrl("https://test.com")
        } else {
            // 인터넷 연결 되어 있지 않을 때 (셀룰러/와이파이)
            showToast("인터넷 연결 상태를 확인해주세요.");
            finish() // Activity 종료
        }
    }

    fun createImageFile(): File? {
        @SuppressLint("SimpleDateFormat")
        val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
        val imageFileName = "img_" + timeStamp + "_"
        val storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
        return File.createTempFile(imageFileName, ".jpg", storageDir)
    }

 

블로그 방문해주셔서 감사합니다.

도움이 되셨으면 '좋아요'를 눌러주세요!
추가로 질문사항이 있으면 댓글 남겨주세요 :)


reference : https://onedaycodeing.tistory.com/154

광고 링크 : https://ogx4.com

 

인공지능이 추천하는 10개의 게임!

보물처럼 숨겨진 게임을 발굴하자! 게이머엑스포 유저들은 누구나 게임 발굴 대회에 참여 가능합니다. 게임 발굴 대회에서 입상한 게임은 2023 에디터 추천 TOP100에 노출됩니다. 단순 조회, 게임

ogx4.com

 

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.