From 54552516734c7aa7102a5654ea109cc1e3351655 Mon Sep 17 00:00:00 2001 From: reddaniel <48639104@qq.com> Date: Wed, 28 Oct 2026 14:23:27 +0800 Subject: [PATCH] v0.4.2: perf - Sina batch quotes (1 request covers indices/commodities/US stocks), Yahoo circuit breaker (2 fails = skip 10min) --- AndroidManifest.xml | 4 +- src/com/dsh/openterm/MainActivity.java | 92 +++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 5a75672..eaf828d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="6" + android:versionName="0.4.2"> diff --git a/src/com/dsh/openterm/MainActivity.java b/src/com/dsh/openterm/MainActivity.java index 9739e7a..c006e09 100644 --- a/src/com/dsh/openterm/MainActivity.java +++ b/src/com/dsh/openterm/MainActivity.java @@ -59,7 +59,7 @@ public class MainActivity extends Activity implements View.OnClickListener { double priceNum = Double.NaN, prevNum = Double.NaN; String highS = "--", lowS = "--", volS = "--", hi52S = "--", lo52S = "--"; String currency = "", exchange = ""; - boolean up, loaded; + boolean up, loaded, batchHit; Quote(String yahoo, String tencent, String name) { this.yahoo = yahoo; this.tencent = tencent; this.name = name; this.sym = yahoo; @@ -706,13 +706,17 @@ public class MainActivity extends Activity implements View.OnClickListener { void fetchList(List list, final boolean mon) { int ok = 0; + // 国内快路径:Yahoo 已熔断时先走新浪批量(1 次请求覆盖指数/商品/美股) + int batched = 0; + if (System.currentTimeMillis() < yahooDeadUntil) batched = fetchSinaBatch(list); for (Quote q : list) { + if (q.batchHit) { ok++; q.batchHit = false; continue; } if (fetchYahoo(q)) ok++; else if (fetchEastMoney(q)) ok++; else if (q.tencent != null && fetchTencent(q)) ok++; else if (fetchSinaHf(q)) ok++; else if (fetchStooq(q)) ok++; - try { Thread.sleep(120); } catch (InterruptedException e) { return; } + if (batched == 0) try { Thread.sleep(120); } catch (InterruptedException e) { return; } } final int n = ok; ui.post(new Runnable() { @Override public void run() { @@ -733,6 +737,8 @@ public class MainActivity extends Activity implements View.OnClickListener { } boolean fetchYahoo(Quote q) { + long now = System.currentTimeMillis(); + if (now < yahooDeadUntil) return false; // 熔断中,直接跳过 try { JSONObject meta = chartJson(q.yahoo, "1d", "5m").getJSONObject("chart") .getJSONArray("result").getJSONObject(0).getJSONObject("meta"); @@ -740,13 +746,93 @@ public class MainActivity extends Activity implements View.OnClickListener { double prev = meta.optDouble("chartPreviousClose", meta.optDouble("previousClose", Double.NaN)); if (Double.isNaN(price) || price == 0) return false; + yahooFailStreak = 0; applyQuote(q, price, prev, meta.optString("currency", ""), meta.optString("exchangeName", ""), meta.optDouble("regularMarketDayHigh", Double.NaN), meta.optDouble("regularMarketDayLow", Double.NaN), meta.optDouble("regularMarketVolume", Double.NaN)); return true; - } catch (Exception e) { return false; } + } catch (Exception e) { + // 连续失败 2 次熔断 10 分钟(国内网络 Yahoo 不可达时省去每标的 ~1s 的等待) + if (++yahooFailStreak >= 2) yahooDeadUntil = System.currentTimeMillis() + 10 * 60 * 1000L; + return false; + } + } + + int yahooFailStreak = 0; + long yahooDeadUntil = 0; + + /* ── 新浪批量:一次请求覆盖指数/商品/美股 ── */ + String sinaCodeFor(Quote q) { + String y = q.yahoo; + if (y.equals("^GSPC")) return "int_sp500"; + if (y.equals("^IXIC")) return "int_nasdaq"; + if (y.equals("^DJI")) return "int_dji"; + if (y.equals("^HSI")) return "int_hangseng"; + if (y.equals("^N225")) return "int_nikkei"; + if (y.equals("^FTSE")) return "int_ftse"; + String hf = sinaHfFor(y); + if (!hf.isEmpty()) return hf; + if (y.matches("[A-Z]{1,5}")) return "gb_" + y.toLowerCase(Locale.US); + return null; + } + + int fetchSinaBatch(List list) { + StringBuilder codes = new StringBuilder(); + List mappable = new ArrayList<>(); + for (Quote q : list) { + String c = sinaCodeFor(q); + if (c != null) { codes.append(c).append(','); mappable.add(q); } + } + if (mappable.isEmpty()) return 0; + codes.deleteCharAt(codes.length() - 1); + try { + String body = httpGet("https://hq.sinajs.cn/list=" + codes, + "https://finance.sina.com.cn"); + int ok = 0; + for (Quote q : mappable) { + String tag = "hq_str_" + sinaCodeFor(q) + "=\""; + int s = body.indexOf(tag); + if (s < 0) continue; + int e = body.indexOf('"', s + tag.length()); + if (e < 0) continue; + String[] f = body.substring(s + tag.length(), e).split(","); + boolean done = false; + String code = sinaCodeFor(q); + if (code.startsWith("int_")) { // 指数: 名,价,涨跌,幅度 + if (f.length >= 4) { + double price = Double.parseDouble(f[1]); + double chg = Double.parseDouble(f[2]); + applyQuote(q, price, price - chg, "", "SINA", + Double.NaN, Double.NaN, Double.NaN); + done = true; + } + } else if (code.startsWith("hf_")) { // 商品 + if (f.length >= 9) { + double price = Double.parseDouble(f[0]); + double prev = Double.parseDouble(f[7]); + if (price != 0) { + applyQuote(q, price, prev, "USD", "SINA", + Double.parseDouble(f[5]), Double.parseDouble(f[6]), Double.NaN); + done = true; + } + } + } else if (code.startsWith("gb_")) { // 美股: 名,价,幅度,时间,涨跌,开,高,低 + if (f.length >= 8) { + double price = Double.parseDouble(f[1]); + if (price != 0) { + double chg = Double.parseDouble(f[4]); + applyQuote(q, price, price - chg, "USD", "SINA", + Double.parseDouble(f[6]), Double.parseDouble(f[7]), Double.NaN); + done = true; + } + } + } + if (done) { ok++; q.batchHit = true; } + } + return ok; + } catch (Exception e) { return 0; } } JSONObject chartJson(String sym, String range, String interval) throws Exception {