플랫폼별 통합

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 프로젝트는 별도 문의해 주세요.