Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit 4508f63

Browse files
committed
improve download progress perf
1 parent c47f083 commit 4508f63

File tree

7 files changed

+126
-74
lines changed

7 files changed

+126
-74
lines changed

CodePush.js

Lines changed: 43 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ const PackageMixins = require("./package-mixins")(NativeCodePush);
99
async function checkForUpdate(deploymentKey = null) {
1010
/*
1111
* Before we ask the server if an update exists, we
12-
* need to retrieve three pieces of information from the
12+
* need to retrieve three pieces of information from the
1313
* native side: deployment key, app version (e.g. 1.0.1)
1414
* and the hash of the currently running update (if there is one).
1515
* This allows the client to only receive updates which are targetted
1616
* for their specific deployment and version and which are actually
1717
* different from the CodePush update they have already installed.
1818
*/
1919
const nativeConfig = await getConfiguration();
20-
20+
2121
/*
2222
* If a deployment key was explicitly provided,
2323
* then let's override the one we retrieved
@@ -30,7 +30,7 @@ async function checkForUpdate(deploymentKey = null) {
3030

3131
// Use dynamically overridden getCurrentPackage() during tests.
3232
const localPackage = await module.exports.getCurrentPackage();
33-
33+
3434
/*
3535
* If the app has a previously installed update, and that update
3636
* was targetted at the same app version that is currently running,
@@ -48,9 +48,9 @@ async function checkForUpdate(deploymentKey = null) {
4848
queryPackage.packageHash = config.packageHash;
4949
}
5050
}
51-
51+
5252
const update = await sdk.queryUpdateWithCurrentPackage(queryPackage);
53-
53+
5454
/*
5555
* There are four cases where checkForUpdate will resolve to null:
5656
* ----------------------------------------------------------------
@@ -69,13 +69,13 @@ async function checkForUpdate(deploymentKey = null) {
6969
* because we want to avoid having to install diff updates against the binary's
7070
* version, which we can't do yet on Android.
7171
*/
72-
if (!update || update.updateAppVersion ||
73-
localPackage && (update.packageHash === localPackage.packageHash) ||
72+
if (!update || update.updateAppVersion ||
73+
localPackage && (update.packageHash === localPackage.packageHash) ||
7474
(!localPackage || localPackage._isDebugOnly) && config.packageHash === update.packageHash) {
7575
if (update && update.updateAppVersion) {
7676
log("An update is available but it is targeting a newer binary version than you are currently running.");
7777
}
78-
78+
7979
return null;
8080
} else {
8181
const remotePackage = { ...update, ...PackageMixins.remote(sdk.reportStatusDownload) };
@@ -93,7 +93,7 @@ const getConfiguration = (() => {
9393
} else if (testConfig) {
9494
return testConfig;
9595
} else {
96-
config = await NativeCodePush.getConfiguration();
96+
config = await NativeCodePush.getConfiguration();
9797
return config;
9898
}
9999
}
@@ -123,7 +123,7 @@ function getPromisifiedSdk(requestFetchAdapter, config) {
123123
} else {
124124
resolve(update);
125125
}
126-
});
126+
});
127127
});
128128
};
129129

@@ -135,7 +135,7 @@ function getPromisifiedSdk(requestFetchAdapter, config) {
135135
} else {
136136
resolve();
137137
}
138-
});
138+
});
139139
});
140140
};
141141

@@ -147,7 +147,7 @@ function getPromisifiedSdk(requestFetchAdapter, config) {
147147
} else {
148148
resolve();
149149
}
150-
});
150+
});
151151
});
152152
};
153153

@@ -159,21 +159,21 @@ function log(message) {
159159
console.log(`[CodePush] ${message}`)
160160
}
161161

