일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- MFC
- DynamicProgramming
- 동적프로그래밍
- 제곱근
- 리틀포레스트
- synergy
- darkmode
- WebView
- Dialog
- SPI
- 피요모리2
- 보늬밤
- 형변환
- math
- FirebaseAuth
- LRU
- 쌓기게임
- 피보나치
- Kotlin
- android
- stack
- DataStructure
- devicedriver
- memory
- 코인거스름돈
- QoS
- Dokka
- AfxMessageBox
- Collection
- Java
- Today
- Total
퉁탕퉁탕 만들어보자
Android Webview Javascript interface 본문
https://chromium.googlesource.com/chromium/src/+/refs/heads/main/android_webview/docs/java-bridge.md
WebView docs (go/webview-docs) - WebView Java Bridge (WebView#addJavascriptInterface())
WebView Java Bridge (WebView#addJavascriptInterface()) Overview This page explains ideas behind the Java ↔ JavaScript bridge implementation. This is to ensure that important use cases and scenarios, which must be preserved regardless of how the bridge is
chromium.googlesource.com
위 문서를 번역한 글입니다. (임의 번역, 수정.삭제 존재)
Webview로 java ↔ javascript를 연결하는 작업을 해야하는경우가 있다.
방법은 매우 간단하다.
// in Java
webView.addJavascriptInterface(new MyObject(), "myObject");
이름을 정해서 Javascript에서 사용될 object를 webview에 inject 해준다.
이름을 가지고 다시 뗄수도 있다.
// in Java
webView.removeJavascriptInterface("myObject");
동작 중 주의해야할 점은 다음과 같다.
- webview에 이미 페이지가 떠있는 상태에서 addJavascriptInterface를 한 경우, 페이지가 새로 로드되기 전까지는 접근이 안된다.
- @JavascriptInterface 어노테이션이 붙은 함수들만 js단에서 접근이 가능하고 field들에는 접근이 안된다.
- java object의 함수를 콜하면 이게 WebView의 private한 background thread 에서 수행되며, 이 동안에는 UI thread는 블락된다.
js <-> java로 Object가 오갈때는 다음 값들만 가능하다.
- primitive values
- 1차원 배열
- “array-like” JavaScript objects ( “length” property를 가진 애들. ES6의 typed array도 가능)
- 이전에 inject되었던 Java Object (JS -> Java)
- 새로운 Java objects (from Java -> JS), addJavascriptInterface가 콜되는 것처럼 JavaScript에 "주입"되지만 이름을 제공하지 않는다. 또한 이러한 일시적인 object 의 수명 주기는 inject된 object와 다르다. (아래 참조)
Java -> JS로 inject된 object의 수명은, java단에서 reference가 없어도 removeJavascriptInterface() 함수가 콜 되기 전까지 GC되지 않는다.
만약에 java -> js로 object를 넘겨준다고 해보자.
// in Java
class MyObject {
class Handler {
@JavascriptInterface
public void printLog() {
Log.d("Handler", "i'm handler");
}
}
@JavascriptInterface
public Object getHandler() { return new Handler(); }
}
// in JavaScript
{
...
let userInfo = myObject.getUserInfo();
...
}
Java VM과 JavaScript VM은 독립적이며 서로의 존재를 모르는 상태다. 서로 다른 프로세스에서 작동할 수 있고, 이론상으로는 서로 다른 물리적 기계에서도 작동할 수 있다. 그래서 지금 위 그림상으로는 서로 객체를 참조하고 있는것처럼 보이지만 진짜 참조가 아니고 실제로는 "Java Bridge" 에서 필요한 기간동안 Java 객체를 보유하고 있다.
자바 Bridge가 어떻게 연결되어있는지 보자.
Bridge의 Java 단은 WebView 클래스의 인스턴스에 밀접하게 연결되어 있고, Bridge의 JavaScript 쪽은 HTML 렌더링 엔진에 바인딩되어 있다. 아래 그림을 보면 둘은 IPC로 통신을 하고있다.
Java VM (연두색)
그림에 나온 WebView는 android.webkit.WebView 클래스고, Chromium 렌더링 머신와 상호 작용한다. WebView는 주입된 Object들의 GC을 방지하기 위해 이 모든 객체를 보유하는 Set<Object>(reference set로 칭함)를 갖고있다.
또 WebView 클래스는 WebContents라는 C++ 객체를 관리하고있는데, Java Bridge 구현이 C++에 있으므로 reference set는 실제로 C++ 측에서 관리한다.(JavaBridgeHost) native쪽의 객체와 상호 순환 참조를 생성하고 WebView 인스턴스가 GC되는 것을 방지하고 있기 때문에, strong references를 하고있지는 않다.(?)
C++ 브라우저 측 (상단 분홍색)
여기에 앞서 언급한 WebContents 객체가 있다. 이 WebContents객체는 Java Bridge 관련 요청을 JavaBridgeHost에 위임한다. WebContents는 Chromium의 IPC 메커니즘을 통해 렌더러 측의 객체와 통신한다.
C++ 렌더러 측 (하단 분홍색)
RenderFrame은 단일 HTML 프레임에 해당하며 JavaScript global context object (일명 window)를 "소유"한다. 각각의 JavaScript 인터페이스 object에 대해 해당 JavaBridgeObject 인스턴스가 유지 관리된다. Chromium 용어로 이 object를 “wrapper" 라고 부른다. Gin 기반 구현에서 wrapper는 memory leak를 방지하기 위해 해당 JavaScript 인터페이스 object에 대한 strong references를 보유하지 않는다. wrapper는 해당 JavaScript Object 가 GC 된 후 JavaScript VM에서 알림을 받게된다.
Javascript VM (하늘색)
Global Context로 addJavascriptInterface()로 주입된 myObject에 대한 참조와, local Context로 리턴값으로 받은 Object의 참조를 갖고있다.
그런데 WebView는 여러 frame (일반적으로 iframe tag를 사용하여 삽입)으로 구성된 복잡한 HTML 문서를 로드할 수 있다. 실제로 이러한 각 프레임에는 고유한 global context 가 있다.
Java Bridge 규칙에 따라 명명된 각 object(여기 예는 myObject) 는 모든 프레임의 context 에 주입된다. 따라서 WebView에 HTML 문서를 로드한 다음 기본 문서와, 모두에서 위의 호출을 반복한다고 했을때 다음 그림과 같다.
MyObject.getHandler()는 매번 새로운 Handler 인스턴스를 반환하기 때문에 Handler 인스턴스가 프레임당 하나씩 두 개 있지만 MyObject 인스턴스는 여전히 하나만 있다.
getHandler는 매번 동일한 Handler 인스턴스를 반환하는 경우에도, 이를 참조하는 여러 JavaScript 인터페이스를 갖게 된다. 따라서 일시적인 Java Object 는 해당 JavaScript 인터페이스 객체가 하나 이상 있을 때까지 Java Bridge에서 활성 상태로 유지한다. Java 측에서는 반환하는 단일 Handler 인스턴스에 대한 weak reference 만 유지할 수 있으므로 Java Bridge는 자체의 strong reference 를 유지한다.
수명 주기 주제를 요약하기 위해 다음은 Java Bridge의 관점에서 본 Java Object 수명 주기의 상태 다이어그램이다.
Arguments and Return Values Conversions
Java Bridge는 세 가지 주요 문제가 있다.
- Java primitive types 은 JavaScript type 과 다름. JavaScript에는 "Number" 타입만 있는 반면 Java는 다양한 숫자 유형이 있다. JavaScript에는 'null'과 'undefined'도 있다. JavaScript에는 String 키도 가질 수 있는 "array-like" 객체가 있다.
- Java 메소드는 고정된 수의 인수를 허용하고 오버로드될 수 있는 반면, JavaScript 메소드는 임의의 수의 인수를 허용하므로 오버로드될 수 없다.
- Java object 는 Java 메서드에서 반환될 수 있으며 이전에 삽입된 Java object는 JavaScript 인터페이스 메서드로 다시 전달할 수 있다.
---
1. 첫 번째 문제는 유형 변환은 Sun Live Connect 2 사양에 설명되어 있다. (https://www.oracle.com/java/technologies/javase/liveconnect-docs.html) - 참고: 안드로이드에서 맞지않음
유일한 문제는 Java Bridge가 사양을 밀접하게 따르지 않는다는 것이다(^^). 이러한 편차는 Java Bridge 코드 및 테스트에서 LIVECONNECT_COMPLIANCE로 표시된다.. 적당히 참고
2 JavaScript "array-like" 객체를 Java 배열로 강제 변환할 때 색인된 속성만 보존되고 명명된 속성은 제거된다.
또한 인터페이스 메서드를 통해 임의의 JavaScript dictionary object를 전달하는 것은 불가능. 단순히 0, "" 또는 null로 변환된다 (대상 Java 유형에 따라 다름).
3. method overloading 을 처리하기 위해 사양은 "가장 적합한" Java overload method가 선택되는 방식이다.
Android Java Bridge 는 다음과 같이 구현되어있다. (1) 인터페이스 메소드에 전달된 실제 매개변수 수와 일치하는 인수 수로 임의의 오버로드된 메소드를 선택한 다음, (2) 대상 Java 유형으로 전달된 각 값을 강제 변환하려고 시도한다. 인수 개수가 일치하는 메서드가 없으면 메서드 호출이 실패한다.
객체에 대한 참조를 전달할 때의 문제는 Java 객체와 JavaScript 인터페이스 간의 대응 관계를 유지하는 것이다. 흥미롭게도 NPAPI 기반 Java Bridge 구현은 메서드에서 Java 개체를 반환할 때 제대로 수행하지 못했다.
// in Java
class MyObject {
@JavascriptInterface
public Object self() { return this; }
}
...
webView.addJavascriptInterface(new MyObject(), "myObject");
JavaScript의 다음 === 검사는 NPAPI 구현에서는 실패한다.
// in JavaScript
myObject.self() === myObject;
이는 NPAPI Java Bridge 구현이 개체가 반환될 때마다 새 JavaScript 래퍼를 생성하기 때문인데, 이 문제는 Gin 기반 구현에서 수정되었다.
Threading
주입된 개체의 메서드 호출을 처리할 때 thread 문제를 고려해야 한다. API 정의에 따라 WebView에서 유지 관리하는 전용 스레드에서 메서드가 호출된다.
인터페이스 메서드에 대한 호출은 synchronous 이다. JavaScript VM은 중지되고 호출된 메서드에서 결과가 반환될 때까지 기다린다. Chromium에서 렌더러에서 브라우저로 보낸 IPC 메시지가 synchronous 라는 의미다.
백그라운드 스레드에서 요청을 처리하기 위한 요구 사항은 다음 코드가 작동해야 함을 의미한다.(https://crbug.com/438255 참조).
// in Java
class Foo {
@JavascriptInterface
void bar() {
// signal the object
}
}
webview.addJavascriptInterface(new Foo(), "foo");
webview.loadUrl("javascript:foo.bar()");
// wait for the object
이를 수행하려면 브라우저 UI thread 가 renderer의 요청 처리에 관여하지 않아야 한다.
Security Issues
처음부터 Java Bridge는 그다지 안전하지 않았다. JellyBean MR1(API 레벨 17)까지 삽입된 Java 객체는 java.lang.Object 메소드를 포함한 모든 메소드가 JavaScript에 노출되었다.; Jesus... 특히 getClass()는 JavaScript에서 모든 시스템 명령을 실행하는 멋진 방법을 제공했다.. WOW?
// in JavaScript
function execute(bridge, cmd) {
return bridge.getClass().forName('java.lang.Runtime')
.getMethod('getRuntime',null).invoke(null,null).exec(cmd);
}
JB MR1에서 @JavascriptInterface 주석은 JavaScript에 노출되도록 허용된 메소드를 명시적으로 표시하기 위해 도입되었다. 그러나 이 제한은 API 레벨 17 이상을 대상으로 하는 애플리케이션에만 적용되므로 새 Android 버전에서도 이전 앱은 보안이 유지되지 않았다. 이를 수정하기 위해 KitKat MR2에서는 모든 애플리케이션에 대해 java.lang.Object의 getClass를 호출하는 것을 금지하고 있다.
다음 문제는 주입된 Java 객체가 프레임 간에 공유된다는 사실에서 비롯된다. 이를 통해 격리된 프레임(예: 교차 출처 정책으로 인해)이 상호 작용할 수 있다. 예를 들어, 주입된 객체에 'storePassword' 및 'getPassword' 메서드가 있는 경우 한 프레임에 저장된 비밀번호를 다른 프레임에서 검색할 수 있다. 헐~ 이를 방지하려면 객체 자체를 주입하는 대신 상태 비저장 팩토리를 주입해야 하므로 각 프레임은 고유한 Java Object 들을 생성해서 사용한다.
더많은 자료는 아래에!
https://chromium.googlesource.com/chromium/src/+/refs/heads/main/android_webview/docs/
android_webview/docs - chromium/src - Git at Google
chromium.googlesource.com
'Computer > Android' 카테고리의 다른 글
ImageButton Ripple 주기 (0) | 2022.07.29 |
---|---|
Android broadcast 수신 (0) | 2022.05.09 |
View Binding (0) | 2022.05.08 |
Android Rendering? (0) | 2022.05.08 |
Android View LifeCycle (0) | 2022.05.07 |