본문 바로가기

[Android] WebView와 React Web간 데이터 전달

@hyeon.s2024. 12. 18. 12:59

인턴 당시 Android 애플리케이션 내에서 WebView를 통해 React 기반 웹 페이지와 데이터를 주고받는 과정이 필요하여 공부하게 되었다. 이 글에서는 WebView에서 JavaScript 인터페이스를 등록하고 React 쪽에서 수신해 뷰어를 보여주는 방법을 살펴보고자 한다. 

WebView 설정 (이미지,PDF 파일 공통)

WebView 초기화 함수

 private fun initWebView() {
        with(binding.webView) {
            settings.javaScriptEnabled = true
            settings.allowFileAccess = true
            webViewClient = WebViewClient()
            addJavascriptInterface(
                WebAppInterface(this@WebViewActivity,
                    this@WebViewActivity
                ),
                "Android"
            )
            loadUrl(WEB_VIEW_URL) // 괄호 안에 웹뷰에 띄울 URL을 작성하시면 됩니다. 
        }
    }

WebAppInterface 클래스

  • React(Web)과 Android를 연결하는 인터페이스 클래스이다.
    • React에서 window.Android.laodDevicePhoto() 호출 시 안드로이드의 loadDevicePhoto()가 호출된다.
 class WebAppInterface(
        private val context: Context,
        private val activity: WebViewActivity
    ) {
        @JavascriptInterface
        fun loadDevicePhoto() {
            activity.runOnUiThread {
                activity.pickDeviceImage()
            }
        }

        @JavascriptInterface
        fun loadDevicePdfFile() {
            activity.runOnUiThread {
                activity.pickDevicePdf()
            }
        }
    }

WebView에 디바이스 이미지 가져오기

React (Web) 코드

Web 실행 순서

  1. 불러오기 버튼 클릭 시 Android 이미지 가져오는 함수 (Android.loadDevicePhoto)실행
  2. Android에서 가져온 이미지(imageUrl) <img src= imageUrl> 로 설정
import React, { useEffect, useState } from 'react';

function LoadDevicePhoto() {

    **const [imageUrl, setImageUrl] = useState("/img/defaultImg.png")**
   
    useEffect(() => {
        **window.onImageSelected = (base64Image) => {
            setImageUrl(`data:image/jpeg;base64,${base64Image}`);
        };**
    }, []);

    **const loadPhoto** = () => {
        if (window.Android) {
            window.Android.loadDevicePhoto() 
            // Android의 loadDevicePhoto 함수 실행
        }
        else {
            console.log("android interface not available. ")
        }
    }

    return (
        <div style={{padding: '20px'}}>
            <h1>사진 불러오기</h1>
            <button onClick={**loadPhoto**}>불러오기</button> 
            <!-- 버튼 클릭시 loadPhoto 함수 실행 --> 
            <div>
                <br></br>
                <img src={imageUrl} 
                style={{width: '500px', height: '500px', objectFit: 'cover'}}/>
            </div>
        </div>
    )
}

export default LoadDevicePhoto;

Android (App) 코드

Android 실행 순서

  1. WebView를 띄울 Activity 준비 및 초기화
  2. Web에서 호출 시 디바이스에서 이미지 가져오는 Intent 실행
  3. 이미지를 가져온 뒤 uri → base64로 형식 변환
  4. evaluateJavascript() 함수를 통해 Web에 base64 이미지 데이터 전달

Intent로 가져온 이미지 처리

 fun getLoadImage() {
        loadDevicePhotoResultLauncher =
            registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
                if (result.resultCode == RESULT_OK) {
                    val data = result.data
                    val image = data?.let { handleImagePicking(it) }
                    binding.webViewNotice.post {
                        binding.webView
                            .evaluateJavascript("javascript:onImageSelected('${image}')", null)
                    }
                }
            }
            // 실행시킨 Intent 의 결과를 처리하는 함수로, handleImagePicking을 통해 이미지를 가져온다.
            // intent 에서 가져온 데이터 > base64로 전환 된 val image 값을
            // webview.evaluateJavascript()를 통해 Web에 전송시킨다.
    }

webview.evaluateJavascript(”javascript:onImageSelected(’${image}’)”, null) 의미

  • android 에서 javascript의 onImageSelected 함수에 image 데이터를 담아 실행시키겠다는 뜻이다.
  • 따라서 web은 useEffect로 해당 함수 실행을 받아, **setImageUrl**을 통해 imageUrl 변수에 값을 받아올 수 있다.
