在react-native中使用crosswalk
首先集成react-native,然后将webview替换成crosswalk。项目可以查看我的github。https://github.com/jackwolf1/android-react-native-crosswalk
因为 crosswalk 包中包含的 javax* 会导致重复引用,需要在包中去掉
{% highlight null %}
unzip -j xwalk_core_library-${ver}.aar classes.jar
zip -d classes.jar javax\*
zip -r xwalk_core_library-${ver}.aar classes.jar
rm -f classes.jar
{% endhighlight %}
将新生成的jar包放入libs中,并放入对应的jnilibs,crosswalk导入成功。
主activity如下:
{% highlight java %}
public class MainActivity extends ReactActivity {
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "testProject";
}
/**
* Returns whether dev mode should be enabled.
* This enables e.g. the dev menu.
*/
@Override
protected boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
/**
* A list of packages used by the app. If the app uses additional views
* or modules besides the default ones, add more packages here.
*/
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),new AppReactPackage(this)
);
}
}
{% endhighlight %}
AppReactPackage如下:
{% highlight java %}
public class AppReactPackage implements ReactPackage {
Activity mActivity;
public AppReactPackage(Activity activity) {
mActivity = activity;
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new XWalkViewManager(mActivity));
}
}
{% endhighlight %}
XWalkViewManager如下:
{% highlight java %}
public class XWalkViewManager extends SimpleViewManager<XWalkView> {
private static final String REACT_CLASS = "XWalkView";
private static final String HTML_ENCODING = "UTF-8";
private static final String HTML_MIME_TYPE = "text/html; charset=utf-8";
private static final String HTTP_METHOD_POST = "POST";
public static final int COMMAND_GO_BACK = 1;
public static final int COMMAND_GO_FORWARD = 2;
public static final int COMMAND_RELOAD = 3;
// Use `webView.loadUrl("about:blank")` to reliably reset the view
// state and release page resources (including any running JavaScript).
private static final String BLANK_URL = "about:blank";
private XWalkViewConfig mWebViewConfig;
private Activity mActivity;
private static class ResourceClient extends XWalkResourceClient {
private boolean mLastLoadFailed = false;
private int progressInPercent=0;
public ResourceClient(XWalkView view) {
super(view);
}
@Override
public void onReceivedResponseHeaders(XWalkView view, XWalkWebResourceRequest request, XWalkWebResourceResponse response) {
super.onReceivedResponseHeaders(view, request, response);
}
@Override
public void onLoadFinished(XWalkView webView, String url) {
super.onLoadFinished(webView, url);
if (!mLastLoadFailed) {
RXWalkView reactWebView = (RXWalkView) webView;
reactWebView.callInjectedJavaScript();
emitFinishEvent(webView, url);
}
}
@Override
public void onLoadStarted(XWalkView webView, String url) {
super.onLoadStarted(webView, url);
mLastLoadFailed = false;
dispatchEvent(
webView,
new TopLoadingStartEvent(
webView.getId(),
SystemClock.nanoTime(),
createWebViewEvent(webView, url)));
}
@Override
public void onReceivedLoadError(XWalkView webView, int errorCode, String description, String failingUrl) {
super.onReceivedLoadError(webView, errorCode, description, failingUrl);
mLastLoadFailed = true;
// In case of an error JS side expect to get a finish event first, and then get an error event
// Android WebView does it in the opposite way, so we need to simulate that behavior
emitFinishEvent(webView, failingUrl);
WritableMap eventData = createWebViewEvent(webView, failingUrl);
eventData.putDouble("code", errorCode);
eventData.putString("description", description);
dispatchEvent(
webView,
new TopLoadingErrorEvent(webView.getId(), SystemClock.nanoTime(), eventData));
}
@Override
public void doUpdateVisitedHistory(XWalkView webView, String url, boolean isReload) {
super.doUpdateVisitedHistory(webView, url, isReload);
dispatchEvent(
webView,
new TopLoadingStartEvent(
webView.getId(),
SystemClock.nanoTime(),
createWebViewEvent(webView, url)));
}
private void emitFinishEvent(XWalkView webView, String url) {
dispatchEvent(
webView,
new TopLoadingFinishEvent(
webView.getId(),
SystemClock.nanoTime(),
createWebViewEvent(webView, url)));
}
private static void dispatchEvent(XWalkView webView, Event event) {
ReactContext reactContext = (ReactContext) ((RXWalkView)webView).getReactContext();
EventDispatcher eventDispatcher =
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
eventDispatcher.dispatchEvent(event);
}
@Override
public void onProgressChanged(XWalkView view, int progressInPercent) {
super.onProgressChanged(view, progressInPercent);
this.progressInPercent = progressInPercent;
}
private WritableMap createWebViewEvent(XWalkView webView, String url) {
WritableMap event = Arguments.createMap();
event.putDouble("target", webView.getId());
// Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
// like onPageFinished
event.putString("url", url);
event.putBoolean("loading", !mLastLoadFailed && this.progressInPercent != 100);
event.putString("title", webView.getTitle());
event.putBoolean("canGoBack", webView.getNavigationHistory().canGoBack());
event.putBoolean("canGoForward", webView.getNavigationHistory().canGoForward());
return event;
}
}
/**
* Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
* to call {@link WebView#destroy} on activty destroy event and also to clear the client
*/
private static class RXWalkView extends XWalkView implements LifecycleEventListener {
private @Nullable String injectedJS;
private ReactContext reactContext;
/**
* WebView must be created with an context of the current activity
*
* Activity Context is required for creation of dialogs internally by WebView
* Reactive Native needed for access to ReactNative internal system functionality
*/
public RXWalkView(Context context) {
super(context);
}
public ReactContext getReactContext() {
return reactContext;
}
public void setReactContext(ReactContext reactContext) {
this.reactContext = reactContext;
}
@Override
public void onHostResume() {
// do nothing
}
@Override
public void onHostPause() {
// do nothing
}
@Override
public void onHostDestroy() {
cleanupCallbacksAndDestroy();
}
public void setInjectedJavaScript(@Nullable String js) {
injectedJS = js;
}
public void callInjectedJavaScript() {
if (
injectedJS != null &&
!TextUtils.isEmpty(injectedJS)) {
super.load(null,"javascript:(function() {\n" + injectedJS + ";\n})();");
}
}
private void cleanupCallbacksAndDestroy() {
super.setResourceClient(null);
super.onDestroy();
}
}
public XWalkViewManager(Activity activity) {
mActivity = activity;
mWebViewConfig = new XWalkViewConfig() {
public void configWebView(XWalkView webView) {
}
};
}
public XWalkViewManager(XWalkViewConfig webViewConfig) {
mWebViewConfig = webViewConfig;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected XWalkView createViewInstance(ThemedReactContext reactContext) {
RXWalkView webView = new RXWalkView(mActivity);
webView.setReactContext(reactContext);
webView.setResourceClient(new ResourceClient(webView));
reactContext.addLifecycleEventListener(webView);
mWebViewConfig.configWebView(webView);
if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
return webView;
}
@ReactProp(name = "url")
public void setUrl(XWalkView view,@android.support.annotation.Nullable String url) {
view.load(url,null);
}
@ReactProp(name = "javaScriptEnabled")
public void setJavaScriptEnabled(XWalkView view,@android.support.annotation.Nullable boolean enabled) {
try {
Method ___getBridge = XWalkView.class.getDeclaredMethod("getBridge");
___getBridge.setAccessible(true);
XWalkViewBridge xWalkViewBridge = null;
xWalkViewBridge = (XWalkViewBridge) ___getBridge.invoke(view);
xWalkViewBridge.getSettings().setJavaScriptEnabled(enabled);
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@ReactProp(name = "scalesPageToFit")
public void setScalesPageToFit(XWalkView view, boolean enabled) {
view.getSettings().setUseWideViewPort(!enabled);
}
@ReactProp(name = "domStorageEnabled")
public void setDomStorageEnabled(XWalkView view, boolean enabled) {
try {
Method ___getBridge = XWalkView.class.getDeclaredMethod("getBridge");
___getBridge.setAccessible(true);
XWalkViewBridge xWalkViewBridge = null;
xWalkViewBridge = (XWalkViewBridge) ___getBridge.invoke(view);
xWalkViewBridge.getSettings().setDomStorageEnabled(enabled);
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@ReactProp(name = "userAgent")
public void setUserAgent(XWalkView view, @Nullable String userAgent) {
view.getSettings().setUserAgentString(userAgent);
}
@ReactProp(name = "mediaPlaybackRequiresUserAction")
public void setMediaPlaybackRequiresUserAction(XWalkView view, boolean requires) {
try {
Method ___getBridge = XWalkView.class.getDeclaredMethod("getBridge");
___getBridge.setAccessible(true);
XWalkViewBridge xWalkViewBridge = null;
xWalkViewBridge = (XWalkViewBridge) ___getBridge.invoke(view);
xWalkViewBridge.getSettings().setMediaPlaybackRequiresUserGesture(requires);
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@ReactProp(name = "injectedJavaScript")
public void setInjectedJavaScript(XWalkView view, @Nullable String injectedJavaScript) {
((RXWalkView) view).setInjectedJavaScript(injectedJavaScript);
}
@ReactProp(name = "source")
public void setSource(XWalkView view, @Nullable ReadableMap source) {
if (source != null) {
if (source.hasKey("html")) {
String html = source.getString("html");
if (source.hasKey("baseUrl")) {
view.load(
source.getString("baseUrl"), html);
} else {
view.load(null,html);
}
return;
}
if (source.hasKey("uri")) {
String url = source.getString("uri");
if (source.hasKey("method")) {
String method = source.getString("method");
if (method.equals(HTTP_METHOD_POST)) {
byte[] postData = null;
if (source.hasKey("body")) {
String body = source.getString("body");
try {
postData = body.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
postData = body.getBytes();
}
}
if (postData == null) {
postData = new byte[0];
}
//view.postUrl(url, postData);
return;
}
}
HashMap<String, String> headerMap = new HashMap<>();
if (source.hasKey("headers")) {
ReadableMap headers = source.getMap("headers");
ReadableMapKeySetIterator iter = headers.keySetIterator();
while (iter.hasNextKey()) {
String key = iter.nextKey();
headerMap.put(key, headers.getString(key));
}
}
//view.loadUrl(url, headerMap);
view.load(url,null);
return;
}
}
view.load(null,BLANK_URL);
}
@Override
protected void addEventEmitters(ThemedReactContext reactContext, XWalkView view) {
// Do not register default touch emitter and let WebView implementation handle touches
view.setResourceClient(new ResourceClient(view));
}
@Override
public @Nullable Map<String, Integer> getCommandsMap() {
return MapBuilder.of(
"goBack", COMMAND_GO_BACK,
"goForward", COMMAND_GO_FORWARD,
"reload", COMMAND_RELOAD);
}
@Override
public void receiveCommand(XWalkView view, int commandId, @Nullable ReadableArray args) {
switch (commandId) {
case COMMAND_GO_BACK:
view.getNavigationHistory().navigate(XWalkNavigationHistory.Direction.BACKWARD, 1);
break;
case COMMAND_GO_FORWARD:
view.getNavigationHistory().navigate(XWalkNavigationHistory.Direction.FORWARD, 1);
break;
case COMMAND_RELOAD:
view.reload(XWalkView.RELOAD_NORMAL);
break;
}
}
@Override
public void onDropViewInstance(XWalkView webView) {
super.onDropViewInstance(webView);
((ThemedReactContext) ((RXWalkView)webView).getReactContext()).removeLifecycleEventListener((RXWalkView) webView);
((RXWalkView) webView).cleanupCallbacksAndDestroy();
}
}
{% endhighlight %}