react-native-update 10.39.0-beta.2 → 10.39.0-beta.3

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.
@@ -3,9 +3,12 @@ package cn.reactnative.modules.update;
3
3
  import android.content.Context;
4
4
  import android.content.pm.ApplicationInfo;
5
5
  import android.content.pm.PackageManager;
6
+ import android.content.res.Resources;
6
7
  import android.os.AsyncTask;
7
8
  import android.os.Build;
9
+ import android.util.DisplayMetrics;
8
10
  import android.util.Log;
11
+ import android.util.TypedValue;
9
12
  import com.facebook.react.bridge.Arguments;
10
13
  import com.facebook.react.bridge.WritableMap;
11
14
 
@@ -29,7 +32,6 @@ import java.util.Enumeration;
29
32
  import java.util.Iterator;
30
33
  import java.util.zip.ZipEntry;
31
34
  import java.util.HashMap;
32
- import java.util.regex.Matcher;
33
35
  import java.util.regex.Pattern;
34
36
 
35
37
  import okio.BufferedSink;
@@ -197,8 +199,7 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
197
199
  JSONObject manifest,
198
200
  ArrayList<String> copyFroms,
199
201
  ArrayList<String> copyTos,
200
- ArrayList<String> deletes,
201
- HashMap<String, String> copiesMap
202
+ ArrayList<String> deletes
202
203
  ) throws JSONException {
203
204
  JSONObject copies = manifest.optJSONObject("copies");
204
205
  if (copies != null) {
@@ -211,9 +212,6 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
211
212
  }
212
213
  copyFroms.add(from);
213
214
  copyTos.add(to);
214
- if (copiesMap != null) {
215
- copiesMap.put(to, from);
216
- }
217
215
  }
218
216
  }
219
217
 
@@ -228,13 +226,20 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
228
226
 
