Android 集成Chrome 浏览器内核 Crosswalk
Crosswalk 内核的兴起与消亡Android 4.4 版本之前,使用的是基于 androidWebKit 的 WebView但实际上,由于 Android 的碎片化问题(大量存在不同的 Android 系统版本,并且各个厂商对内置应用进行定制化,有可能用的不是最新的浏览器内核)。这就导致 WebView 在真实环境中对 API 的支持根本无迹可寻,越发混乱。随着混合开发的兴起,前端对 ...
Crosswalk 内核的兴起与消亡
Android 4.4 版本之前,使用的是基于 androidWebKit 的 WebView
但实际上,由于 Android 的碎片化问题(大量存在不同的 Android 系统版本,并且各个厂商对内置应用进行定制化,有可能用的不是最新的浏览器内核)。这就导致 WebView 在真实环境中对 API 的支持根本无迹可寻,越发混乱。
Android 碎片化问题集中表现在下面几个方面:
- 设备繁多,硬件配置参差不弃,设备性能各异,差距很大
- 品牌众多,厂商标准不一致,定制化系统体验不同
- 版本各异,国内外系统环境差异巨大
- 分辨率不统一,各种类型尺寸众多
随着混合开发的兴起,前端对 API 的支持程度和网页的表现效果都有了更严格的要求,原生WebView 由于碎片化严重,API支持程度未知,容易引发很多意料之外的BUG。
这时候,就诞生了一些第三方浏览器内核
-
Intel 开源的基于 Chrome 的 Crosswalk 内核 和 XWalkView 浏览器(2017年5月停止维护)
https://github.com/crosswalk-project/crosswalk
https://github.com/tenta-browser/crosswalk -
腾讯出品的 X5 浏览器内核
https://x5.tencent.com/tbs/sdk.html
从 Android 5.0 开始,Google 把 Chromium blink内核 webview 作为 apk 单独从系统抽离出去,可以在应用市场(Google Play)上面接收安装更新。应用可以直接使用该webview内核,Google也可以及时发布更新,不用再通过更新系统才能更新浏览器内核,也避免部分了 Android 系统碎片化问题。
因此 Intel 的 Crosswalk 就停止维护了。然而由于国内被墙,并没有接入谷歌服务,因此 腾讯X5 内核 还流传至今,并且被广泛的应用
集成原因
现在代的手机上,原生的 webkit 内嵌的谷歌内核版本并不是很统一,这就导致了有些手机支持的API到另一个手机,又不支持了。为了达到体验一致,也方便测试,我建议在国内,尽量使用腾讯X5进行替换,X5的API和原生的基本一致,仅需要改动较小的部分。
那么corsswalk,一个包40M,是不是就毫无用处了呢?答案是否定的,crosswalk现在多用于集成到
智能设备中。智能设备的网络不一定好用,更别说安装QQ微信了,而且即使安装了,也不一定支持腾讯X5,因为现在还是有部分手机无法兼容X5转而降级为原生浏览器内核的。
集成方式
添加依赖
可以在项目根路径下的 build.gradle 中添加,针对所有module
buildscript {
repositorities {
……
}
}
allprojects {
repositories {
……
maven { url 'https://download.01.org/crosswalk/releases/crosswalk/android/maven2'}
}
}
两个位置的 repositories 的区别
- buildscript 里是 gradle 脚本执行所需依赖,分别是对应的 maven 库和插件
- allprojects 里是项目本身需要的依赖
也可以仅在对应 module 的 build.gradle 中添加 respositories,然后再添加对应依赖
android {
}
repositories {
maven { url 'https://download.01.org/crosswalk/releases/crosswalk/android/maven2'}
}
dependencies {
implementation 'org.xwalk:xwalk_core_library:23.53.589.4'
}
注意:添加依赖后不可能一次就同步成功,需要多同步好几次
申请权限
在 AndroidManifest.xml 中添加如下权限声明
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
开启硬件加速
从Android3.0(API Level 11)开始,Android 2D渲染管道k开始支持硬件加速(默认是关闭的)。
可以在 AndroidManifest.xml 中,为 Application 添加属性,开启全局硬件加速
<Application
……
android:hardwareAccelerated="true" >
……
</Application>
硬件加速执行的所有的绘图操作,都是使用GPU在 View 对象的画布上来进行的。因为启用硬件加速会增加资源的需求,因此这样的应用会占用更多的内存。
为了让应用能申请使用更多的内存,还需要添加一个 largeHeap 属性。机器的内存限制,在/system/build.prop文件中配置的,例如
dalvik.vm.heapsize=128m
dalvik.vm.heapgrowthlimit=64m
heapgrowthlimit 是一个普通应用的内存限制,用ActivityManager.getLargeMemoryClass() 获得的值就是这个。而 heapsize 是在 manifest 中设置了 largeHeap=true 之后,可以使用最大内存值。
习惯性的为应用多申请一点内存,可以使用如下代码
<Application
……
android:hardwareAccelerated="true"
android:largeHeap="true" >
……
</Application>
布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<org.xwalk.core.XWalkView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
测试代码
XWResourceClient.java
public class XWResourceClient extends XWalkResourceClient {
private static final String TAG = "XWalkResourceClient";
public XWResourceClient(XWalkView view) {
super(view);
}
@Override
public void onLoadStarted(XWalkView view, String url) {
Log.i(TAG, "onLoadStarted " + url);
super.onLoadStarted(view, url);
}
@Override
public void onLoadFinished(XWalkView view, String url) {
Log.i(TAG, "onLoadFinished " + url);
super.onLoadFinished(view, url);
}
@Override
public void onProgressChanged(XWalkView view, int progressInPercent) {
Log.i(TAG, "onProgressChanged " + progressInPercent);
super.onProgressChanged(view, progressInPercent);
}
@Override
public boolean shouldOverrideUrlLoading(XWalkView view, String url) {
Log.i(TAG, "shouldOverrideUrlLoading " + url);
return super.shouldOverrideUrlLoading(view, url);
}
@Override
public WebResourceResponse shouldInterceptLoadRequest(XWalkView view, String url) {
Log.i(TAG, "shouldInterceptLoadRequest " + url);
return super.shouldInterceptLoadRequest(view, url);
}
@Override
public XWalkWebResourceResponse shouldInterceptLoadRequest(XWalkView view, XWalkWebResourceRequest request) {
Log.i(TAG, "shouldInterceptLoadRequest " + request.isForMainFrame() + ", " + request.getUrl() + ", " + new JSONObject(request.getRequestHeaders()).toString());
return super.shouldInterceptLoadRequest(view, request);
}
@Override
public void onReceivedSslError(XWalkView view, ValueCallback<Boolean> callback, SslError error) {
Log.i(TAG, "onReceivedSslError " + error.toString());
super.onReceivedSslError(view, callback, error);
}
@Override
public void onReceivedLoadError(XWalkView view, int errorCode, String description, String failingUrl) {
Log.i(TAG, "onReceivedLoadError " + errorCode + ", " + description + ", " + failingUrl);
super.onReceivedLoadError(view, errorCode, description, failingUrl);
}
@Override
public void onDocumentLoadedInFrame(XWalkView view, long frameId) {
Log.i(TAG, "onDocumentLoadedInFrame " + frameId);
super.onDocumentLoadedInFrame(view, frameId);
}
@Override
public void onReceivedClientCertRequest(XWalkView view, ClientCertRequest handler) {
Log.i(TAG, "onReceivedClientCertRequest " + handler.getHost() + ", " + handler.getPort() + ", " + Arrays.toString(handler.getKeyTypes()));
super.onReceivedClientCertRequest(view, handler);
}
@Override
public void onReceivedHttpAuthRequest(XWalkView view, XWalkHttpAuthHandler handler, String host, String realm) {
Log.i(TAG, "onReceivedHttpAuthRequest " + host + ", " + realm);
super.onReceivedHttpAuthRequest(view, handler, host, realm);
}
@Override
public void onReceivedResponseHeaders(XWalkView view, XWalkWebResourceRequest request, XWalkWebResourceResponse response) {
Log.i(TAG, "onReceivedResponseHeaders " + request.isForMainFrame() + ", " + request.getUrl() + ", " + new JSONObject(request.getRequestHeaders()).toString());
super.onReceivedResponseHeaders(view, request, response);
}
@Override
public XWalkWebResourceResponse createXWalkWebResourceResponse(String mimeType, String encoding, InputStream data) {
Log.i(TAG, "createXWalkWebResourceResponse " + mimeType + ", " + encoding);
return super.createXWalkWebResourceResponse(mimeType, encoding, data);
}
@Override
public XWalkWebResourceResponse createXWalkWebResourceResponse(String mimeType, String encoding, InputStream data, int statusCode, String reasonPhrase, Map<String, String> responseHeaders) {
Log.i(TAG, "createXWalkWebResourceResponse " + mimeType + ", " + encoding + ", " + statusCode + ", " + reasonPhrase + ", " + new JSONObject(responseHeaders));
return super.createXWalkWebResourceResponse(mimeType, encoding, data, statusCode, reasonPhrase, responseHeaders);
}
@Override
public void doUpdateVisitedHistory(XWalkView view, String url, boolean isReload) {
Log.i(TAG, "doUpdateVisitedHistory " + url + ", " + isReload);
super.doUpdateVisitedHistory(view, url, isReload);
}
@Override
protected Object getBridge() {
Object obj = super.getBridge();
if(obj != null) {
Log.i(TAG, "getBridge " + obj.getClass().getSimpleName());
} else {
Log.i(TAG, "getBridge()");
}
return obj;
}
}
XWUIClient.java
public class XWUIClient extends XWalkUIClient {
private static final String TAG = "XWalkUIClient";
public XWUIClient(XWalkView view) {
super(view);
}
@Override
public void onPageLoadStarted(XWalkView view, String url) {
Log.i(TAG, "onPageLoadStarted " + url);
super.onPageLoadStarted(view, url);
}
@Override
public void onPageLoadStopped(XWalkView view, String url, LoadStatus status) {
Log.i(TAG, "onPageLoadStopped " + url + ", " + status.toString());
super.onPageLoadStopped(view, url, status);
}
@Override
public boolean onJsAlert(XWalkView view, String url, String message, XWalkJavascriptResult result) {
Log.i(TAG, "onJsAlert " + url + ", " + message);
return super.onJsAlert(view, url, message, result);
}
@Override
public boolean onJsConfirm(XWalkView view, String url, String message, XWalkJavascriptResult result) {
Log.i(TAG, "onJsConfirm " + url + ", " + message);
return super.onJsConfirm(view, url, message, result);
}
@Override
public boolean onJsPrompt(XWalkView view, String url, String message, String defaultValue, XWalkJavascriptResult result) {
Log.i(TAG, "onJsPrompt " + url + ", " + message);
return super.onJsPrompt(view, url, message, defaultValue, result);
}
@Override
public boolean onConsoleMessage(XWalkView view, String message, int lineNumber, String sourceId, ConsoleMessageType messageType) {
Log.i(TAG, "onConsoleMessage " + message + ", " + lineNumber + ", " + sourceId + ", " + messageType.toString());
return super.onConsoleMessage(view, message, lineNumber, sourceId, messageType);
}
@Override
public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) {
Log.i(TAG, "onShowCustomView " + requestedOrientation);
super.onShowCustomView(view, requestedOrientation, callback);
}
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
Log.i(TAG, "onShowCustomView");
super.onShowCustomView(view, callback);
}
@Override
public boolean onCreateWindowRequested(XWalkView view, InitiateBy initiator, ValueCallback<XWalkView> callback) {
Log.i(TAG, "onCreateWindowRequested");
return super.onCreateWindowRequested(view, initiator, callback);
}
@Override
public boolean onJavascriptModalDialog(XWalkView view, JavascriptMessageType type, String url, String message, String defaultValue, XWalkJavascriptResult result) {
Log.i(TAG, "onJavascriptModalDialog " + type.toString() + ", " + url + ", " + message + ", " + defaultValue);
return super.onJavascriptModalDialog(view, type, url, message, defaultValue, result);
}
@Override
public void onFullscreenToggled(XWalkView view, boolean enterFullscreen) {
Log.i(TAG, "onFullscreenToggled " + enterFullscreen);
super.onFullscreenToggled(view, enterFullscreen);
}
@Override
public void onHideCustomView() {
Log.i(TAG, "onHideCustomView");
super.onHideCustomView();
}
@Override
public void onIconAvailable(XWalkView view, String url, Message startDownload) {
Log.i(TAG, "onIconAvailable " + url + ", " + startDownload.toString());
super.onIconAvailable(view, url, startDownload);
}
@Override
public void onJavascriptCloseWindow(XWalkView view) {
Log.i(TAG, "onJavascriptCloseWindow");
super.onJavascriptCloseWindow(view);
}
@Override
public void onReceivedIcon(XWalkView view, String url, Bitmap icon) {
Log.i(TAG, "onReceivedIcon " + url);
super.onReceivedIcon(view, url, icon);
}
@Override
public void onReceivedTitle(XWalkView view, String title) {
Log.i(TAG, "onReceivedTitle " + title);
super.onReceivedTitle(view, title);
}
@Override
public void onRequestFocus(XWalkView view) {
Log.i(TAG, "onRequestFocus");
super.onRequestFocus(view);
}
@Override
public void onScaleChanged(XWalkView view, float oldScale, float newScale) {
Log.i(TAG, "onScaleChanged " + oldScale + ", " + newScale);
super.onScaleChanged(view, oldScale, newScale);
}
@Override
public void onUnhandledKeyEvent(XWalkView view, KeyEvent event) {
Log.i(TAG, "onUnhandledKeyEvent " + event.getAction() + ", " + event.getKeyCode());
super.onUnhandledKeyEvent(view, event);
}
@Override
public void openFileChooser(XWalkView view, ValueCallback<Uri> uploadFile, String acceptType, String capture) {
Log.i(TAG, "openFileChooser " + acceptType + ", " + capture);
super.openFileChooser(view, uploadFile, acceptType, capture);
}
@Override
public boolean shouldOverrideKeyEvent(XWalkView view, KeyEvent event) {
Log.i(TAG, "shouldOverrideKeyEvent " + event.getAction() + ", " + event.getKeyCode());
return super.shouldOverrideKeyEvent(view, event);
}
@Override
protected Object getBridge() {
Object obj = super.getBridge();
if(obj != null) {
Log.i(TAG, "getBridge " + obj.getClass().getSimpleName());
} else {
Log.i(TAG, "getBridge()");
}
return obj;
}
}
MainActivity.java
public class MainActivity extends XWalkActivity {
private XWalkView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = findViewById(R.id.webview);
// Crosswalk's APIs are not ready yet
}
@Override
protected void onXWalkReady() {
initSettings();
webView.setUIClient(new XWUIClient(webView));
webView.setResourceClient(new XWResourceClient(webView));
webView.addJavascriptInterface(new AppShell(this), AppShell.TAG);
webView.loadUrl("http://www.baidu.com");
}
@Override
public void onPointerCaptureChanged(boolean hasCapture) {
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if(isXWalkReady()) webView.onNewIntent(intent);
}
@Override
protected void onPause() {
super.onPause();
if(isXWalkReady()) {
webView.pauseTimers();
webView.onHide();
}
}
@Override
protected void onResume() {
super.onResume();
if(isXWalkReady()) {
webView.resumeTimers();
webView.onShow();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if(isXWalkReady()) {
webView.onDestroy();
}
}
@Override
public void onBackPressed() {
if(isXWalkReady()) {
XWalkNavigationHistory history = webView.getNavigationHistory();
if (history.canGoBack()) {
history.navigate(XWalkNavigationHistory.Direction.BACKWARD, 1);
} else {
super.onBackPressed();
}
} else {
super.onBackPressed();
}
}
/**
* 没有允许定位的设置
*/
public void initSettings() {
XWalkSettings webSettings = webView.getSettings();
//启用JavaScript
webSettings.setJavaScriptEnabled(true);
//允许js弹窗alert等,window.open方法打开新的网页,默认不允许
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
//localStorage和sessionStorage
webSettings.setDomStorageEnabled(true);
//Web SQL Databases
webSettings.setDatabaseEnabled(true);
//是否可访问Content Provider的资源,默认值 true
webSettings.setAllowContentAccess(true);
/*
是否允许访问文件系统,默认值 true
file:///androMSG_asset和file:///androMSG_res始终可以访问,不受其影响
*/
webSettings.setAllowFileAccess(true);
//是否允许通过file url加载的Javascript读取本地文件,默认值 false
webSettings.setAllowFileAccessFromFileURLs(true);
//是否允许通过file url加载的Javascript读取全部资源(包括文件,http,https),默认值 false
webSettings.setAllowUniversalAccessFromFileURLs(true);
//设置是否支持缩放
webSettings.setSupportZoom(false);
//设置内置的缩放控件
webSettings.setBuiltInZoomControls(false);
/*
当该属性被设置为false时,加载页面的宽度总是适应WebView控件宽度;
当被设置为true,当前页面包含viewport属性标签,在标签中指定宽度值生效,如果页面不包含viewport标签,无法提供一个宽度值,这个时候该方法将被使用。
*/
webSettings.setUseWideViewPort(false);
//缩放至屏幕大小
webSettings.setLoadWithOverviewMode(true);
//支持多窗口
webSettings.setSupportMultipleWindows(true);
/*
缓存模式
LOAD_CACHE_ONLY 不使用网络,只读取本地缓存
LOAD_DEFAULT 根据cache-control决定是否从网络上获取数据
LOAD_NO_CACHE 不使用缓存,只从网络获取数据
LOAD_CACHE_ELSE_NETWORK 只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据
*/
webSettings.setCacheMode(XWalkSettings.LOAD_DEFAULT);
//设置是否加载图片
webSettings.setLoadsImagesAutomatically(true);
//允许远程调试
XWalkPreferences.setValue("enable-javascript", true);
XWalkPreferences.setValue(XWalkPreferences.REMOTE_DEBUGGING, true);
}
}
生命周期
XWalk 内核加载的生命周期
- 预加载 xwalk 内核
- 将共享的XWalk内核与当前Activity绑定
public static void reserveReflectObject(Object object) {
String tag = (String)sReservedActivities.getLast();
Log.d("XWalkLib", "Reserve object " + object.getClass() + " to " + tag);
((LinkedList)sReservedActions.get(tag)).add(new XWalkCoreWrapper.ReservedAction(object));
}
-
在Activity中初始化XWalk内核
-
解压、激活、将内核附着在Activity上
-
下载模式禁用
下载模式和共享模式差不多,只是共享模式是把APK下载下来当成一个应用安装到手机上,而下载模式干脆把APK下载到自己的私有目录下,把所有的so文件、资源解压出来保存到自己的内部私有目录下只供自己使用。 -
通过反射,api版本是否等于lib版本(mApiVersion == minLibVersion)
private boolean checkCoreVersion() {
Log.d("XWalkLib", "[Environment] SDK:" + VERSION.SDK_INT);
Log.d("XWalkLib", "[App Version] build:23.53.589.4, api:" + this.mApiVersion + ", min_api:" + this.mMinApiVersion);
try {
Class<?> clazz = this.getBridgeClass("XWalkCoreVersion");
String buildVersion = "";
try {
buildVersion = (String)(new ReflectField(clazz, "XWALK_BUILD_VERSION")).get();
} catch (RuntimeException var5) {
}
int libVersion = (Integer)(new ReflectField(clazz, "API_VERSION")).get();
int minLibVersion = (Integer)(new ReflectField(clazz, "MIN_API_VERSION")).get();
Log.d("XWalkLib", "[Lib Version] build:" + buildVersion + ", api:" + libVersion + ", min_api:" + minLibVersion);
if (XWalkEnvironment.isDownloadMode() && XWalkEnvironment.isDownloadModeUpdate() && !buildVersion.isEmpty() && !buildVersion.equals("23.53.589.4")) {
this.mCoreStatus = 8;
return false;
}
if (this.mMinApiVersion > libVersion) {
this.mCoreStatus = 3;
return false;
}
if (this.mApiVersion < minLibVersion) {
this.mCoreStatus = 4;
return false;
}
} catch (RuntimeException var6) {
Log.d("XWalkLib", "XWalk core not found");
this.mCoreStatus = 2;
return false;
}
Log.d("XWalkLib", "XWalk core version matched");
return true;
}
- 打印出,当前库是基于ARM架构编译的。当前设备是arm64-v8a,使用嵌入模式运行
- 搭建环境、初始化内核、初始化视图,激活任务执行完毕,开始在 SurfaceView 上进行绘制。
函数 | 用途 |
---|---|
getBridge | XWalkUIClient 和 XWalkResourceClient 获取反射的对象 |
shouldInterceptLoadRequest | 拦截请求,在这里可以使用缓存 |
onPageLoadStarted、onPageLoadStopped | 页面加载开始、结束,可以用来做自定义定时器 |
onLoadStarted、onLoadStopped | 页面中的元素加载开始和结束 |
onReceivedResponseHeaders | 处理接收的头部 |
shouldOverrideUrlLoading | 用来处理意图 |
doUpdateVisitedHistory | 更新访问历史记录 |
onReceivedTitle | 收到标题 |
遇到的问题
1. Crosswalk‘s APIs are not ready yet
首先, org.xwalk.core.XWalkView 控件,只能在 XWalkActivity 里,也就是当前 Activity 必须继承自 XWalkActivity。
对 XWalkView 的设置,只能 onXWalkReady 里。
在 Activity 的生命周期函数中调用 XWalkView 的方法需要先判断是否初始化完毕。
2. Mismatch of CPU Architecture
so分32位和64位!
理论上官方应该提供2种包含不同so的aar包。一种是32位的另一种是64位,32位的aar中只包含x86和armeabi-v7a两种so文件,同理64位的包中只包含x86_64和arm64-v8a两种so文件。
由于arm64-v8a平台能兼容32位的so文件、x86_64也能兼容32位的x86 so文件,在不考虑性能(暂时未知性能问题)的情况下就可以直接集成32位的aar包,可以大大减少安装包的大小。
腾讯X5的话,是仅支持32位的。我猜测 crosswalk 应该也是这个问题,因此仅集成32位即可。
因此可以在 module 的 build.gradle 中添加如下内容
android {
defaultConfig {
ndk {
abiFilters 'armeabi', 'armeabi-v7a'
}
}
}
然后,再跟腾讯 X5 的做法一样,在 src/mian/jniLibs/armeabi-v7a 目录下,创建一个空的 so
3. 文件选择的问题
在 HTML5 中共有 4 种选择文件的方式,代码如下
<label>下面是选择文件</label>
<input type="file" name="filename" />
<label>下面是通过摄像头获取文件</label>
<input capture="camera" id="cameraFile" name="imgFile" type="file">
<label>下面是打开摄像头拍照</label>
<input accept="image/*" capture="camera" id="imgFile" name="imgFile" type="file">
<label>下面是打开摄像头录像</label>
<input accept="video/*" capture="camera" id="videoFile" name="imgFile" type="file">
在 CrossWalk 中对应的函数是 XWalkUIClient.java 中的 openFileChooser。如果主动不触发回调函数,即为未选择,那么内部会一直处于等待的过程中,即使下一次点击选择文件,也不会有响应。
public void openFileChooser(XWalkView view, ValueCallback<Uri> uploadFile, String acceptType, String capture) {
……
}
获取文件(文件、拍照、相册、录像)
序号 | acceptType | capture | 含义 |
---|---|---|---|
1 | false | 获取文件 | |
2 | true | 打开摄像头获取文件 | |
3 | image/* | true | 打开摄像头拍照 |
4 | video/* | true | 打开摄像头录像 |
实现大致逻辑(再细的代码就不贴了,讲个思路)
@Override
public void openFileChooser(XWalkView view, ValueCallback<Uri> uploadFile, String acceptType, String capture) {
Log.i(TAG, "openFileChooser " + acceptType + ", " + capture);
super.openFileChooser(view, uploadFile, acceptType, capture);
if ("true".equals(capture)) {
//判断是拍视频,还是拍照
boolean isVideo = "video/*".equals(acceptType);
String path;
DataCallback1<Uri> callback = uri -> {
LogUtil.i(TAG, "uri: " + (uri == null ? null : uri.toString()));
uploadFile.onReceiveValue(uri);
};
if (isVideo) {
//拍视频
path = genMoviesPath(activity);
LogUtil.i(TAG, path);
if(path == null) return;
FileUtil.mkParentDirs(path);
LogUtil.d(TAG, "videoCapture: " + path);
IntentUtil.videoCapture(activity, path, callback);
} else {
//拍照片
path = genPicturePath(activity);
LogUtil.i(TAG, path);
if(path == null) return;
FileUtil.mkParentDirs(path);
LogUtil.d(TAG, "videoCapture: " + path);
IntentUtil.imageCaptureToUri(activity, path, callback);
}
} else {
if(TextUtils.isEmpty(acceptType)) acceptType = "*/*";
IntentUtil.selectFile(activity, acceptType, (uri, path, mimeType) -> uploadFile.onReceiveValue(uri));
}
}
4. 注解 @JavascriptInterface
注意一个细节,原生 webkit 乃至腾讯X5,用的都是 android.webkit.JavascriptInterface
,然而 crosswalk 不同,它用的是 org.xwalk.core.JavascriptInterface
。如果注解用错了,抱歉 Uncaught TypeError: <JavaScriptInterfaceName.Method> is not a function
5. 截图
普通的 View 控件,直接用下面函数即可截图。但 Crosswalk 用下面方法,只能得到一张白纸
public static Bitmap getView(View view) {
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap cache = view.getDrawingCache();
Bitmap bitmap = BitmapUtil.clone(cache);
view.setDrawingCacheEnabled(false);
view.destroyDrawingCache();
return bitmap;
}
网上有人说是 “硬件加速” 的问题,如下两种方法关闭硬件加速
- 在 AndroidManifest.xml 的 application 或 activity 里添加
android:hardwareAccelerated="false"
- 在代码中设置
webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
经过测试发现行不通,最后发现 XWalkView 自带异步的截图方法,源码如下
public void captureBitmapAsync(XWalkGetBitmapCallback callback) {
try {
this.captureBitmapAsyncXWalkGetBitmapCallbackInternalMethod.invoke(new Object[]{callback.getBridge()});
} catch (UnsupportedOperationException var3) {
if (this.coreWrapper == null) {
throw new RuntimeException("Crosswalk's APIs are not ready yet");
}
XWalkCoreWrapper.handleRuntimeError(var3);
}
}
6. 拦截请求
public class XWResourceClient {
@Override
public XWalkWebResourceResponse shouldInterceptLoadRequest(XWalkView view, XWalkWebResourceRequest request) {
String url = request.getUrl().toString();
if(!TextUtils.isEmpty(url)&&url.endsWith(".png")) {
Log.i(TAG, String.format("拦截网址:%s", url));
return createXWalkWebResourceResponse("text/html","UTF-8",null);//不向过滤的地址发出请求
}
return super.shouldInterceptLoadRequest(view, request);
}
}
7. CrossWalk 支持 websocket
注意,if (typeof WebSocket != 'undefined')
并不能判断 websocket 是否被支持。因为 Android 中,即使浏览器不支持 WebSocket ,但是它还是存在这个属性。
正确的验证方法如下
function isSupportWebsocket() {
if (!!window.WebSocket && window.WebSocket.prototype.send) {
return true;
} else {
return false;
}
}
部分方法不支持
Object #<HTMLElement> has no method 'remove'
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)