플랫폼별 통합
Android WebView
Android 통합은 두 가지 방식이 있습니다. 대부분의 경우 방식 A(호스트 페이지 로드)를 권장합니다 — 웹에서 검증한 화면을 그대로 재사용하므로 앱·웹 동작이 항상 일치합니다.
| 방식 A — 호스트 페이지 로드 (권장) | 방식 B — 네이티브 직결 | |
|---|---|---|
| WebView가 여는 것 | 웹 SDK가 임베드된 고객사 페이지 | 뷰어 본체 /pdfv/index.html 직접 |
| 데이터 통신 | 페이지 JS의 onSave → 서버 API | @JavascriptInterface(이름 conn) + evaluateJavascript |
| 적합한 경우 | 웹 시스템이 이미 있는 경우 | 앱 단독·오프라인 우선, PDF가 앱 안에 있는 경우 |
방식 A — 호스트 페이지 로드 (권장)
MainActivity.kt kotlin
// MainActivity.kt — 권장: 웹 SDK를 임베드한 호스트 페이지를 로드
val webView = findViewById<WebView>(R.id.webView)
webView.settings.apply {
javaScriptEnabled = true // 필수
domStorageEnabled = true // 필수
loadWithOverviewMode = true
useWideViewPort = true
}
// 웹에서 검증한 페이지(JSP·PHP·React 등)를 그대로 재사용
webView.loadUrl("https://erp.example.com/contract-view?id=123")이 방식에서는 Android 측 추가 작업이 사실상 없습니다. 저장·버전 관리도 웹 페이지의 onSave 흐름을 그대로 사용합니다 — 저장과 버전 관리 참고.
방식 B — 네이티브 직결 (브리지)
뷰어를 직접 로드하고 네이티브 코드와 양방향 브리지로 통신합니다.
뷰어는 conn이라는 이름의 JavascriptInterface를 찾아 콜백을 보냅니다.
MainActivity.kt — 직결 모드 kotlin
// 네이티브 직결 모드 — 뷰어(index.html)를 직접 로드하고 브리지로 통신
val webView = findViewById<WebView>(R.id.webView)
webView.settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
allowFileAccess = true // 로컬 파일 로드 시
}
// 1) 뷰어 → 네이티브 콜백: 인터페이스 이름은 반드시 "conn"
class InkoBridge(private val activity: Activity) {
@JavascriptInterface
fun onSaveComplete(canvasDataJson: String) {
// 뷰어의 저장 버튼 → canvasData 수신. 자체 DB·서버에 저장
activity.runOnUiThread { saveToServer(canvasDataJson) }
}
@JavascriptInterface
fun closeViewer() {
activity.runOnUiThread { activity.finish() }
}
@JavascriptInterface
fun setOrientation(orientation: String) {
// "portrait" | "landscape" — 문서 방향에 맞춰 화면 회전 처리(선택)
}
}
webView.addJavascriptInterface(InkoBridge(this), "conn")
// 2) 뷰어 로드
webView.loadUrl("https://erp.example.com/pdfv/index.html")
// 3) 네이티브 → 뷰어: 페이지 로드 완료 후 전역 함수 호출
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
val pdfBase64 = loadPdfAsBase64() // 앱이 가진 PDF
val latestCanvas = loadLatestCanvasData() // 직전 저장본 (없으면 null)
view.evaluateJavascript(
"loadPdfBase64('" + pdfBase64 + "', 'contract.pdf', " +
(latestCanvas?.let { "'" + it + "'" } ?: "null") + ", false)",
null
)
}
}뷰어 → 네이티브 (conn 인터페이스)
| 메서드 | 호출 시점 |
|---|---|
onSaveComplete(canvasDataJson: String) | 뷰어 저장 시 — 편집 데이터 전달. 자체 DB·서버에 INSERT |
closeViewer() | 뷰어 내 닫기 동작 — Activity 종료 등 처리 |
setOrientation(orientation: String) | 문서 방향 전환 — "portrait" · "landscape" (선택 구현) |
네이티브 → 뷰어 (전역 함수, evaluateJavascript)
| 함수 | 설명 |
|---|---|
loadPdf(path, fileName, canvasData?, readOnly?) | 경로(WebView가 접근 가능한 URL·파일 경로)로 PDF 로드 |
loadPdfBase64(base64, fileName, canvasData?, readOnly?) | Base64 문자열로 PDF 로드 — canvasData를 주면 이어서 편집 |
getCanvasData() | 현재 편집 상태를 즉시 반환 (자동저장용) |
loadUserCanvasData(base64EncodedData) | 다중 사용자 레이어 주입 — 항목 배열 JSON을 Base64 인코딩해 전달 |
clearCanvas() | 현재 페이지 편집 초기화 |
직결 모드의 대용량 전달 한계
evaluateJavascript 문자열 인자로 수십 MB급 PDF를 넘기면 느려질 수 있습니다.
대용량 문서는 loadPdf(경로 방식)를 쓰거나, 방식 A로 서버 URL 로드를 권장합니다.
스타일러스 (S펜)
Inko의 펜 도구는 압력 감지를 지원합니다. 갤럭시 탭 S펜 등 스타일러스 입력의 필압이 WebView에서도 그대로 반영되므로, 태블릿 검토·서명 시나리오에 추가 작업이 필요 없습니다.
주의사항
- HTTPS — 운영 서버가 HTTP면 WebView 기본 정책에 막힐 수 있습니다. HTTPS를 사용하거나
usesCleartextTraffic정책을 검토하세요. - 뒤로가기 — 방식 B에서는
conn.closeViewer()콜백으로 종료 UX를 일관되게 처리하세요. - iOS — WKWebView에서 방식 A(호스트 페이지 로드)는 동일하게 동작합니다. 네이티브 직결이 필요한 iOS 프로젝트는 별도 문의해 주세요.