Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 76 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,27 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Android 10 (API 29) 及以上版本,可能需要添加 -->
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Android 10 (API 29) 及以上版本,可能需要添加 -->
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" /> <!-- Android 14+ 前台服务类型 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> <!-- 后台下载保活用 -->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> <!-- 请求电池优化豁免,防止后台被限制 -->
<uses-permission android:name="android.permission.CAMERA" /> <!-- 用于获取wifi状态 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!-- 使用系统自带下载器 -->
<uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" /> <!-- 自动安装apk -->
Expand Down Expand Up @@ -179,6 +187,11 @@
android:name=".ui.SettingsActivity"
android:configChanges="screenSize|uiMode"
android:theme="@style/AppTheme.Settings" />
<activity
android:name=".ui.SystemInfoActivity"
android:configChanges="screenSize|uiMode"
android:label="@string/settings_about_system_info"
android:theme="@style/AppTheme.Toolbar" />
<activity
android:name=".ui.ExcludedLanguagesActivity"
android:configChanges="screenSize|uiMode"
Expand Down Expand Up @@ -219,6 +232,24 @@
android:configChanges="screenSize|uiMode"
android:label="@string/my_tags"
android:theme="@style/AppTheme.Toolbar" />
<activity
android:name="com.hippo.ehviewer.ui.task.BackgroundTaskActivity"
android:configChanges="screenSize|uiMode"
android:label="@string/background_task_management"
android:theme="@style/AppTheme.Toolbar" />

<activity
android:name="com.hippo.ehviewer.ui.NetworkDiagnosticActivity"
android:configChanges="screenSize|uiMode"
android:label="@string/settings_advanced_network_diagnostic"
android:theme="@style/AppTheme.Toolbar" />

<activity
android:name="com.hippo.ehviewer.ui.task.BackgroundTaskDetailActivity"
android:configChanges="screenSize|uiMode"
android:label="@string/background_task_detail"
android:theme="@style/AppTheme.Toolbar" />

<activity
android:name=".ui.HostsActivity"
android:configChanges="screenSize|uiMode"
Expand All @@ -241,9 +272,53 @@
android:label="@string/wifi_client"
android:theme="@style/AppTheme.Toolbar" />

<activity
android:name="com.hippo.ehviewer.ui.transfer.TransferActivity"
android:configChanges="screenSize|uiMode"
android:label="@string/settings_transfer"
android:theme="@style/AppTheme.Toolbar" />

<service
android:name="com.hippo.ehviewer.transfer.TransferService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="dataSync" />

<activity
android:name="com.hippo.ehviewer.ui.DatabaseViewerActivity"
android:configChanges="screenSize|uiMode"
android:label="@string/settings_advanced_database_viewer"
android:theme="@style/AppTheme.Toolbar" />

<activity
android:name="com.hippo.ehviewer.ui.local.LocalGalleryActivity"
android:configChanges="screenSize|uiMode"
android:label="@string/local_gallery_title"
android:theme="@style/AppTheme.Toolbar" />

<activity
android:name="com.hippo.ehviewer.ui.local.LocalGalleryDetailActivity"
android:configChanges="screenSize|uiMode"
android:label="@string/local_gallery_detail_title"
android:theme="@style/AppTheme.Toolbar" />

<activity
android:name="com.hippo.ehviewer.ui.local.LocalGalleryViewerActivity"
android:configChanges="screenSize|uiMode"
android:label="@string/local_gallery_viewer_title"
android:theme="@style/AppTheme.Toolbar" />

<service
android:name="com.hippo.ehviewer.service.LocalGalleryScanService"
android:exported="false" />

<service
android:name="com.hippo.ehviewer.service.RecycleBinScanService"
android:exported="false" />

<service
android:name=".download.DownloadService"
android:foregroundServiceType="dataSync"
android:foregroundServiceType="dataSync|specialUse"
android:label="@string/download_service_label" />

