版权声明:

欢迎转载,但请保留文章原始出处

作者:GavinCT

出处:http://www.cnblogs.com/ct2011/p/4100132.html

为什么放弃WebView

Android WebView做普通浏览还好,做富文本编辑器(执行js:document.body.contentEditable=true;),常常会遇到各种奇葩的bug,而且很难修复。

尽管Google在版本迭代中不断修复bug,但依旧没法用它来做富文本编辑。

Kitkat的改变

Google为了加强WebView的功能,在Kitkat引入了Chromium内核。但还是存在着编辑的bug。

我所知道的一个bug是:

Kitkat版WebView在删除Html标签时处理不好,例如<img>标签,就无法删除。点击删除时直接越过此元素,将光标定位在图片前方,对图片不做处理。

当然,这个bug在Android 5.0 修复了。

Lollipop新策略

Although WebView has been based on Chromium since Android 4.4, the Chromium layer is now updatable from Google Play.

As new versions of Chromium become available, users can update from Google Play to ensure they get the latest enhancements and bug fixes for WebView, providing the latest web APIs and bug fixes for apps using WebView on Android 5.0 and higher.

可见在Lollipop里,可以通过GooglePlay来更新Chromium内核。

但是问题来了:

  • 国内容易更新么?
  • 如果不是自动更新,用户会手动更新么?当然GooglePlay是自动更新,那国内手机没有自己市场的厂商呢?
  • Lollipop以前的版本怎么办? Lollipop目前只有很少用户可以更新。

探索新的富文本编辑方案

显然,即便是有了Lollipop的解决方案,但问题依然很多。我们还是需要一个替代方案,来保证我们在所有的Android手机上表现一致。

这个方案就是在应用中集成Chromium。

由于自己编译Chromium的难度较大,于是转而寻找编译好的Chromium库来使用。

需要声明的是:Chromium内核只能在Android 4.0以上才能使用,之后提到的所有Chromium库都只能在4.0以上平台使用。

过渡方案

最初在寻找替代方案的时候,应该是2013年10月左右,找到了两个Chromium库:

  1. chromeview

    这个库封装的较好,但是有一个致命的bug是不能滚动。

    README中声明:

    Attempting to scroll the view (by swiping a finger across the screen) does not update the displayed image.

    However, internally, the view is scrolled.

    This can be seen by displaying a stack of buttons and trying to click on the topmost one.

    This issue makes ChromeView mostly unusable in production.

    注:这个库的README最新声明里面推荐了Crosswalk,作者还是很用心的。

  2. android-chromium

    这个库整体稳定,不存在上面的bug。用它作为编辑器差不多一年,没有出现什么问题。

    但在今年6、7月的时候,突然间发现在三星新出的几款平板上(搭载了Kitkat)表现为花屏,屏幕上出现了各种颜色的横条,无法进行编辑。其他搭载了Kitkat的手机当时没有发现过什么问题。

    这里说一下这个库,自从作者看到Kitkat使用Chromium后,作者就声明不再更新了,其实差不多一年前就已经不更新了。

    这个库使用起来比较麻烦,需要自己再进行封装,甚至连onPageFinished都需要自己来做。

可以看到,上面的替代方案,到今年6、7月,实际上已经无法使用。

而且非组织维护的代码,通常都有些不可靠的意味。

于是不得不继续寻找替代方案。终于在Google I/O上看到了希望 —— Crosswalk

Crosswalk入门

上面的链接可以看到Crosswalk的介绍,Crosswalk种种吹牛逼的描述我就不写了。

写一下我的使用感受:

  1. 不用费力搞什么自己封装了,直接像用WebView一样使用。

    在使用android-chromium这个库时,不仅要自己封装API来方便使用,还要操心Chromium的初始化,甚至还需要在清单文件里写一堆关于Chromium的东西,用来帮助Chromium建立单独的进程(Crosswalk只会创建Chromium的线程,不需要独立进程)。
  2. Crosswalk由组织维护,比个人维护强多了。
  3. 跟随最新的Chromium不断更新,js等不用担心有函数没法使用。而且不断更新过程中,肯定也会修复以前存在的bug,稳定性也是不用担心的。

