またまた一年以上空いてしまった。
生きてました。
最近は、KotlinでのAndroidアプリもやってたりします。
Unityも手出し始めてて、もう何屋さんかわからなくなってる。
Unityの記事もそのうち書きたい。
さて、さっそく表記の話。
どういうことかというと、Kotlin(というかAndroid)のWebViewはキャッシュ関連のコントロールが少しめんどいので、その対応についてである。
早い話が、キャッシュがクリアできない問題だ。
KotlinのWebViewのキャッシュは基本強い。
デフォルトの設定では、キャッシュが存在する場合は間違いなくキャッシュを使用する。
サーバー側から新しい状態をレスポンスしてもお構い無しだ。
ただし、通常は、キャッシュを使わない設定をすれば解決する。
import android.webkit.WebSettings // 中略 webView.settings.cacheMode = WebSettings.LOAD_NO_CHACHE // webViewには、WebViewクラスのインスタンス等が格納されているとする
見た感じから分かる通り、キャッシュ使いません!設定をするのだが、
厄介なことに、AOSやデバイスによって、この設定が効かないパターンが存在する。
私自身、この、効かないパターンにより、デバイス別で現象が変わるという厄介な状況に追い込まれ、調査等を行っていった。
そして、ある一定の効果がある方法を編み出した。
まず一つは、事前にhttpリクエストでキャッシュを介さないアクセスをし、ファイル更新日で比較する手法だ。
下記のようになる。
(以下ソースコードは、WebViewを拡張したクラスでのソースコードとなる)
package プロジェクトパッケージ
import android.content.Context
import android.content.SharedPreferences
import android.util.AttributeSet
import android.util.Log
import android.webkit.WebView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.net.HttpURLConnection
import java.net.URL
import java.util.Calendar
import java.util.Locale
class WebViewExtends : WebView {
private var _cacheKey: String? = null
// 通常のWebViewと同じコンストラクタ群
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int)
: super(context, attrs, defStyleAttr, defStyleRes)
constructor(context: Context, cacheKey: String) : super(context) {
this._cacheKey = cacheKey
}
fun setCacheKey(key: String = ""){
this._cacheKey = key
}
private fun fetchLastModified(urlString: String, onResult: (String?) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
var connection: HttpURLConnection? = null
try {
val url = URL(urlString)
connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "HEAD"
val lastModified = connection.getHeaderField("Last-Modified")
// 結果をメインスレッドに返す場合
kotlinx.coroutines.withContext(Dispatchers.Main) {
onResult(lastModified)
}
} catch (e: Exception) {
e.printStackTrace()
kotlinx.coroutines.withContext(Dispatchers.Main) {
onResult(null)
}
} finally {
connection?.disconnect()
}
}
}
override fun loadUrl(url: String) {
fetchLastModified(url) { lastModified ->
// アプリ保持更新日と比較し、新しければアプリ内キャッシュクリア
if (lastModified != null) {
// 値を利用
val sharedPref: SharedPreferences = context.getSharedPreferences(
"cache_pref", // ファイル名(任意の文字列でOK)
Context.MODE_PRIVATE
)
val stringValue = sharedPref.getString(this._cacheKey, "default modi")
if (stringValue != lastModified) {
this.clearCache(true)
sharedPref.edit().putString(this._cacheKey, lastModified)
}
}
// 必ず親クラスのloadUrlを呼び出す
super.loadUrl(url)
}
}
}
キャッシュを介さないアクセスを事前にし、ファイル更新日によって、キャッシュをクリアするか、しないかをコントロールする手法だ。
比較的スマートにできていると思う。
ただし、弱点としては、アクセスが重複している点だ。
よって、下記の方法が望ましいように思う。
// 基本のインポート文定義は省略
class WebViewExtends : WebView {
private var _cacheKey: String? = null
// コンストラクターも省略
fun setCacheKey(key: String = ""){
this._cacheKey = key
}
// 10分区切りで丸めた日付文字列を付与
private fun appendTimestamp(url: String): String {
val ts = getRoundedTimestamp()
val separator = if (url.contains("?")) "&" else "?"
return "$url${separator}ts=$ts"
}
// 10分単位で丸めた日付文字列(例: 202507141750)を生成
private fun getRoundedTimestamp(): String {
val cal = Calendar.getInstance()
val year = cal.get(Calendar.YEAR)
val month = cal.get(Calendar.MONTH) + 1 // Calendar.MONTHは0始まり
val day = cal.get(Calendar.DAY_OF_MONTH)
val hour = cal.get(Calendar.HOUR_OF_DAY)
val minute = (cal.get(Calendar.MINUTE) / 10) * 10
// ゼロ埋めしてフォーマット
return String.format(Locale.US, "%04d%02d%02d%02d%02d", year, month, day, hour, minute)
}
override fun loadUrl(url: String) {
val setUrl = appendTimestamp(url)
super.loadUrl(setUrl)
}
}
要は、アクセスURLに対して、タイムスタンプパラメータを付与するというものだ。
(本記事では、10分おきのタイムスタンプとしている)
さしものKotlinのWebViewといえど、さすがにURLパラメータが違えば、新規アクセスとなる。
ただし、こちらの手法も弱点があり、キャッシュが使用される時間が決まった時間内のみとなる点だ。
だが、以上の手法であれば、キャッシュを使わない設定が効かないパターンでも有効だ。
KotlinというかAndroidは面白いね。
アプリ作る際も、Swift(Xcode)での開発よりはるかに自由度が高く作れるから、いろいろできそう。
ただ、その分、全部自分で作らなきゃいけない感があって、ハードルが高い印象はある・・・。
また、いろいろ記事書いていこうと思うよ。
Unityのノウハウ記事も書きたい。
ではまた。

コメントする