分享

Android 集成Chrome 浏览器内核 Crosswalk

 看见就非常 2020-05-27

Crosswalk 内核的兴起与消亡

Android 4.4 版本之前,使用的是基于 androidWebKit 的 WebView

但实际上,由于 Android 的碎片化问题(大量存在不同的 Android 系统版本,并且各个厂商对内置应用进行定制化,有可能用的不是最新的浏览器内核)。这就导致 WebView 在真实环境中对 API 的支持根本无迹可寻,越发混乱。

Android 碎片化问题集中表现在下面几个方面:

  • 设备繁多,硬件配置参差不弃,设备性能各异,差距很大
  • 品牌众多,厂商标准不一致,定制化系统体验不同
  • 版本各异,国内外系统环境差异巨大
  • 分辨率不统一,各种类型尺寸众多

随着混合开发的兴起,前端对 API 的支持程度和网页的表现效果都有了更严格的要求,原生WebView 由于碎片化严重,API支持程度未知,容易引发很多意料之外的BUG。

这时候,就诞生了一些第三方浏览器内核

从 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./crosswalk/releases/crosswalk/android/maven2'}
    }
}

两个位置的 repositories 的区别

  1. buildscript 里是 gradle 脚本执行所需依赖,分别是对应的 maven 库和插件
  2. allprojects 里是项目本身需要的依赖

也可以仅在对应 module 的 build.gradle 中添加 respositories,然后再添加对应依赖

android {
}
repositories {
	maven { url 'https://download./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./apk/res/android"
    xmlns:app="http://schemas./apk/res-auto"
    xmlns:tools="http://schemas./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 内核加载的生命周期

  1. 预加载 xwalk 内核
  2. 将共享的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));
}
  1. 在Activity中初始化XWalk内核

  2. 解压、激活、将内核附着在Activity上

  3. 下载模式禁用
    下载模式和共享模式差不多,只是共享模式是把APK下载下来当成一个应用安装到手机上,而下载模式干脆把APK下载到自己的私有目录下,把所有的so文件、资源解压出来保存到自己的内部私有目录下只供自己使用。

  4. 通过反射,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;
 }
  1. 打印出,当前库是基于ARM架构编译的。当前设备是arm64-v8a,使用嵌入模式运行
  2. 搭建环境、初始化内核、初始化视图,激活任务执行完毕,开始在 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-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

部分方法不支持

Object #<HTMLElement> has no method 'remove'

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多