Android WebView a une fonction appelée * addJavascriptInterface * qui incorpore des objets Java dans WebView. Cela permet d'appeler la méthode définie dans l'objet Java à partir du Javascript embarqué dans la page Web affichée dans la WebView.
Cette fois, j'ai brièvement étudié comment la méthode de rappel de WebView est appelée lorsque OkHttp est utilisé pour la communication HTTP dans WebView. Si vous êtes fatigué de lire, vous ne pouvez lire que le "Résumé" final.
Le code source de l'activité qui utilise WebView ressemble à ceci. C'est mignon que l'exemple de code soit pauvre.
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()
}
}
Les points sont les suivants.
--Ajout de MainActivity en tant qu'interface Javascript avec le nom "Android" --Définissez une méthode appelée showToast afin qu'elle puisse être exécutée par Javascript à partir de WebView --Log est sorti lorsque chaque rappel est appelé, et la façon dont la communication HTTP est contrôlée est confirmée.
La mise en œuvre côté serveur est la suivante. Cette fois, il est supposé que le client accédera au point de terminaison racine.
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"
~
Ce qui suit est un résumé du flux de communication lors de l'accès au point de terminaison racine du serveur Web ci-dessus.
root
"submitFormByJs"
"getJsBySrc"
"doHttpReqFromJsCode.js"
"fromXMLHttpReq"
"onReceivedXHRResAndSetLocationHref"
root Redirection vers "submitFormByJs" avec le statut HTTP 302
"submitFormByJs" La balise \ <form > répond à html, qui envoie une requête POST au point de terminaison "getJsBySrc". La requête POST ci-dessus est exécutée lorsque WebView interprète ce HTML.
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>
La sortie du journal lorsque loadUrl ("http: // $ {server_ip}: 10000 /") est effectuée dans WebView est la suivante.
Balise "WebViewTest_Filtrer avec
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
Le résultat de sortie du journal de la partie de communication OkHttp est le suivant.
Tag "OkHttp_Filtrer avec "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
Résumons les résultats ci-dessus. (En raison du résultat de la vérification dans mon environnement local, il peut y avoir une différence de comportement.)