[Android] 웹뷰 인터페이스를 통해 좀 많은 데이터 주고받기 1
by Yena Choi
Study Note
이 예제는 xml 웹뷰 기준으로 작성되었습니다. Accompanist의 Compose WebView 쓰려고 보니까 xml 웹뷰를 감싸고 있는 Wrapper 형태더라고요. 그렇다면 굳이…? 🤔
참고 : Accompanist WebView Document
웹뷰를 표시할 화면 만들기
안드로이드 프로젝트를 하나 만들고 전체 화면을 웹뷰로 채워봅시다.
후루룩 만든 MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var webview: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
webview = findViewById(R.id.webView)
}
}
후루룩 만든 activity_main.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=".MainActivity">
<WebView
android:id="@+id/testWebView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
테스트에 필요한 웹뷰 준비하기 (웹뷰가 이미 있다면 패스)
저는 웹알못이라 이 예제에 쓸 웹페이지를 만들 지식이 없습니다. 대신 Chat GPT 가 있죠. ~~핑거 프린세스~
우리는 이 html 파일을 안드로이드 프로젝트 내에 넣고, 로컬에서 웹페이지를 띄워 테스트를 할 겁니다.
app/src/main
경로에서 우클릭 > New > Directory 를 통해 assets
디렉토리를 하나 만들어줍니다. 거기에 아무이름.html
파일을 만들고 내용을 작성해봅시다. 저는 index.html 로 만들었습니다.
<!DOCTYPE html>
<html>
<head>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
flex-direction: column;
font-size: 24px; /* Adjust font size as needed */
}
button {
padding: 10px 20px;
font-size: 24px; /* Adjust font size as needed */
margin-top: 20px;
}
</style>
</head>
<body>
<p id="message">안녕 예나쓰</p>
<button id="button">웹으로 데이터 보내기</button>
<script>
document.getElementById("button").onclick = function() {
// Define action when button is clicked
}
</script>
</body>
</html>
다시 MainActivity 로 돌아와 onCreate의 맨 아래에 해당 경로의 페이지를 여는 코드를 추가하고 앱을 빌드하면 폰에서 이 웹뷰를 확인할 수 있습니다.
//MainActivity.kt
webview.loadUrl("file:///android_asset/index.html")
웹뷰 기본 세팅 하기
이제 메인 액티비티로 가서 웹뷰에서 범용적으로 쓰이는 세팅 값들을 추가할 것입니다. 아래의 값이 전부 다 필요하진 않지만, 최소한 javaScriptEnabled = true
는 하도록 합시다.
MainActivity 에 웹뷰 기본 세팅을 설정하는 함수를 하나 만듭니다.
@SuppressLint("SetJavaScriptEnabled")
private fun WebView.setDefaultSettings() {
val webSettings = this.settings
with(webSettings) {
// 자바스크립트를 활성화. 기본값 false// 워닝을 보기 싫으면 @SuppressLint("SetJavaScriptEnabled") 를 추가
javaScriptEnabled = true
// WebView가 JavaScript의 window.open() 함수 호출을 허용. 기본값 false
javaScriptCanOpenWindowsAutomatically = true
// 뷰포트 메타태그를 지원. 기본값 false
useWideViewPort = false
// 화면 크기에 맞춰 컨텐츠가 확대/축소되도록 함. 기본값 false
loadWithOverviewMode = true
// 화면에 맞추어 확대/축소. 기본값 true
setSupportZoom(true)
// 내장 줌 컨트롤을 사용할 수 있도록 함. 기본값 true
builtInZoomControls = true
// 줌 컨트롤을 화면에 표시하지 않음. 기본값 false
displayZoomControls = false
// 캐시 모드 설정. 기본값 LOAD_DEFAULT
cacheMode = WebSettings.LOAD_NO_CACHE
// 로컬 스토리지 활성화. 기본값 false
domStorageEnabled = true
}
}
웹뷰 인터페이스 붙이기
이제 웹뷰에 붙여줄 Interface를 만들어봅시다. 이 친구는 웹뷰에서 안드로이드 Native 함수를 호출할 수 있게 해주는 인터페이스 입니다. 이 인터페이스에 등록된 함수는 웹 페이지에서 직접적으로 호출할 수 있습니다. 이 인터페이스를 통해 “웹에서 모바일로” 데이터를 좀 보내볼 생각입니다.
- 주고 받을 데이터의 Wrapper class 만들기
- 웹 -> 모바일로 데이터를 받아서 처리하는 함수 만들기
- 통신을 위한 JavascriptInterface 만들기
- Activity에서 설정하고 로드하기
1. 주고 받을 데이터의 Wrapper class 만들기
먼저 Wrapper로 사용할 클래스를 하나 만들어줍니다. 그냥 String 형식으로 데이터를 주고 받아도 문제 없으나, 그것만으로는 모자랄걸…?? 이 예제처럼 웹 -> 모바일 통신 시 Int
, String
값이 하나씩 필요하다고 칩니다.
data class WebViewData(
val intValue: Int,
val stringValue: String,
)
2. 웹 -> 모바일로 데이터를 받아서 처리하는 함수 만들기
원래 안드로이드 예제에서 만만한게 토스트니 토스트를 띄워보는 것으로 하겠습니다.
private fun onReceiveJavascript(intValue: Int, stringValue: String) {
Toast.makeText(this, "int: $intValue, string: $stringValue", Toast.LENGTH_SHORT).show()
}
3. 통신을 위한 JavascriptInterface 만들기
웹과 통신은 json으로 하고, 이 클래스로 파싱해서 사용할 것입니다. 저는 Gson을 사용했습니다. JavascriptInterface 라는 이름의 클래스를 만들었습니다.
이 클래스를 만들 때 (Int, String) -> Unit 람다를 인자로 받아 웹에서 데이터를 받아올 때 사용할 것입니다.
//JavascriptInterface.kt
import android.webkit.JavascriptInterface
import com.google.gson.Gson
import java.net.URLDecoder
class JavascriptInterface(val onReceiveJavascript: (Int, String) -> Unit) {
@JavascriptInterface
fun messageFromWebToMobile(json: String?) {
var jsonDecoded = ""
if (json == null) {
return
} else {
try {
jsonDecoded = URLDecoder.decode(json, "UTF-8")
} catch (e: Exception) {
e.printStackTrace()
}
}
val data = Gson().fromJson(jsonDecoded, WebViewData::class.java)
onReceiveJavascript(data.intValue, data.stringValue)
}
}
4. Activity에서 설정하고 로드하기
javascriptInterface 객체를 하나 만들어주고 웹뷰에 추가합니다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val javascriptInterface = JavascriptInterface(::onReceiveJavascript)
webview = findViewById<WebView?>(R.id.webView).apply {
setDefaultSettings()
addJavascriptInterface(javascriptInterface, NAME_YENA_WEB_VIEW)
loadUrl("file:///android_asset/index.html")
}
}
override fun onDestroy() {
webview.apply {
removeJavascriptInterface(NAME_YENA_WEB_VIEW)
destroy()
}
super.onDestroy()
}
const val NAME_YENA_WEB_VIEW = "YenaWebView"
이대로 넣고 실행하면! 아무 일도 안 일어납니다. 웹뷰 수정을 안 했잖아… 저는 웹뷰 테스트를 위해 index.html 에 버튼 클릭 처리를 추가했습니다. 안드로이드에서 만든 JavascriptInterface name 값과 동일한 이름을 쓰셔야 작동합니다 (여기서는 YenaWebView)
<script>
document.getElementById("button").onclick = function() {
var jsonData = {
intValue: 1234,
stringValue: "테스트 그만하고 자라"
};
var jsonString = JSON.stringify(jsonData);
// Call Android function
YenaWebView.messageFromWebToMobile(jsonString);
}
</script>