diff --git a/build.gradle b/build.gradle index ed39c4e..b18fe79 100644 --- a/build.gradle +++ b/build.gradle @@ -75,8 +75,8 @@ android { minSdkVersion 26 targetSdkVersion 28 - versionCode 206 - versionName "1.6.0731" + versionCode 209 + versionName "1.6.0914" ndk { //选择要添加的对应 cpu 类型的 .so 库。 @@ -256,13 +256,36 @@ android { v1SigningEnabled true v2SigningEnabled true } + + MT6789 { + storeFile file("keystore/MT6789.keystore") + storePassword "123456" + keyAlias "mt6789" + keyPassword "123456" + v1SigningEnabled true + v2SigningEnabled true + } } buildTypes { + G12NLDebug.initWith(debug) + G12NLDebug { + buildConfigField "String", "platform", '"G12NL"' + versionNameSuffix "_debug" + debuggable true + signingConfig signingConfigs.MT6789 + } + + G12NLRelease.initWith(release) + G12NLRelease { + buildConfigField "String", "platform", '"G12NL"' + signingConfig signingConfigs.MT6789 + } + DX11Debug.initWith(debug) DX11Debug { buildConfigField "String", "platform", '"DX11"' - versionNameSuffix "-debug" + versionNameSuffix "_debug" debuggable true signingConfig signingConfigs.DX11 } @@ -276,7 +299,7 @@ android { G122Debug.initWith(debug) G122Debug { buildConfigField "String", "platform", '"MT6765"' - versionNameSuffix "-debug" + versionNameSuffix "_debug" debuggable true signingConfig signingConfigs.G122 } @@ -290,7 +313,7 @@ android { QualcommDebug.initWith(debug) QualcommDebug { buildConfigField "String", "platform", '"QualcommA"' - versionNameSuffix "-debug" + versionNameSuffix "_debug" debuggable true signingConfig signingConfigs.zhanRui } @@ -304,7 +327,7 @@ android { G128TDebug.initWith(debug) G128TDebug { buildConfigField "String", "platform", '"G128T"' - versionNameSuffix "-debug" + versionNameSuffix "_debug" debuggable true signingConfig signingConfigs.G128T } @@ -318,7 +341,7 @@ android { QZ15Debug.initWith(debug) QZ15Debug { buildConfigField "String", "platform", '"QZA15"' - versionNameSuffix "-debug" + versionNameSuffix "_debug" debuggable true signingConfig signingConfigs.U1302 } @@ -331,7 +354,7 @@ android { seewoDebug.initWith(debug) seewoDebug { - versionNameSuffix "-debug" + versionNameSuffix "_debug" debuggable true signingConfig signingConfigs.seewo buildConfigField "String", "platform", '"seewo"' @@ -345,7 +368,7 @@ android { P50Debug.initWith(debug) P50Debug { - versionNameSuffix "-debug" + versionNameSuffix "_debug" debuggable true signingConfig signingConfigs.P50 buildConfigField "String", "platform", '"P50TD"' @@ -359,7 +382,7 @@ android { T40AirDebug.initWith(debug) T40AirDebug { - versionNameSuffix "-debug" + versionNameSuffix "_debug" debuggable true signingConfig signingConfigs.T40Air buildConfigField "String", "platform", '"HL500"' @@ -373,7 +396,7 @@ android { // AiUduDebug.initWith(debug) // AiUduDebug { -// versionNameSuffix "-debug" +// versionNameSuffix "_debug" // debuggable true // signingConfig signingConfigs.G10P // buildConfigField "String", "platform", '"AUDG104"' @@ -387,7 +410,7 @@ android { Huaruian8768Debug.initWith(debug) Huaruian8768Debug { - versionNameSuffix "-debug" + versionNameSuffix "_debug" debuggable true signingConfig signingConfigs.Huaruian8768 buildConfigField "String", "platform", '"MT8768"' @@ -401,7 +424,7 @@ android { G11Debug.initWith(debug) G11Debug { - versionNameSuffix "-debug" + versionNameSuffix "_debug" debuggable true signingConfig signingConfigs.G11Userdebug buildConfigField "String", "platform", '"MTKG11"' @@ -415,7 +438,7 @@ android { YXPD1Debug.initWith(debug) YXPD1Debug { - versionNameSuffix "-debug" + versionNameSuffix "_debug" debuggable true signingConfig signingConfigs.G10P buildConfigField "String", "platform", '"YXPD1"' @@ -429,7 +452,7 @@ android { c2Debug.initWith(debug) c2Debug { - versionNameSuffix "-debug" + versionNameSuffix "_debug" debuggable true signingConfig signingConfigs.teclast8183 buildConfigField "String", "platform", '"MTK8183"' @@ -443,7 +466,7 @@ android { G10PDebug.initWith(debug) G10PDebug { - versionNameSuffix "-debug" + versionNameSuffix "_debug" debuggable true signingConfig signingConfigs.G10P buildConfigField "String", "platform", '"G10P"' @@ -458,7 +481,7 @@ android { G104Debug.initWith(debug) G104Debug { buildConfigField "String", "platform", '"MTK11"' - versionNameSuffix "-debug" + versionNameSuffix "_debug" debuggable true signingConfig signingConfigs.G10P } @@ -471,7 +494,7 @@ android { zhanRuiDebug.initWith(debug) zhanRuiDebug { - versionNameSuffix "-debug" + versionNameSuffix "_debug" buildConfigField "String", "platform", '"ZhanRui"' debuggable true signingConfig signingConfigs.zhanRui @@ -486,7 +509,7 @@ android { //userdebug rom使用这个版本 zhanRuiUserdebug.initWith(zhanRuiDebug) zhanRuiUserdebug { - versionNameSuffix "-debug" + versionNameSuffix "_debug" buildConfigField "String", "platform", '"ZhanRui"' debuggable true signingConfig signingConfigs.zhanRuiUserdebug @@ -494,7 +517,7 @@ android { zhanRui12Debug.initWith(debug) zhanRui12Debug { - versionNameSuffix "-debug" + versionNameSuffix "_debug" buildConfigField "String", "platform", '"AH6016"' debuggable true signingConfig signingConfigs.zhanRui12 @@ -509,7 +532,7 @@ android { TeclastP20SDebug.initWith(debug) TeclastP20SDebug { debuggable true - versionNameSuffix "-debug" + versionNameSuffix "_debug" buildConfigField "String", "platform", '"P20S"' signingConfig signingConfigs.TeclastP20S } @@ -523,7 +546,7 @@ android { zhanruiG10ZDebug.initWith(debug) zhanruiG10ZDebug { buildConfigField "String", "platform", '"ZR6016"' - versionNameSuffix "-debug" + versionNameSuffix "_debug" debuggable true signingConfig signingConfigs.zhanruiG10Z } @@ -537,7 +560,7 @@ android { debug { buildConfigField "String", "platform", '"MTK"' debuggable true - versionNameSuffix "-debug" + versionNameSuffix "_debug" minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' signingConfig signingConfigs.mtk diff --git a/iconloaderlib/build.gradle b/iconloaderlib/build.gradle index cf92c89..d7ffc18 100644 --- a/iconloaderlib/build.gradle +++ b/iconloaderlib/build.gradle @@ -15,6 +15,10 @@ android { } buildTypes { + MX11Debug {} + MX11Release {} + G12NLDebug {} + G12NLRelease {} DX11Debug {} DX11Release {} G122Debug {} diff --git a/keystore/MT6789.keystore b/keystore/MT6789.keystore new file mode 100644 index 0000000..33a16e4 Binary files /dev/null and b/keystore/MT6789.keystore differ diff --git a/src/com/aoleyun/os/network/NetInterfaceManager.java b/src/com/aoleyun/os/network/NetInterfaceManager.java index 438e55f..cfafb5d 100644 --- a/src/com/aoleyun/os/network/NetInterfaceManager.java +++ b/src/com/aoleyun/os/network/NetInterfaceManager.java @@ -208,6 +208,10 @@ public class NetInterfaceManager { set.add(JgyUtils.AUDG104Tag); } else if (platform == JgyUtils.seewoPlatform) { set.add(JgyUtils.seewoTag); + } else if (platform == JgyUtils.DX11Platform) { + set.add(JgyUtils.DX11_TAG); + } else if (platform == JgyUtils.G12NLPlatform) { + set.add(JgyUtils.G12NL_TAG); } } }); diff --git a/src/com/aoleyun/os/network/interceptor/RepeatRequestInterceptor.java b/src/com/aoleyun/os/network/interceptor/RepeatRequestInterceptor.java index 1949303..c7f7612 100644 --- a/src/com/aoleyun/os/network/interceptor/RepeatRequestInterceptor.java +++ b/src/com/aoleyun/os/network/interceptor/RepeatRequestInterceptor.java @@ -8,9 +8,13 @@ import com.aoleyun.os.uiuiutils.MD5Util; import org.jetbrains.annotations.NotNull; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import okhttp3.Headers; import okhttp3.Interceptor; +import okhttp3.MediaType; import okhttp3.Protocol; import okhttp3.Request; import okhttp3.RequestBody; @@ -19,89 +23,292 @@ import okhttp3.ResponseBody; import okio.Buffer; /** - * v1.0 2022-07-15 16:16:52 + * 重复请求拦截器。 + * + *

原实现是在 {@code chain.proceed()} 之后才判断是否为重复请求,此时请求早已发到服务器, + * 只能丢弃返回结果,所以服务端依然会收到两次(甚至多次)请求;并且记录在 finally 中立即删除, + * 去重窗口几乎为零,重复请求根本拦不住。

+ * + *

优化后的策略:

+ * + * + *

v1.0 2022-07-15 16:16:52

*/ public class RepeatRequestInterceptor implements Interceptor { - private static final String TAG = RepeatRequestInterceptor.class.getSimpleName(); + private static final String TAG = "RepeatRequestInterceptor"; - private final ConcurrentHashMap requestIdsMap = new ConcurrentHashMap<>(); - public static final String REPEAT_REQUEST_PROTOCOL = "OKHTTP_REPEAT_REQUEST_PROTOCOL"; + /** + * 等待进行中请求返回结果的最长时间,超时后本次请求自己访问服务器。 + * 需要大于 OkHttpClient 的超时时间(当前为 5 秒)。 + */ + private static final long WAIT_IN_FLIGHT_TIMEOUT_MS = 10_000L; + + /** + * 请求完成后结果保留的时间。在该时间内收到相同请求,直接复用结果,不再访问服务器。 + * 需要小于业务侧的刷新间隔(ConnectMode 最小为 1 分钟)。 + */ + private static final long REUSE_WINDOW_MS = 500L; + + /** 超过该大小的响应体不参与去重(直接透传),避免内存占用 */ + private static final long MAX_DEDUP_BODY_BYTES = 1024 * 1024L; + + /** 参与去重 key 计算的请求体最大字节数 */ + private static final int MAX_BODY_BYTES_FOR_KEY = 8 * 1024; + + /** 日志中请求体、响应体的最大长度 */ + private static final int MAX_LOG_BODY_LENGTH = 10240; + + private final ConcurrentHashMap recordMap = new ConcurrentHashMap<>(); @NotNull @Override public Response intercept(@NotNull Chain chain) throws IOException { Request request = chain.request(); - Response response = chain.proceed(request); - ResponseBody responseBody = response.body(); + String bodyText = requestBodyToString(request.body()); + String requestKey = buildRequestKey(request, bodyText); - //会消费请求,导致请求多次 - String content = responseBody.string(); -// Response copy = response.newBuilder().body(responseBody).build(); - ResponseBody copy = ResponseBody.create(responseBody.contentType(), content); - if (BuildConfig.DEBUG) { - Log.e(TAG, "请求体返回:| Response: " + request.url().encodedPath() + "\t body: " + content); + clearExpiredRecords(); + + RepeatRecord record = acquireRecord(requestKey); + if (record == null) { + // 已有相同请求,但没有拿到可用结果(上一次失败、超时或结果过大),本次请求直接访问服务器 + return chain.proceed(request); } - //相同的请求 - String requestKey = MD5Util.getUpperMD5Str(request.method() + request.url().toString() + requestBodyToString(request.body())); - long time = System.currentTimeMillis();//请求时间 + if (!record.isOwner()) { + // 复用相同请求的结果:服务端只会收到一次请求 + log("复用请求:", requestKey, request, bodyText); + return record.newResponse(request); + } + + log("注册请求:", requestKey, request, bodyText); try { - if (requestIdsMap.size() > 0 && requestIdsMap.containsKey(requestKey)) { - log("重复请求:", requestKey, request); - //下面这行写了不会抛出onerror -// chain.call().cancel(); - return new Response.Builder() - .protocol(Protocol.get(REPEAT_REQUEST_PROTOCOL)) - .request(request) //multi thread - .build(); + Response response = chain.proceed(request); + ResponseBody responseBody = response.body(); + if (responseBody == null || responseBody.contentLength() > MAX_DEDUP_BODY_BYTES) { + // 无响应体或响应体过大:不消费 body,也不参与去重,直接透传 + logResponse(request, null); + return response; } - requestIdsMap.put(requestKey, time); - log("注册请求:", requestKey, request); -// RepeatRequestInterceptor.Builder builder = request.newBuilder(); -// builder.addHeader("header", jsonObject.toString()); - return response.newBuilder().body(copy).build(); + + // body 只能被消费一次,读出来之后重建一个新的 body 返回给调用方 + MediaType contentType = responseBody.contentType(); + byte[] bytes = responseBody.bytes(); + logResponse(request, bytes); + if (bytes.length <= MAX_DEDUP_BODY_BYTES) { + // 缓存结果,供等待中的、以及短时间内后续的相同请求复用 + record.complete(response, bytes, contentType); + } + return response.newBuilder().body(ResponseBody.create(contentType, bytes)).build(); } catch (IOException e) { Log.e(TAG, "intercept: " + e.getMessage()); throw e; } finally { - if (requestIdsMap.containsKey(requestKey) && requestIdsMap.containsValue(time)) {//请求任务完成删除map中的数据 - requestIdsMap.remove(requestKey); - log("移除请求:", requestKey, request); + // 兜底:无论成功、失败还是异常,都结束记录,避免等待中的相同请求被一直阻塞 + record.finish(); + } + } + + /** + * 获取请求对应的去重记录。 + * + * @return 返回记录表示本次请求要么成为 owner 去访问服务器、要么直接复用已有结果; + * 返回 null 表示已有相同请求但结果不可用,本次请求自己访问服务器(不参与去重) + */ + private RepeatRecord acquireRecord(String requestKey) { + while (true) { + long now = System.currentTimeMillis(); + RepeatRecord record = recordMap.get(requestKey); + if (record == null) { + RepeatRecord created = new RepeatRecord(); + if (recordMap.putIfAbsent(requestKey, created) == null) { + created.markOwner(); + return created; + } + continue;// 其他线程抢先注册,重新判断 + } + if (record.isExpired(now)) { + // 结果已过期,替换为本次请求的记录并成为 owner + RepeatRecord created = new RepeatRecord(); + if (recordMap.replace(requestKey, record, created)) { + created.markOwner(); + return created; + } + continue; + } + // 已有相同请求:等待它的结果,拿到结果直接复用 + if (record.await(WAIT_IN_FLIGHT_TIMEOUT_MS)) { + return record; + } + return null; + } + } + + /** 清理过期记录,释放其缓存的响应体 */ + private void clearExpiredRecords() { + if (recordMap.isEmpty()) { + return; + } + long now = System.currentTimeMillis(); + for (Map.Entry entry : recordMap.entrySet()) { + RepeatRecord record = entry.getValue(); + if (record.isExpired(now)) { + recordMap.remove(entry.getKey(), record); } } } - private void log(String action, String requestKey, Request request) { + private static String buildRequestKey(Request request, String bodyText) { + return MD5Util.getUpperMD5Str(request.method() + "|" + request.url() + "|" + bodyText); + } + + /** + * 请求体转字符串,用于计算去重 key 和输出日志。 + * 只读取可重复读写的请求体,避免把一次性(one-shot)请求体读空。 + */ + private static String requestBodyToString(RequestBody body) { + if (body == null || body.isOneShot()) { + return ""; + } + try { + Buffer buffer = new Buffer(); + body.writeTo(buffer); + long size = buffer.size(); + if (size > MAX_BODY_BYTES_FOR_KEY) { + // 请求体过大时只取前一部分参与去重,避免不必要的内存开销 + return size + ":" + buffer.readByteString(MAX_BODY_BYTES_FOR_KEY).hex(); + } + return buffer.readUtf8(); + } catch (Exception e) { + return ""; + } + } + + private static void log(String action, String requestKey, Request request, String bodyText) { if (BuildConfig.DEBUG) { - Log.e("REPEAT-REQUEST", action + requestKey + " Method @" + request.method() + " --- " + " URL = " + request.url().encodedPath() + "\t" + bodyToString(request)); + String body = bodyText.length() > MAX_LOG_BODY_LENGTH ? "-too long" : bodyText; + Log.e("REPEAT-REQUEST", action + requestKey + " Method @" + request.method() + + " --- " + " URL = " + request.url().encodedPath() + "\t" + body); } else { Log.e("REPEAT-REQUEST", action + requestKey + " Method @" + request.method()); } } - private static String bodyToString(final Request request) { - try { - final Request copy = request.newBuilder().build(); - final Buffer buffer = new Buffer(); - copy.body().writeTo(buffer); - if (buffer.size() > 4096) { - return "-too long"; - } - return buffer.readUtf8(); - } catch (Exception e) { - return "-"; + private static void logResponse(Request request, byte[] bytes) { + if (!BuildConfig.DEBUG) { + return; + } + if (bytes == null || bytes.length > MAX_LOG_BODY_LENGTH) { + Log.e(TAG, "请求体返回:| Response: " + request.url() + "\t body: too long"); + } else { + Log.e(TAG, "请求体返回:| Response: " + request.url() + + "\t body: " + new String(bytes, StandardCharsets.UTF_8)); } } - private static String requestBodyToString(RequestBody body) { - try { - final Buffer buffer = new Buffer(); - body.writeTo(buffer); - if (buffer.size() > 4096) { - return "-too long"; + /** + * 一次请求的去重记录:owner 线程负责访问服务器,其他相同请求等待并复用它的结果 + */ + private static final class RepeatRecord { + private final Object lock = new Object(); + private final long createdTime = System.currentTimeMillis(); + + private boolean owner; + private boolean finished; + private boolean succeed; + private long finishedTime; + private byte[] body; + private MediaType contentType; + private Headers headers; + private Protocol protocol; + private int code; + private String message; + + void markOwner() { + owner = true; + } + + boolean isOwner() { + return owner; + } + + /** + * 等待 owner 请求结束 + * + * @return true 表示已拿到可复用的结果 + */ + boolean await(long timeoutMs) { + synchronized (lock) { + if (!finished) { + try { + lock.wait(timeoutMs); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + return finished && succeed; + } + } + + /** 请求成功,缓存结果并唤醒等待的请求 */ + void complete(Response response, byte[] body, MediaType contentType) { + synchronized (lock) { + if (finished) { + return; + } + this.body = body; + this.contentType = contentType; + this.headers = response.headers(); + this.protocol = response.protocol(); + this.code = response.code(); + this.message = response.message(); + this.succeed = true; + finished = true; + finishedTime = System.currentTimeMillis(); + lock.notifyAll(); + } + } + + /** 请求结束(失败或异常时没有可复用的结果);重复调用忽略 */ + void finish() { + synchronized (lock) { + if (finished) { + return; + } + finished = true; + finishedTime = System.currentTimeMillis(); + lock.notifyAll(); + } + } + + /** 进行中的记录超过等待超时时间、已完成的记录超过结果保留时间即视为过期 */ + boolean isExpired(long now) { + synchronized (lock) { + if (!finished) { + return now - createdTime > WAIT_IN_FLIGHT_TIMEOUT_MS; + } + return now - finishedTime > REUSE_WINDOW_MS; + } + } + + /** 用缓存的结果构建响应,状态码、响应头、响应体都是真实数据 */ + Response newResponse(Request request) { + synchronized (lock) { + return new Response.Builder() + .request(request) + .protocol(protocol) + .code(code) + .message(message) + .headers(headers) + .body(ResponseBody.create(contentType, body)) + .build(); } - return buffer.readUtf8(); - } catch (Exception e) { - return "-"; } } } diff --git a/src/com/aoleyun/os/uiuiutils/JgyUtils.java b/src/com/aoleyun/os/uiuiutils/JgyUtils.java index fb07860..95564e2 100644 --- a/src/com/aoleyun/os/uiuiutils/JgyUtils.java +++ b/src/com/aoleyun/os/uiuiutils/JgyUtils.java @@ -45,6 +45,9 @@ public class JgyUtils { public static final int MT8768Platform = 17; public static final int AUDG104Platform = 18; public static final int seewoPlatform = 25; + public static final int DX11Platform = 31; + public static final int G12NLPlatform = 32; + public static final String Other = "其他"; public static final String MTKTag = "MTK"; @@ -62,6 +65,8 @@ public class JgyUtils { public static final String MT8768Tag = "MT8768"; public static final String AUDG104Tag = "AUDG104"; public static final String seewoTag = "seewo"; + public static final String DX11_TAG = "DX11"; + public static final String G12NL_TAG = "G12NL"; private JgyUtils(Context context) { @@ -120,6 +125,12 @@ public class JgyUtils { } else if (seewoTag.equalsIgnoreCase(platform)) { Log.i(TAG, "checkAppPlatform: " + "seewo"); return seewoPlatform; + } else if (DX11_TAG.equalsIgnoreCase(platform)) { + Log.i(TAG, "checkAppPlatform: " + "DX11"); + return DX11Platform; + } else if (G12NL_TAG.equalsIgnoreCase(platform)) { + Log.i(TAG, "checkAppPlatform: " + "G12NL"); + return G12NLPlatform; } else { Log.i(TAG, "checkAppPlatform: " + "没有数据"); return UnknowPlatform; @@ -154,6 +165,10 @@ public class JgyUtils { getAppPlatformCallback.AppPlatform(AUDG104Platform); } else if (seewoTag.equalsIgnoreCase(platform)) { getAppPlatformCallback.AppPlatform(seewoPlatform); + } else if (DX11_TAG.equalsIgnoreCase(platform)) { + getAppPlatformCallback.AppPlatform(DX11Platform); + } else if (G12NL_TAG.equalsIgnoreCase(platform)) { + getAppPlatformCallback.AppPlatform(G12NLPlatform); } else { getAppPlatformCallback.AppPlatform(UnknowPlatform); }