2929import java .util .Iterator ;
3030import java .util .zip .ZipEntry ;
3131import java .util .HashMap ;
32+ import java .util .regex .Matcher ;
33+ import java .util .regex .Pattern ;
3234
3335import okio .BufferedSink ;
3436import okio .BufferedSource ;
@@ -285,7 +287,16 @@ private void doFullPatch(DownloadTaskParams param) throws IOException {
285287 }
286288 }
287289
288- private String findDrawableFallback (String originalToPath , HashMap <String , String > copiesMap , HashMap <String , ZipEntry > availableEntries ) {
290+ // Pattern to strip -vN version qualifiers from resource directory paths
291+ // e.g., "res/drawable-xxhdpi-v4/img.png" → "res/drawable-xxhdpi/img.png"
292+ private static final Pattern VERSION_QUALIFIER_PATTERN =
293+ Pattern .compile ("-v\\ d+(?=/)" );
294+
295+ private String normalizeResPath (String path ) {
296+ return VERSION_QUALIFIER_PATTERN .matcher (path ).replaceAll ("" );
297+ }
298+
299+ private String findDrawableFallback (String originalToPath , HashMap <String , String > copiesMap , HashMap <String , ZipEntry > availableEntries , HashMap <String , String > normalizedEntryMap ) {
289300 // 检查是否是 drawable 路径
290301 if (!originalToPath .contains ("drawable" )) {
291302 return null ;
@@ -309,13 +320,22 @@ private String findDrawableFallback(String originalToPath, HashMap<String, Strin
309320 // 检查这个 key 是否在 copies 映射中
310321 if (copiesMap .containsKey (fallbackToPath )) {
311322 String fallbackFromPath = copiesMap .get (fallbackToPath );
312- // 检查对应的 value 路径是否在 APK 中存在
323+ // 检查对应的 value 路径是否在 APK 中存在(精确匹配)
313324 if (availableEntries .containsKey (fallbackFromPath )) {
314325 if (UpdateContext .DEBUG ) {
315326 Log .d ("react-native-update" , "Found fallback for " + originalToPath + ": " + fallbackToPath + " -> " + fallbackFromPath );
316327 }
317328 return fallbackFromPath ;
318329 }
330+ // 尝试版本限定符无关匹配(APK ↔ AAB 兼容)
331+ String normalizedFallback = normalizeResPath (fallbackFromPath );
332+ String actualEntry = normalizedEntryMap .get (normalizedFallback );
333+ if (actualEntry != null ) {
334+ if (UpdateContext .DEBUG ) {
335+ Log .d ("react-native-update" , "Found normalized fallback for " + originalToPath + ": " + fallbackToPath + " -> " + actualEntry );
336+ }
337+ return actualEntry ;
338+ }
319339 }
320340 }
321341
@@ -369,6 +389,14 @@ private void copyFromResource(HashMap<String, ArrayList<File> > resToCopy, HashM
369389 }
370390 }
371391
392+ // 构建规范化路径映射,用于 APK ↔ AAB 版本限定符无关匹配
393+ // 例如 "res/drawable-xxhdpi-v4/img.png" → "res/drawable-xxhdpi/img.png"
394+ HashMap <String , String > normalizedEntryMap = new HashMap <>();
395+ for (String entryName : availableEntries .keySet ()) {
396+ String normalized = normalizeResPath (entryName );
397+ normalizedEntryMap .putIfAbsent (normalized , entryName );
398+ }
399+
372400 // 使用基础 APK 的 ZipFile 作为主要操作对象
373401 SafeZipFile zipFile = zipFileMap .get (context .getPackageResourcePath ());
374402
@@ -387,7 +415,21 @@ private void copyFromResource(HashMap<String, ArrayList<File> > resToCopy, HashM
387415 ZipEntry ze = availableEntries .get (fromPath );
388416 String actualSourcePath = fromPath ;
389417
390- // 如果文件不存在,尝试 fallback
418+ // 如果精确匹配找不到,尝试版本限定符无关匹配(APK ↔ AAB 兼容)
419+ // 例如 __diff.json 中的 "res/drawable-xxhdpi-v4/img.png" 匹配设备上的 "res/drawable-xxhdpi/img.png"
420+ if (ze == null ) {
421+ String normalizedFrom = normalizeResPath (fromPath );
422+ String actualEntry = normalizedEntryMap .get (normalizedFrom );
423+ if (actualEntry != null ) {
424+ ze = availableEntries .get (actualEntry );
425+ actualSourcePath = actualEntry ;
426+ if (UpdateContext .DEBUG ) {
427+ Log .d ("react-native-update" , "Normalized match: " + fromPath + " -> " + actualEntry );
428+ }
429+ }
430+ }
431+
432+ // 如果仍然找不到,尝试 drawable 密度降级 fallback
391433 if (ze == null ) {
392434 if (UpdateContext .DEBUG ) {
393435 Log .d ("react-native-update" , "File not found in APK: " + fromPath + ", trying fallback" );
@@ -405,28 +447,10 @@ private void copyFromResource(HashMap<String, ArrayList<File> > resToCopy, HashM
405447 if (UpdateContext .DEBUG ) {
406448 Log .d ("react-native-update" , "Found toPath: " + toPath + " for fromPath: " + fromPath );
407449 }
408- String fallbackFromPath = findDrawableFallback (toPath , copiesMap , availableEntries );
450+ String fallbackFromPath = findDrawableFallback (toPath , copiesMap , availableEntries , normalizedEntryMap );
409451 if (fallbackFromPath != null ) {
410452 ze = availableEntries .get (fallbackFromPath );
411453 actualSourcePath = fallbackFromPath ;
412- // 确保 fallback 路径也在 entryToZipFileMap 中
413- if (!entryToZipFileMap .containsKey (fallbackFromPath )) {
414- // 查找包含该 fallback 路径的 ZipFile
415- for (String apkPath : apkPaths ) {
416- SafeZipFile testZipFile = zipFileMap .get (apkPath );
417- if (testZipFile != null ) {
418- try {
419- ZipEntry testEntry = testZipFile .getEntry (fallbackFromPath );
420- if (testEntry != null ) {
421- entryToZipFileMap .put (fallbackFromPath , testZipFile );
422- break ;
423- }
424- } catch (Exception e ) {
425- // 继续查找
426- }
427- }
428- }
429- }
430454 if (UpdateContext .DEBUG ) {
431455 Log .w ("react-native-update" , "Using fallback: " + fallbackFromPath + " for " + fromPath );
432456 }
0 commit comments