<provider
Expand Down
127 changes: 126 additions & 1 deletion app/src/main/java/com/hippo/ehviewer/EhApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,12 @@
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.ehviewer.client.data.userTag.UserTagList;
import com.hippo.ehviewer.download.DownloadManager;
import com.hippo.ehviewer.download.DownloadLogger;
import com.hippo.ehviewer.DownloadedFileManager;
import com.hippo.ehviewer.spider.SpiderDen;
import com.hippo.ehviewer.ui.CommonOperations;
import com.hippo.ehviewer.util.MiuiOptimizationHelper;
import com.hippo.ehviewer.network.NetworkLogger;
import com.hippo.lib.image.Image;
//import com.hippo.lib.image.Image1;
//import com.hippo.lib.image.ImageBitmap;
Expand Down Expand Up @@ -84,6 +88,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
Expand All @@ -94,6 +99,7 @@
import javax.net.ssl.X509TrustManager;

import okhttp3.Cache;
import okhttp3.ConnectionPool;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import okhttp3.Response;
Expand Down Expand Up @@ -175,11 +181,15 @@ public void onCreate() {
GetText.initialize(this);
StatusCodeException.initialize(this);
Settings.initialize(this);
NetworkLogger.INSTANCE.init(this); // 初始化网络日志系统
ReadableTime.initialize(this);
Html.initialize(this);
AppConfig.initialize(this);
SpiderDen.initialize(this);
EhDB.initialize(this);
DownloadedFileManager.initialize(this);
DownloadLogger.initialize(this);
BackgroundTaskManager.initialize(this);
EhEngine.initialize();
BitmapUtils.initialize(this);
// Image1.initialize(this);
Expand Down Expand Up @@ -225,6 +235,18 @@ protected Void doInBackground(Void... voids) {
} catch (Throwable ignored) {
}

// 针对 HyperOS/Android 14+ 请求电池优化豁免
try {
if (MiuiOptimizationHelper.INSTANCE.needsAggressiveOptimization()
&& !MiuiOptimizationHelper.INSTANCE.isBatteryOptimizationExempted(EhApplication.this)) {
// 在后台线程检查,然后回到主线程请求豁免
SimpleHandler.getInstance().post(() -> {
MiuiOptimizationHelper.INSTANCE.requestBatteryOptimizationExemption(EhApplication.this);
});
}
} catch (Throwable ignored) {
}

return null;
}
}.executeOnExecutor(IoThreadPoolExecutor.Companion.getInstance());
Expand Down Expand Up @@ -289,6 +311,16 @@ public void onTrimMemory(int level) {

if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
clearMemoryCache();

// 刷新下载日志
try {
DownloadLogger logger = DownloadLogger.getInstance();
if (logger != null) {
logger.flushLogs();
}
} catch (Exception e) {
// 忽略异常,避免影响应用正常运行
}
}
}

