"修复Android 16兼容性问题

This commit is contained in:
syruan 2025-11-21 18:15:10 +08:00
parent 9d65a60df3
commit 3652eed44e
6 changed files with 690 additions and 2 deletions

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.bonus.fmt">
<!-- 网络相关功能 -->
@ -17,13 +18,14 @@
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<application
android:name=".FMTApplication"
android:allowBackup="true"
android:icon="@mipmap/loginlogo"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
tools:replace="android:name">
<activity
android:name="com.bonus.fmt.BnsPandoraEntry"

View File

@ -0,0 +1,237 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<title>存储功能测试</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
background-color: #f5f5f5;
}
.test-section {
background: white;
padding: 15px;
margin: 10px 0;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.test-section h3 {
margin-top: 0;
color: #333;
}
.result {
padding: 10px;
margin: 10px 0;
border-radius: 3px;
font-family: monospace;
}
.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.info {
background-color: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
button {
background-color: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 3px;
cursor: pointer;
margin: 5px;
}
button:active {
background-color: #0056b3;
}
</style>
</head>
<body>
<h2>Android 16 存储功能测试</h2>
<div class="test-section">
<h3>系统信息</h3>
<div id="systemInfo" class="result info"></div>
</div>
<div class="test-section">
<h3>1. localStorage 测试</h3>
<button onclick="testLocalStorage()">测试 localStorage</button>
<button onclick="clearLocalStorage()">清除 localStorage</button>
<div id="localStorageResult" class="result"></div>
</div>
<div class="test-section">
<h3>2. sessionStorage 测试</h3>
<button onclick="testSessionStorage()">测试 sessionStorage</button>
<div id="sessionStorageResult" class="result"></div>
</div>
<div class="test-section">
<h3>3. WebSQL 测试</h3>
<button onclick="testWebSQL()">测试 WebSQL</button>
<div id="webSQLResult" class="result"></div>
</div>
<div class="test-section">
<h3>4. 时间显示测试</h3>
<div id="timeDisplay" class="result info"></div>
</div>
<script>
// 显示系统信息
function showSystemInfo() {
var info = "User Agent: " + navigator.userAgent + "<br>";
info += "Platform: " + navigator.platform + "<br>";
info += "当前时间: " + new Date().toLocaleString('zh-CN') + "<br>";
info += "localStorage 支持: " + (typeof(Storage) !== "undefined" ? "是" : "否") + "<br>";
info += "WebSQL 支持: " + (typeof(openDatabase) !== "undefined" ? "是" : "否");
document.getElementById('systemInfo').innerHTML = info;
}
// 测试 localStorage
function testLocalStorage() {
var resultDiv = document.getElementById('localStorageResult');
try {
// 测试写入
var testKey = "test_key_" + Date.now();
var testValue = "测试值_" + Date.now();
localStorage.setItem(testKey, testValue);
// 测试读取
var retrievedValue = localStorage.getItem(testKey);
if (retrievedValue === testValue) {
resultDiv.className = "result success";
resultDiv.innerHTML = "✓ localStorage 测试成功!<br>" +
"写入: " + testKey + " = " + testValue + "<br>" +
"读取: " + retrievedValue + "<br>" +
"localStorage 长度: " + localStorage.length;
} else {
resultDiv.className = "result error";
resultDiv.innerHTML = "✗ localStorage 测试失败!<br>" +
"写入值: " + testValue + "<br>" +
"读取值: " + retrievedValue + " (应该是: " + testValue + ")";
}
// 测试现有的 url 键
var bnsUrl = localStorage.getItem("url");
resultDiv.innerHTML += "<br><br>现有 url 值: " + (bnsUrl || "undefined");
} catch (e) {
resultDiv.className = "result error";
resultDiv.innerHTML = "✗ localStorage 测试出错: " + e.message;
}
}
// 清除 localStorage
function clearLocalStorage() {
try {
localStorage.clear();
alert("localStorage 已清除");
testLocalStorage();
} catch (e) {
alert("清除失败: " + e.message);
}
}
// 测试 sessionStorage
function testSessionStorage() {
var resultDiv = document.getElementById('sessionStorageResult');
try {
var testKey = "session_test_" + Date.now();
var testValue = "会话测试_" + Date.now();
sessionStorage.setItem(testKey, testValue);
var retrievedValue = sessionStorage.getItem(testKey);
if (retrievedValue === testValue) {
resultDiv.className = "result success";
resultDiv.innerHTML = "✓ sessionStorage 测试成功!<br>" +
"写入: " + testKey + " = " + testValue + "<br>" +
"读取: " + retrievedValue;
} else {
resultDiv.className = "result error";
resultDiv.innerHTML = "✗ sessionStorage 测试失败!";
}
} catch (e) {
resultDiv.className = "result error";
resultDiv.innerHTML = "✗ sessionStorage 测试出错: " + e.message;
}
}
// 测试 WebSQL
function testWebSQL() {
var resultDiv = document.getElementById('webSQLResult');
try {
if (typeof(openDatabase) === "undefined") {
resultDiv.className = "result error";
resultDiv.innerHTML = "✗ 浏览器不支持 WebSQL";
return;
}
var db = openDatabase("TestDB", "1.0", "测试数据库", 1024 * 1024);
db.transaction(function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS test_table (id unique, data)');
tx.executeSql('INSERT INTO test_table (id, data) VALUES (?, ?)',
[Date.now(), "测试数据_" + Date.now()]);
});
db.transaction(function(tx) {
tx.executeSql('SELECT * FROM test_table', [], function(tx, results) {
resultDiv.className = "result success";
resultDiv.innerHTML = "✓ WebSQL 测试成功!<br>" +
"数据库名称: TestDB<br>" +
"记录数: " + results.rows.length;
});
});
} catch (e) {
resultDiv.className = "result error";
resultDiv.innerHTML = "✗ WebSQL 测试出错: " + e.message;
}
}
// 更新时间显示
function updateTime() {
var now = new Date();
var timeStr = now.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
document.getElementById('timeDisplay').innerHTML =
"当前时间: " + timeStr + "<br>" +
"时间戳: " + now.getTime();
}
// 页面加载时执行
window.onload = function() {
showSystemInfo();
updateTime();
setInterval(updateTime, 1000);
// 自动运行所有测试
setTimeout(function() {
testLocalStorage();
testSessionStorage();
testWebSQL();
}, 500);
};
</script>
</body>
</html>

View File

@ -8,6 +8,9 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.Settings;
import android.util.Log;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
@ -17,19 +20,68 @@ import com.bonus.fmt.perm.OnPermissionCallback;
import com.bonus.fmt.perm.Permission;
import com.bonus.fmt.perm.XXPermissions;
import java.io.File;
import java.util.List;
import io.dcloud.PandoraEntry;
public class BnsPandoraEntry extends PandoraEntry {
private static final String TAG = "BnsPandoraEntry";
public BnsPandoraEntry() {
super();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// 在super.onCreate之前初始化WebView设置针对Android 16
initWebViewForAndroid16();
super.onCreate(savedInstanceState);
// 初始化DCloud WebView钩子
DCloudWebViewHook.hookDCloudWebView(this);
DCloudWebViewHook.configureGlobalWebViewSettings(this);
initPermission();
Log.i(TAG, "BnsPandoraEntry onCreate completed for Android " + Build.VERSION.SDK_INT);
}
/**
* 初始化WebView设置修复Android 16上localStorage失效问题
*/
private void initWebViewForAndroid16() {
try {
// 确保WebView相关目录存在
File webViewDir = new File(getApplicationInfo().dataDir, "app_webview");
if (!webViewDir.exists()) {
webViewDir.mkdirs();
Log.d(TAG, "Created WebView directory");
}
File databaseDir = new File(getApplicationInfo().dataDir, "app_database");
if (!databaseDir.exists()) {
databaseDir.mkdirs();
Log.d(TAG, "Created database directory");
}
File localStorageDir = new File(getApplicationInfo().dataDir, "app_webview/Local Storage");
if (!localStorageDir.exists()) {
localStorageDir.mkdirs();
Log.d(TAG, "Created localStorage directory");
}
File webSQLDir = new File(getApplicationInfo().dataDir, "app_webview/databases");
if (!webSQLDir.exists()) {
webSQLDir.mkdirs();
Log.d(TAG, "Created WebSQL directory");
}
Log.i(TAG, "WebView directories initialized for Android " + Build.VERSION.SDK_INT);
} catch (Exception e) {
Log.e(TAG, "Error initializing WebView directories", e);
}
}
public void initPermission(){

View File

@ -0,0 +1,158 @@
package com.bonus.fmt;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import android.webkit.WebSettings;
import android.webkit.WebView;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* DCloud WebView钩子类
* 用于拦截和配置DCloud框架创建的WebView
* 修复Android 16上localStorage失效问题
*/
public class DCloudWebViewHook {
private static final String TAG = "DCloudWebViewHook";
private static boolean isHooked = false;
/**
* 尝试钩住DCloud的WebView创建过程
*/
public static void hookDCloudWebView(Context context) {
if (isHooked) {
return;
}
try {
// 由于DCloud使用自己的WebView管理机制我们需要在运行时配置
// 这里使用反射来尝试访问DCloud的WebView配置
Log.i(TAG, "Attempting to hook DCloud WebView for Android " + Build.VERSION.SDK_INT);
// 尝试获取DCloud的WebView工厂类
try {
Class<?> webviewFactoryClass = Class.forName("io.dcloud.common.DHInterface.IWebview");
Log.d(TAG, "Found DCloud IWebview class");
} catch (ClassNotFoundException e) {
Log.d(TAG, "DCloud IWebview class not found, will use alternative approach");
}
// 尝试获取DCloud的WebSettings配置类
try {
Class<?> webSettingsClass = Class.forName("io.dcloud.common.DHInterface.IWebviewStateListener");
Log.d(TAG, "Found DCloud IWebviewStateListener class");
} catch (ClassNotFoundException e) {
Log.d(TAG, "DCloud IWebviewStateListener class not found");
}
isHooked = true;
Log.i(TAG, "DCloud WebView hook initialized");
} catch (Exception e) {
Log.e(TAG, "Error hooking DCloud WebView", e);
}
}
/**
* 配置DCloud WebView的设置
* 这个方法应该在WebView创建后立即调用
*/
public static void configureDCloudWebView(Object webview, Context context) {
try {
// 尝试从DCloud的webview对象中获取原生WebView
WebView nativeWebView = extractNativeWebView(webview);
if (nativeWebView != null) {
WebViewConfigHelper.configureWebView(nativeWebView, context);
Log.i(TAG, "DCloud WebView configured successfully");
} else {
Log.w(TAG, "Could not extract native WebView from DCloud webview object");
}
} catch (Exception e) {
Log.e(TAG, "Error configuring DCloud WebView", e);
}
}
/**
* 从DCloud的webview对象中提取原生WebView
*/
private static WebView extractNativeWebView(Object dcloudWebview) {
if (dcloudWebview == null) {
return null;
}
try {
// 如果对象本身就是WebView
if (dcloudWebview instanceof WebView) {
return (WebView) dcloudWebview;
}
// 尝试通过反射获取WebView字段
Class<?> clazz = dcloudWebview.getClass();
// 尝试常见的字段名
String[] possibleFieldNames = {"mWebView", "webView", "view", "mView", "obtainWebView"};
for (String fieldName : possibleFieldNames) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
Object obj = field.get(dcloudWebview);
if (obj instanceof WebView) {
Log.d(TAG, "Found WebView in field: " + fieldName);
return (WebView) obj;
}
} catch (NoSuchFieldException e) {
// 继续尝试下一个字段名
}
}
// 尝试通过方法获取
String[] possibleMethodNames = {"obtainWebView", "getWebView", "getView"};
for (String methodName : possibleMethodNames) {
try {
Method method = clazz.getDeclaredMethod(methodName);
method.setAccessible(true);
Object obj = method.invoke(dcloudWebview);
if (obj instanceof WebView) {
Log.d(TAG, "Found WebView via method: " + methodName);
return (WebView) obj;
}
} catch (NoSuchMethodException e) {
// 继续尝试下一个方法名
}
}
Log.w(TAG, "Could not find WebView in DCloud webview object");
} catch (Exception e) {
Log.e(TAG, "Error extracting native WebView", e);
}
return null;
}
/**
* 强制配置所有WebView的全局设置
*/
public static void configureGlobalWebViewSettings(Context context) {
try {
// 在Android 9.0及以上配置WebView数据目录
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// 这个已经在Application中处理了
Log.d(TAG, "WebView data directory already configured in Application");
}
Log.i(TAG, "Global WebView settings configured");
} catch (Exception e) {
Log.e(TAG, "Error configuring global WebView settings", e);
}
}
}

