Ich habe mir kurz angesehen, wie die Javascript-Oberfläche von WebView bei Verwendung von OkHttp aufgerufen wird.

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.

Implementierung der Aktivität mit WebView

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.

Serverseitige Implementierung

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"
~                                                    

Zusammenfassung des Kommunikationsflusses

Das Folgende ist eine Zusammenfassung des Kommunikationsflusses beim Zugriff auf den Stammendpunkt des oben genannten Webservers.

  1. root

  2. "submitFormByJs"

  3. "getJsBySrc"

  4. "doHttpReqFromJsCode.js"

  5. "fromXMLHttpReq"

  6. "onReceivedXHRResAndSetLocationHref"

  7. root Weiterleiten an "submitFormByJs" mit HTTP-Status 302

  8. "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>
~               
  1. "getJsBySrc" Durch Ausführen des Tags << script > wird der HTML-Code zurückgegeben, der eine GET-Anforderung an den Endpunkt "doHttpReqFromJsCode" sendet.

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>                                                                                                                                   
  1. "doHttpReqFromJsCode.js" XMLHttpReqeust antwortet auf Javascript, das die HTTP-Kommunikation zum Endpunkt "fromXMLHttpReq" durchführt. Wenn die obige Kommunikation erfolgreich ist, leitet location.href per GET-Kommunikation zum Endpunkt "onReceivedXHRResAndSetLocationHref" um.

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 }
  1. "fromXMLHttpReq" Der Zugriff erfolgt durch Ausführen von XMLHttpRequest in doHttpReqFromJsCode.js.

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>
  1. ""onReceivedXHRResAndSetLocationHref" Zugriff, wenn die XMLHttpRequest-Kommunikation von doHttpReqFromJsCode.js erfolgreich ist.

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>

Protokollausgabeergebnis

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

Zusammenfassung

Lassen Sie uns die obigen Ergebnisse zusammenfassen. (Aufgrund des Ergebnisses der Überprüfung in meiner lokalen Umgebung kann es zu Verhaltensunterschieden kommen.)

WebView shouldInterceptRequest wird aufgerufen

--HTTP-Kommunikation, die mit dem Tag << form > in HTML erfolgt --HTTP-Kommunikation, die in \

Recommended Posts

Ich habe mir kurz angesehen, wie die Javascript-Oberfläche von WebView bei Verwendung von OkHttp aufgerufen wird.
Ich habe mir die Ressourcen der Azure Container-Instanz angesehen
Ein kurzer Blick auf das Monty Hall-Problem
Ich möchte eine Verbindung herstellen, wenn eine Datenbank mit Spring und MyBatis erstellt wird
Ich erinnerte mich an Tribuo, das von Oracle veröffentlicht wurde. Tribuo - Eine Java-Vorhersagebibliothek (v4.0)