Vintage appMaker의 Tech Blog

[Webview] 웹뷰에서 fileupload 구현하기 본문

Source code or Tip/Android(Java, Kotlin)

[Webview] 웹뷰에서 fileupload 구현하기

VintageappMaker 2021. 10. 3. 14:47

 

App을 만들다보면 생각보다 많은 부분에서 Webview를 사용해야 할 경우가 있다. 주로 서버에서 기존에 사용했던 mobile web이 존재할 경우인데, 이 때 가끔은 Fileupload를 구현해야 할 경우가 발생한다. 

 

Android Webview에서 Fileupload를 구현할 때는 아래와 같은 순서로 구현해야 한다. 

1. 파일 읽기/쓰기 퍼미션(이미지일 경우, 카메라도 포함)
   AndroidManifest.xml에서 정의하고 동적으로 구현해야 한다. 
2. setWebChromeClient()에서 파라메터로 onShowFileChooser()가 오버라이드된 
   WebChromeClient 객체를 넘긴다. 
3. onShowFileChooser()를 오버라이드할 때, 
   filePathCallback(ValueCallback<Array<Uri>>?)을 보관하고 
   startActivityForResult로 이미지나 파일정보를 가져온다. 
4. onActivityResult()에서 filePathCallback의 onReceiveValue()를 호출하며 
   가져온 Uri 정보를 넘긴다.

 


[XML 소스]

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="oftenutilbox.viam.psw.Test.WebViewActivity">

    <WebView
        android:background="@color/black"
        android:id="@+id/web_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

 

[Activity 소스]

package oftenutilbox.viam.psw.Test

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
import android.net.Uri
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.webkit.*
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import com.test.psw.oftenutilbox.R
import com.test.psw.oftenutilbox.databinding.ActivityWebViewBinding
import oftenutilbox.viam.psw.util.toast

class WebViewActivity : AppCompatActivity() {
    lateinit var binding: ActivityWebViewBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityWebViewBinding.inflate(layoutInflater)

        // 퍼미션 요청
        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            val b: Boolean = checkPermission()
            if (b == false) {
                requestPermission()
            } else {
                setUpUI()
            }

        } else {
            setUpUI()
        }

        setContentView(binding.root)
    }

    private fun setUpUI() {
        binding.apply {
            webView.apply {
                setBackgroundColor(Color.TRANSPARENT)
                webViewClient = object : WebViewClient() {
                    override fun onPageFinished(view: WebView?, url: String?) {
                        super.onPageFinished(view, url)
                    }

                    override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
                        // 주소파싱 후,
                        // 이동없음 -> true, 이동하기 -> false
                        return false
                    }
                }

                settings.apply {
                    javaScriptEnabled = true
                    builtInZoomControls = false
                    displayZoomControls = false
                    cacheMode = WebSettings.LOAD_DEFAULT
                    allowFileAccess = false
                    allowUniversalAccessFromFileURLs = false
                    allowFileAccessFromFileURLs = false
                    domStorageEnabled = true
                }

                addJavascriptInterface(AndroidJavascriptInterface(), "android")
                setWebChromeClient(MyChrome())

                val sUrl = "https://www.text-image.com/convert/ascii.html"
                loadUrl(sUrl)
            }
        }
    }

    final var FILE_CHOOSE : Int = 111
    fun takePicture(){
        Intent(Intent.ACTION_GET_CONTENT).apply {
            addCategory(Intent.CATEGORY_OPENABLE)
            setType("image/*")
            startActivityForResult(Intent.createChooser(this, ""), FILE_CHOOSE)
        }
    }

    var mFileChooserCallback : ValueCallback<Array<Uri>>? =null
    inner class MyChrome : WebChromeClient(){
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        override fun onShowFileChooser(
            webView: WebView?,
            filePathCallback: ValueCallback<Array<Uri>>?,
            fileChooserParams: WebChromeClient.FileChooserParams?
        ): Boolean {

            if (mFileChooserCallback !=null){
                mFileChooserCallback!!.onReceiveValue(null)
                mFileChooserCallback = null
            }

            mFileChooserCallback = filePathCallback
            takePicture()
            //return super.onShowFileChooser(webView, filePathCallback, fileChooserParams)
            return true
        }
    }

    inner class AndroidJavascriptInterface{
        @JavascriptInterface
        fun test(s:String){
            toast(s)
        }
    }

    // ******이 부분만 수정하면 됨 *********
    // 퍼미션 종류를 나열함.
    var PERMISSIONS = arrayOf(
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.CAMERA
    )

    private fun checkPermission(): Boolean {
        for (i in PERMISSIONS.indices) {
            val result = ActivityCompat.checkSelfPermission(applicationContext, PERMISSIONS[i])
            if (result != PackageManager.PERMISSION_GRANTED) return false
        }
        return true
    }

    private fun requestPermission() {
        ActivityCompat.requestPermissions(this, PERMISSIONS, REQUEST_CODE)
    }

    private val REQUEST_CODE = 1112
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        when (requestCode) {
            REQUEST_CODE -> if (grantResults.size > 0) {

                var bResult = true
                grantResults.forEach {
                    if ( it != PackageManager.PERMISSION_GRANTED){
                        bResult = false
                        return@forEach
                    }}

                if ( bResult){
                    Toast.makeText(this, "모든 기능을 사용가능합니다.", Toast.LENGTH_LONG).show()
                    // [TODO] 퍼미션을 모두 수락해야만 수행가능하게 하려고 한다면
                    // 초기화 코드를 이곳에서 진행한다.
                    setUpUI()

                }  else{
                    Toast.makeText(this, "필수기능을 거부하셨습니다. 사용상 제약이 있습니다.", Toast.LENGTH_LONG).show()
                    finish()
                }
            }
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        when(requestCode){
            FILE_CHOOSE -> {
                mFileChooserCallback!!.onReceiveValue(
                    if (data == null )
                        null
                    else{
                        if ( data!!.data == null ) null
                        else arrayOf<Uri>(data!!.data as Uri)
                    }
                )
            }
        }
    }
}
Comments