diff --git a/android/app/src/main/kotlin/io/openim/flutter/openim/MainActivity.kt b/android/app/src/main/kotlin/io/openim/flutter/openim/MainActivity.kt index db65e1f..7fc98c2 100644 --- a/android/app/src/main/kotlin/io/openim/flutter/openim/MainActivity.kt +++ b/android/app/src/main/kotlin/io/openim/flutter/openim/MainActivity.kt @@ -13,6 +13,7 @@ import android.util.Base64 import android.util.Log import android.webkit.ServiceWorkerController import android.webkit.WebSettings +import android.webkit.WebView import androidx.core.content.FileProvider import io.flutter.embedding.android.FlutterActivity import org.conscrypt.Conscrypt @@ -29,8 +30,10 @@ class MainActivity : FlutterActivity() { private val WEBVIEW_CACHE_CHANNEL = "io.openim.flutter.im_webview_app/webview_cache" private val TAG = "MainActivity" private val MAX_BRANDING_ICON_SIZE = 192 + private val WEBVIEW_DATA_DIRECTORY_SUFFIX = "h5_shell_fresh_profile_v2" override fun onCreate(savedInstanceState: android.os.Bundle?) { + configureWebViewDataDirectory() // 华为/荣耀/OPPO 等国产设备:在任意网络请求之前同步安装 Conscrypt,修复 SSL 握手失败(无 GMS 时系统 SSL 实现不完整) try { Security.insertProviderAt(Conscrypt.newProvider(), 1) @@ -133,6 +136,20 @@ class MainActivity : FlutterActivity() { } } + private fun configureWebViewDataDirectory() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + return + } + + try { + WebView.setDataDirectorySuffix(WEBVIEW_DATA_DIRECTORY_SUFFIX) + } catch (e: IllegalStateException) { + Log.w(TAG, "WebView data directory already initialized: ${e.message}") + } catch (e: Exception) { + Log.w(TAG, "WebView data directory configure failed: ${e.message}") + } + } + private fun readLongArgument(value: Any?): Long? { return when (value) { is Int -> value.toLong() diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 31cbefc..4de5d4d 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -38,10 +38,13 @@ class AppConfig { static String _normalizeHomeUrl(String host) { final value = host.trim(); - if (value.startsWith('http://') || value.startsWith('https://')) { - return value.endsWith('/') ? value : '$value/'; - } - return 'https://$value/'; + final normalized = + value.startsWith('http://') || value.startsWith('https://') + ? value + : 'https://$value'; + final uri = Uri.parse(normalized); + final path = uri.path.isEmpty ? '/' : uri.path; + return uri.replace(path: path).toString(); } static String _withShellBranding( diff --git a/test/widget_test.dart b/test/widget_test.dart index 5916b0f..f500283 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -11,16 +11,20 @@ void main() { ); }); - test('passes shell branding name and logo to the H5 URL', () { + test('loads the configured H5 root URL with shell branding', () { const logo = 'data:image/png;base64,abc+/='; final uri = Uri.parse( AppConfig.homeUrl(appName: 'Shell Test', appLogo: logo), ); + expect(uri.scheme, 'https'); + expect(uri.host, 'h5-im.imharry.work'); + expect(uri.path, '/'); expect(uri.queryParameters['flutter_shell'], '1'); expect(uri.queryParameters['shell_app_name'], 'Shell Test'); expect(uri.queryParameters.containsKey('shell_app_logo'), isFalse); + expect(uri.queryParameters['shell_cache_bust'], isNotEmpty); expect(uri.toString(), isNot(contains('%25'))); expect(Uri.splitQueryString(uri.fragment)['shell_app_logo'], logo); });