diff --git a/.gitignore b/.gitignore
index c7b1543e..5fa56d22 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,37 @@
-.idea
-.gradle
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Log/OS Files
+*.log
+
+# Android Studio generated files and folders
+captures/
+.externalNativeBuild/
+.cxx/
+*.apk
+*.aab
+output.json
+
+# IntelliJ
+*.iml
+.idea/
+misc.xml
+deploymentTargetDropDown.xml
+render.experimental.xml
+
+# Keystore files
*.jks
-lib-*.aar
-*build
-/media*
-/Release
-/local.properties
\ No newline at end of file
+*.keystore
+
+# Google Services (e.g. APIs or Firebase)
+google-services.json
+
+# Android Profiling
+*.hprof
+
+# APK files
+apk/release/*.apk
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..e0f15db2
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "java.configuration.updateBuildConfiguration": "automatic"
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/fongmi/android/tv/App.java b/app/src/main/java/com/fongmi/android/tv/App.java
index 5d88da44..9e8af84d 100644
--- a/app/src/main/java/com/fongmi/android/tv/App.java
+++ b/app/src/main/java/com/fongmi/android/tv/App.java
@@ -14,6 +14,7 @@ import androidx.core.os.HandlerCompat;
import com.fongmi.android.tv.event.EventIndex;
import com.fongmi.android.tv.ui.activity.CrashActivity;
+import com.fongmi.android.tv.utils.CacheCleaner;
import com.fongmi.android.tv.utils.Notify;
import com.fongmi.hook.Hook;
import com.github.catvod.Init;
@@ -41,6 +42,7 @@ public class App extends Application {
private final Gson gson;
private final long time;
private Hook hook;
+ private final Runnable cleanTask;
public App() {
instance = this;
@@ -48,6 +50,7 @@ public class App extends Application {
handler = HandlerCompat.createAsync(Looper.getMainLooper());
time = System.currentTimeMillis();
gson = new Gson();
+ cleanTask = this::checkCacheClean;
}
public static App get() {
@@ -119,6 +122,10 @@ public class App extends Application {
OkHttp.get().setDoh(Doh.objectFrom(Setting.getDoh()));
EventBus.builder().addIndex(new EventIndex()).installDefaultEventBus();
CaocConfig.Builder.create().backgroundMode(CaocConfig.BACKGROUND_MODE_SILENT).errorActivity(CrashActivity.class).apply();
+
+ // 初始化自动缓存清理
+ initCacheCleaner();
+
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
@@ -133,6 +140,8 @@ public class App extends Application {
@Override
public void onActivityResumed(@NonNull Activity activity) {
if (activity != activity()) setActivity(activity);
+ // 应用回到前台时检查缓存
+ checkCacheClean();
}
@Override
@@ -156,6 +165,20 @@ public class App extends Application {
});
}
+ private void initCacheCleaner() {
+ CacheCleaner cleaner = CacheCleaner.get();
+ cleaner.setCacheThreshold(200 * 1024 * 1024); // 固定使用200MB阈值
+
+ // 定期检查缓存 (每30分钟)
+ post(cleanTask, 30 * 60 * 1000);
+ }
+
+ private void checkCacheClean() {
+ CacheCleaner.get().checkAndClean();
+ // 每30分钟定期检查缓存
+ post(cleanTask, 30 * 60 * 1000);
+ }
+
@Override
public PackageManager getPackageManager() {
return hook != null ? hook : getBaseContext().getPackageManager();
diff --git a/app/src/main/java/com/fongmi/android/tv/Setting.java b/app/src/main/java/com/fongmi/android/tv/Setting.java
index 84b4eee7..97ced020 100644
--- a/app/src/main/java/com/fongmi/android/tv/Setting.java
+++ b/app/src/main/java/com/fongmi/android/tv/Setting.java
@@ -201,6 +201,14 @@ public class Setting {
Prefers.put("update", update);
}
+ public static boolean getUseCnMirror() {
+ return Prefers.getBoolean("use_cn_mirror", false);
+ }
+
+ public static void putUseCnMirror(boolean useCnMirror) {
+ Prefers.put("use_cn_mirror", useCnMirror);
+ }
+
public static boolean isCaption() {
return Prefers.getBoolean("caption");
}
diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Config.java b/app/src/main/java/com/fongmi/android/tv/bean/Config.java
index 2c2e2dfb..c4e1e96c 100644
--- a/app/src/main/java/com/fongmi/android/tv/bean/Config.java
+++ b/app/src/main/java/com/fongmi/android/tv/bean/Config.java
@@ -267,6 +267,19 @@ public class Config {
Keep.delete(getId());
}
+ public Config copy() {
+ Config copy = new Config();
+ copy.setType(type);
+ copy.setUrl(url);
+ copy.setJson(json);
+ copy.setName(TextUtils.isEmpty(name) ? url + " 副本" : name + " 副本");
+ copy.setLogo(logo);
+ copy.setHome(home);
+ copy.setParse(parse);
+ copy.setTime(System.currentTimeMillis());
+ return copy;
+ }
+
@NonNull
@Override
public String toString() {
diff --git a/app/src/main/java/com/fongmi/android/tv/utils/CacheCleaner.java b/app/src/main/java/com/fongmi/android/tv/utils/CacheCleaner.java
new file mode 100644
index 00000000..aa8b0b5a
--- /dev/null
+++ b/app/src/main/java/com/fongmi/android/tv/utils/CacheCleaner.java
@@ -0,0 +1,92 @@
+package com.fongmi.android.tv.utils;
+
+import android.os.StatFs;
+
+import com.fongmi.android.tv.App;
+import com.fongmi.android.tv.impl.Callback;
+import com.github.catvod.utils.Path;
+
+/**
+ * 缓存自动清理管理器
+ */
+public class CacheCleaner {
+
+ // 默认缓存清理阈值 200MB
+ private static final long DEFAULT_CACHE_THRESHOLD = 200 * 1024 * 1024;
+ // 最小保留空间 500MB
+ private static final long MIN_FREE_SPACE = 500 * 1024 * 1024;
+ // 单例实例
+ private static CacheCleaner instance;
+ // 缓存清理阈值
+ private long cacheThreshold;
+
+ private CacheCleaner() {
+ this.cacheThreshold = DEFAULT_CACHE_THRESHOLD;
+ }
+
+ public static CacheCleaner get() {
+ if (instance == null) {
+ synchronized (CacheCleaner.class) {
+ if (instance == null) {
+ instance = new CacheCleaner();
+ }
+ }
+ }
+ return instance;
+ }
+
+ /**
+ * 设置缓存阈值
+ * @param threshold 阈值大小(字节)
+ */
+ public void setCacheThreshold(long threshold) {
+ this.cacheThreshold = threshold;
+ }
+
+ /**
+ * 检查缓存,如果超过阈值则清理
+ */
+ public void checkAndClean() {
+ App.execute(() -> {
+ try {
+ // 获取当前缓存大小
+ long cacheSize = FileUtil.getDirectorySize(Path.cache());
+ // 获取剩余存储空间
+ long freeSpace = getAvailableStorageSpace();
+
+ // 如果缓存超过阈值或可用空间低于最小要求,清理缓存
+ if (cacheSize > cacheThreshold || freeSpace < MIN_FREE_SPACE) {
+ cleanCache();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ }
+
+ /**
+ * 清理缓存
+ */
+ private void cleanCache() {
+ FileUtil.clearCache(new Callback() {
+ @Override
+ public void success() {
+ // 缓存清理成功
+ }
+ });
+ }
+
+ /**
+ * 获取设备可用存储空间
+ * @return 可用空间(字节)
+ */
+ private long getAvailableStorageSpace() {
+ try {
+ StatFs stat = new StatFs(Path.cache().getPath());
+ return stat.getAvailableBlocksLong() * stat.getBlockSizeLong();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index a405ab0b..30c081ea 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -78,6 +78,8 @@
DoH
Proxy
缓存
+ 自动清理缓存
+ 清理阈值
备份
恢复
版本
@@ -88,6 +90,8 @@
应用设置
网络设置
数据管理
+ v3.0.3
+ 在GitHub上查看
选择备份
@@ -143,6 +147,7 @@
不支持的文件格式
设备授权数已达上限
该订阅无直播内容
+ 当前源没有直播内容
发现新版本 %s
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 2a2684ef..a569911f 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -76,7 +76,9 @@
圖片尺寸
DoH
Proxy
- 暫存
+ 緩存
+ 自動清理緩存
+ 清理閾值
備份
還原
版本
@@ -87,6 +89,8 @@
應用設置
網絡設置
數據管理
+ v3.0.3
+ 在GitHub上查看
選擇備份
@@ -138,6 +142,10 @@
連線逾時
暫無播放資料
找不到資料
+ 不支持的檔案格式
+ 設備授權數已達上限
+ 該訂閱無直播內容
+ 當前源沒有直播內容
發現新版本 %s
@@ -204,4 +212,8 @@
- 選擇字幕
+ 已設為當前點播源
+
+ 點我添加源
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 081e1b13..c966ebdd 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -82,13 +82,17 @@
Image size
DoH
Proxy
- Cache
- Backup
- Restore
- Version
+ 缓存
+ Auto Clean Cache
+ Cache Threshold
+ 备份
+ 恢复
+ 版本
Choose
Off
On
+ v3.0.3
+ View on GitHub
Select backup
@@ -146,6 +150,7 @@
Unsupported file format
Device authorization limit reached
This subscription has no live content
+ Current source has no live content
Not found
No play data
No flag data
@@ -224,4 +229,10 @@
已设为当前点播源
+ Remember settings
+ Target size
+ Scan result
+
+ 点我添加源
+
\ No newline at end of file
diff --git a/app/src/mobile/java/com/fongmi/android/tv/Updater.java b/app/src/mobile/java/com/fongmi/android/tv/Updater.java
index 4f80248d..bd9385d7 100644
--- a/app/src/mobile/java/com/fongmi/android/tv/Updater.java
+++ b/app/src/mobile/java/com/fongmi/android/tv/Updater.java
@@ -16,7 +16,9 @@ import com.github.catvod.net.OkHttp;
import com.github.catvod.utils.Github;
import com.github.catvod.utils.Path;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.orhanobut.logger.Logger;
+import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
@@ -25,20 +27,25 @@ import java.util.Locale;
public class Updater implements Download.Callback {
private DialogUpdateBinding binding;
- private final Download download;
+ private Download download;
private AlertDialog dialog;
private boolean dev;
+ private String downloadUrl;
private File getFile() {
return Path.cache("update.apk");
}
- private String getJson() {
- return Github.getJson(dev, BuildConfig.FLAVOR_mode);
+ private String getApkName() {
+ return "mobile-" + BuildConfig.FLAVOR_abi + ".apk";
}
- private String getApk() {
- return Github.getApk(dev, BuildConfig.FLAVOR_mode + "-" + BuildConfig.FLAVOR_abi);
+ private String getJson() {
+ String url = Github.getReleaseApi();
+ boolean usingCnMirror = Github.useCnMirror();
+ Logger.d("Using CN Mirror: " + usingCnMirror);
+ Logger.d("Update check URL: " + url);
+ return url;
}
public static Updater create() {
@@ -46,7 +53,7 @@ public class Updater implements Download.Callback {
}
public Updater() {
- this.download = Download.create(getApk(), getFile(), this);
+ this.download = Download.create("", getFile(), this);
}
public Updater force() {
@@ -74,18 +81,43 @@ public class Updater implements Download.Callback {
App.execute(() -> doInBackground(activity));
}
- private boolean need(int code, String name) {
- return Setting.getUpdate() && (dev ? !name.equals(BuildConfig.VERSION_NAME) && code >= BuildConfig.VERSION_CODE : code > BuildConfig.VERSION_CODE);
+ private boolean need(String tagName) {
+ Logger.d("Current version: " + BuildConfig.VERSION_NAME);
+ Logger.d("Latest version: " + tagName);
+ if (tagName.startsWith("v")) tagName = tagName.substring(1);
+ return Setting.getUpdate() && !tagName.equals(BuildConfig.VERSION_NAME);
}
private void doInBackground(Activity activity) {
try {
- JSONObject object = new JSONObject(OkHttp.string(getJson()));
- String name = object.optString("name");
- String desc = object.optString("desc");
- int code = object.optInt("code");
- if (need(code, name)) App.post(() -> show(activity, name, desc));
+ String jsonUrl = getJson();
+ Logger.d("Fetching update info from: " + jsonUrl);
+ String response = OkHttp.string(jsonUrl);
+ Logger.d("Update check response: " + response);
+
+ JSONObject release = new JSONObject(response);
+ String tagName = release.getString("tag_name");
+ String body = release.getString("body");
+ JSONArray assets = release.getJSONArray("assets");
+
+ // Find the correct APK asset
+ String apkName = getApkName();
+ for (int i = 0; i < assets.length(); i++) {
+ JSONObject asset = assets.getJSONObject(i);
+ if (asset.getString("name").equals(apkName)) {
+ downloadUrl = asset.getString("browser_download_url");
+ break;
+ }
+ }
+
+ if (downloadUrl != null && need(tagName)) {
+ download = Download.create(downloadUrl, getFile(), this);
+ App.post(() -> show(activity, tagName, body));
+ } else {
+ Logger.d("No update needed or APK not found");
+ }
} catch (Exception e) {
+ Logger.e("Update check failed", e);
e.printStackTrace();
}
}
@@ -127,6 +159,7 @@ public class Updater implements Download.Callback {
@Override
public void error(String msg) {
+ Logger.e("Download error: " + msg);
Notify.show(msg);
dismiss();
}
diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HomeActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HomeActivity.java
index 117cbaca..d120a759 100644
--- a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HomeActivity.java
+++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HomeActivity.java
@@ -179,7 +179,13 @@ public class HomeActivity extends BaseActivity implements NavigationBarView.OnIt
if (mBinding.navigation.getSelectedItemId() == item.getItemId()) return false;
if (item.getItemId() == R.id.setting) return mManager.change(1);
if (item.getItemId() == R.id.vod) return mManager.change(0);
- if (item.getItemId() == R.id.live) return openLive();
+ if (item.getItemId() == R.id.live) {
+ if (LiveConfig.isEmpty()) {
+ Notify.showCenter(R.string.error_no_live);
+ return false;
+ }
+ return openLive();
+ }
return false;
}
diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/VideoActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/VideoActivity.java
index 2e2765ec..f077fccb 100644
--- a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/VideoActivity.java
+++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/VideoActivity.java
@@ -4,17 +4,24 @@ import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.BatteryManager;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.provider.Settings;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
+import android.text.format.DateFormat;
import android.text.style.ClickableSpan;
import android.view.MotionEvent;
import android.view.View;
@@ -22,6 +29,7 @@ import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.RelativeLayout;
import android.widget.TextView;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -63,6 +71,7 @@ import com.fongmi.android.tv.event.RefreshEvent;
import com.fongmi.android.tv.model.SiteViewModel;
import com.fongmi.android.tv.player.Players;
import com.fongmi.android.tv.player.exo.ExoUtil;
+import com.fongmi.android.tv.player.Source;
import com.fongmi.android.tv.service.PlaybackService;
import com.fongmi.android.tv.ui.adapter.EpisodeAdapter;
import com.fongmi.android.tv.ui.adapter.FlagAdapter;
@@ -98,6 +107,7 @@ import com.github.catvod.utils.Trans;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.permissionx.guolindev.PermissionX;
+import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
@@ -148,6 +158,10 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo
private Clock mClock;
private String tag;
private PiP mPiP;
+ private Handler mHandler;
+ private Runnable mTimeUpdateRunnable;
+ private BroadcastReceiver mBatteryReceiver;
+ private int mBatteryLevel = -1;
public static void push(FragmentActivity activity, String text) {
if (FileChooser.isValid(activity, Uri.parse(text))) file(activity, FileChooser.getPathFromUri(activity, Uri.parse(text)));
@@ -302,6 +316,64 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo
showProgress();
showDanmaku();
checkId();
+ mHandler = new Handler(Looper.getMainLooper());
+ initTimeBatteryUpdate();
+ }
+
+ private void initTimeBatteryUpdate() {
+ mBatteryReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
+ int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+ int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
+ if (level != -1 && scale != -1) {
+ mBatteryLevel = (int) ((level / (float) scale) * 100);
+ updateTimeBattery();
+ }
+ }
+ }
+ };
+
+ mTimeUpdateRunnable = new Runnable() {
+ @Override
+ public void run() {
+ updateTimeBattery();
+ mHandler.postDelayed(this, 30000);
+ }
+ };
+ }
+
+ private void updateTimeBattery() {
+ TextView timeBattery = mBinding.getRoot().findViewById(R.id.time_battery);
+ if (timeBattery == null) return;
+
+ // 只在横屏模式下显示
+ if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ String time = DateFormat.getTimeFormat(this).format(System.currentTimeMillis());
+ String battery = mBatteryLevel >= 0 ? mBatteryLevel + "%" : "";
+ String text = time + (battery.isEmpty() ? "" : " | " + battery);
+ timeBattery.setText(text);
+ timeBattery.setVisibility(View.VISIBLE);
+ } else {
+ timeBattery.setVisibility(View.GONE);
+ }
+ }
+
+ private void startTimeBatteryUpdates() {
+ registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ updateTimeBattery();
+ mHandler.post(mTimeUpdateRunnable);
+ }
+
+ private void stopTimeBatteryUpdates() {
+ try {
+ if (mBatteryReceiver != null) {
+ unregisterReceiver(mBatteryReceiver);
+ }
+ } catch (Exception e) {
+ }
+ mHandler.removeCallbacks(mTimeUpdateRunnable);
}
@Override
@@ -960,6 +1032,7 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo
mBinding.control.bottom.setVisibility(isLock() ? View.GONE : View.VISIBLE);
mBinding.control.top.setVisibility(isLock() ? View.GONE : View.VISIBLE);
mBinding.control.getRoot().setVisibility(View.VISIBLE);
+ updateTimeBattery();
setR1Callback();
checkPlayImg();
}
@@ -1555,6 +1628,7 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo
if (isAutoRotate() && isPort() && newConfig.orientation == Configuration.ORIENTATION_PORTRAIT && !isRotate()) exitFullscreen();
if (isAutoRotate() && isPort() && newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) enterFullscreen();
if (isFullscreen()) Util.hideSystemUI(this);
+ updateTimeBattery();
}
@Override
@@ -1574,6 +1648,7 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo
@Override
protected void onResume() {
super.onResume();
+ startTimeBatteryUpdates();
if (isRedirect()) onPlay();
setRedirect(false);
}
@@ -1581,6 +1656,7 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo
@Override
protected void onPause() {
super.onPause();
+ stopTimeBatteryUpdates();
if (isRedirect()) onPaused();
}
@@ -1608,14 +1684,17 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo
protected void onDestroy() {
super.onDestroy();
stopSearch();
- mClock.release();
mPlayers.release();
+ mClock.release();
Timer.get().reset();
RefreshEvent.history();
PlaybackService.stop();
+ mHandler.removeCallbacksAndMessages(null);
App.removeCallbacks(mR1, mR2, mR3, mR4);
+ EventBus.getDefault().unregister(this);
mViewModel.result.removeObserver(mObserveDetail);
mViewModel.player.removeObserver(mObservePlayer);
mViewModel.search.removeObserver(mObserveSearch);
+ stopTimeBatteryUpdates();
}
}
diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ConfigAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ConfigAdapter.java
index 63ffad8d..03f115b7 100644
--- a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ConfigAdapter.java
+++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ConfigAdapter.java
@@ -11,6 +11,7 @@ import com.fongmi.android.tv.api.config.VodConfig;
import com.fongmi.android.tv.bean.Config;
import com.fongmi.android.tv.databinding.AdapterConfigBinding;
+import java.util.ArrayList;
import java.util.List;
public class ConfigAdapter extends RecyclerView.Adapter {
@@ -26,19 +27,36 @@ public class ConfigAdapter extends RecyclerView.Adapter();
+ List configs = Config.getAll(type);
+ Config currentConfig = type == 0 ? VodConfig.get().getConfig() : LiveConfig.get().getConfig();
+
+ for (Config config : configs) {
+ if (config.equals(currentConfig) || config.isEmpty()) continue;
+ mItems.add(config);
+ }
+
return this;
}
+ public void addItem(Config item) {
+ if (item.isEmpty()) return;
+
+ mItems.add(0, item);
+ notifyItemInserted(0);
+ }
+
public int remove(Config item) {
+ int position = mItems.indexOf(item);
item.delete();
mItems.remove(item);
- notifyDataSetChanged();
+ notifyItemRemoved(position);
return getItemCount();
}
@@ -58,6 +76,7 @@ public class ConfigAdapter extends RecyclerView.Adapter mListener.onTextClick(item));
+ holder.binding.copy.setOnClickListener(v -> mListener.onCopyClick(item));
holder.binding.delete.setOnClickListener(v -> mListener.onDeleteClick(item));
}
diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/TypeAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/TypeAdapter.java
index fd90a30c..482db8f2 100644
--- a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/TypeAdapter.java
+++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/TypeAdapter.java
@@ -44,7 +44,6 @@ public class TypeAdapter extends RecyclerView.Adapter {
public void addAll(Result result) {
mItems.addAll(result.getTypes());
- if (!result.getList().isEmpty()) mItems.add(0, home());
if (!mItems.isEmpty()) mItems.get(0).setActivated(true);
notifyDataSetChanged();
}
diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ConfigDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ConfigDialog.java
index e61daa8d..16776a2f 100644
--- a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ConfigDialog.java
+++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ConfigDialog.java
@@ -144,8 +144,18 @@ public class ConfigDialog {
private void onPositive(DialogInterface dialog, int which) {
String url = binding.url.getText().toString().trim();
String name = binding.name.getText().toString().trim();
+
+ // 如果是编辑模式,更新现有配置
if (edit) Config.find(ori, type).url(url).name(name).update();
- if (url.isEmpty()) Config.delete(ori, type);
+
+ // 如果URL为空,删除配置
+ if (url.isEmpty()) {
+ Config.delete(ori, type);
+ dialog.dismiss();
+ return;
+ }
+
+ // 只有URL不为空时,才设置配置
callback.setConfig(Config.find(url, type));
dialog.dismiss();
}
diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/HistoryDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/HistoryDialog.java
index dbed6096..25f3339c 100644
--- a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/HistoryDialog.java
+++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/HistoryDialog.java
@@ -73,6 +73,13 @@ public class HistoryDialog implements ConfigAdapter.OnClickListener {
@Override
public void onDeleteClick(Config item) {
- if (adapter.remove(item) == 0) dialog.dismiss();
+ int count = adapter.remove(item);
+ if (count == 0) {
+ dialog.dismiss();
+ } else {
+ // 强制重新测量布局高度
+ binding.recycler.requestLayout();
+ dialog.getWindow().setLayout(dialog.getWindow().getAttributes().width, android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
}
}
diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/SettingFragment.java b/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/SettingFragment.java
index b582d6d7..1481b488 100644
--- a/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/SettingFragment.java
+++ b/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/SettingFragment.java
@@ -3,9 +3,15 @@ package com.fongmi.android.tv.ui.fragment;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.RelativeSizeSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -32,6 +38,7 @@ import com.fongmi.android.tv.impl.SiteCallback;
import com.fongmi.android.tv.player.Source;
import com.fongmi.android.tv.ui.activity.HomeActivity;
import com.fongmi.android.tv.ui.base.BaseFragment;
+import com.fongmi.android.tv.ui.dialog.AboutDialog;
import com.fongmi.android.tv.ui.dialog.ConfigDialog;
import com.fongmi.android.tv.ui.dialog.HistoryDialog;
import com.fongmi.android.tv.ui.dialog.LiveDialog;
@@ -91,9 +98,9 @@ public class SettingFragment extends BaseFragment implements ConfigCallback, Sit
@Override
protected void initView() {
- mBinding.vodUrl.setText(VodConfig.getDesc());
- mBinding.liveUrl.setText(LiveConfig.getDesc());
- mBinding.wallUrl.setText(WallConfig.getDesc());
+ setSourceHintText(mBinding.vodUrl, VodConfig.getDesc());
+ setSourceHintText(mBinding.liveUrl, LiveConfig.getDesc());
+ setSourceHintText(mBinding.wallUrl, WallConfig.getDesc());
mBinding.versionText.setText(BuildConfig.VERSION_NAME);
// 设置开关的颜色为黄色
@@ -145,6 +152,7 @@ public class SettingFragment extends BaseFragment implements ConfigCallback, Sit
mBinding.player.setOnClickListener(this::onPlayer);
mBinding.restore.setOnClickListener(this::onRestore);
mBinding.version.setOnClickListener(this::onVersion);
+ mBinding.about.setOnClickListener(this::onAbout);
mBinding.vod.setOnLongClickListener(this::onVodEdit);
mBinding.vodHome.setOnClickListener(this::onVodHome);
mBinding.live.setOnLongClickListener(this::onLiveEdit);
@@ -162,6 +170,9 @@ public class SettingFragment extends BaseFragment implements ConfigCallback, Sit
@Override
public void setConfig(Config config) {
+ // 如果URL为空,不进行任何操作
+ if (config.isEmpty()) return;
+
if (config.getUrl().startsWith("file") && !PermissionX.isGranted(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
PermissionX.init(this).permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE).request((allGranted, grantedList, deniedList) -> load(config));
} else {
@@ -204,7 +215,18 @@ public class SettingFragment extends BaseFragment implements ConfigCallback, Sit
@Override
public void error(String msg) {
Notify.show(msg);
- setConfig(type);
+ Notify.dismiss();
+ switch (type) {
+ case 0:
+ setSourceHintText(mBinding.vodUrl, VodConfig.getDesc());
+ break;
+ case 1:
+ setSourceHintText(mBinding.liveUrl, LiveConfig.getDesc());
+ break;
+ case 2:
+ setSourceHintText(mBinding.wallUrl, WallConfig.getDesc());
+ break;
+ }
}
};
}
@@ -216,24 +238,37 @@ public class SettingFragment extends BaseFragment implements ConfigCallback, Sit
Notify.dismiss();
RefreshEvent.video();
RefreshEvent.config();
- mBinding.vodUrl.setText(VodConfig.getDesc());
- mBinding.liveUrl.setText(LiveConfig.getDesc());
- mBinding.wallUrl.setText(WallConfig.getDesc());
+ setSourceHintText(mBinding.vodUrl, VodConfig.getDesc());
+ setSourceHintText(mBinding.liveUrl, LiveConfig.getDesc());
+ setSourceHintText(mBinding.wallUrl, WallConfig.getDesc());
break;
case 1:
setCacheText();
Notify.dismiss();
RefreshEvent.config();
- mBinding.liveUrl.setText(LiveConfig.getDesc());
+ setSourceHintText(mBinding.liveUrl, LiveConfig.getDesc());
break;
case 2:
setCacheText();
Notify.dismiss();
- mBinding.wallUrl.setText(WallConfig.getDesc());
+ setSourceHintText(mBinding.wallUrl, WallConfig.getDesc());
break;
}
}
+ private void setSourceHintText(TextView textView, String desc) {
+ if (TextUtils.isEmpty(desc)) {
+ SpannableString spannable = new SpannableString(getString(R.string.source_hint));
+ spannable.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.white)), 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ spannable.setSpan(new RelativeSizeSpan(0.8f), 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ int alpha = (int)(255 * 0.5f);
+ spannable.setSpan(new ForegroundColorSpan(android.graphics.Color.argb(alpha, 255, 255, 255)), 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ textView.setText(spannable);
+ } else {
+ textView.setText(desc);
+ }
+ }
+
@Override
public void setSite(Site item) {
VodConfig.get().setHome(item);
@@ -299,6 +334,10 @@ public class SettingFragment extends BaseFragment implements ConfigCallback, Sit
private void onVersion(View view) {
Updater.create().force().release().start(getActivity());
}
+
+ private void onAbout(View view) {
+ AboutDialog.show(this);
+ }
private boolean onVersionDev(View view) {
Updater.create().force().dev().start(getActivity());
@@ -415,9 +454,9 @@ public class SettingFragment extends BaseFragment implements ConfigCallback, Sit
@Override
public void onHiddenChanged(boolean hidden) {
if (hidden) return;
- mBinding.vodUrl.setText(VodConfig.getDesc());
- mBinding.liveUrl.setText(LiveConfig.getDesc());
- mBinding.wallUrl.setText(WallConfig.getDesc());
+ setSourceHintText(mBinding.vodUrl, VodConfig.getDesc());
+ setSourceHintText(mBinding.liveUrl, LiveConfig.getDesc());
+ setSourceHintText(mBinding.wallUrl, WallConfig.getDesc());
setCacheText();
}
diff --git a/app/src/mobile/res/color-night/nav.xml b/app/src/mobile/res/color-night/nav.xml
index 1d6ea80e..b3981e62 100644
--- a/app/src/mobile/res/color-night/nav.xml
+++ b/app/src/mobile/res/color-night/nav.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/app/src/mobile/res/color/nav.xml b/app/src/mobile/res/color/nav.xml
index 1d6ea80e..b3981e62 100644
--- a/app/src/mobile/res/color/nav.xml
+++ b/app/src/mobile/res/color/nav.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/app/src/mobile/res/drawable/bg_nav_selected.xml b/app/src/mobile/res/drawable/bg_nav_selected.xml
new file mode 100644
index 00000000..ea085c16
--- /dev/null
+++ b/app/src/mobile/res/drawable/bg_nav_selected.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/mobile/res/drawable/ic_nav_live.xml b/app/src/mobile/res/drawable/ic_nav_live.xml
index ffcecf33..549989c9 100644
--- a/app/src/mobile/res/drawable/ic_nav_live.xml
+++ b/app/src/mobile/res/drawable/ic_nav_live.xml
@@ -1,10 +1,18 @@
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ android:fillColor="#FF000000"
+ android:fillAlpha="0"
+ android:strokeWidth="1.5"
+ android:strokeColor="#FF000000"
+ android:pathData="M3,6C3,4.89543 3.89543,4 5,4H19C20.1046,4 21,4.89543 21,6V18C21,19.1046 20.1046,20 19,20H5C3.89543,20 3,19.1046 3,18V6Z"/>
+
diff --git a/app/src/mobile/res/drawable/ic_nav_live_selected.xml b/app/src/mobile/res/drawable/ic_nav_live_selected.xml
new file mode 100644
index 00000000..0f7e5f99
--- /dev/null
+++ b/app/src/mobile/res/drawable/ic_nav_live_selected.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/mobile/res/drawable/ic_nav_vod.xml b/app/src/mobile/res/drawable/ic_nav_vod.xml
index 2a9172ca..8bc75346 100644
--- a/app/src/mobile/res/drawable/ic_nav_vod.xml
+++ b/app/src/mobile/res/drawable/ic_nav_vod.xml
@@ -1,7 +1,6 @@
+
+
\ No newline at end of file
diff --git a/app/src/mobile/res/drawable/ic_setting_copy.xml b/app/src/mobile/res/drawable/ic_setting_copy.xml
new file mode 100644
index 00000000..1419ed55
--- /dev/null
+++ b/app/src/mobile/res/drawable/ic_setting_copy.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/mobile/res/drawable/ic_setting_doh.xml b/app/src/mobile/res/drawable/ic_setting_doh.xml
new file mode 100644
index 00000000..fdad7480
--- /dev/null
+++ b/app/src/mobile/res/drawable/ic_setting_doh.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/mobile/res/drawable/ic_setting_incognito.xml b/app/src/mobile/res/drawable/ic_setting_incognito.xml
new file mode 100644
index 00000000..c08f9829
--- /dev/null
+++ b/app/src/mobile/res/drawable/ic_setting_incognito.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/mobile/res/drawable/ic_setting_size.xml b/app/src/mobile/res/drawable/ic_setting_size.xml
new file mode 100644
index 00000000..33c3c6db
--- /dev/null
+++ b/app/src/mobile/res/drawable/ic_setting_size.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/mobile/res/drawable/selector_nav_live.xml b/app/src/mobile/res/drawable/selector_nav_live.xml
new file mode 100644
index 00000000..a98570b5
--- /dev/null
+++ b/app/src/mobile/res/drawable/selector_nav_live.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/mobile/res/drawable/selector_nav_setting.xml b/app/src/mobile/res/drawable/selector_nav_setting.xml
new file mode 100644
index 00000000..c65f4ec8
--- /dev/null
+++ b/app/src/mobile/res/drawable/selector_nav_setting.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/mobile/res/drawable/selector_nav_vod.xml b/app/src/mobile/res/drawable/selector_nav_vod.xml
new file mode 100644
index 00000000..94ab5a41
--- /dev/null
+++ b/app/src/mobile/res/drawable/selector_nav_vod.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/mobile/res/drawable/shape_about_button.xml b/app/src/mobile/res/drawable/shape_about_button.xml
new file mode 100644
index 00000000..795bf3ee
--- /dev/null
+++ b/app/src/mobile/res/drawable/shape_about_button.xml
@@ -0,0 +1,15 @@
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/mobile/res/drawable/shape_about_dialog.xml b/app/src/mobile/res/drawable/shape_about_dialog.xml
new file mode 100644
index 00000000..d65639d6
--- /dev/null
+++ b/app/src/mobile/res/drawable/shape_about_dialog.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/mobile/res/drawable/shape_dialog_button.xml b/app/src/mobile/res/drawable/shape_dialog_button.xml
index 91207197..2a52f166 100644
--- a/app/src/mobile/res/drawable/shape_dialog_button.xml
+++ b/app/src/mobile/res/drawable/shape_dialog_button.xml
@@ -4,9 +4,6 @@
-
-
+
+
+
+
\ No newline at end of file
diff --git a/app/src/mobile/res/layout-land/view_control_vod.xml b/app/src/mobile/res/layout-land/view_control_vod.xml
new file mode 100644
index 00000000..91f8108d
--- /dev/null
+++ b/app/src/mobile/res/layout-land/view_control_vod.xml
@@ -0,0 +1,235 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/mobile/res/layout/activity_home.xml b/app/src/mobile/res/layout/activity_home.xml
index 277a91d6..16810611 100644
--- a/app/src/mobile/res/layout/activity_home.xml
+++ b/app/src/mobile/res/layout/activity_home.xml
@@ -14,14 +14,14 @@
diff --git a/app/src/mobile/res/layout/adapter_config.xml b/app/src/mobile/res/layout/adapter_config.xml
index 4ddeb35e..5265dcd6 100644
--- a/app/src/mobile/res/layout/adapter_config.xml
+++ b/app/src/mobile/res/layout/adapter_config.xml
@@ -6,17 +6,37 @@
android:gravity="center_vertical"
android:orientation="horizontal">
-
+ android:layout_weight="1">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/mobile/res/layout/fragment_setting.xml b/app/src/mobile/res/layout/fragment_setting.xml
index 96a975a8..bc8a1c84 100644
--- a/app/src/mobile/res/layout/fragment_setting.xml
+++ b/app/src/mobile/res/layout/fragment_setting.xml
@@ -58,15 +58,31 @@
android:paddingBottom="24dp">
-
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
+
+
+
+
+
+
+
-
-
-
+ android:layout_marginTop="12dp"
+ android:background="@drawable/shape_item"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+
+
+
+
+
+
+
+
+
+
+
-
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+
+
+
+
+
-
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+
+
+
-
-
+ android:layout_weight="1"
+ android:layout_marginEnd="12dp"
+ android:background="@drawable/shape_item"
+ android:orientation="horizontal">
+
+
-
+
+
+
+
+
+
+ android:layout_weight="1"
+ android:background="@drawable/shape_item"
+ android:orientation="horizontal">
+
+
+
+
+
diff --git a/app/src/mobile/res/layout/fragment_setting_player.xml b/app/src/mobile/res/layout/fragment_setting_player.xml
index 7fea954d..253a73d8 100644
--- a/app/src/mobile/res/layout/fragment_setting_player.xml
+++ b/app/src/mobile/res/layout/fragment_setting_player.xml
@@ -55,7 +55,10 @@
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical"
- android:padding="16dp">
+ android:paddingStart="24dp"
+ android:paddingTop="16dp"
+ android:paddingEnd="24dp"
+ android:paddingBottom="16dp">
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/mobile/res/values/styles.xml b/app/src/mobile/res/values/styles.xml
index cf66efe7..3b77ce03 100644
--- a/app/src/mobile/res/values/styles.xml
+++ b/app/src/mobile/res/values/styles.xml
@@ -60,7 +60,8 @@
diff --git a/catvod/src/main/java/com/github/catvod/utils/Github.java b/catvod/src/main/java/com/github/catvod/utils/Github.java
index 18f46fe0..a74c6753 100644
--- a/catvod/src/main/java/com/github/catvod/utils/Github.java
+++ b/catvod/src/main/java/com/github/catvod/utils/Github.java
@@ -1,18 +1,118 @@
package com.github.catvod.utils;
+import android.os.SystemClock;
+
+import com.github.catvod.net.OkHttp;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.Call;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
public class Github {
- public static final String URL = "https://github.com/Tosencen/XMBOX";
+ public static final String URL = "https://raw.githubusercontent.com/Tosencen/XMBOX/main";
+ public static final String API_URL = "https://api.github.com/repos/Tosencen/XMBOX/releases/latest";
+
+ // 国内镜像地址 - 使用Gitee作为示例,实际应替换为您的镜像地址
+ public static final String CN_URL = "https://gitee.com/tosencen/XMBOX/raw/main";
+ public static final String CN_API_URL = "https://gitee.com/api/v5/repos/tosencen/XMBOX/releases/latest";
+
+ // 存储测速结果
+ private static Boolean useCnMirror = null;
+ private static long lastCheckTime = 0;
+ private static final long CHECK_INTERVAL = 24 * 60 * 60 * 1000; // 24小时
private static String getUrl(String path, String name) {
return URL + "/" + path + "/" + name;
}
+
+ private static String getCnUrl(String path, String name) {
+ return CN_URL + "/" + path + "/" + name;
+ }
+
+ public static String getReleaseApi() {
+ return useCnMirror() ? CN_API_URL : API_URL;
+ }
public static String getJson(boolean dev, String name) {
- return getUrl("apk/" + (dev ? "dev" : "release"), name + ".json");
+ if (useCnMirror()) {
+ return getCnUrl("apk/" + (dev ? "dev" : "release"), name + ".json");
+ } else {
+ return getUrl("apk/" + (dev ? "dev" : "release"), name + ".json");
+ }
}
public static String getApk(boolean dev, String name) {
- return getUrl("apk/" + (dev ? "dev" : "release"), name + ".apk");
+ if (useCnMirror()) {
+ return getCnUrl("apk/" + (dev ? "dev" : "release"), name + ".apk");
+ } else {
+ return getUrl("apk/" + (dev ? "dev" : "release"), name + ".apk");
+ }
+ }
+
+ // 智能检测是否使用国内镜像
+ public static boolean useCnMirror() {
+ // 如果已经测试过并且在24小时内,直接返回上次的结果
+ long currentTime = SystemClock.elapsedRealtime();
+ if (useCnMirror != null && (currentTime - lastCheckTime < CHECK_INTERVAL)) {
+ return useCnMirror;
+ }
+
+ // 进行网络测速
+ useCnMirror = testMirrorSpeed();
+ lastCheckTime = currentTime;
+ return useCnMirror;
+ }
+
+ // 测试镜像速度
+ private static boolean testMirrorSpeed() {
+ try {
+ OkHttpClient client = new OkHttpClient.Builder()
+ .connectTimeout(3, TimeUnit.SECONDS)
+ .readTimeout(3, TimeUnit.SECONDS)
+ .build();
+
+ // 测试国际源
+ long startTime = System.currentTimeMillis();
+ boolean intlSuccess = testUrl(client, URL + "/README.md");
+ long intlTime = System.currentTimeMillis() - startTime;
+
+ // 测试国内源
+ startTime = System.currentTimeMillis();
+ boolean cnSuccess = testUrl(client, CN_URL + "/README.md");
+ long cnTime = System.currentTimeMillis() - startTime;
+
+ // 如果两个都成功,选择更快的
+ if (intlSuccess && cnSuccess) {
+ return cnTime < intlTime;
+ }
+
+ // 如果只有一个成功,选择成功的那个
+ if (intlSuccess) return false;
+ if (cnSuccess) return true;
+
+ // 如果都失败,默认国际源
+ return false;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false; // 出错时默认使用国际源
+ }
+ }
+
+ private static boolean testUrl(OkHttpClient client, String url) {
+ Request request = new Request.Builder().url(url).build();
+ Call call = client.newCall(request);
+ try {
+ Response response = call.execute();
+ boolean success = response.isSuccessful();
+ response.close();
+ return success;
+ } catch (IOException e) {
+ return false;
+ }
}
}