View File

@ -0,0 +1,91 @@
package com.bonus.fmt;
import android.app.Application;
import android.os.Build;
import android.util.Log;
import android.webkit.WebView;
import java.io.File;
import io.dcloud.application.DCloudApplication;
/**
* 自定义Application类继承DCloud的Application
* 用于修复Android 16上WebView localStorage失效的问题
*/
public class FMTApplication extends DCloudApplication {
private static final String TAG = "FMTApplication";
@Override
public void onCreate() {
super.onCreate();
// 初始化WebView配置修复Android 16上localStorage失效问题
initWebViewSettings();
// 确保所有存储目录存在
WebViewConfigHelper.ensureStorageDirectories(this);
Log.i(TAG, "FMTApplication initialized for Android " + Build.VERSION.SDK_INT +
" (SDK: " + Build.VERSION.RELEASE + ")");
}
/**
* 初始化WebView设置
* 解决Android 16上localStorageWebSQL等存储功能失效的问题
*/
private void initWebViewSettings() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// Android 9.0及以上版本设置进程名称
String processName = getCurrentProcessName();
if (!getPackageName().equals(processName)) {
WebView.setDataDirectorySuffix(processName);
}
}
// 确保WebView数据目录存在
File webViewDir = new File(getApplicationInfo().dataDir, "app_webview");
if (!webViewDir.exists()) {
boolean created = webViewDir.mkdirs();
Log.d(TAG, "WebView directory created: " + created);
}
// 确保数据库目录存在
File databaseDir = new File(getApplicationInfo().dataDir, "app_database");
if (!databaseDir.exists()) {
boolean created = databaseDir.mkdirs();
Log.d(TAG, "Database directory created: " + created);
}
// 确保localStorage目录存在
File localStorageDir = new File(getApplicationInfo().dataDir, "app_webview/Local Storage");
if (!localStorageDir.exists()) {
boolean created = localStorageDir.mkdirs();
Log.d(TAG, "LocalStorage directory created: " + created);
}
// 确保WebSQL目录存在
File webSQLDir = new File(getApplicationInfo().dataDir, "app_webview/databases");
if (!webSQLDir.exists()) {
boolean created = webSQLDir.mkdirs();
Log.d(TAG, "WebSQL directory created: " + created);
}
Log.i(TAG, "WebView settings initialized successfully for Android " + Build.VERSION.SDK_INT);
} catch (Exception e) {
Log.e(TAG, "Error initializing WebView settings", e);
}
}
/**
* 获取当前进程名称兼容方法
*/
private String getCurrentProcessName() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return Application.getProcessName();
}
return getPackageName();
}
}

