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 6b5230d..6fe1592 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 @@ -11,34 +11,22 @@ import android.net.Uri import android.os.Build import android.util.Base64 import android.util.Log -import android.webkit.ServiceWorkerController -import android.webkit.WebSettings -import android.webkit.WebStorage -import android.webkit.WebView import androidx.core.content.FileProvider import io.flutter.embedding.android.FlutterActivity import org.conscrypt.Conscrypt import java.security.Security import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel -import io.flutter.plugins.webviewflutter.WebViewFlutterAndroidExternalApi import java.io.ByteArrayOutputStream import java.io.File class MainActivity : FlutterActivity() { private val CHANNEL = "io.openim.flutter.openim/apk_info" private val SHELL_BRANDING_CHANNEL = "io.openim.flutter.im_webview_app/shell_branding" - 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_v5" - private val WEBVIEW_STORAGE_RESET_PREFS = "h5_shell_webview_storage" - private val WEBVIEW_STORAGE_RESET_KEY = "reset_version" - private val WEBVIEW_STORAGE_RESET_VERSION = 5 override fun onCreate(savedInstanceState: android.os.Bundle?) { - configureWebViewDataDirectory() - clearWebViewPersistentStorageOnce() // 华为/荣耀/OPPO 等国产设备:在任意网络请求之前同步安装 Conscrypt,修复 SSL 握手失败(无 GMS 时系统 SSL 实现不完整) try { Security.insertProviderAt(Conscrypt.newProvider(), 1) @@ -119,109 +107,6 @@ class MainActivity : FlutterActivity() { } } } - MethodChannel(flutterEngine.dartExecutor.binaryMessenger, WEBVIEW_CACHE_CHANNEL).setMethodCallHandler { call, result -> - when (call.method) { - "configureNoCache" -> { - val webViewId = readLongArgument(call.argument("webViewId")) - if (webViewId == null) { - result.error("INVALID_ARGUMENT", "webViewId 不能为空", null) - return@setMethodCallHandler - } - - try { - result.success(configureWebViewNoCache(flutterEngine, webViewId)) - } catch (e: Exception) { - result.error("ERROR", "无法配置 WebView 缓存策略: ${e.message}", null) - } - } - else -> { - result.notImplemented() - } - } - } - } - - 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 clearWebViewPersistentStorageOnce() { - val prefs = getSharedPreferences(WEBVIEW_STORAGE_RESET_PREFS, MODE_PRIVATE) - if (prefs.getInt(WEBVIEW_STORAGE_RESET_KEY, 0) >= WEBVIEW_STORAGE_RESET_VERSION) { - return - } - - try { - WebStorage.getInstance().deleteAllData() - Log.d(TAG, "WebView persistent storage cleared") - } catch (e: Exception) { - Log.w(TAG, "WebView persistent storage clear failed: ${e.message}") - } - - clearKnownWebViewCacheDirectories() - prefs.edit().putInt(WEBVIEW_STORAGE_RESET_KEY, WEBVIEW_STORAGE_RESET_VERSION).apply() - } - - private fun clearKnownWebViewCacheDirectories() { - val cacheDirectoryNames = listOf( - "WebView", - "webview", - "org.chromium.android_webview", - "com.android.webview" - ) - - for (name in cacheDirectoryNames) { - val directory = File(cacheDir, name) - if (!directory.exists()) { - continue - } - try { - if (!directory.deleteRecursively()) { - Log.w(TAG, "WebView cache directory delete returned false: ${directory.absolutePath}") - } - } catch (e: Exception) { - Log.w(TAG, "WebView cache directory delete failed: ${directory.absolutePath}, ${e.message}") - } - } - } - - private fun readLongArgument(value: Any?): Long? { - return when (value) { - is Int -> value.toLong() - is Long -> value - is Number -> value.toLong() - else -> null - } - } - - private fun configureWebViewNoCache(flutterEngine: FlutterEngine, webViewId: Long): Boolean { - val webView = WebViewFlutterAndroidExternalApi.getWebView(flutterEngine, webViewId) ?: return false - - webView.settings.cacheMode = WebSettings.LOAD_NO_CACHE - webView.clearCache(true) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - try { - ServiceWorkerController - .getInstance() - .serviceWorkerWebSettings - .cacheMode = WebSettings.LOAD_NO_CACHE - } catch (e: Exception) { - Log.w(TAG, "ServiceWorker cache mode configure failed: ${e.message}") - } - } - - return true } private fun getCurrentAppName(): String { diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index a5fb4d8..21ded60 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -8,7 +8,7 @@ class AppConfig { static const String appLogo = ''; static const String canonicalWebHost = 'h5-im.imharry.work'; static const Set legacyWebHosts = { - 'h5-im.imharry.work', + 'h5-test.imharry.work', }; static final Map> _environmentHosts = { @@ -25,10 +25,7 @@ class AppConfig { final host = environmentHosts.isNotEmpty ? environmentHosts.first : canonicalWebHost; final uri = Uri.parse(_normalizeHomeUrl(host)); - final queryParameters = Map.from(uri.queryParameters) - ..['_h5_t'] = DateTime.now().millisecondsSinceEpoch.toString(); - - return uri.replace(queryParameters: queryParameters).toString(); + return uri.toString(); } static String withFreshShellParams(String url) { @@ -52,14 +49,10 @@ class AppConfig { return uri.toString(); } - final queryParameters = Map.from(uri.queryParameters) - ..['_h5_t'] = DateTime.now().millisecondsSinceEpoch.toString(); - return uri .replace( scheme: 'https', host: canonicalWebHost, - queryParameters: queryParameters, ) .toString(); } diff --git a/lib/main.dart b/lib/main.dart index 8e761f3..02d9bf3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -11,48 +11,8 @@ import 'config/app_config.dart'; const _shellBackground = Color(0xFFF8FBFF); const _shellAccent = Color(0xFF0089FF); const _shellSubText = Color(0xFF8E9AB0); -const _showH5DebugOverlay = bool.fromEnvironment( - 'H5_SHELL_DEBUG', - defaultValue: true, -); const _shellBrandingChannel = MethodChannel('io.openim.flutter.im_webview_app/shell_branding'); -const _h5MountedCheckScript = r''' -(() => document.body?.classList.contains('app-mounted') === true)(); -'''; -const _h5SnapshotScript = r''' -(() => { - const shrinkAssetUrl = (value) => { - try { - const absolute = new URL(value, window.location.href).href; - const match = absolute.match(/\/assets\/[^?#]+/); - return match ? match[0] : absolute; - } catch (_) { - return value || ''; - } - }; - - const scripts = Array.from(document.scripts) - .map((script) => script.src) - .filter(Boolean) - .map(shrinkAssetUrl) - .filter((src) => src.includes('/assets/')); - const bodyText = (document.body?.innerText || '') - .replace(/\s+/g, ' ') - .slice(0, 180); - let shellBrand = ''; - try { - shellBrand = window.sessionStorage.getItem('OPENIM_FLUTTER_SHELL_BRAND') || ''; - } catch (_) {} - - return JSON.stringify({ - href: window.location.href, - scripts, - bodyText, - shellBrand, - }); -})(); -'''; Future main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -137,7 +97,6 @@ class _H5ShellPageState extends State { int _progress = 0; String? _loadError; - String _h5DebugText = 'H5 loading...'; bool _showShellCover = true; bool _shellBrandingLoaded = false; late ShellBranding _shellBranding; @@ -202,9 +161,7 @@ class _H5ShellPageState extends State { Future _handlePageFinished() async { await _loadShellBrandingIfNeeded(); - await _waitForH5Mounted(); await _syncShellBranding(); - await _updateH5DebugSnapshot(); if (mounted) { setState(() { _progress = 100; @@ -247,58 +204,6 @@ class _H5ShellPageState extends State { return _runJavaScriptSafely(script); } - Future _waitForH5Mounted() async { - for (var index = 0; index < 20; index += 1) { - try { - final result = await _controller.runJavaScriptReturningResult( - _h5MountedCheckScript, - ); - if (result == true || result.toString() == 'true') { - return; - } - } catch (_) {} - await Future.delayed(const Duration(milliseconds: 100)); - } - } - - Future _updateH5DebugSnapshot() async { - if (!_showH5DebugOverlay) { - return; - } - - try { - final result = await _controller.runJavaScriptReturningResult( - _h5SnapshotScript, - ); - final snapshot = _decodeJavaScriptStringResult(result); - debugPrint('[H5Shell] snapshot: $snapshot'); - if (mounted) { - setState(() => _h5DebugText = snapshot); - } - } catch (error) { - debugPrint('[H5Shell] snapshot failed: $error'); - if (mounted) { - setState(() => _h5DebugText = 'H5 snapshot failed: $error'); - } - } - } - - String _decodeJavaScriptStringResult(Object? result) { - if (result == null) { - return ''; - } - if (result is String) { - try { - final decoded = jsonDecode(result); - if (decoded is String) { - return decoded; - } - } catch (_) {} - return result; - } - return result.toString(); - } - Future _loadHome() async { await _loadUrl(_freshHomeUrl()); } @@ -314,18 +219,9 @@ class _H5ShellPageState extends State { }); } - await _clearWebViewHttpCache(); await _controller.loadRequest(Uri.parse(targetUrl)); } - Future _clearWebViewHttpCache() async { - try { - await _controller.clearCache(); - } catch (_) { - // Some WebView implementations can reject cache operations before first use. - } - } - Future _handleNavigationRequest( NavigationRequest request, ) async { @@ -392,15 +288,6 @@ class _H5ShellPageState extends State { message: _loadError!, onRetry: () => unawaited(_loadHome()), ), - if (_showH5DebugOverlay) - Positioned( - left: 8, - right: 8, - bottom: 10, - child: IgnorePointer( - child: _H5DebugOverlay(text: _h5DebugText), - ), - ), ], ), ), @@ -450,36 +337,6 @@ class _ErrorPanel extends StatelessWidget { } } -class _H5DebugOverlay extends StatelessWidget { - const _H5DebugOverlay({required this.text}); - - final String text; - - @override - Widget build(BuildContext context) { - return DecoratedBox( - decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.72), - borderRadius: BorderRadius.circular(6), - ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), - child: Text( - text, - maxLines: 5, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: Colors.white, - fontSize: 10, - height: 1.25, - letterSpacing: 0, - ), - ), - ), - ); - } -} - class _ShellFallback extends StatelessWidget { const _ShellFallback(); diff --git a/test/widget_test.dart b/test/widget_test.dart index 308a061..ad2dbbf 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -51,7 +51,7 @@ void main() { test('rewrites legacy H5 host main-frame URLs to the canonical host', () { final uri = Uri.parse( AppConfig.canonicalizeMainFrameUrl( - 'https://h5-im.imharry.work/login?from=runtime' + 'https://h5-test.imharry.work/login?from=runtime' '&shell_app_name=Old#shell_app_logo=old', ), ); @@ -60,7 +60,6 @@ void main() { expect(uri.host, 'h5-im.imharry.work'); expect(uri.path, '/login'); expect(uri.queryParameters['from'], 'runtime'); - expect(uri.queryParameters.containsKey('_h5_t'), isTrue); expect(uri.queryParameters.containsKey('shell_app_name'), isFalse); expect(Uri.splitQueryString(uri.fragment).containsKey('shell_app_logo'), isFalse);