162-
// This ensures that notifyApplicationReadyInternal is only called once
162+
// This ensures that notifyApplicationReadyInternal is only called once
163163
// in the lifetime of this module instance.
164164
const notifyApplicationReady = (() => {
165165
let notifyApplicationReadyPromise;
166166
return () => {
167167
if (!notifyApplicationReadyPromise) {
168168
notifyApplicationReadyPromise = notifyApplicationReadyInternal();
169169
}
170-
170+
171171
return notifyApplicationReadyPromise;
172172
};
173173
})();
174174

175175
async function notifyApplicationReadyInternal() {
176-
await NativeCodePush.notifyApplicationReady();
176+
await NativeCodePush.notifyApplicationReady();
177177
const statusReport = await NativeCodePush.getNewStatusReport();
178178
if (statusReport) {
179179
const config = await getConfiguration();
@@ -208,15 +208,15 @@ function setUpTestDependencies(testSdk, providedTestConfig, testNativeBridge) {
208208
const sync = (() => {
209209
let syncInProgress = false;
210210
const setSyncCompleted = () => { syncInProgress = false; };
211-
211+
212212
return (options = {}, syncStatusChangeCallback, downloadProgressCallback) => {
213213
if (syncInProgress) {
214214
typeof syncStatusChangeCallback === "function"
215215
? syncStatusChangeCallback(CodePush.SyncStatus.SYNC_IN_PROGRESS)
216216
: log("Sync already in progress.");
217217
return Promise.resolve(CodePush.SyncStatus.SYNC_IN_PROGRESS);
218-
}
219-
218+
}
219+
220220
syncInProgress = true;
221221
const syncPromise = syncInternal(options, syncStatusChangeCallback, downloadProgressCallback);
222222
syncPromise
@@ -230,7 +230,7 @@ const sync = (() => {
230230
/*
231231
* The syncInternal method provides a simple, one-line experience for
232232
* incorporating the check, download and installation of an update.
233-
*
233+
*
234234
* It simply composes the existing API methods together and adds additional
235235
* support for respecting mandatory updates, ignoring previously failed
236236
* releases, and displaying a standard confirmation UI to the end-user
@@ -245,9 +245,9 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
245245
mandatoryInstallMode: CodePush.InstallMode.IMMEDIATE,
246246
minimumBackgroundDuration: 0,
247247
updateDialog: null,
248-
...options
248+
...options
249249
};
250-
250+
251251
syncStatusChangeCallback = typeof syncStatusChangeCallback === "function"
252252
? syncStatusChangeCallback
253253
: (syncStatus) => {
@@ -271,7 +271,7 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
271271
log("User cancelled the update.");
272272
break;
273273
case CodePush.SyncStatus.UPDATE_INSTALLED:
274-
/*
274+
/*
275275
* If the install mode is IMMEDIATE, this will not get returned as the
276276
* app will be restarted to a new Javascript context.
277277
*/
@@ -290,40 +290,34 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
290290
break;
291291
}
292292
};
293-
294-
downloadProgressCallback = typeof downloadProgressCallback === "function"
295-
? downloadProgressCallback
296-
: (downloadProgress) => {
297-
log(`Expecting ${downloadProgress.totalBytes} bytes, received ${downloadProgress.receivedBytes} bytes.`);
298-
};
299-
293+
300294
try {
301295
await CodePush.notifyApplicationReady();
302-
296+
303297
syncStatusChangeCallback(CodePush.SyncStatus.CHECKING_FOR_UPDATE);
304298
const remotePackage = await checkForUpdate(syncOptions.deploymentKey);
305-
299+
306300
const doDownloadAndInstall = async () => {
307301
syncStatusChangeCallback(CodePush.SyncStatus.DOWNLOADING_PACKAGE);
308302
const localPackage = await remotePackage.download(downloadProgressCallback);
309-
303+
310304
// Determine the correct install mode based on whether the update is mandatory or not.
311305
resolvedInstallMode = localPackage.isMandatory ? syncOptions.mandatoryInstallMode : syncOptions.installMode;
312-
306+
313307
syncStatusChangeCallback(CodePush.SyncStatus.INSTALLING_UPDATE);
314308
await localPackage.install(resolvedInstallMode, syncOptions.minimumBackgroundDuration, () => {
315309
syncStatusChangeCallback(CodePush.SyncStatus.UPDATE_INSTALLED);
316310
});
317-
311+
318312
return CodePush.SyncStatus.UPDATE_INSTALLED;
319313
};
320-
314+
321315
const updateShouldBeIgnored = remotePackage && (remotePackage.failedInstall && syncOptions.ignoreFailedUpdates);
322316
if (!remotePackage || updateShouldBeIgnored) {
323317
if (updateShouldBeIgnored) {
324318
log("An update is available, but it is being ignored due to having been previously rolled back.");
325319
}
326-
320+
327321
syncStatusChangeCallback(CodePush.SyncStatus.UP_TO_DATE);
328322
return CodePush.SyncStatus.UP_TO_DATE;
329323
} else if (syncOptions.updateDialog) {
@@ -334,24 +328,24 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
334328
} else {
335329
syncOptions.updateDialog = { ...CodePush.DEFAULT_UPDATE_DIALOG, ...syncOptions.updateDialog };
336330
}
337-
338-
return await new Promise((resolve, reject) => {
331+
332+
return await new Promise((resolve, reject) => {
339333
let message = null;
340334
const dialogButtons = [{
341335
text: null,
342-
onPress: async () => {
336+
onPress: async () => {
343337
resolve(await doDownloadAndInstall());
344338
}
345339
}];
346-
340+
347341
if (remotePackage.isMandatory) {
348342
message = syncOptions.updateDialog.mandatoryUpdateMessage;
349343
dialogButtons[0].text = syncOptions.updateDialog.mandatoryContinueButtonLabel;
350344
} else {
351345
message = syncOptions.updateDialog.optionalUpdateMessage;
352-
dialogButtons[0].text = syncOptions.updateDialog.optionalInstallButtonLabel;
346+
dialogButtons[0].text = syncOptions.updateDialog.optionalInstallButtonLabel;
353347
// Since this is an optional update, add another button
354-
// to allow the end-user to ignore it
348+
// to allow the end-user to ignore it
355349
dialogButtons.push({
356350
text: syncOptions.updateDialog.optionalIgnoreButtonLabel,
357351
onPress: () => {
@@ -360,13 +354,13 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
360354
}
361355
});
362356
}
363-
357+
364358
// If the update has a description, and the developer
365359
// explicitly chose to display it, then set that as the message
366360
if (syncOptions.updateDialog.appendReleaseDescription && remotePackage.description) {
367-
message += `${syncOptions.updateDialog.descriptionPrefix} ${remotePackage.description}`;
361+
message += `${syncOptions.updateDialog.descriptionPrefix} ${remotePackage.description}`;
368362
}
369-
363+
370364
syncStatusChangeCallback(CodePush.SyncStatus.AWAITING_USER_ACTION);
371365
Alert.alert(syncOptions.updateDialog.title, message, dialogButtons);
372366
});
@@ -375,16 +369,16 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
375369
}
376370
} catch (error) {
377371
syncStatusChangeCallback(CodePush.SyncStatus.UNKNOWN_ERROR);
378-
log(error.message);
372+
log(error.message);
379373
throw error;
380-
}
374+
}
381375
};
382376

383377
let CodePush;
384378

385-
// If the "NativeCodePush" variable isn't defined, then
379+
// If the "NativeCodePush" variable isn't defined, then
386380
// the app didn't properly install the native module,
387-
// and therefore, it doesn't make sense initializing
381+
// and therefore, it doesn't make sense initializing
388382
// the JS interface when it wouldn't work anyways.
389383
if (NativeCodePush) {
390384
CodePush = {

android/app/src/main/java/com/microsoft/codepush/react/CodePush.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.facebook.react.bridge.WritableMap;
1414
import com.facebook.react.bridge.WritableNativeMap;
1515
import com.facebook.react.modules.core.DeviceEventManagerModule;
16+
import com.facebook.react.uimanager.ReactChoreographer;
1617
import com.facebook.react.uimanager.ViewManager;
1718
import com.facebook.soloader.SoLoader;
1819

@@ -25,6 +26,7 @@
2526
import android.content.pm.PackageManager;
2627
import android.os.AsyncTask;
2728
import android.provider.Settings;
29+
import android.view.Choreographer;
2830

2931
import org.json.JSONArray;
3032
import org.json.JSONException;
@@ -439,19 +441,43 @@ public void run() {
439441
}
440442

441443
@ReactMethod
442-
public void downloadUpdate(final ReadableMap updatePackage, final Promise promise) {
444+
public void downloadUpdate(final ReadableMap updatePackage, final boolean notifyProgress, final Promise promise) {
443445
AsyncTask<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() {
444446
@Override
445447
protected Void doInBackground(Void... params) {
446448
try {
447449
WritableMap mutableUpdatePackage = CodePushUtils.convertReadableMapToWritableMap(updatePackage);
448450
mutableUpdatePackage.putString(BINARY_MODIFIED_TIME_KEY, "" + getBinaryResourcesModifiedTime());
449451
codePushPackage.downloadPackage(mutableUpdatePackage, CodePush.this.assetsBundleFileName, new DownloadProgressCallback() {
452+
private boolean nextFrameBusy = false;
453+
private DownloadProgress latestDownloadProgress = null;
454+
450455
@Override
451456
public void call(DownloadProgress downloadProgress) {
452-
getReactApplicationContext()
453-
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
454-
.emit(DOWNLOAD_PROGRESS_EVENT_NAME, downloadProgress.createWritableMap());
457+
if (!notifyProgress) {
458+
return;
459+
}
460+
461+
this.latestDownloadProgress = downloadProgress;
462+
if (nextFrameBusy) {
463+
return;
464+
}
465+
466+
nextFrameBusy = true;
467+
mainActivity.runOnUiThread(new Runnable() {
468+
@Override
469+
public void run() {
470+
ReactChoreographer.getInstance().postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, new Choreographer.FrameCallback() {
471+
@Override
472+
public void doFrame(long frameTimeNanos) {
473+
getReactApplicationContext()
474+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
475+
.emit(DOWNLOAD_PROGRESS_EVENT_NAME, latestDownloadProgress.createWritableMap());
476+
nextFrameBusy = false;
477+
}
478+
});
479+
}
480+
});
455481
}
456482
});
457483

ios/CodePush/CodePush.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,13 @@
5454
@property (strong) NSOutputStream *outputFileStream;
5555
@property long long expectedContentLength;
5656
@property long long receivedContentLength;
57+
@property dispatch_queue_t usingQueue;
5758
@property (copy) void (^progressCallback)(long long, long long);
5859
@property (copy) void (^doneCallback)(BOOL);
5960
@property (copy) void (^failCallback)(NSError *err);
6061

6162
- (id)init:(NSString *)downloadFilePath
63+
usingQueue:(dispatch_queue_t)usingQueue
6264
progressCallback:(void (^)(long long, long long))progressCallback
6365
doneCallback:(void (^)(BOOL))doneCallback
6466
failCallback:(void (^)(NSError *err))failCallback;
@@ -78,6 +80,7 @@ failCallback:(void (^)(NSError *err))failCallback;
7880

7981
+ (void)downloadPackage:(NSDictionary *)updatePackage
8082
expectedBundleFileName:(NSString *)expectedBundleFileName
83+
usingQueue:(dispatch_queue_t)usingQueue
8184
progressCallback:(void (^)(long long, long long))progressCallback
8285
doneCallback:(void (^)())doneCallback
8386
failCallback:(void (^)(NSError *err))failCallback;

0 commit comments

Comments
 (0)