CrossWalk 的共享模式允许多个不同APP共用同一个 XWalk Core 浏览器内核。这是怎么做到的呢?
一、共享资源
XWalk Core 需要调用到一些字符串和图标资源,这些是在APP内,而非 XWalk Core 中。APP 引入 CrossWalk 的 aar 后,编译过程会合并 aar 中的资源,而其中各个资源文件的 ID,已经写好在 aar 压缩包中的 R.txt 内,大概不会变化。 XWalk Core 就是依据与 aar 中相同的资源 ID 调用 APP 的资源。
引入 CrossWalk 的 aar 后,APP 项目可以建立同目录、同名的文件,覆盖原有资源,但 ID 不变。
若没有 aar 压缩包中的 R.txt,XWalk Core 也能正常启动,但一些关键的地方,比如选择文本弹出上下文菜单时,就会出现资源加载错误,导致程序崩溃。
二、共享类
APP 通过安卓API createPackageContext 加载 XWalk Core,通过反射调用其中的类。
mBridgeContext = mWrapperContext.createPackageContext(packageName,
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
mBridgeLoader = mBridgeContext.getClassLoader();
public Class<?> getBridgeClass(String name) {
try {
return mBridgeLoader.loadClass(BRIDGE_PACKAGE + "." + name);
} catch (ClassNotFoundException e) {
}
return null;
}
XWalkView.Java
Class<?>[] paramTypes = ...;
ReflectConstructor constructor = new ReflectConstructor(this.coreWrapper.getBridgeClass("XWalkViewBridge"), paramTypes);
this.bridge = constructor.newInstance(this.constructorParams.toArray());
XWalkViewBridge 是什么呢?其实是 XWalk Core 中定义的一个类,但是这个类在官方开源仓库上竟然找不到,难道是生成的?
尽管如此,我们还是可以通过反编译一睹原貌。
反编译流程先用 dex2jar 反编译 dex 为 jar,然后结合上上文一样地,用 IDEA 自带工具还原出 Java 源代码。
>>>D:\assets\Debug\dex2jar-2.0\d2j-dex2jar.bat classes.dex
dex2jar classes.dex -> .\classes-dex2jar.jar
>>>java -cp "C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.2.3\plugins\java-decompiler\lib\java-decompiler.jar" org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true . src
INFO: Decompiling class android/support/annotation/AnimRes
…
反编译后,就可以看到 XWalkViewBridge 其实是扩展自 XWalkViewInternal 这个类。
我们看一下 XWalk 内核独有的方法,captureBitmapAsync,webview屏幕截图,是怎么调用的。
APP / XWalkView.java
public void captureBitmapAsync(XWalkGetBitmapCallback callback) {
try {
this.captureBitmapAsyncXWalkGetBitmapCallbackInternalMethod.invoke(callback.getBridge());
} catch (UnsupportedOperationException var3) {
if (this.coreWrapper == null) {
throw new RuntimeException("Crosswalk's APIs are not ready yet");
}
XWalkCoreWrapper.handleRuntimeError(var3);
}
}
在共享模式下, XWalkView 通过反射调用 XWalkViewBridge 中的 captureBitmapAsyncSuper 方法。
XWalk Core / XWalkViewBridge.java
public void captureBitmapAsyncSuper(XWalkGetBitmapCallbackBridge var1) {
super.captureBitmapAsync(var1);
}
也就是 XWalkViewInternal 中的原方法。
XWalk Core / XWalkViewInternal.java
private XWalkContent mContent;
@XWalkAPI
public void captureBitmapAsync(XWalkGetBitmapCallbackInternal var1) {
if (this.mContent != null) {
checkThreadSafety();
this.mContent.captureBitmapAsync(var1);
}
}
mContent 又是什么呢?
This class is the implementation class for XWalkViewInternal by calling internal various classes.
public void captureBitmapAsync(XWalkGetBitmapCallbackInternal callback) {
if (mNativeContent == 0) return;
mXWalkGetBitmapCallbackInternal = callback;
mWebContents.getContentBitmapAsync(Bitmap.Config.ARGB_8888, 1.0f,
new Rect(), mGetBitmapCallback);
}
mWebContents 又又是什么呢?
private void setNativeContent(long newNativeContent, String animatable) {
...
mNativeContent = newNativeContent;
...
mWebContents = nativeGetWebContents(mNativeContent);
}
private native WebContents nativeGetWebContents(long nativeXWalkContent);
mWebContents 由 native 方法构造,其实本身是个 interface。
public interface WebContents extends Parcelable {
void getContentBitmapAsync(Config var1, float var2, Rect var3, ContentBitmapCallback var4);
...
}
@Override
public void getContentBitmapAsync(Bitmap.Config config, float scale, Rect srcRect,
ContentBitmapCallback callback) {
nativeGetContentBitmap(mNativeWebContentsAndroid, callback, config, scale,
srcRect.left, srcRect.top, srcRect.width(), srcRect.height());
}
private native void nativeGetContentBitmap(long nativeWebContentsAndroid,
ContentBitmapCallback callback, Bitmap.Config config, float scale,
float x, float y, float width, float height);
nativeGetContentBitmap 在哪里呢?需去掉 native 前缀才能在 github 上搜索到。
void WebContentsAndroid::GetContentBitmap(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jobject>& jcallback,
const JavaParamRef<jobject>& color_type,
jfloat scale,
jfloat x,
jfloat y,
jfloat width,
jfloat height) {
RenderWidgetHostViewAndroid* view = GetRenderWidgetHostViewAndroid();
const ReadbackRequestCallback result_callback =
base::Bind(&WebContentsAndroid::OnFinishGetContentBitmap,
weak_factory_.GetWeakPtr(),
base::Owned(new ScopedJavaGlobalRef<jobject>(env, obj)),
base::Owned(new ScopedJavaGlobalRef<jobject>(env, jcallback)));
SkColorType pref_color_type = gfx::ConvertToSkiaColorType(color_type.obj());
if (!view || pref_color_type == kUnknown_SkColorType) {
result_callback.Run(SkBitmap(), READBACK_FAILED);
return;
}
if (!view->IsSurfaceAvailableForCopy()) {
result_callback.Run(SkBitmap(), READBACK_SURFACE_UNAVAILABLE);
return;
}
view->GetScaledContentBitmap(scale,
pref_color_type,
gfx::Rect(x, y, width, height),
result_callback);
}
更深入可能就需要编译源代码才能探究了。机械硬盘报废后,看着电脑里仅剩的两块小容量SSD,还是算了吧……
XWalk 首次启动比 X5 较快,但会黑屏闪一下,源码里有解释:
The moment when the XWalkViewBridge is added to the XWalkView, the screen flashes black. The reason is when the SurfaceView appears in the window the fist time, it requests the window's parameters changing by calling IWindowSession.relayout(). But if the window already has appropriate parameters, it will not refresh all the window's stuff and the screen will not blink. So we add a 0px SurfaceView at first. This will recreate the window before the activity is shown on the screen, and when the actual XWalkViewBridge is added, it will just continue to use the window with current parameters. The temporary SurfaceView can be removed at last.
SurfaceView 在低版本平台无法正确响应 scale,所以会有如下现象:
Wiki : Android SurfaceView vs TextureView
XWalkView 的 captureBitmapAsync 方法可以捕捉到 HTML 画布中的内容以及 webgl 内容,这是普通 webview 的 onDraw 方法做不到的。
网友评论