Android WebView verfügt über eine Funktion namens * addJavascriptInterface *, die Java-Objekte in WebView einbettet. Auf diese Weise können Sie die im Java-Objekt definierte Methode aus dem in die in WebView angezeigte Webseite eingebetteten Javascript aufrufen.
Dieses Mal habe ich kurz untersucht, wie die Rückrufmethode von WebView aufgerufen wird, wenn OkHttp für die HTTP-Kommunikation in WebView verwendet wird. Wenn Sie es leid sind zu lesen, können Sie nur die letzte "Zusammenfassung" lesen.
Der Quellcode von Activity, der WebView verwendet, sieht folgendermaßen aus. Es ist süß, dass der Beispielcode schlecht ist.
MainActivity.kt
class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "WebViewTest_"
const val TAG_OKHTTP = "OkHttp_WebViewTest"
const val TAG_TID = "Tid_webView"
const val JS_INTERFACE_NAME = "Android"
const val INITIAL_ENDPOINT = "http://${sever_ipAddress}:10000/"
}
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
initWebView()
}
@SuppressLint("JavascriptInterface", "SetJavaScriptEnabled")
fun initWebView() {
Log.d(TAG_TID, "MainActivity#initWebView called. tid = " + Thread.currentThread().id)
binding.webView.apply {
settings.javaScriptEnabled = true
addJavascriptInterface(this@MainActivity, JS_INTERFACE_NAME)
webViewClient = object: WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
Log.d(TAG, "shouldOverrideUrlLoading called. url = " + request?.url.toString())
return super.shouldOverrideUrlLoading(view, request)
}
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
Log.d(TAG, "onPageStarted called. url = " + url!!)
super.onPageStarted(view, url, favicon)
}
override fun onPageFinished(view: WebView?, url: String?) {
Log.d(TAG, "onPageFinished called. url = " + url!!)
super.onPageFinished(view, url)
}
override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
Log.d(TAG, "onReceivedError error = " + error!!)
super.onReceivedError(view, request, error)
}
override fun onLoadResource(view: WebView?, url: String?) {
Log.d(TAG, "onLoadResource called. url = " + url!!)
super.onLoadResource(view, url)
}
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
Log.d(TAG_TID, "shouldInterceptRequest called. tid = " + Thread.currentThread().id)
if (!request?.url.toString().endsWith("/favicon.ico")) {
Log.d(TAG, "shouldInterceptRequest. url = " + request?.url.toString())
}
val latch = CountDownLatch(1)
var res: InputStream? = null
val call = if (request!!.url.path!!.endsWith("/getJsBySrc") or
request.url.path!!.endsWith("/doHttpReqFromJsCode.js")
) {
createOkHttpClient().newCall(Request.Builder().url(request.url.toString()).method("POST", RequestBody.create(null, "hoge")).build())
} else {
createOkHttpClient().newCall(Request.Builder().url(request.url.toString()).build())
}
call.enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {
//Log.d(TAG_OKHTTP, "okhttp Callback#onFailure called. callUrl = " + call.request().url())
Log.d(TAG_OKHTTP, "okhttp Callback#onFailure called. error = " + e.message.toString())
latch.countDown()
}
override fun onResponse(call: Call, response: Response) {
//Log.d(TAG_OKHTTP, "okhttp Callback#onResponse called. callUrl = " + call.request().url())
Log.d(TAG_OKHTTP, "okhttp Callback#onResponse called. resUrl = " + response.request().url())
res = response.body()?.byteStream()
latch.countDown()
}
})
latch.await()
return WebResourceResponse(
"text/html", "UTF-8",
res
)
}
}
loadUrl(INITIAL_ENDPOINT)
}
}
private val cookieStore = HashMap<String, MutableList<Cookie>>()
fun createOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.addNetworkInterceptor { chain ->
Log.d(TAG_TID, "okhttp intercepted: tid = " + Thread.currentThread().id)
Log.d(TAG_OKHTTP, "okhttp intercepted: " + chain.request().url().toString())
chain.proceed(chain.request())
}
//.connectTimeout(1, TimeUnit.MILLISECONDS)
.cookieJar(object: CookieJar {
override fun saveFromResponse(url: HttpUrl, cookies: MutableList<Cookie>) {
cookieStore[url.host()] = cookies
}
override fun loadForRequest(url: HttpUrl): MutableList<Cookie> {
val cookies = cookieStore[url.host()]
return cookies ?: ArrayList()
}
})
.build()
}
@JavascriptInterface
fun showToast(str: String) {
Toast.makeText(this, str, Toast.LENGTH_LONG).show()
}
}
Die Punkte sind wie folgt.
Die Implementierung auf der Serverseite ist wie folgt. Diesmal wird davon ausgegangen, dass der Client auf den Root-Endpunkt zugreift.
app/Main.hs
1 {-# LANGUAGE OverloadedStrings #-}
2 module Main where
3
4 import Network.Wai.Middleware.Static
5 import Network.HTTP.Types.Status
6 import Web.Spock
7 import Web.Spock.Config
8
9 import Control.Monad.Trans
10 import qualified Data.Text as T
11
12 data MySession = MySession { msUserId :: Maybe String }
13
14 main :: IO ()
15 main = do
16 spockCfg <- defaultSpockCfg (MySession Nothing) PCNoDatabase ()
17 runSpock 10000 (spock spockCfg app)
18
19 app :: SpockM () MySession () ()
20 app = do
21 middleware $ staticPolicy (addBase "static")
22 get root $
23 (modifySession $ \sess -> sess { msUserId = Just "dummy" }) >> redirect "http://${server_ipAddress}:10000/submitFormByJs"
24 get ("submitFormByJs") $ do
25 sess <- readSession
26 case msUserId sess of
27 Nothing -> setStatus status401 >> text "No session"
28 Just _ -> (liftIO . readFile $ "static/submitFormByJs.html") >>= html . T.pack
29 post ("getJsBySrc") $
30 (liftIO . readFile $ "static/getJsBySrc.html") >>= html . T.pack
31 post ("doHttpReqFromJsCode.js") $
32 file "application/javascript" "static/doHttpReqFromJsCode.js"
33 get ("fromXMLHttpReq") $
34 (liftIO . readFile $ "static/fromXMLHttpReq.html") >>= html . T.pack
35 get ("onReceivedXHRResAndSetLocationHref") $
36 (liftIO . readFile $ "static/onReceivedXHRResAndSetLocationHref.html") >>= html . T.pack
37 get ("favicon.ico") $
38 file "image/png" "favicon.ico"
~
Das Folgende ist eine Zusammenfassung des Kommunikationsflusses beim Zugriff auf den Stammendpunkt des oben genannten Webservers.
root
"submitFormByJs"
"getJsBySrc"
"doHttpReqFromJsCode.js"
"fromXMLHttpReq"
"onReceivedXHRResAndSetLocationHref"
root Weiterleiten an "submitFormByJs" mit HTTP-Status 302
"submitFormByJs" Das Tag << form > antwortet auf HTML, das eine POST-Anforderung an den Endpunkt "getJsBySrc" sendet. Die obige POST-Anforderung wird ausgeführt, wenn WebView diesen HTML-Code interpretiert.
submitFormByJs.html
1 <html>
2 <body>
3 <script type="text/javascript">
4 const ua = window.navigator.userAgent
5 if (ua.includes("Android")) {
6 Android.showToast("A post request is going to be sent from a <form> tag.");
7 }
8 </script>
9 <script type="text/javascript">
10 function doPost() {
11 document.form1.method = "post";
12 document.form1.submit();
13 }
14 </script>
15 <form name="form1" action="http://${server_ip}:10000/getJsBySrc" method="post">
16 </form>
17 <h1>submitFormByJs</h1>
18 <script type="text/javascript">
19 doPost();
20 </script>
21
22 </body>
23 </html>
~
getJsBySrc.html
1 <html>
2 <body>
3 <h1>GetJSBySrc</h1>
4 <!-- <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> -->
5 <script type="text/javascript">
6 const ua = window.navigator.userAgent
7 if (ua.includes("Android")) {
8 Android.showToast("A javascript file is going to be loaded by <script ... src=...>");
9 }
10 </script>
11 <script type="text/javascript" src="http://${server_ip}:10000/doHttpReqFromJsCode.js"></script>
12 </body>
13 </html>
doHttpReqFromJsCode.js
1 const xhr = new XMLHttpRequest();
2
3 xhr.open('POST', 'http://${server_ip}:10000/fromXMLHttpReq.html');
4 xhr.send();
5
6 xhr.onreadystatechange = function() {
7 if(xhr.readyState === 4 && xhr.status === 200) {
8 const ua = window.navigator.userAgent
9 if (ua.includes("Android")) {
10 Android.showToast("doHttpReqFromJsCode.js");
11 }
12 location.href="http://${server_ip}:10000/onReceivedXHRResAndSetLocationHref"
13 }
14 }
fromXMLHttpReq.html
1 <html>
2 <body>
3 <h1>XMLHttpRequest</h1>
4 <script type="text/javascript">
5 const ua = window.navigator.userAgent
6 if (ua.includes("Android")) {
7 Android.showToast("XMLHttpRequest succeeded. location.href is going to be modified.");
8 }
9 </script>
10 </body>
11 </html>
onReceivedXHRResAndSetLocationHref.html
1 <html>
2 <body>
3 <script type="text/javascript">
4 const ua = window.navigator.userAgent
5 if (ua.includes("Android")) {
6 Android.showToast("location.href has been modified.");
7 }
8 </script>
9 <h1>Received XMLHttpRequest's response</h1>
10 </body>
11 </html>
Die Protokollausgabe bei loadUrl ("http: // $ {server_ip}: 10000 /") in WebView lautet wie folgt.
Tag "WebViewTest_Filtern mit
1 D/WebViewTest_: shouldInterceptRequest. url = http://192.168.100.151:10000/
2 D/WebViewTest_: onPageStarted called. url = http://192.168.100.151:10000/
3 D/WebViewTest_: JavascriptInterface method called. str = A post request is going to be sent from a <form> tag.
4 D/WebViewTest_: shouldInterceptRequest. url = http://192.168.100.151:10000/getJsBySrc
5 D/WebViewTest_: onPageFinished called. url = http://192.168.100.151:10000/
6 D/WebViewTest_: onPageStarted called. url = http://192.168.100.151:10000/getJsBySrc
7 D/WebViewTest_: shouldInterceptRequest. url = http://192.168.100.151:10000/doHttpReqFromJsCode.js
8 D/WebViewTest_: JavascriptInterface method called. str = A javascript file is going to be loaded by <script ... src=...>
9 D/WebViewTest_: shouldInterceptRequest. url = http://192.168.100.151:10000/fromXMLHttpReq.html
10 D/WebViewTest_: onPageFinished called. url = http://192.168.100.151:10000/getJsBySrc
11 D/WebViewTest_: JavascriptInterface method called. str = doHttpReqFromJsCode.js
12 D/WebViewTest_: shouldOverrideUrlLoading called. url = http://192.168.100.151:10000/onReceivedXHRResAndSetLocationHref
13 D/WebViewTest_: onPageStarted called. url = http://192.168.100.151:10000/onReceivedXHRResAndSetLocationHref
14 D/WebViewTest_: shouldInterceptRequest. url = http://192.168.100.151:10000/onReceivedXHRResAndSetLocationHref
15 D/WebViewTest_: JavascriptInterface method called. str = location.href has been modified.
16 D/WebViewTest_: onPageFinished called. url = http://192.168.100.151:10000/onReceivedXHRResAndSetLocationHref
Das Protokollausgabeergebnis des OkHttp-Kommunikationsteils lautet wie folgt.
Tag "OkHttp_Filtern mit "WebViewTest"
18 D/OkHttp_WebViewTest: okhttp intercepted: http://192.168.100.151:10000/
19 D/OkHttp_WebViewTest: okhttp intercepted: http://192.168.100.151:10000/submitFormByJs
20 D/OkHttp_WebViewTest: okhttp Callback#onResponse called. resUrl = http://192.168.100.151:10000/submitFormByJs
21 D/OkHttp_WebViewTest: okhttp intercepted: http://192.168.100.151:10000/getJsBySrc
22 D/OkHttp_WebViewTest: okhttp intercepted: http://192.168.100.151:10000/favicon.ico
23 D/OkHttp_WebViewTest: okhttp Callback#onResponse called. resUrl = http://192.168.100.151:10000/getJsBySrc
24 D/OkHttp_WebViewTest: okhttp Callback#onResponse called. resUrl = http://192.168.100.151:10000/favicon.ico
25 D/OkHttp_WebViewTest: okhttp intercepted: http://192.168.100.151:10000/doHttpReqFromJsCode.js
26 D/OkHttp_WebViewTest: okhttp Callback#onResponse called. resUrl = http://192.168.100.151:10000/doHttpReqFromJsCode.js
27 D/OkHttp_WebViewTest: okhttp intercepted: http://192.168.100.151:10000/fromXMLHttpReq.html
28 D/OkHttp_WebViewTest: okhttp Callback#onResponse called. resUrl = http://192.168.100.151:10000/fromXMLHttpReq.html
29 D/OkHttp_WebViewTest: okhttp intercepted: http://192.168.100.151:10000/favicon.ico
30 D/OkHttp_WebViewTest: okhttp Callback#onResponse called. resUrl = http://192.168.100.151:10000/favicon.ico
31 D/OkHttp_WebViewTest: okhttp intercepted: http://192.168.100.151:10000/onReceivedXHRResAndSetLocationHref
32 D/OkHttp_WebViewTest: okhttp Callback#onResponse called. resUrl = http://192.168.100.151:10000/onReceivedXHRResAndSetLocationHref
33 D/OkHttp_WebViewTest: okhttp intercepted: http://192.168.100.151:10000/favicon.ico
34 D/OkHttp_WebViewTest: okhttp Callback#onResponse called. resUrl = http://192.168.100.151:10000/favicon.ico
Lassen Sie uns die obigen Ergebnisse zusammenfassen. (Aufgrund des Ergebnisses der Überprüfung in meiner lokalen Umgebung kann es zu Verhaltensunterschieden kommen.)
--HTTP-Kommunikation, die mit dem Tag << form > in HTML erfolgt --HTTP-Kommunikation, die in \
Recommended Posts