인턴 당시 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 실행 순서
- 불러오기 버튼 클릭 시 Android 이미지 가져오는 함수 (Android.loadDevicePhoto)실행
- 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 실행 순서
- WebView를 띄울 Activity 준비 및 초기화
- Web에서 호출 시 디바이스에서 이미지 가져오는 Intent 실행
- 이미지를 가져온 뒤 uri → base64로 형식 변환
- 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 실행 순서
- 파일 불러오기 버튼 클릭 시 Android 파일 가져오는 함수 (Android.loadDevicePdfFile)실행
- 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 실행 순서
- WebView를 띄울 Activity 준비 및 초기화
- Web에서 호출 시 Intent로 디바이스 pdf 파일 가져오는 함수 실행
- pdf 파일 가져온 뒤 uri → base64로 전환
- 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를 웹뷰를 통해 웹에 전송하여 띄울 수 있다.