capacitor-dex-editor 0.0.55 → 0.0.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -537,6 +537,40 @@ public class DexEditorPluginPlugin extends Plugin {
|
|
|
537
537
|
dexManager.closeMultiDexSession(params.getString("sessionId"));
|
|
538
538
|
break;
|
|
539
539
|
|
|
540
|
+
case "addClassToSession":
|
|
541
|
+
dexManager.addClassToSession(
|
|
542
|
+
params.getString("sessionId"),
|
|
543
|
+
params.getString("className"),
|
|
544
|
+
params.getString("smaliContent")
|
|
545
|
+
);
|
|
546
|
+
break;
|
|
547
|
+
|
|
548
|
+
case "deleteClassFromSession":
|
|
549
|
+
dexManager.deleteClassFromSession(
|
|
550
|
+
params.getString("sessionId"),
|
|
551
|
+
params.getString("className")
|
|
552
|
+
);
|
|
553
|
+
break;
|
|
554
|
+
|
|
555
|
+
case "getMethodFromSession":
|
|
556
|
+
result.put("data", dexManager.getMethodFromSession(
|
|
557
|
+
params.getString("sessionId"),
|
|
558
|
+
params.getString("className"),
|
|
559
|
+
params.getString("methodName"),
|
|
560
|
+
params.optString("methodSignature", "")
|
|
561
|
+
));
|
|
562
|
+
break;
|
|
563
|
+
|
|
564
|
+
case "modifyMethodInSession":
|
|
565
|
+
dexManager.modifyMethodInSession(
|
|
566
|
+
params.getString("sessionId"),
|
|
567
|
+
params.getString("className"),
|
|
568
|
+
params.getString("methodName"),
|
|
569
|
+
params.optString("methodSignature", ""),
|
|
570
|
+
params.getString("newMethodCode")
|
|
571
|
+
);
|
|
572
|
+
break;
|
|
573
|
+
|
|
540
574
|
case "listSessions":
|
|
541
575
|
result.put("data", dexManager.listAllSessions());
|
|
542
576
|
break;
|
|
@@ -98,6 +98,30 @@ public class DexManager {
|
|
|
98
98
|
|
|
99
99
|
// 存储多 DEX 会话(MCP 工作流)
|
|
100
100
|
private final Map<String, MultiDexSession> multiDexSessions = new HashMap<>();
|
|
101
|
+
|
|
102
|
+
// APK DEX 缓存 - 用于加速编译(key: apkPath + ":" + dexPath)
|
|
103
|
+
private final Map<String, ApkDexCache> apkDexCaches = new HashMap<>();
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* APK DEX 缓存 - 缓存从 APK 读取的 DEX 数据和 ClassDef
|
|
107
|
+
*/
|
|
108
|
+
private static class ApkDexCache {
|
|
109
|
+
String apkPath;
|
|
110
|
+
String dexPath;
|
|
111
|
+
long lastModified;
|
|
112
|
+
Map<String, ClassDef> classDefMap; // type -> ClassDef
|
|
113
|
+
int dexVersion;
|
|
114
|
+
|
|
115
|
+
ApkDexCache(String apkPath, String dexPath) {
|
|
116
|
+
this.apkPath = apkPath;
|
|
117
|
+
this.dexPath = dexPath;
|
|
118
|
+
this.classDefMap = new HashMap<>();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
String getCacheKey() {
|
|
122
|
+
return apkPath + ":" + dexPath;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
101
125
|
|
|
102
126
|
/**
|
|
103
127
|
* DEX 会话 - 存储加载的 DEX 文件及其修改状态
|
|
@@ -1959,6 +1983,194 @@ public class DexManager {
|
|
|
1959
1983
|
Log.d(TAG, "Modified class in session: " + className);
|
|
1960
1984
|
}
|
|
1961
1985
|
|
|
1986
|
+
/**
|
|
1987
|
+
* 添加新类到会话
|
|
1988
|
+
*/
|
|
1989
|
+
public void addClassToSession(String sessionId, String className, String smaliContent) throws Exception {
|
|
1990
|
+
MultiDexSession session = multiDexSessions.get(sessionId);
|
|
1991
|
+
if (session == null) {
|
|
1992
|
+
throw new IllegalArgumentException("Session not found: " + sessionId);
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
// 编译 Smali
|
|
1996
|
+
ClassDef newClassDef = compileSmaliToClass(smaliContent, Opcodes.getDefault());
|
|
1997
|
+
|
|
1998
|
+
// 添加到第一个 DEX(默认 classes.dex)
|
|
1999
|
+
String targetDex = "classes.dex";
|
|
2000
|
+
if (!session.dexFiles.containsKey(targetDex) && !session.dexFiles.isEmpty()) {
|
|
2001
|
+
targetDex = session.dexFiles.keySet().iterator().next();
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
session.modifiedClasses.put(targetDex + "|" + className, newClassDef);
|
|
2005
|
+
session.modified = true;
|
|
2006
|
+
|
|
2007
|
+
Log.d(TAG, "Added class to session: " + className);
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
/**
|
|
2011
|
+
* 从会话中删除类
|
|
2012
|
+
*/
|
|
2013
|
+
public void deleteClassFromSession(String sessionId, String className) throws Exception {
|
|
2014
|
+
MultiDexSession session = multiDexSessions.get(sessionId);
|
|
2015
|
+
if (session == null) {
|
|
2016
|
+
throw new IllegalArgumentException("Session not found: " + sessionId);
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
String targetType = convertClassNameToType(className);
|
|
2020
|
+
|
|
2021
|
+
// 找到类所在的 DEX 并标记为删除
|
|
2022
|
+
String targetDex = null;
|
|
2023
|
+
for (Map.Entry<String, DexBackedDexFile> entry : session.dexFiles.entrySet()) {
|
|
2024
|
+
for (ClassDef classDef : entry.getValue().getClasses()) {
|
|
2025
|
+
if (classDef.getType().equals(targetType)) {
|
|
2026
|
+
targetDex = entry.getKey();
|
|
2027
|
+
break;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
if (targetDex != null) break;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
if (targetDex == null) {
|
|
2034
|
+
throw new IllegalArgumentException("Class not found: " + className);
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
// 用 null 标记删除
|
|
2038
|
+
session.modifiedClasses.put(targetDex + "|" + className, null);
|
|
2039
|
+
session.modified = true;
|
|
2040
|
+
|
|
2041
|
+
Log.d(TAG, "Deleted class from session: " + className);
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
/**
|
|
2045
|
+
* 从会话中获取单个方法的 Smali 代码
|
|
2046
|
+
*/
|
|
2047
|
+
public JSObject getMethodFromSession(String sessionId, String className, String methodName, String methodSignature) throws Exception {
|
|
2048
|
+
JSObject result = new JSObject();
|
|
2049
|
+
|
|
2050
|
+
MultiDexSession session = multiDexSessions.get(sessionId);
|
|
2051
|
+
if (session == null) {
|
|
2052
|
+
throw new IllegalArgumentException("Session not found: " + sessionId);
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
// 先获取整个类的 Smali
|
|
2056
|
+
JSObject classResult = getClassSmaliFromSession(sessionId, className);
|
|
2057
|
+
String smaliContent = classResult.optString("smaliContent", "");
|
|
2058
|
+
|
|
2059
|
+
if (smaliContent.isEmpty()) {
|
|
2060
|
+
result.put("methodCode", "# 类未找到: " + className);
|
|
2061
|
+
return result;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
// 解析并提取方法
|
|
2065
|
+
String methodCode = extractMethodFromSmali(smaliContent, methodName, methodSignature);
|
|
2066
|
+
result.put("methodCode", methodCode);
|
|
2067
|
+
result.put("className", className);
|
|
2068
|
+
result.put("methodName", methodName);
|
|
2069
|
+
|
|
2070
|
+
return result;
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
/**
|
|
2074
|
+
* 修改会话中的单个方法
|
|
2075
|
+
*/
|
|
2076
|
+
public void modifyMethodInSession(String sessionId, String className, String methodName, String methodSignature, String newMethodCode) throws Exception {
|
|
2077
|
+
MultiDexSession session = multiDexSessions.get(sessionId);
|
|
2078
|
+
if (session == null) {
|
|
2079
|
+
throw new IllegalArgumentException("Session not found: " + sessionId);
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
// 获取整个类的 Smali
|
|
2083
|
+
JSObject classResult = getClassSmaliFromSession(sessionId, className);
|
|
2084
|
+
String smaliContent = classResult.optString("smaliContent", "");
|
|
2085
|
+
|
|
2086
|
+
if (smaliContent.isEmpty()) {
|
|
2087
|
+
throw new IllegalArgumentException("Class not found: " + className);
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
// 替换方法
|
|
2091
|
+
String newSmaliContent = replaceMethodInSmali(smaliContent, methodName, methodSignature, newMethodCode);
|
|
2092
|
+
|
|
2093
|
+
// 保存修改后的类
|
|
2094
|
+
modifyClassInSession(sessionId, className, newSmaliContent);
|
|
2095
|
+
|
|
2096
|
+
Log.d(TAG, "Modified method in session: " + className + "." + methodName);
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
/**
|
|
2100
|
+
* 从 Smali 代码中提取指定方法
|
|
2101
|
+
*/
|
|
2102
|
+
private String extractMethodFromSmali(String smaliContent, String methodName, String methodSignature) {
|
|
2103
|
+
String[] lines = smaliContent.split("\n");
|
|
2104
|
+
StringBuilder methodCode = new StringBuilder();
|
|
2105
|
+
boolean inMethod = false;
|
|
2106
|
+
boolean found = false;
|
|
2107
|
+
|
|
2108
|
+
for (String line : lines) {
|
|
2109
|
+
if (line.startsWith(".method ")) {
|
|
2110
|
+
// 检查是否是目标方法
|
|
2111
|
+
if (line.contains(" " + methodName + "(") || line.contains(" " + methodName + ";")) {
|
|
2112
|
+
// 如果指定了签名,进一步匹配
|
|
2113
|
+
if (methodSignature == null || methodSignature.isEmpty() || line.contains(methodSignature)) {
|
|
2114
|
+
inMethod = true;
|
|
2115
|
+
found = true;
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
if (inMethod) {
|
|
2121
|
+
methodCode.append(line).append("\n");
|
|
2122
|
+
if (line.equals(".end method")) {
|
|
2123
|
+
break;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
if (!found) {
|
|
2129
|
+
return "# 方法未找到: " + methodName + (methodSignature != null ? methodSignature : "");
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
return methodCode.toString();
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
/**
|
|
2136
|
+
* 替换 Smali 代码中的指定方法
|
|
2137
|
+
*/
|
|
2138
|
+
private String replaceMethodInSmali(String smaliContent, String methodName, String methodSignature, String newMethodCode) {
|
|
2139
|
+
String[] lines = smaliContent.split("\n");
|
|
2140
|
+
StringBuilder result = new StringBuilder();
|
|
2141
|
+
boolean inMethod = false;
|
|
2142
|
+
boolean replaced = false;
|
|
2143
|
+
|
|
2144
|
+
for (String line : lines) {
|
|
2145
|
+
if (line.startsWith(".method ")) {
|
|
2146
|
+
if (line.contains(" " + methodName + "(") || line.contains(" " + methodName + ";")) {
|
|
2147
|
+
if (methodSignature == null || methodSignature.isEmpty() || line.contains(methodSignature)) {
|
|
2148
|
+
// 插入新方法代码
|
|
2149
|
+
result.append(newMethodCode.trim()).append("\n");
|
|
2150
|
+
inMethod = true;
|
|
2151
|
+
replaced = true;
|
|
2152
|
+
continue;
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
if (inMethod) {
|
|
2158
|
+
if (line.equals(".end method")) {
|
|
2159
|
+
inMethod = false;
|
|
2160
|
+
}
|
|
2161
|
+
continue;
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
result.append(line).append("\n");
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
if (!replaced) {
|
|
2168
|
+
throw new IllegalArgumentException("Method not found: " + methodName);
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
return result.toString();
|
|
2172
|
+
}
|
|
2173
|
+
|
|
1962
2174
|
/**
|
|
1963
2175
|
* 保存多 DEX 会话的修改到 APK
|
|
1964
2176
|
*/
|
|
@@ -2119,27 +2331,28 @@ public class DexManager {
|
|
|
2119
2331
|
}
|
|
2120
2332
|
|
|
2121
2333
|
/**
|
|
2122
|
-
*
|
|
2334
|
+
* 获取或创建 APK DEX 缓存
|
|
2123
2335
|
*/
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2336
|
+
private ApkDexCache getOrCreateDexCache(String apkPath, String dexPath) throws Exception {
|
|
2337
|
+
String cacheKey = apkPath + ":" + dexPath;
|
|
2338
|
+
java.io.File apkFile = new java.io.File(apkPath);
|
|
2339
|
+
long currentModified = apkFile.lastModified();
|
|
2127
2340
|
|
|
2128
|
-
|
|
2341
|
+
ApkDexCache cache = apkDexCaches.get(cacheKey);
|
|
2129
2342
|
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2343
|
+
// 检查缓存是否有效
|
|
2344
|
+
if (cache != null && cache.lastModified == currentModified && !cache.classDefMap.isEmpty()) {
|
|
2345
|
+
Log.d(TAG, "Using DEX cache for: " + cacheKey + " (" + cache.classDefMap.size() + " classes)");
|
|
2346
|
+
return cache;
|
|
2133
2347
|
}
|
|
2134
2348
|
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2349
|
+
// 需要重新加载
|
|
2350
|
+
Log.d(TAG, "Loading DEX into cache: " + cacheKey);
|
|
2351
|
+
cache = new ApkDexCache(apkPath, dexPath);
|
|
2352
|
+
cache.lastModified = currentModified;
|
|
2139
2353
|
|
|
2354
|
+
java.util.zip.ZipFile zipFile = new java.util.zip.ZipFile(apkPath);
|
|
2140
2355
|
try {
|
|
2141
|
-
zipFile = new java.util.zip.ZipFile(apkPath);
|
|
2142
|
-
|
|
2143
2356
|
// 尝试多种可能的 dexPath 格式
|
|
2144
2357
|
java.util.zip.ZipEntry dexEntry = zipFile.getEntry(dexPath);
|
|
2145
2358
|
if (dexEntry == null) {
|
|
@@ -2154,33 +2367,70 @@ public class DexManager {
|
|
|
2154
2367
|
}
|
|
2155
2368
|
|
|
2156
2369
|
if (dexEntry == null) {
|
|
2157
|
-
|
|
2158
|
-
return result;
|
|
2370
|
+
throw new Exception("DEX 文件未找到: " + dexPath);
|
|
2159
2371
|
}
|
|
2160
2372
|
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
// 读取 DEX 文件到内存
|
|
2373
|
+
// 读取 DEX 文件
|
|
2374
|
+
java.io.InputStream dexInputStream = zipFile.getInputStream(dexEntry);
|
|
2164
2375
|
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
|
2165
|
-
byte[] buffer = new byte[
|
|
2376
|
+
byte[] buffer = new byte[16384];
|
|
2166
2377
|
int len;
|
|
2167
2378
|
while ((len = dexInputStream.read(buffer)) != -1) {
|
|
2168
2379
|
baos.write(buffer, 0, len);
|
|
2169
2380
|
}
|
|
2381
|
+
dexInputStream.close();
|
|
2170
2382
|
byte[] dexBytes = baos.toByteArray();
|
|
2171
2383
|
|
|
2172
|
-
// 解析 DEX
|
|
2384
|
+
// 解析 DEX 文件并缓存所有 ClassDef
|
|
2173
2385
|
DexBackedDexFile dexFile = new DexBackedDexFile(Opcodes.getDefault(), dexBytes);
|
|
2386
|
+
cache.dexVersion = 35; // 默认 DEX 版本
|
|
2174
2387
|
|
|
2175
|
-
// 查找目标类
|
|
2176
|
-
ClassDef targetClass = null;
|
|
2177
2388
|
for (ClassDef classDef : dexFile.getClasses()) {
|
|
2178
|
-
|
|
2179
|
-
targetClass = classDef;
|
|
2180
|
-
break;
|
|
2181
|
-
}
|
|
2389
|
+
cache.classDefMap.put(classDef.getType(), classDef);
|
|
2182
2390
|
}
|
|
2183
2391
|
|
|
2392
|
+
Log.d(TAG, "Cached " + cache.classDefMap.size() + " classes from " + dexPath);
|
|
2393
|
+
} finally {
|
|
2394
|
+
zipFile.close();
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
apkDexCaches.put(cacheKey, cache);
|
|
2398
|
+
return cache;
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
/**
|
|
2402
|
+
* 清除指定 APK 的 DEX 缓存
|
|
2403
|
+
*/
|
|
2404
|
+
public void clearDexCache(String apkPath) {
|
|
2405
|
+
java.util.Iterator<String> it = apkDexCaches.keySet().iterator();
|
|
2406
|
+
while (it.hasNext()) {
|
|
2407
|
+
if (it.next().startsWith(apkPath + ":")) {
|
|
2408
|
+
it.remove();
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
Log.d(TAG, "Cleared DEX cache for: " + apkPath);
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
/**
|
|
2415
|
+
* 从 APK 中的 DEX 文件获取类的 Smali 代码(使用缓存)
|
|
2416
|
+
*/
|
|
2417
|
+
public JSObject getClassSmaliFromApk(String apkPath, String dexPath, String className) throws Exception {
|
|
2418
|
+
JSObject result = new JSObject();
|
|
2419
|
+
|
|
2420
|
+
Log.d(TAG, "getClassSmaliFromApk: apkPath=" + apkPath + ", dexPath=" + dexPath + ", className=" + className);
|
|
2421
|
+
|
|
2422
|
+
if (className == null || className.isEmpty()) {
|
|
2423
|
+
result.put("smali", "# 未指定类名");
|
|
2424
|
+
return result;
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
String targetType = convertClassNameToType(className);
|
|
2428
|
+
|
|
2429
|
+
try {
|
|
2430
|
+
// 使用缓存获取 ClassDef
|
|
2431
|
+
ApkDexCache cache = getOrCreateDexCache(apkPath, dexPath);
|
|
2432
|
+
ClassDef targetClass = cache.classDefMap.get(targetType);
|
|
2433
|
+
|
|
2184
2434
|
if (targetClass == null) {
|
|
2185
2435
|
result.put("smali", "# 类未找到: " + className + "\n# 目标类型: " + targetType);
|
|
2186
2436
|
return result;
|
|
@@ -2197,13 +2447,8 @@ public class DexManager {
|
|
|
2197
2447
|
|
|
2198
2448
|
result.put("smali", stringWriter.toString());
|
|
2199
2449
|
|
|
2200
|
-
}
|
|
2201
|
-
|
|
2202
|
-
try { dexInputStream.close(); } catch (Exception ignored) {}
|
|
2203
|
-
}
|
|
2204
|
-
if (zipFile != null) {
|
|
2205
|
-
try { zipFile.close(); } catch (Exception ignored) {}
|
|
2206
|
-
}
|
|
2450
|
+
} catch (Exception e) {
|
|
2451
|
+
result.put("smali", "# 加载失败: " + e.getMessage());
|
|
2207
2452
|
}
|
|
2208
2453
|
|
|
2209
2454
|
return result;
|
|
@@ -2250,72 +2495,42 @@ public class DexManager {
|
|
|
2250
2495
|
return result;
|
|
2251
2496
|
}
|
|
2252
2497
|
|
|
2253
|
-
reportProgress(
|
|
2498
|
+
reportProgress(20, 100);
|
|
2254
2499
|
Log.d(TAG, "Smali compiled successfully to ClassDef");
|
|
2255
2500
|
|
|
2256
|
-
//
|
|
2501
|
+
// 使用缓存获取 ClassDef(核心优化)
|
|
2257
2502
|
reportTitle("合并 DEX");
|
|
2258
|
-
reportMessage("
|
|
2259
|
-
|
|
2260
|
-
java.util.zip.ZipFile zipFile = new java.util.zip.ZipFile(apkPath);
|
|
2261
|
-
java.util.zip.ZipEntry dexEntry = zipFile.getEntry(dexPath);
|
|
2262
|
-
|
|
2263
|
-
if (dexEntry == null) {
|
|
2264
|
-
result.put("success", false);
|
|
2265
|
-
result.put("error", "DEX 文件未找到: " + dexPath);
|
|
2266
|
-
zipFile.close();
|
|
2267
|
-
return result;
|
|
2268
|
-
}
|
|
2269
|
-
|
|
2270
|
-
// 读取原 DEX
|
|
2271
|
-
java.io.InputStream dexInputStream = zipFile.getInputStream(dexEntry);
|
|
2272
|
-
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
|
2273
|
-
byte[] buffer = new byte[16384];
|
|
2274
|
-
int len;
|
|
2275
|
-
while ((len = dexInputStream.read(buffer)) != -1) {
|
|
2276
|
-
baos.write(buffer, 0, len);
|
|
2277
|
-
}
|
|
2278
|
-
byte[] originalDexBytes = baos.toByteArray();
|
|
2279
|
-
dexInputStream.close();
|
|
2280
|
-
|
|
2281
|
-
// 解析原 DEX
|
|
2282
|
-
DexBackedDexFile originalDex = new DexBackedDexFile(Opcodes.getDefault(), originalDexBytes);
|
|
2283
|
-
|
|
2284
|
-
reportMessage("收集类定义...");
|
|
2285
|
-
reportProgress(40, 100);
|
|
2503
|
+
reportMessage("获取缓存...");
|
|
2286
2504
|
|
|
2287
2505
|
String targetType = "L" + className.replace(".", "/") + ";";
|
|
2288
|
-
|
|
2506
|
+
ApkDexCache cache = getOrCreateDexCache(apkPath, dexPath);
|
|
2289
2507
|
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
if (!classDef.getType().equals(targetType)) {
|
|
2293
|
-
mergedClasses.add(classDef);
|
|
2294
|
-
}
|
|
2295
|
-
}
|
|
2508
|
+
reportProgress(30, 100);
|
|
2509
|
+
reportMessage("更新缓存...");
|
|
2296
2510
|
|
|
2297
|
-
//
|
|
2298
|
-
|
|
2511
|
+
// 更新缓存中的 ClassDef
|
|
2512
|
+
cache.classDefMap.put(targetType, newClassDef);
|
|
2299
2513
|
|
|
2300
|
-
Log.d(TAG, "
|
|
2514
|
+
Log.d(TAG, "Using cached " + cache.classDefMap.size() + " classes");
|
|
2301
2515
|
|
|
2302
|
-
// 创建新的 DEX
|
|
2303
|
-
reportMessage("写入 DEX (" +
|
|
2304
|
-
reportProgress(
|
|
2516
|
+
// 创建新的 DEX 文件(使用缓存的 ClassDef)
|
|
2517
|
+
reportMessage("写入 DEX (" + cache.classDefMap.size() + " 个类)...");
|
|
2518
|
+
reportProgress(40, 100);
|
|
2305
2519
|
|
|
2306
2520
|
DexPool dexPool = new DexPool(Opcodes.getDefault());
|
|
2307
|
-
int totalClasses =
|
|
2521
|
+
int totalClasses = cache.classDefMap.size();
|
|
2308
2522
|
int currentClass = 0;
|
|
2309
|
-
for (ClassDef classDef :
|
|
2523
|
+
for (ClassDef classDef : cache.classDefMap.values()) {
|
|
2310
2524
|
dexPool.internClass(classDef);
|
|
2311
2525
|
currentClass++;
|
|
2312
|
-
if (currentClass %
|
|
2313
|
-
reportProgress(
|
|
2526
|
+
if (currentClass % 200 == 0 || currentClass == totalClasses) {
|
|
2527
|
+
reportProgress(40 + (currentClass * 40 / totalClasses), 100);
|
|
2314
2528
|
}
|
|
2315
2529
|
}
|
|
2316
2530
|
|
|
2317
2531
|
// 使用内存写入 DEX(避免临时文件)
|
|
2318
2532
|
reportProgress(80, 100);
|
|
2533
|
+
reportMessage("生成 DEX...");
|
|
2319
2534
|
com.android.tools.smali.dexlib2.writer.io.MemoryDataStore memoryStore =
|
|
2320
2535
|
new com.android.tools.smali.dexlib2.writer.io.MemoryDataStore();
|
|
2321
2536
|
dexPool.writeTo(memoryStore);
|
|
@@ -2323,8 +2538,6 @@ public class DexManager {
|
|
|
2323
2538
|
|
|
2324
2539
|
Log.d(TAG, "Merged DEX size: " + newDexBytes.length + " bytes");
|
|
2325
2540
|
|
|
2326
|
-
zipFile.close();
|
|
2327
|
-
|
|
2328
2541
|
// MT 风格:直接替换 APK 内的 DEX
|
|
2329
2542
|
reportTitle("更新 APK");
|
|
2330
2543
|
reportMessage("正在替换 DEX...");
|
|
@@ -2389,6 +2602,9 @@ public class DexManager {
|
|
|
2389
2602
|
Log.d(TAG, "APK updated successfully: " + apkPath);
|
|
2390
2603
|
reportProgress(100, 100);
|
|
2391
2604
|
|
|
2605
|
+
// 更新缓存的 lastModified 以匹配新的 APK
|
|
2606
|
+
cache.lastModified = new java.io.File(apkPath).lastModified();
|
|
2607
|
+
|
|
2392
2608
|
result.put("success", true);
|
|
2393
2609
|
result.put("message", "Smali 编译成功!APK 已更新");
|
|
2394
2610
|
result.put("apkPath", apkPath);
|