229
227
  private void copyBundledAssetToFile(String assetName, File destination) throws IOException {
230
228
  InputStream in = context.getAssets().open(assetName);
229
+ copyInputStreamToFile(in, destination);
230
+ }
231
+
232
+ private void copyInputStreamToFile(InputStream in, File destination) throws IOException {
231
233
  FileOutputStream fout = new FileOutputStream(destination);
232
- int count;
233
- while ((count = in.read(buffer)) != -1) {
234
- fout.write(buffer, 0, count);
234
+ try {
235
+ int count;
236
+ while ((count = in.read(buffer)) != -1) {
237
+ fout.write(buffer, 0, count);
238
+ }
239
+ } finally {
240
+ fout.close();
241
+ in.close();
235
242
  }
236
- fout.close();
237
- in.close();
238
243
  }
239
244
 
240
245
  private HashMap<String, ArrayList<File>> buildCopyList(
@@ -303,53 +308,127 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
303
308
  return VERSION_QUALIFIER_PATTERN.matcher(result).replaceAll("");
304
309
  }
305
310
 
306
- private String findDrawableFallback(String originalToPath, HashMap<String, String> copiesMap, HashMap<String, ZipEntry> availableEntries, HashMap<String, String> normalizedEntryMap) {
307
- // 检查是否是 drawable 路径
308
- if (!originalToPath.contains("drawable")) {
311
+ private static class ResolvedResourceSource {
312
+ final int resourceId;
313
+ final String assetPath;
314
+
315
+ ResolvedResourceSource(int resourceId, String assetPath) {
316
+ this.resourceId = resourceId;
317
+ this.assetPath = assetPath;
318
+ }
319
+ }
320
+
321
+ private String extractResourceType(String directoryName) {
322
+ int qualifierIndex = directoryName.indexOf('-');
323
+ if (qualifierIndex == -1) {
324
+ return directoryName;
325
+ }
326
+ return directoryName.substring(0, qualifierIndex);
327
+ }
328
+
329
+ private String extractResourceName(String fileName) {
330
+ if (fileName.endsWith(".9.png")) {
331
+ return fileName.substring(0, fileName.length() - ".9.png".length());
332
+ }
333
+ int extensionIndex = fileName.lastIndexOf('.');
334
+ if (extensionIndex == -1) {
335
+ return fileName;
336
+ }
337
+ return fileName.substring(0, extensionIndex);
338
+ }
339
+
340
+ private Integer parseDensityQualifier(String directoryName) {
341
+ String[] qualifiers = directoryName.split("-");
342
+ for (String qualifier : qualifiers) {
343
+ if ("ldpi".equals(qualifier)) {
344
+ return DisplayMetrics.DENSITY_LOW;
345
+ }
346
+ if ("mdpi".equals(qualifier)) {
347
+ return DisplayMetrics.DENSITY_MEDIUM;
348
+ }
349
+ if ("hdpi".equals(qualifier)) {
350
+ return DisplayMetrics.DENSITY_HIGH;
351
+ }
352
+ if ("xhdpi".equals(qualifier)) {
353
+ return DisplayMetrics.DENSITY_XHIGH;
354
+ }
355
+ if ("xxhdpi".equals(qualifier)) {
356
+ return DisplayMetrics.DENSITY_XXHIGH;
357
+ }
358
+ if ("xxxhdpi".equals(qualifier)) {
359
+ return DisplayMetrics.DENSITY_XXXHIGH;
360
+ }
361
+ if ("tvdpi".equals(qualifier)) {
362
+ return DisplayMetrics.DENSITY_TV;
363
+ }
364
+ }
365
+ return null;
366
+ }
367
+
368
+ private ResolvedResourceSource resolveBundledResource(String resourcePath) {
369
+ String normalizedPath = normalizeResPath(resourcePath);
370
+ if (normalizedPath.startsWith("res/")) {
371
+ normalizedPath = normalizedPath.substring("res/".length());
372
+ }
373
+
374
+ int slash = normalizedPath.indexOf('/');
375
+ if (slash == -1 || slash == normalizedPath.length() - 1) {
309
376
  return null;
310
377
  }
311
378
 
312
- // 提取文件名(路径的最后部分)
313
- int lastSlash = originalToPath.lastIndexOf('/');
314
- if (lastSlash == -1) {
379
+ String directoryName = normalizedPath.substring(0, slash);
380
+ String fileName = normalizedPath.substring(slash + 1);
381
+ String resourceType = extractResourceType(directoryName);
382
+ String resourceName = extractResourceName(fileName);
383
+ if (resourceType == null || resourceType.isEmpty() || resourceName.isEmpty()) {
315
384
  return null;
316
385
  }
317
- String fileName = originalToPath.substring(lastSlash + 1);
318
-
319
- // 定义密度优先级(从高到低)
320
- String[] densities = {"xxxhdpi", "xxhdpi", "xhdpi", "hdpi", "mdpi", "ldpi"};
321
-
322
- // 尝试找到相同文件名但不同密度的 key
323
- for (String density : densities) {
324
- // 构建可能的 key 路径(替换密度部分)
325
- String fallbackToPath = originalToPath.replaceFirst("drawable-[^/]+", "drawable-" + density);
326
-
327
- // 检查这个 key 是否在 copies 映射中
328
- if (copiesMap.containsKey(fallbackToPath)) {
329
- String fallbackFromPath = copiesMap.get(fallbackToPath);
330
- // 检查对应的 value 路径是否在 APK 中存在(精确匹配)
331
- if (availableEntries.containsKey(fallbackFromPath)) {
332
- if (UpdateContext.DEBUG) {
333
- Log.d("react-native-update", "Found fallback for " + originalToPath + ": " + fallbackToPath + " -> " + fallbackFromPath);
334
- }
335
- return fallbackFromPath;
336
- }
337
- // 尝试版本限定符无关匹配(APK ↔ AAB 兼容)
338
- String normalizedFallback = normalizeResPath(fallbackFromPath);
339
- String actualEntry = normalizedEntryMap.get(normalizedFallback);
340
- if (actualEntry != null) {
341
- if (UpdateContext.DEBUG) {
342
- Log.d("react-native-update", "Found normalized fallback for " + originalToPath + ": " + fallbackToPath + " -> " + actualEntry);
343
- }
344
- return actualEntry;
345
- }
386
+
387
+ Resources resources = context.getResources();
388
+ int resourceId = resources.getIdentifier(resourceName, resourceType, context.getPackageName());
389
+ if (resourceId == 0) {
390
+ return null;
391
+ }
392
+
393
+ TypedValue typedValue = new TypedValue();
394
+ try {
395
+ Integer density = parseDensityQualifier(directoryName);
396
+ if (density != null) {
397
+ resources.getValueForDensity(resourceId, density, typedValue, true);
398
+ } else {
399
+ resources.getValue(resourceId, typedValue, true);
400
+ }
401
+ } catch (Resources.NotFoundException e) {
402
+ if (UpdateContext.DEBUG) {
403
+ Log.d("react-native-update", "Failed to resolve resource value for " + resourcePath + ": " + e.getMessage());
346
404
  }
405
+ return null;
406
+ }
407
+
408
+ if (typedValue.string == null) {
409
+ return null;
410
+ }
411
+
412
+ String assetPath = typedValue.string.toString();
413
+ if (assetPath.startsWith("/")) {
414
+ assetPath = assetPath.substring(1);
415
+ }
416
+
417
+ if (UpdateContext.DEBUG) {
418
+ Log.d("react-native-update", "Resolved resource path " + resourcePath + " -> " + assetPath);
419
+ }
420
+ return new ResolvedResourceSource(resourceId, assetPath);
421
+ }
422
+
423
+ private InputStream openResolvedResourceStream(ResolvedResourceSource source) throws IOException {
424
+ try {
425
+ return context.getResources().openRawResource(source.resourceId);
426
+ } catch (Resources.NotFoundException e) {
427
+ throw new IOException("Unable to open resolved resource: " + source.assetPath, e);
347
428
  }
348
-
349
- return null;
350
429
  }
351
430
 
352
- private void copyFromResource(HashMap<String, ArrayList<File> > resToCopy, HashMap<String, String> copiesMap) throws IOException {
431
+ private void copyFromResource(HashMap<String, ArrayList<File> > resToCopy) throws IOException {
353
432
  if (UpdateContext.DEBUG) {
354
433
  Log.d("react-native-update", "copyFromResource called, resToCopy size: " + resToCopy.size());
355
434
  }
@@ -421,6 +500,7 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
421
500
 
422
501
  ZipEntry ze = availableEntries.get(fromPath);
423
502
  String actualSourcePath = fromPath;
503
+ ResolvedResourceSource resolvedResource = null;
424
504
 
425
505
  // 如果精确匹配找不到,尝试版本限定符无关匹配(APK ↔ AAB 兼容)
426
506
  // 例如 __diff.json 中的 "res/drawable-xxhdpi-v4/img.png" 匹配设备上的 "res/drawable-xxhdpi/img.png"
@@ -435,45 +515,17 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
435
515
  }
436
516
  }
437
517
  }
438
-
439
- // 如果仍然找不到,尝试 drawable 密度降级 fallback
518
+
519
+ // release APK 可能会将资源 entry 名压缩为 res/9w.png 之类的短路径;
520
+ // 这时通过 Resources 解析逻辑资源名,再直接读取资源内容。
440
521
  if (ze == null) {
441
- if (UpdateContext.DEBUG) {
442
- Log.d("react-native-update", "File not found in APK: " + fromPath + ", trying fallback");
443
- }
444
- // 找到对应的 to 路径(从 copiesMap 的反向查找)
445
- String toPath = null;
446
- for (String to : copiesMap.keySet()) {
447
- if (copiesMap.get(to).equals(fromPath)) {
448
- toPath = to;
449
- break;
450
- }
451
- }
452
-
453
- if (toPath != null) {
454
- if (UpdateContext.DEBUG) {
455
- Log.d("react-native-update", "Found toPath: " + toPath + " for fromPath: " + fromPath);
456
- }
457
- String fallbackFromPath = findDrawableFallback(toPath, copiesMap, availableEntries, normalizedEntryMap);
458
- if (fallbackFromPath != null) {
459
- ze = availableEntries.get(fallbackFromPath);
460
- actualSourcePath = fallbackFromPath;
461
- if (UpdateContext.DEBUG) {
462
- Log.w("react-native-update", "Using fallback: " + fallbackFromPath + " for " + fromPath);
463
- }
464
- } else {
465
- if (UpdateContext.DEBUG) {
466
- Log.w("react-native-update", "No fallback found for: " + fromPath + " (toPath: " + toPath + ")");
467
- }
468
- }
469
- } else {
470
- if (UpdateContext.DEBUG) {
471
- Log.w("react-native-update", "No toPath found for fromPath: " + fromPath);
472
- }
522
+ resolvedResource = resolveBundledResource(fromPath);
523
+ if (resolvedResource != null) {
524
+ actualSourcePath = resolvedResource.assetPath;
473
525
  }
474
526
  }
475
527
 
476
- if (ze != null) {
528
+ if (ze != null || resolvedResource != null) {
477
529
  File lastTarget = null;
478
530
  for (File target: targets) {
479
531
  if (UpdateContext.DEBUG) {
@@ -489,12 +541,17 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
489
541
  if (lastTarget != null) {
490
542
  copyFile(lastTarget, target);
491
543
  } else {
492
- // 从保存的映射中获取包含该条目的 ZipFile
493
- SafeZipFile sourceZipFile = entryToZipFileMap.get(actualSourcePath);
494
- if (sourceZipFile == null) {
495
- sourceZipFile = zipFile; // 回退到基础 APK
544
+ if (ze != null) {
545
+ // 从保存的映射中获取包含该条目的 ZipFile
546
+ SafeZipFile sourceZipFile = entryToZipFileMap.get(actualSourcePath);
547
+ if (sourceZipFile == null) {
548
+ sourceZipFile = zipFile; // 回退到基础 APK
549
+ }
550
+ sourceZipFile.unzipToFile(ze, target);
551
+ } else {
552
+ InputStream in = openResolvedResourceStream(resolvedResource);
553
+ copyInputStreamToFile(in, target);
496
554
  }
497
- sourceZipFile.unzipToFile(ze, target);
498
555
  lastTarget = target;
499
556
  }
500
557
  } catch (IOException e) {
@@ -526,7 +583,6 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
526
583
 
527
584
  removeDirectory(param.unzipDirectory);
528
585
  param.unzipDirectory.mkdirs();
529
- HashMap<String, String> copiesMap = new HashMap<String, String>(); // to -> from 映射
530
586
  ArrayList<String> entryNames = new ArrayList<String>();
531
587
  ArrayList<String> copyFroms = new ArrayList<String>();
532
588
  ArrayList<String> copyTos = new ArrayList<String>();
@@ -544,7 +600,7 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
544
600
  byte[] bytes = readBytes(zipFile.getInputStream(ze));
545
601
  String json = new String(bytes, "UTF-8");
546
602
  JSONObject obj = (JSONObject)new JSONTokener(json).nextValue();
547
- appendManifestEntries(obj, copyFroms, copyTos, deletes, copiesMap);
603
+ appendManifestEntries(obj, copyFroms, copyTos, deletes);
548
604
  continue;
549
605
  }
550
606
  zipFile.unzipToPath(ze, param.unzipDirectory);
@@ -587,13 +643,13 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
587
643
  }
588
644
 
589
645
  if (UpdateContext.DEBUG) {
590
- Log.d("react-native-update", "copyList size: " + copyList.size() + ", copiesMap size: " + copiesMap.size());
646
+ Log.d("react-native-update", "copyList size: " + copyList.size());
591
647
  for (String from : copyList.keySet()) {
592
648
  Log.d("react-native-update", "copyList entry: " + from + " -> " + copyList.get(from).size() + " targets");
593
649
  }
594
650
  }
595
651
 
596
- copyFromResource(copyList, copiesMap);
652
+ copyFromResource(copyList);
597
653
 
598
654
  if (UpdateContext.DEBUG) {
599
655
  Log.d("react-native-update", "Unzip finished");
@@ -625,7 +681,7 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
625
681
  byte[] bytes = readBytes(zipFile.getInputStream(ze));
626
682
  String json = new String(bytes, "UTF-8");
627
683
  JSONObject obj = (JSONObject)new JSONTokener(json).nextValue();
628
- appendManifestEntries(obj, copyFroms, copyTos, deletes, null);
684
+ appendManifestEntries(obj, copyFroms, copyTos, deletes);
629
685
  continue;
630
686
  }
631
687
  zipFile.unzipToPath(ze, param.unzipDirectory);
package/harmony/pushy.har CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-update",
3
- "version": "10.39.0-beta.2",
3
+ "version": "10.39.0-beta.3",
4
4
  "description": "react-native hot update",
5
5
  "main": "src/index",
6
6
  "scripts": {