最新稳定版Crosswalk基于Chromium38编译。

注:此库也可以配合Cordova(PhoneGap)使用。

OK,感受说完,上教程。

集成到应用中

  1. 下载zip包,解压后导入。

  2. 关联此Library。

  3. 在清单文件中写入下列权限

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    注:使用过程中,观察Logcat可以看到报需要蓝牙权限,可以不用管它,不添加蓝牙权限可以正常使用。

    此外,使用XWalkView必须开启硬件加速。

    XWalkView needs hardware acceleration to render web pages. As a result, the AndroidManifest.xml of the caller's app must be appended with the attribute "android:hardwareAccelerated" and its value must be set as "true".

    android:hardwareAccelerated : The default value is "true" if you've set either minSdkVersion or targetSdkVersion to "14" or higher; otherwise, it's "false".

    在清单文件Application中声明即可。

    <application android:name="android.app.Application" android:label="XWalkUsers"
    android:hardwareAccelerated="true">

基本使用

Crosswalk中用来替代WebView的控件叫XWalkView。

layout文件写法

和其他自定义控件一样。

<org.xwalk.core.XWalkView android:id="@+id/activity_main"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
</org.xwalk.core.XWalkView>

代码中使用

重中之重:防止内存泄漏

和其他Android的控件不同,这个类需要监听系统事件。例如:生命周期、intent、Activity result。

控件内置的Web引擎需要获取并处理这些信息。并且当XWalkView 不再需要使用的时候,在onDestroy方法中XWalkView必须显式的调用destroy方法,否则容易造成Web引擎的内存泄漏。

原文如下:

Unlike other Android views, this class has to listen to system events like application life cycle, intents, and activity result. The web engine inside this view need to get and handle them. And the onDestroy() method of XWalkView MUST be called explicitly when an XWalkView won't be used anymore, otherwise it will cause the memory leak from the native side of the web engine. It's similar to the destroy() method of Android WebView.

这段文字来自XWalkView官方API文档。奇怪的是官方的范例中并没有在意这些事情,直接像WebView一样使用,更没有使用destroy方法。

考虑到之前使用android-chromium库也是需要显式调用。这里还是加上,避免内存泄漏。

   import android.app.Activity;
import android.os.Bundle; import org.xwalk.core.XWalkView; public class MyActivity extends Activity {
private XWalkView mXWalkView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mXWalkView = (XWalkView) findViewById(R.id.activity_main);
mXWalkView.load("http://crosswalk-project.org/", null);
} @Override
protected void onPause() {
super.onPause();
if (mXWalkView != null) {
mXWalkView.pauseTimers();
mXWalkView.onHide();
}
} @Override
protected void onResume() {
super.onResume();
if (mXWalkView != null) {
mXWalkView.resumeTimers();
mXWalkView.onShow();
}
} @Override
protected void onDestroy() {
super.onDestroy();
if (mXWalkView != null) {
mXWalkView.onDestroy();
}
} @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mXWalkView != null) {
mXWalkView.onActivityResult(requestCode, resultCode, data);
}
} @Override
protected void onNewIntent(Intent intent) {
if (mXWalkView != null) {
mXWalkView.onNewIntent(intent);
}
}
}

loadUrl去哪了?

上面的代码中其实已经剧透了,使用load方法即可。

// url
mXWalkView.load("http://crosswalk-project.org/", null); // this loads a file from the assets/ directory
mXWalkView.load("file:///android_asset/index.html", null);

public void load (String url, String content)

Load a web page/app from a given base URL or a content. If url is null or empty and content is null or empty, then this function will do nothing. If content is not null, load the web page/app from the content. If content is not null and the url is not set, return "about:blank" ifi calling getUrl(). If content is null, try to load the content from the url. It supports URL schemes like 'http:', 'https:' and 'file:'. It can also load files from Android assets, e.g. 'file:///android_asset/'.