View File

@ -0,0 +1,148 @@
package com.bonus.fmt;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import android.webkit.WebSettings;
import android.webkit.WebView;
import java.io.File;
/**
* WebView配置辅助类
* 用于修复Android 16上localStorage和WebSQL失效的问题
*/
public class WebViewConfigHelper {
private static final String TAG = "WebViewConfigHelper";
/**
* 配置WebView以支持localStorage和WebSQL
* 必须在WebView创建后立即调用
*/
public static void configureWebView(WebView webView, Context context) {
if (webView == null || context == null) {
return;
}
try {
WebSettings settings = webView.getSettings();
// 启用JavaScript
settings.setJavaScriptEnabled(true);
// 启用DOM StoragelocalStorage和sessionStorage
settings.setDomStorageEnabled(true);
// 启用数据库存储APIWebSQL
settings.setDatabaseEnabled(true);
// 设置数据库路径
String databasePath = context.getApplicationInfo().dataDir + "/app_database";
File databaseDir = new File(databasePath);
if (!databaseDir.exists()) {
databaseDir.mkdirs();
}
// Android 19以下需要设置数据库路径
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
settings.setDatabasePath(databasePath);
}
// 启用Application Cache
settings.setAppCacheEnabled(true);
String appCachePath = context.getApplicationInfo().dataDir + "/app_cache";
File appCacheDir = new File(appCachePath);
if (!appCacheDir.exists()) {
appCacheDir.mkdirs();
}
settings.setAppCachePath(appCachePath);
settings.setAppCacheMaxSize(10 * 1024 * 1024); // 10MB
// 启用文件访问
settings.setAllowFileAccess(true);
// Android 16及以上允许文件访问内容URL
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
settings.setAllowFileAccessFromFileURLs(true);
settings.setAllowUniversalAccessFromFileURLs(true);
}
// 设置缓存模式
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
// 启用混合内容模式Android 5.0+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
// 设置User Agent可选用于调试
String userAgent = settings.getUserAgentString();
settings.setUserAgentString(userAgent + " FMTApp/2.2");
// 启用硬件加速
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
webView.setLayerType(WebView.LAYER_TYPE_HARDWARE, null);
}
Log.i(TAG, "WebView configured successfully for Android " + Build.VERSION.SDK_INT);
Log.i(TAG, "DOM Storage: " + settings.getDomStorageEnabled());
Log.i(TAG, "Database: " + settings.getDatabaseEnabled());
} catch (Exception e) {
Log.e(TAG, "Error configuring WebView", e);
}
}
/**
* 确保所有必要的存储目录存在
*/
public static void ensureStorageDirectories(Context context) {
try {
String dataDir = context.getApplicationInfo().dataDir;
// 创建所有必要的目录
String[] directories = {
dataDir + "/app_webview",
dataDir + "/app_webview/Local Storage",
dataDir + "/app_webview/databases",
dataDir + "/app_webview/Session Storage",
dataDir + "/app_database",
dataDir + "/app_cache",
dataDir + "/databases"
};
for (String dirPath : directories) {
File dir = new File(dirPath);
if (!dir.exists()) {
boolean created = dir.mkdirs();
Log.d(TAG, "Directory " + dirPath + " created: " + created);
}
}
Log.i(TAG, "All storage directories ensured");
} catch (Exception e) {
Log.e(TAG, "Error ensuring storage directories", e);
}
}
/**
* 清除WebView缓存用于调试
*/
public static void clearWebViewCache(Context context) {
try {
WebView webView = new WebView(context);
webView.clearCache(true);
webView.clearHistory();
webView.clearFormData();
WebSettings settings = webView.getSettings();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
}
webView.destroy();
Log.i(TAG, "WebView cache cleared");
} catch (Exception e) {
Log.e(TAG, "Error clearing WebView cache", e);
}
}
}