Skip to content

Commit

Permalink
Hook up native delta client
Browse files Browse the repository at this point in the history
Summary:
Adds support for native clients to `ReactAndroid`:
- `.devsupport.BundleDeltaClient` is now abstract with two implementations: the existing Java client, and a native client
- `BundleDeltaClient#processDelta(...)` can now return a native delta client object
- if that client object is non-null, the bridge is started up with that client rather than a script written to disk

Reviewed By: fromcelticpark

Differential Revision: D7845135

fbshipit-source-id: 379a9c6f9319c62eec3c370cda9ffa0969266a29
  • Loading branch information
davidaurelio authored and facebook-github-bot committed May 3, 2018
1 parent 8f85abd commit dd036c2
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import com.facebook.react.bridge.JavaJSExecutor;
import com.facebook.react.bridge.JavaScriptExecutor;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
import com.facebook.react.bridge.NativeDeltaClient;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.NativeModuleRegistry;
import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
Expand Down Expand Up @@ -84,6 +85,7 @@
import com.facebook.soloader.SoLoader;
import com.facebook.systrace.Systrace;
import com.facebook.systrace.SystraceMessage;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -270,8 +272,8 @@ public void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) {
}

@Override
public void onJSBundleLoadedFromServer() {
ReactInstanceManager.this.onJSBundleLoadedFromServer();
public void onJSBundleLoadedFromServer(@Nullable NativeDeltaClient nativeDeltaClient) {
ReactInstanceManager.this.onJSBundleLoadedFromServer(nativeDeltaClient);
}

@Override
Expand Down Expand Up @@ -360,7 +362,7 @@ private void recreateReactContextInBackgroundInner() {
!devSettings.isRemoteJSDebugEnabled()) {
// If there is a up-to-date bundle downloaded from server,
// with remote JS debugging disabled, always use that.
onJSBundleLoadedFromServer();
onJSBundleLoadedFromServer(null);
} else if (mBundleLoader == null) {
mDevSupportManager.handleReloadJS();
} else {
Expand Down Expand Up @@ -848,12 +850,17 @@ private void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) {
}

@ThreadConfined(UI)
private void onJSBundleLoadedFromServer() {
private void onJSBundleLoadedFromServer(@Nullable NativeDeltaClient nativeDeltaClient) {
Log.d(ReactConstants.TAG, "ReactInstanceManager.onJSBundleLoadedFromServer()");
recreateReactContextInBackground(
mJavaScriptExecutorFactory,
JSBundleLoader.createCachedBundleFromNetworkLoader(
mDevSupportManager.getSourceUrl(), mDevSupportManager.getDownloadedJSBundleFile()));

JSBundleLoader bundleLoader = nativeDeltaClient == null
? JSBundleLoader.createCachedBundleFromNetworkLoader(
mDevSupportManager.getSourceUrl(),
mDevSupportManager.getDownloadedJSBundleFile())
: JSBundleLoader.createDeltaFromNetworkLoader(
mDevSupportManager.getSourceUrl(), nativeDeltaClient);

recreateReactContextInBackground(mJavaScriptExecutorFactory, bundleLoader);
}

@ThreadConfined(UI)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,121 +1,197 @@
/**
* Copyright (c) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.devsupport;

import android.util.JsonReader;
import android.util.JsonToken;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedHashMap;
import javax.annotation.Nullable;

import android.util.JsonReader;
import android.util.JsonToken;
import android.util.Pair;
import com.facebook.react.bridge.NativeDeltaClient;
import okhttp3.Headers;
import okio.Buffer;
import okio.BufferedSource;

public class BundleDeltaClient {
public abstract class BundleDeltaClient {

final LinkedHashMap<Number, byte[]> mPreModules = new LinkedHashMap<Number, byte[]>();
final LinkedHashMap<Number, byte[]> mDeltaModules = new LinkedHashMap<Number, byte[]>();
final LinkedHashMap<Number, byte[]> mPostModules = new LinkedHashMap<Number, byte[]>();
@Nullable String mDeltaId;
private static final String METRO_DELTA_ID_HEADER = "X-Metro-Delta-ID";
@Nullable private String mDeltaId;

static boolean isDeltaUrl(String bundleUrl) {
return bundleUrl.indexOf(".delta?") != -1;
public enum ClientType {
NONE,
DEV_SUPPORT,
NATIVE
}

public void reset() {
mDeltaId = null;
mDeltaModules.clear();
mPreModules.clear();
mPostModules.clear();
static boolean isDeltaUrl(String bundleUrl) {
return bundleUrl.indexOf(".delta?") != -1;
}

public String toDeltaUrl(String bundleURL) {
if (isDeltaUrl(bundleURL) && mDeltaId != null) {
return bundleURL + "&deltaBundleId=" + mDeltaId;
@Nullable
static BundleDeltaClient create(ClientType type) {
switch (type) {
case DEV_SUPPORT:
return new BundleDeltaJavaClient();
case NATIVE:
return new BundleDeltaNativeClient();
}
return bundleURL;
return null;
}

public synchronized boolean storeDeltaInFile(BufferedSource body, File outputFile)
throws IOException {
abstract public boolean canHandle(ClientType type);

JsonReader jsonReader = new JsonReader(new InputStreamReader(body.inputStream()));
abstract protected Pair<Boolean, NativeDeltaClient> processDelta(
BufferedSource body,
File outputFile) throws IOException;

jsonReader.beginObject();
final public String extendUrlForDelta(String bundleURL) {
return mDeltaId != null ? bundleURL + "&deltaBundleId=" + mDeltaId : bundleURL;
}

int numChangedModules = 0;
public void reset() {
mDeltaId = null;
}

while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (name.equals("id")) {
mDeltaId = jsonReader.nextString();
} else if (name.equals("pre")) {
numChangedModules += patchDelta(jsonReader, mPreModules);
} else if (name.equals("post")) {
numChangedModules += patchDelta(jsonReader, mPostModules);
} else if (name.equals("delta")) {
numChangedModules += patchDelta(jsonReader, mDeltaModules);
} else {
jsonReader.skipValue();
}
}
public Pair<Boolean, NativeDeltaClient> processDelta(
Headers headers,
BufferedSource body,
File outputFile) throws IOException {

mDeltaId = headers.get(METRO_DELTA_ID_HEADER);
return processDelta(body, outputFile);
}

jsonReader.endObject();
jsonReader.close();
private static class BundleDeltaJavaClient extends BundleDeltaClient {

if (numChangedModules == 0) {
// If we receive an empty delta, we don't need to save the file again (it'll have the
// same content).
return false;
final LinkedHashMap<Number, byte[]> mPreModules = new LinkedHashMap<Number, byte[]>();
final LinkedHashMap<Number, byte[]> mDeltaModules = new LinkedHashMap<Number, byte[]>();
final LinkedHashMap<Number, byte[]> mPostModules = new LinkedHashMap<Number, byte[]>();

@Override
public boolean canHandle(ClientType type) {
return type == ClientType.DEV_SUPPORT;
}

FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
public void reset() {
super.reset();
mDeltaModules.clear();
mPreModules.clear();
mPostModules.clear();
}

try {
for (byte[] code : mPreModules.values()) {
fileOutputStream.write(code);
fileOutputStream.write('\n');
@Override
public synchronized Pair<Boolean, NativeDeltaClient> processDelta(
BufferedSource body,
File outputFile) throws IOException {

JsonReader jsonReader = new JsonReader(new InputStreamReader(body.inputStream()));
jsonReader.beginObject();
int numChangedModules = 0;

while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (name.equals("pre")) {
numChangedModules += patchDelta(jsonReader, mPreModules);
} else if (name.equals("post")) {
numChangedModules += patchDelta(jsonReader, mPostModules);
} else if (name.equals("delta")) {
numChangedModules += patchDelta(jsonReader, mDeltaModules);
} else {
jsonReader.skipValue();
}
}

for (byte[] code : mDeltaModules.values()) {
fileOutputStream.write(code);
fileOutputStream.write('\n');
jsonReader.endObject();
jsonReader.close();

if (numChangedModules == 0) {
// If we receive an empty delta, we don't need to save the file again (it'll have the
// same content).
return Pair.create(Boolean.FALSE, null);
}

for (byte[] code : mPostModules.values()) {
fileOutputStream.write(code);
fileOutputStream.write('\n');
FileOutputStream fileOutputStream = new FileOutputStream(outputFile);

try {
for (byte[] code : mPreModules.values()) {
fileOutputStream.write(code);
fileOutputStream.write('\n');
}

for (byte[] code : mDeltaModules.values()) {
fileOutputStream.write(code);
fileOutputStream.write('\n');
}

for (byte[] code : mPostModules.values()) {
fileOutputStream.write(code);
fileOutputStream.write('\n');
}
} finally {
fileOutputStream.flush();
fileOutputStream.close();
}
} finally {
fileOutputStream.flush();
fileOutputStream.close();

return Pair.create(Boolean.TRUE, null);
}

return true;
}
private static int patchDelta(JsonReader jsonReader, LinkedHashMap<Number, byte[]> map)
throws IOException {
jsonReader.beginArray();

private static int patchDelta(JsonReader jsonReader, LinkedHashMap<Number, byte[]> map)
throws IOException {
jsonReader.beginArray();
int numModules = 0;
while (jsonReader.hasNext()) {
jsonReader.beginArray();

int numModules = 0;
while (jsonReader.hasNext()) {
jsonReader.beginArray();
int moduleId = jsonReader.nextInt();

int moduleId = jsonReader.nextInt();
if (jsonReader.peek() == JsonToken.NULL) {
jsonReader.skipValue();
map.remove(moduleId);
} else {
map.put(moduleId, jsonReader.nextString().getBytes());
}

if (jsonReader.peek() == JsonToken.NULL) {
jsonReader.skipValue();
map.remove(moduleId);
} else {
map.put(moduleId, jsonReader.nextString().getBytes());
jsonReader.endArray();
numModules++;
}

jsonReader.endArray();
numModules++;

return numModules;
}
}

private static class BundleDeltaNativeClient extends BundleDeltaClient {
private final NativeDeltaClient nativeClient = new NativeDeltaClient();

@Override
public boolean canHandle(ClientType type) {
return type == ClientType.NATIVE;
}

jsonReader.endArray();
@Override
protected Pair<Boolean, NativeDeltaClient> processDelta(
BufferedSource body,
File outputFile) throws IOException {
nativeClient.processDelta(body);
return Pair.create(Boolean.FALSE, nativeClient);
}

return numModules;
@Override
public void reset() {
super.reset();
nativeClient.reset();
}
}
}
Loading

0 comments on commit dd036c2

Please sign in to comment.