Parameters

url the url for web page/app.

content the content for the web page/app. Could be empty.

WebViewClient?

对应WebView的WebViewClient,XWalkView中有XWalkResourceClient。

mXWalkView.setResourceClient(new XWalkResourceClient(mXWalkView){
@Override
public void onLoadFinished(XWalkView view, String url) {
super.onLoadFinished(view, url);
}
@Override
public void onLoadStarted(XWalkView view, String url) {
super.onLoadStarted(view, url);
}
});

调用JavaScript

不像WebView一样获取setting设置setJavaScriptEnabled为true才能执行。

Crosswalk可以直接执行js。

mXWalkView.load("javascript:document.body.contentEditable=true;", null);

当然,按照Kitkat引入的方式,使用evaluateJavascript方法也是可以的。(大神们推荐)

JavaScript回调Java

  1. 定义js回调接口

    public class JsInterface {
    public JsInterface() {
    }
    @JavascriptInterface
    public String sayHello() {
    return "Hello World!";
    }
    }

Caution: If you've set your targetSdkVersion to 17 or higher, you must add the @JavascriptInterface annotation to any method that you want available to your JavaScript (the method must also be public). If you do not provide the annotation, the method is not accessible by your web page when running on Android 4.2 or higher.

From developer.android.com

备注:这里的`@JavaScriptInterface`所在的包是`import org.xwalk.core.JavascriptInterface;`
  1. XWalkView设置JavaScript可用且绑定对象

    //绑定
    mXWalkView.addJavascriptInterface(new JsInterface(), "NativeInterface");
  2. 调用html执行JavaScript或直接执行Javascript调用Java

    mXWalkView.load("file:///android_asset/index.html", null);

    index.html源码:

    <a href="#" onclick="clicked()">Say Hello</a>
    <script>
    function clicked() {
    alert(NativeInterface.sayHello());
    }
    </script>

高级使用

调试

Kitkat开始,Android提供了和Chrome联调功能。可以很方便的在Chrome中调试WebView中的代码。

Crosswalk使用Chromium内核当然也具备这个功能。

开启调试的语句如下:

  // turn on debugging
XWalkPreferences.setValue(XWalkPreferences.REMOTE_DEBUGGING, true);

对于Crosswalk来说,这个设置是全局的。

使用动画或者设置隐藏可见注意

默认XWalkView不能使用动画,甚至setVisibility也不行。

XWalkView represents an Android view for web apps/pages. Thus most of attributes for Android view are valid for this class. Since it internally uses android.view.SurfaceView for rendering web pages by default, it can't be resized, rotated, transformed and animated due to the limitations of SurfaceView. Alternatively, if the preference key ANIMATABLE_XWALK_VIEW is set to True, XWalkView can be transformed and animated because TextureView is intentionally used to render web pages for animation support. Besides, XWalkView won't be rendered if it's invisible.

开启动画模式:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // ANIMATABLE_XWALK_VIEW preference key MUST be set before XWalkView creation.
XWalkPreferences.setValue(XWalkPreferences.ANIMATABLE_XWALK_VIEW, true); setContentView(R.layout.animatable_xwview_layout);
}
@Override
public void onDestroy() {
super.onDestroy(); // Reset the preference for animatable XWalkView.
XWalkPreferences.setValue(XWalkPreferences.ANIMATABLE_XWALK_VIEW, false);
}

由于设置也像调试一样是全局的,在onDestroy时记得关闭。

暂停JS timer

html代码

<!DOCTYPE html>
<html>
<body> <p>A script on this page starts this clock:</p>
<p id="demo"></p> <script>
var myVar = setInterval(function(){ myTimer(); }, 1000); function myTimer()
{
var d = new Date();
var t = d.toLocaleTimeString();
document.getElementById("demo").innerHTML = t;
}
</script> </body>
</html>

XWalkView对应方法:

mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mXWalkView != null) {
if (!isPaused) {
// Pause JS timer
mXWalkView.pauseTimers();
isPaused = true;
mButton.setImageResource(android.R.drawable.ic_media_play);
} else {
// Resume JS timer
mXWalkView.resumeTimers();
isPaused = false;
mButton.setImageResource(android.R.drawable.ic_media_pause);
}
}
}
});

这也在防止内存泄漏,监听系统事件示例代码中提到过:

@Override
protected void onPause() {
super.onPause();
if (mXWalkView != null) {
mXWalkView.pauseTimers();
mXWalkView.onHide();
}
} @Override
protected void onResume() {
super.onResume();
if (mXWalkView != null) {
mXWalkView.resumeTimers();
mXWalkView.onShow();
}
}

历史记录

mPrevButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Go backward
if (mXWalkView != null &&
mXWalkView.getNavigationHistory().canGoBack()) {
mXWalkView.getNavigationHistory().navigate(
XWalkNavigationHistory.Direction.BACKWARD, 1);
}
XWalkNavigationItem navigationItem = mXWalkView.getNavigationHistory().getCurrentItem();
showNavigationItemInfo(navigationItem);
}
}); mNextButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Go forward
if (mXWalkView != null &&
mXWalkView.getNavigationHistory().canGoForward()) {
mXWalkView.getNavigationHistory().navigate(
XWalkNavigationHistory.Direction.FORWARD, 1);
}
XWalkNavigationItem navigationItem = mXWalkView.getNavigationHistory().getCurrentItem();
showNavigationItemInfo(navigationItem);
}
}); private void showNavigationItemInfo(XWalkNavigationItem navigationItem){
url = navigationItem.getUrl();// Get the url of current navigation item.
originalUrl = navigationItem.getOriginalUrl();// Get the original url of current navigation item
title = navigationItem.getTitle(); text1.setText(title);
text2.setText(url);
text3.setText(originalUrl);
}

自动视频暂停

// The web page below will display a video.
// When home button is pressed, the activity will be in background, and the video will be paused.
mXWalkView.load("http://www.w3.org/2010/05/video/mediaevents.html", null);

loadAppFromManifest

mXWalkView.loadAppFromManifest("file:///android_asset/manifest.json", null);

manifest.json

{
"name": "ManifestTest",
"start_url": "index.html",
"description": "Manifest test",
"version": "1.0.0"
}

已知问题

Crosswalk 9.38.208.10 和 10.39.235.15 在MX3 flyme3.5.2 编辑html的时候点击会崩溃。

Crosswalk 8.37.189.12在各已知手机上编辑表现正常,已上传Github --> 代码库地址

最后

Crosswalk介绍完了,有了Chrome,配上JavaScript,你的编辑器就可以实现了,这里不再啰嗦了。