Expand Down Expand Up @@ -377,13 +409,26 @@ public static OkHttpClient getOkHttpClient(@NonNull Context context) {
if (application.mOkHttpClient == null) {
// Dispatcher dispatcher = new Dispatcher();
// dispatcher.setMaxRequestsPerHost(4);

// 创建优化的连接池 - 针对后台长时间下载优化
// 增加空闲连接数和保活时间,避免后台切回时连接被回收后重建
ConnectionPool connectionPool = new ConnectionPool(
20, // 最大空闲连接数(从10提升到20,适应多线程下载)
10, // 连接保活时间(从5分钟提升到10分钟,防止后台短暂切换时断连)
TimeUnit.MINUTES
);

OkHttpClient.Builder builder = new OkHttpClient.Builder()
.followRedirects(true)
.followSslRedirects(true)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
// .callTimeout(10, TimeUnit.SECONDS)
.callTimeout(10, TimeUnit.SECONDS)
.connectionPool(connectionPool) // 优化的连接池
.retryOnConnectionFailure(true) // 连接失败时重试
// HTTP/2 连接使用 ping 保活,防止后台连接被 NAT/代理断开
.pingInterval(5, TimeUnit.MINUTES)
.cookieJar(getEhCookieStore(application))
.cache(getOkHttpCache(application))
// .hostnameVerifier((hostname, session) -> true)
Expand Down Expand Up @@ -413,6 +458,42 @@ public static OkHttpClient getOkHttpClient(@NonNull Context context) {
}
return response;
})
.addNetworkInterceptor(chain -> {
// 网络活动日志拦截器 - 记录所有 HTTP 请求/响应
if (!NetworkLogger.INSTANCE.getEnabled()) {
return chain.proceed(chain.request());
}
okhttp3.Request request = chain.request();
long startTime = System.currentTimeMillis();
okhttp3.Response response;
try {
response = chain.proceed(request);
long duration = System.currentTimeMillis() - startTime;
String logMsg = String.format(Locale.getDefault(),
"[%s] %s %s → %d (%dms, %dB)",
request.method(),
request.url().encodedPath(),
request.url().host(),
response.code(),
duration,
response.body() != null ? response.body().contentLength() : -1
);
NetworkLogger.INSTANCE.logOkHttp(logMsg);
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
String logMsg = String.format(Locale.getDefault(),
"[%s] %s %s → FAIL (%dms, %s)",
request.method(),
request.url().encodedPath(),
request.url().host(),
duration,
e.toString()
);
NetworkLogger.INSTANCE.logError(logMsg, e);
throw e;
}
return response;
})
.proxySelector(getEhProxySelector(application));
if (Settings.getDF() && AppHelper.checkVPN(context)) {
if (Build.VERSION.SDK_INT < 29) {
Expand Down Expand Up @@ -462,13 +543,20 @@ public static OkHttpClient getOkHttpClient(@NonNull Context context) {
public static OkHttpClient getImageOkHttpClient(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mImageOkHttpClient == null) {
// 图片下载也使用优化的连接池
ConnectionPool imageConnectionPool = new ConnectionPool(
10, 5, TimeUnit.MINUTES
);

OkHttpClient.Builder builder = new OkHttpClient.Builder()
.followRedirects(false)
.followSslRedirects(false)
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.callTimeout(20, TimeUnit.SECONDS)
.connectionPool(imageConnectionPool)
.pingInterval(5, TimeUnit.MINUTES)
.cookieJar(getEhCookieStore(application))
.cache(getOkHttpCache(application))
// .hostnameVerifier((hostname, session) -> true)
Expand Down Expand Up @@ -606,6 +694,43 @@ public static Hosts getHosts(@NonNull Context context) {
return application.mHosts;
}

/**
* 网络切换时刷新 OkHttp 连接池和 DNS 缓存
* VPN 切换后必须调用,否则旧连接仍绑定在旧网络接口上
*/
public static void onNetworkChanged() {
EhApplication app = instance;
if (app == null) return;

// 刷新 OkHttp 主客户端连接池
if (app.mOkHttpClient != null) {
app.mOkHttpClient.connectionPool().evictAll();
Log.i(TAG, "NetworkLogger: OkHttp connection pool evicted for main client");
}

// 刷新图片客户端连接池
if (app.mImageOkHttpClient != null) {
app.mImageOkHttpClient.connectionPool().evictAll();
Log.i(TAG, "NetworkLogger: OkHttp connection pool evicted for image client");
}

// 刷新 DNS 缓存(清空自定义 hosts 数据库中的条目)
// 这样下次 lookup 会重新走 DoH 或系统 DNS
if (app.mHosts != null) {
try {
// 只清除非手动添加的条目,保留用户自定义 hosts
// 这里我们清除所有缓存过的 DNS 条目
app.mHosts.deleteAll(); // 需要新增 deleteAll 方法
Log.i(TAG, "NetworkLogger: DNS cache cleared");
} catch (Exception e) {
Log.e(TAG, "Failed to clear DNS cache", e);
}
}

NetworkLogger.INSTANCE.logBackground(
"Network changed - OkHttp pools evicted, DNS cache cleared");
}

@NonNull
public static FavouriteStatusRouter getFavouriteStatusRouter() {
return getFavouriteStatusRouter(getInstance());
Expand Down
Loading
Loading