**// web코드** 
useEffect(() => {
        **window.onImageSelected = (base64Image) => {
            setImageUrl(`data:image/jpeg;base64,${base64Image}`);
        };**
    }, []); 

handleImagePicking(), convertImageUriToBase64()

  • intent를 통해 이미지 데이터 처리 > base64로 전환하는 함수들이다.
	private fun handleImagePicking(data: Intent): String {
        val selectedImage: Uri? = data.data
        val picturePath: String?
        
        if (selectedImage != null) {
            val filePathColumn = arrayOf(MediaStore.Images.Media.DATA)
            val cursor: Cursor? =
                contentResolver.query(selectedImage, filePathColumn, null, null, null)
            if (cursor != null && cursor.moveToFirst()) {
                val columnIndex = cursor.getColumnIndex(filePathColumn[0])
                if (columnIndex >= 0) {
                    picturePath = cursor.getString(columnIndex)
                    return convertImageUriToBase64(selectedImage!!)// 선택한 이미지 파일 경로
                } else {
                    // 예외 처리 로직 작성 (파일 경로가 유효하지 않은 경우)
                }
                cursor.close()
            } else {
                // 예외 처리 로직 작성 (선택한 이미지가 없는 경우)
            }
        }
        return ""
    } 

    private fun convertImageUriToBase64(uri: Uri): String {
        return try {
            contentResolver.openInputStream(uri)?.use { inputStream ->
                val bytes = inputStream.readBytes()
                Base64.encodeToString(bytes, Base64.NO_WRAP)
            } ?: ""
        } catch (e: IOException) {
            e.printStackTrace()
            ""
        }
    }

WebView에 PDF 파일 가져오기

React (Web) 코드

Web 실행 순서

  1. 파일 불러오기 버튼 클릭 시 Android 파일 가져오는 함수 (Android.loadDevicePdfFile)실행
  2. Android에서 onPdfUriReceived()base64EncodedPdf 담아서 Web에 데이터 전달
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Document, Page } from 'react-pdf';
import { pdfjs } from 'react-pdf';

function LoadDevicePdfFile() {

    const navigate = useNavigate();
    
    useEffect(() => {
        window.onPdfUriReceived = (base64EncodedPdf) => {
            if (base64EncodedPdf) {
                // Android에서 전송한 base64EncodedPdf를 받아 파일 처리 진행
            }
        };
        return () => {
            window.onPdfUriReceived = null;
        };
    }, []);

    const loadPdfFile = () => {
        if (window.Android) {
            window.Android.loadDevicePdfFile();
        } else {
            console.log('Android interface not available');
        }
    };

    return (
        <div>
             <h1>PDF 파일 불러오기</h1>
            <button onClick={loadPdfFile}>불러오기</button>
            <h2></h2>
        </div>
    )
}

export default LoadDevicePdfFile;

Android (App) 코드

Android 실행 순서

  1. WebView를 띄울 Activity 준비 및 초기화
  2. Web에서 호출 시 Intent로 디바이스 pdf 파일 가져오는 함수 실행
  3. pdf 파일 가져온 뒤 uri → base64로 전환
  4. evaluateJavascript() 함수를 통해 Web에 base64 파일 데이터 전달

Intent 로 가져온 pdf 파일 데이터 처리

    fun getLoadPdf() {
        loadDevicePdfResultLauncher =
            registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
                if (result.resultCode == RESULT_OK) {
                    val uri = result.data?.data
                    val inputStream = contentResolver.openInputStream(uri!!)
                    val bytes = inputStream?.readBytes()
                    val base64EncodedPdf = Base64.encodeToString(bytes, Base64.NO_WRAP)
                    binding.webViewNotice.post {
                        binding.webViewNotice.evaluateJavascript("javascript:onPdfUriReceived('$base64EncodedPdf')", null)
                    }
                }
            }
    }
  • uri → base64로 변환한 base64Encodedpdf 를 web에 전송한다.

이렇게하면, 안드로이드 디바이스에서 가져온 이미지나 pdf를 웹뷰를 통해 웹에 전송하여 띄울 수 있다. 

hyeon.s
@hyeon.s :: 개발로그
목차