放弃WebView,使用Crosswalk做富文本编辑器的更多相关文章

  1. 我为什么要做富文本编辑器【wangEditor5个月总结】

    请访问wangEditor官网:www.wangEditor.com ----------------------------------------------------------------- ...

  2. django的admin或者应用中使用KindEditor富文本编辑器

    由于django后台管理没有富文本编辑器,看着好丑,展示出来的页面不美观,无法做到所见即所得的编辑方式,所以我们需要引入第三方富文本编辑器. 之前找了好多文档已经博客才把这个功能做出来,有些博客虽然写 ...

  3. 基于ABP做一个简单的系统——实战篇:4.基于富文本编辑器,Razor模板引擎生成内容并导出Word 填坑记录

    起因 需求是这样的,有一种协议需要生成,协议的模板是可配置的,在生成过程中,模板中的内容可以根据约定的标记进行替换(就像mvc的razor模板一样).生成后的内容还需要导出成word或pdf. 常见的 ...

  4. wangEditor-基于javascript和css开发的 Web富文本编辑器, 轻量、简洁、易用、开源免费(2)

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. 前端富文本编辑器 vue-html5-editor

    1..项目创建与初始化 在安装好脚手架的依赖后,要执行 npm install vue-html5-editor -S 来安装这个富文本插件,由于这个富文本插件的图标是依赖font-awesome.c ...

  6. koa2.0富文本编辑器的选择历程

    本人学习vue和koa2.0,做了一个简单的个人博客,博客自然会需要富文本编辑器的选择,由于nodejs和koa2.0,于是便开始了不断尝试的历程. 一.ueditor 刚开始在百度搜索,自然第一个发 ...

  7. Kz.layedit-layui.layedit富文本编辑器拓展

    项目介绍 首先欢迎使用 Kz.layedit!本项目基于layui.layedit富文本编辑器,在其之上拓展而来. 新增功能 html源码模式.插入hr水平线.段落格式.字体颜色.字体背景色.批量上传 ...

  8. 从零开始, 开发一个 Web Office 套件 (2): 富文本编辑器

    书接前文: 从零开始, 开发一个 Web Office 套件 (1): 富文本编辑器 这是一个系列博客, 最终目的是要做一个基于HTML Canvas 的, 类似于微软 Office 的 Web Of ...

  9. 富文本编辑器Simditor的简易使用

    最近打算自己做一个博客系统,并不打算使用帝国cms或者wordpress之类的做后台管理!自己处于学习阶段也就想把从前台到后台一起谢了.好了,废话不多说了,先来看看富文本编辑器SimDitor,这里是 ...

随机推荐

  1. CodeForces - 327D Block Tower

    D. Block Tower time limit per test 2 seconds memory limit per test 256 megabytes input standard inpu ...

  2. centos7 cannot find a valid baseurl for repo

    出现这个问题是因为yum在安装包的过程中,虽然已经联网,但是没法解析远程包管理库对应的域名,所以我们只需要在网络配置中添加上DNS对应的ip地址即可. 操作 1.打开网络配置文件 vi /etc/sy ...

  3. WebAPI跨域处理

    原文来自:http://www.cnblogs.com/heifengwll/p/6243374.html WebApi2跨域问题   一.跨域问题产生的原因:同源策略(Same origin pol ...

  4. 后台拿webshell方法(2)

    在这里总结一下后台拿webshell的方法: 备份突破(在博客上有随笔介绍) 一般在后台上传文件类型受到限制,可以利用数据库备份将其上传的格式改为后门执行格式即可: asp更改为jpg     jpg ...

  5. 20155335俞昆 《java程序设计》第八周总结

    2016-2017-2 <Java程序设计>第X周学习总结 ##认识NIO 在java中,输入与输出,基本上是以字节为单位进行的低层次处理,实际上多半是对字节数组中整个区块进行处理,对于d ...

  6. JavaScript之原型|typeof|instanceof

    //var 变量X --> 对象 --> 构造器 --> 原型对象 function Person(){}; var stu = new Person(); //var stu = ...

  7. NFS配置不当导致的那些事儿

    NFS(Network File System):是FreeBSD支持的文件系统中的一种,它允许网络中的计算机之间通过TCP/IP网络共享资源: NFS配置:(声明:以下NFS实验是在RedHat7上 ...

  8. 2017-2018-2 20155303『网络对抗技术』Exp4:恶意代码分析

    2017-2018-2 20155303『网络对抗技术』Exp4:恶意代码分析 --------CONTENTS-------- 一.原理与实践说明 1.实践目标 2.实践内容概述 3.基础问题回答 ...

  9. SpringBoot扩展SpringMVC自动配置

    SpringBoot中自动配置了 ViewResolver(视图解析器) ContentNegotiatingViewResolver(组合所有的视图解析器) 自动配置了静态资源文件夹.静态首页.fa ...

  10. windows下caffe GPU版本配置

    由于项目需要,所以在自己本子上配置了一下windows下GPU版本的caffe; 硬件:  win10    ;      gtx1070独显(计算能力6.1): 安装软件:     cudnn-8. ...