"修复Android 16兼容性问题
This commit is contained in:
parent
9d65a60df3
commit
3652eed44e
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="com.bonus.fmt">
|
package="com.bonus.fmt">
|
||||||
|
|
||||||
<!-- 网络相关功能 -->
|
<!-- 网络相关功能 -->
|
||||||
|
|
@ -17,13 +18,14 @@
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".FMTApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/loginlogo"
|
android:icon="@mipmap/loginlogo"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||||
|
tools:replace="android:name">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.bonus.fmt.BnsPandoraEntry"
|
android:name="com.bonus.fmt.BnsPandoraEntry"
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
@ -8,6 +8,9 @@ import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.core.app.ActivityCompat;
|
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.Permission;
|
||||||
import com.bonus.fmt.perm.XXPermissions;
|
import com.bonus.fmt.perm.XXPermissions;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import io.dcloud.PandoraEntry;
|
import io.dcloud.PandoraEntry;
|
||||||
|
|
||||||
public class BnsPandoraEntry extends PandoraEntry {
|
public class BnsPandoraEntry extends PandoraEntry {
|
||||||
|
private static final String TAG = "BnsPandoraEntry";
|
||||||
|
|
||||||
public BnsPandoraEntry() {
|
public BnsPandoraEntry() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
// 在super.onCreate之前初始化WebView设置(针对Android 16)
|
||||||
|
initWebViewForAndroid16();
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// 初始化DCloud WebView钩子
|
||||||
|
DCloudWebViewHook.hookDCloudWebView(this);
|
||||||
|
DCloudWebViewHook.configureGlobalWebViewSettings(this);
|
||||||
|
|
||||||
initPermission();
|
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(){
|
public void initPermission(){
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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上localStorage、WebSQL等存储功能失效的问题
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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 Storage(localStorage和sessionStorage)
|
||||||
|
settings.setDomStorageEnabled(true);
|
||||||
|
|
||||||
|
// 启用数据库存储API(WebSQL)
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in New Issue