Flutter Engine
The Flutter Engine
ResourceExtractor.java
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package io.flutter.embedding.engine.loader;
6
7import static io.flutter.Build.API_LEVELS;
8
9import android.content.pm.PackageInfo;
10import android.content.pm.PackageManager;
11import android.content.res.AssetManager;
12import android.os.AsyncTask;
13import android.os.Build;
14import androidx.annotation.NonNull;
15import androidx.annotation.WorkerThread;
16import io.flutter.BuildConfig;
17import io.flutter.Log;
18import java.io.*;
19import java.util.Collection;
20import java.util.HashSet;
21import java.util.concurrent.CancellationException;
22import java.util.concurrent.ExecutionException;
23
24/** A class to initialize the native code. */
26 private static final String TAG = "ResourceExtractor";
27 private static final String TIMESTAMP_PREFIX = "res_timestamp-";
28 private static final String[] SUPPORTED_ABIS = Build.SUPPORTED_ABIS;
29
30 @SuppressWarnings("deprecation")
31 static long getVersionCode(@NonNull PackageInfo packageInfo) {
32 // Linter needs P (28) hardcoded or else it will fail these lines.
33 if (Build.VERSION.SDK_INT >= API_LEVELS.API_28) {
34 return packageInfo.getLongVersionCode();
35 } else {
36 return packageInfo.versionCode;
37 }
38 }
39
40 private static class ExtractTask extends AsyncTask<Void, Void, Void> {
41 @NonNull private final String mDataDirPath;
42 @NonNull private final HashSet<String> mResources;
43 @NonNull private final AssetManager mAssetManager;
44 @NonNull private final String mPackageName;
45 @NonNull private final PackageManager mPackageManager;
46
47 ExtractTask(
48 @NonNull String dataDirPath,
49 @NonNull HashSet<String> resources,
50 @NonNull String packageName,
51 @NonNull PackageManager packageManager,
52 @NonNull AssetManager assetManager) {
53 mDataDirPath = dataDirPath;
54 mResources = resources;
55 mAssetManager = assetManager;
56 mPackageName = packageName;
57 mPackageManager = packageManager;
58 }
59
60 @Override
61 protected Void doInBackground(Void... unused) {
62 final File dataDir = new File(mDataDirPath);
63
64 final String timestamp = checkTimestamp(dataDir, mPackageManager, mPackageName);
65 if (timestamp == null) {
66 return null;
67 }
68
69 deleteFiles(mDataDirPath, mResources);
70
71 if (!extractAPK(dataDir)) {
72 return null;
73 }
74
75 if (timestamp != null) {
76 try {
77 new File(dataDir, timestamp).createNewFile();
78 } catch (IOException e) {
79 Log.w(TAG, "Failed to write resource timestamp");
80 }
81 }
82
83 return null;
84 }
85
86 /// Returns true if successfully unpacked APK resources,
87 /// otherwise deletes all resources and returns false.
88 @WorkerThread
89 private boolean extractAPK(@NonNull File dataDir) {
90 for (String asset : mResources) {
91 try {
92 final String resource = "assets/" + asset;
93 final File output = new File(dataDir, asset);
94 if (output.exists()) {
95 continue;
96 }
97 if (output.getParentFile() != null) {
98 output.getParentFile().mkdirs();
99 }
100
101 try (InputStream is = mAssetManager.open(asset);
102 OutputStream os = new FileOutputStream(output)) {
103 copy(is, os);
104 }
105 if (BuildConfig.DEBUG) {
106 Log.i(TAG, "Extracted baseline resource " + resource);
107 }
108 } catch (FileNotFoundException fnfe) {
109 continue;
110
111 } catch (IOException ioe) {
112 Log.w(TAG, "Exception unpacking resources: " + ioe.getMessage());
113 deleteFiles(mDataDirPath, mResources);
114 return false;
115 }
116 }
117
118 return true;
119 }
120 }
121
122 @NonNull private final String mDataDirPath;
123 @NonNull private final String mPackageName;
124 @NonNull private final PackageManager mPackageManager;
125 @NonNull private final AssetManager mAssetManager;
126 @NonNull private final HashSet<String> mResources;
127 private ExtractTask mExtractTask;
128
130 @NonNull String dataDirPath,
131 @NonNull String packageName,
132 @NonNull PackageManager packageManager,
133 @NonNull AssetManager assetManager) {
134 mDataDirPath = dataDirPath;
135 mPackageName = packageName;
136 mPackageManager = packageManager;
137 mAssetManager = assetManager;
138 mResources = new HashSet<>();
139 }
140
142 mResources.add(resource);
143 return this;
144 }
145
146 ResourceExtractor addResources(@NonNull Collection<String> resources) {
147 mResources.addAll(resources);
148 return this;
149 }
150
152 if (BuildConfig.DEBUG && mExtractTask != null) {
153 Log.e(
154 TAG, "Attempted to start resource extraction while another extraction was in progress.");
155 }
156 mExtractTask =
157 new ExtractTask(mDataDirPath, mResources, mPackageName, mPackageManager, mAssetManager);
158 mExtractTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
159 return this;
160 }
161
163 if (mExtractTask == null) {
164 return;
165 }
166
167 try {
168 mExtractTask.get();
169 } catch (CancellationException | ExecutionException | InterruptedException e) {
170 deleteFiles(mDataDirPath, mResources);
171 }
172 }
173
174 private static String[] getExistingTimestamps(File dataDir) {
175 return dataDir.list(
176 new FilenameFilter() {
177 @Override
178 public boolean accept(File dir, String name) {
179 return name.startsWith(TIMESTAMP_PREFIX);
180 }
181 });
182 }
183
184 private static void deleteFiles(@NonNull String dataDirPath, @NonNull HashSet<String> resources) {
185 final File dataDir = new File(dataDirPath);
186 for (String resource : resources) {
187 final File file = new File(dataDir, resource);
188 if (file.exists()) {
189 file.delete();
190 }
191 }
192 final String[] existingTimestamps = getExistingTimestamps(dataDir);
193 if (existingTimestamps == null) {
194 return;
195 }
196 for (String timestamp : existingTimestamps) {
197 new File(dataDir, timestamp).delete();
198 }
199 }
200
201 // Returns null if extracted resources are found and match the current APK version
202 // and update version if any, otherwise returns the current APK and update version.
203 private static String checkTimestamp(
204 @NonNull File dataDir, @NonNull PackageManager packageManager, @NonNull String packageName) {
205 PackageInfo packageInfo = null;
206
207 try {
208 packageInfo = packageManager.getPackageInfo(packageName, 0);
209 } catch (PackageManager.NameNotFoundException e) {
210 return TIMESTAMP_PREFIX;
211 }
212
213 if (packageInfo == null) {
214 return TIMESTAMP_PREFIX;
215 }
216
217 String expectedTimestamp =
218 TIMESTAMP_PREFIX + getVersionCode(packageInfo) + "-" + packageInfo.lastUpdateTime;
219
220 final String[] existingTimestamps = getExistingTimestamps(dataDir);
221
222 if (existingTimestamps == null) {
223 if (BuildConfig.DEBUG) {
224 Log.i(TAG, "No extracted resources found");
225 }
226 return expectedTimestamp;
227 }
228
229 if (existingTimestamps.length == 1) {
230 if (BuildConfig.DEBUG) {
231 Log.i(TAG, "Found extracted resources " + existingTimestamps[0]);
232 }
233 }
234
235 if (existingTimestamps.length != 1 || !expectedTimestamp.equals(existingTimestamps[0])) {
236 if (BuildConfig.DEBUG) {
237 Log.i(TAG, "Resource version mismatch " + expectedTimestamp);
238 }
239 return expectedTimestamp;
240 }
241
242 return null;
243 }
244
245 private static void copy(@NonNull InputStream in, @NonNull OutputStream out) throws IOException {
246 byte[] buf = new byte[16 * 1024];
247 for (int i; (i = in.read(buf)) >= 0; ) {
248 out.write(buf, 0, i);
249 }
250 }
251}
static bool unused
static SkString resource(SkPDFResourceType type, int index)
static final boolean DEBUG
static final int API_28
Definition: Build.java:18
static void e(@NonNull String tag, @NonNull String message)
Definition: Log.java:84
ResourceExtractor addResources(@NonNull Collection< String > resources)
ResourceExtractor addResource(@NonNull String resource)
static long getVersionCode(@NonNull PackageInfo packageInfo)
ResourceExtractor( @NonNull String dataDirPath, @NonNull String packageName, @NonNull PackageManager packageManager, @NonNull AssetManager assetManager)
void Log(const char *format,...) SK_PRINTF_LIKE(1
Definition: TestRunner.cpp:137
def Build(configs, env, options)
Definition: build.py:232
Definition: copy.py:1
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however Start app with an specific route defined on the framework flutter assets dir
Definition: switches.h:145