Skip to content

Commit 6754a9f

Browse files
committed
refactor android
1 parent 8b59fb9 commit 6754a9f

14 files changed

Lines changed: 1242 additions & 1196 deletions

File tree

android/build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,6 @@ repositories {
176176

177177
dependencies {
178178
implementation 'com.facebook.react:react-native:+'
179-
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
180179
}
181180
if (isNewArchitectureEnabled()) {
182181
react {
Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
package cn.reactnative.modules.update;
2+
3+
import android.content.Context;
4+
import android.content.pm.ApplicationInfo;
5+
import android.content.pm.PackageManager;
6+
import android.content.res.Resources;
7+
import android.os.Build;
8+
import android.util.DisplayMetrics;
9+
import android.util.Log;
10+
import android.util.TypedValue;
11+
import java.io.File;
12+
import java.io.IOException;
13+
import java.io.InputStream;
14+
import java.util.ArrayList;
15+
import java.util.Enumeration;
16+
import java.util.HashMap;
17+
import java.util.zip.ZipEntry;
18+
import java.util.regex.Pattern;
19+
20+
final class BundledResourceCopier {
21+
private static final Pattern VERSION_QUALIFIER_PATTERN = Pattern.compile("-v\\d+(?=/)");
22+
private static final String AAB_BASE_PREFIX = "base/";
23+
24+
private final Context context;
25+
26+
private static final class ResolvedResourceSource {
27+
final int resourceId;
28+
final String assetPath;
29+
30+
ResolvedResourceSource(int resourceId, String assetPath) {
31+
this.resourceId = resourceId;
32+
this.assetPath = assetPath;
33+
}
34+
}
35+
36+
BundledResourceCopier(Context context) {
37+
this.context = context.getApplicationContext();
38+
}
39+
40+
void copyFromResource(HashMap<String, ArrayList<File>> resToCopy) throws IOException {
41+
if (UpdateContext.DEBUG) {
42+
Log.d(UpdateContext.TAG, "copyFromResource called, resToCopy size: " + resToCopy.size());
43+
}
44+
45+
ArrayList<String> apkPaths = collectApkPaths();
46+
HashMap<String, ZipEntry> availableEntries = new HashMap<String, ZipEntry>();
47+
HashMap<String, SafeZipFile> zipFileMap = new HashMap<String, SafeZipFile>();
48+
HashMap<String, SafeZipFile> entryToZipFileMap = new HashMap<String, SafeZipFile>();
49+
50+
try {
51+
for (String apkPath : apkPaths) {
52+
SafeZipFile zipFile = new SafeZipFile(new File(apkPath));
53+
zipFileMap.put(apkPath, zipFile);
54+
Enumeration<? extends ZipEntry> entries = zipFile.entries();
55+
while (entries.hasMoreElements()) {
56+
ZipEntry ze = entries.nextElement();
57+
String entryName = ze.getName();
58+
if (!availableEntries.containsKey(entryName)) {
59+
availableEntries.put(entryName, ze);
60+
entryToZipFileMap.put(entryName, zipFile);
61+
}
62+
}
63+
}
64+
65+
HashMap<String, String> normalizedEntryMap = new HashMap<String, String>();
66+
for (String entryName : availableEntries.keySet()) {
67+
String normalized = normalizeResPath(entryName);
68+
normalizedEntryMap.putIfAbsent(normalized, entryName);
69+
}
70+
71+
SafeZipFile baseZipFile = zipFileMap.get(context.getPackageResourcePath());
72+
HashMap<String, ArrayList<File>> remainingFiles =
73+
new HashMap<String, ArrayList<File>>(resToCopy);
74+
75+
for (String fromPath : new ArrayList<String>(remainingFiles.keySet())) {
76+
if (UpdateContext.DEBUG) {
77+
Log.d(UpdateContext.TAG, "Processing fromPath: " + fromPath);
78+
}
79+
80+
ArrayList<File> targets = remainingFiles.get(fromPath);
81+
if (targets == null || targets.isEmpty()) {
82+
continue;
83+
}
84+
85+
ZipEntry entry = availableEntries.get(fromPath);
86+
String actualSourcePath = fromPath;
87+
ResolvedResourceSource resolvedResource = null;
88+
89+
if (entry == null) {
90+
String normalizedFrom = normalizeResPath(fromPath);
91+
String actualEntry = normalizedEntryMap.get(normalizedFrom);
92+
if (actualEntry != null) {
93+
entry = availableEntries.get(actualEntry);
94+
actualSourcePath = actualEntry;
95+
if (UpdateContext.DEBUG) {
96+
Log.d(UpdateContext.TAG, "Normalized match: " + fromPath + " -> " + actualEntry);
97+
}
98+
}
99+
}
100+
101+
if (entry == null) {
102+
resolvedResource = resolveBundledResource(fromPath);
103+
if (resolvedResource != null) {
104+
actualSourcePath = resolvedResource.assetPath;
105+
}
106+
}
107+
108+
if (entry == null && resolvedResource == null) {
109+
continue;
110+
}
111+
112+
File lastTarget = null;
113+
for (File target : targets) {
114+
if (UpdateContext.DEBUG) {
115+
Log.d(UpdateContext.TAG, "Copying from resource " + actualSourcePath + " to " + target);
116+
}
117+
try {
118+
if (lastTarget != null) {
119+
UpdateFileUtils.copyFile(lastTarget, target);
120+
} else if (entry != null) {
121+
SafeZipFile sourceZipFile = entryToZipFileMap.get(actualSourcePath);
122+
if (sourceZipFile == null) {
123+
sourceZipFile = baseZipFile;
124+
}
125+
sourceZipFile.unzipToFile(entry, target);
126+
} else {
127+
InputStream in = openResolvedResourceStream(resolvedResource);
128+
UpdateFileUtils.copyInputStreamToFile(in, target);
129+
}
130+
lastTarget = target;
131+
} catch (IOException e) {
132+
if (UpdateContext.DEBUG) {
133+
Log.w(
134+
UpdateContext.TAG,
135+
"Failed to copy resource "
136+
+ actualSourcePath
137+
+ " to "
138+
+ target
139+
+ ": "
140+
+ e.getMessage()
141+
);
142+
}
143+
}
144+
}
145+
remainingFiles.remove(fromPath);
146+
}
147+
148+
if (!remainingFiles.isEmpty() && UpdateContext.DEBUG) {
149+
for (String fromPath : remainingFiles.keySet()) {
150+
Log.w(UpdateContext.TAG, "Resource not found and no fallback available: " + fromPath);
151+
}
152+
}
153+
} finally {
154+
closeZipFiles(zipFileMap);
155+
}
156+
}
157+
158+
private String normalizeResPath(String path) {
159+
String result = path;
160+
if (result.startsWith(AAB_BASE_PREFIX)) {
161+
result = result.substring(AAB_BASE_PREFIX.length());
162+
}
163+
return VERSION_QUALIFIER_PATTERN.matcher(result).replaceAll("");
164+
}
165+
166+
private String extractResourceType(String directoryName) {
167+
int qualifierIndex = directoryName.indexOf('-');
168+
if (qualifierIndex == -1) {
169+
return directoryName;
170+
}
171+
return directoryName.substring(0, qualifierIndex);
172+
}
173+
174+
private String extractResourceName(String fileName) {
175+
if (fileName.endsWith(".9.png")) {
176+
return fileName.substring(0, fileName.length() - ".9.png".length());
177+
}
178+
int extensionIndex = fileName.lastIndexOf('.');
179+
if (extensionIndex == -1) {
180+
return fileName;
181+
}
182+
return fileName.substring(0, extensionIndex);
183+
}
184+
185+
private Integer parseDensityQualifier(String directoryName) {
186+
String[] qualifiers = directoryName.split("-");
187+
for (String qualifier : qualifiers) {
188+
if ("ldpi".equals(qualifier)) {
189+
return DisplayMetrics.DENSITY_LOW;
190+
}
191+
if ("mdpi".equals(qualifier)) {
192+
return DisplayMetrics.DENSITY_MEDIUM;
193+
}
194+
if ("hdpi".equals(qualifier)) {
195+
return DisplayMetrics.DENSITY_HIGH;
196+
}
197+
if ("xhdpi".equals(qualifier)) {
198+
return DisplayMetrics.DENSITY_XHIGH;
199+
}
200+
if ("xxhdpi".equals(qualifier)) {
201+
return DisplayMetrics.DENSITY_XXHIGH;
202+
}
203+
if ("xxxhdpi".equals(qualifier)) {
204+
return DisplayMetrics.DENSITY_XXXHIGH;
205+
}
206+
if ("tvdpi".equals(qualifier)) {
207+
return DisplayMetrics.DENSITY_TV;
208+
}
209+
}
210+
return null;
211+
}
212+
213+
private ResolvedResourceSource resolveBundledResource(String resourcePath) {
214+
String normalizedPath = normalizeResPath(resourcePath);
215+
if (normalizedPath.startsWith("res/")) {
216+
normalizedPath = normalizedPath.substring("res/".length());
217+
}
218+
219+
int slash = normalizedPath.indexOf('/');
220+
if (slash == -1 || slash == normalizedPath.length() - 1) {
221+
return null;
222+
}
223+
224+
String directoryName = normalizedPath.substring(0, slash);
225+
String fileName = normalizedPath.substring(slash + 1);
226+
String resourceType = extractResourceType(directoryName);
227+
String resourceName = extractResourceName(fileName);
228+
if (resourceType == null || resourceType.isEmpty() || resourceName.isEmpty()) {
229+
return null;
230+
}
231+
232+
Resources resources = context.getResources();
233+
int resourceId = resources.getIdentifier(resourceName, resourceType, context.getPackageName());
234+
if (resourceId == 0) {
235+
return null;
236+
}
237+
238+
TypedValue typedValue = new TypedValue();
239+
try {
240+
Integer density = parseDensityQualifier(directoryName);
241+
if (density != null) {
242+
resources.getValueForDensity(resourceId, density, typedValue, true);
243+
} else {
244+
resources.getValue(resourceId, typedValue, true);
245+
}
246+
} catch (Resources.NotFoundException e) {
247+
if (UpdateContext.DEBUG) {
248+
Log.d(
249+
UpdateContext.TAG,
250+
"Failed to resolve resource value for " + resourcePath + ": " + e.getMessage()
251+
);
252+
}
253+
return null;
254+
}
255+
256+
if (typedValue.string == null) {
257+
return null;
258+
}
259+
260+
String assetPath = typedValue.string.toString();
261+
if (assetPath.startsWith("/")) {
262+
assetPath = assetPath.substring(1);
263+
}
264+
265+
if (UpdateContext.DEBUG) {
266+
Log.d(UpdateContext.TAG, "Resolved resource path " + resourcePath + " -> " + assetPath);
267+
}
268+
return new ResolvedResourceSource(resourceId, assetPath);
269+
}
270+
271+
private InputStream openResolvedResourceStream(ResolvedResourceSource source) throws IOException {
272+
try {
273+
return context.getResources().openRawResource(source.resourceId);
274+
} catch (Resources.NotFoundException e) {
275+
throw new IOException("Unable to open resolved resource: " + source.assetPath, e);
276+
}
277+
}
278+
279+
private ArrayList<String> collectApkPaths() {
280+
ArrayList<String> apkPaths = new ArrayList<String>();
281+
apkPaths.add(context.getPackageResourcePath());
282+
283+
try {
284+
ApplicationInfo appInfo =
285+
context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
286+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && appInfo.splitSourceDirs != null) {
287+
for (String splitPath : appInfo.splitSourceDirs) {
288+
apkPaths.add(splitPath);
289+
if (UpdateContext.DEBUG) {
290+
Log.d(UpdateContext.TAG, "Found split APK: " + splitPath);
291+
}
292+
}
293+
}
294+
} catch (PackageManager.NameNotFoundException e) {
295+
if (UpdateContext.DEBUG) {
296+
Log.w(UpdateContext.TAG, "Failed to get application info: " + e.getMessage());
297+
}
298+
}
299+
300+
return apkPaths;
301+
}
302+
303+
private void closeZipFiles(HashMap<String, SafeZipFile> zipFileMap) {
304+
for (SafeZipFile zipFile : zipFileMap.values()) {
305+
try {
306+
zipFile.close();
307+
} catch (IOException e) {
308+
if (UpdateContext.DEBUG) {
309+
Log.w(UpdateContext.TAG, "Failed to close zip file", e);
310+
}
311+
}
312+
}
313+
}
314+
}

0 commit comments

Comments
 (0)