Android WebView作普通浏览还好,作富文本编辑器(执行js:document.body.contentEditable=true;
),经常会遇到各类奇葩的bug,并且很难修复。
尽管Google在版本迭代中不断修复bug,但依旧无法用它来作富文本编辑。git
Google为了增强WebView的功能,在Kitkat引入了Chromium内核。但仍是存在着编辑的bug。
我所知道的一个bug是:
Kitkat版WebView在删除Html标签时处理很差,例如<img>
标签,就没法删除。点击删除时直接越过此元素,将光标定位在图片前方,对图片不作处理。
固然,这个bug在Android 5.0 修复了。github
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.web
可见在Lollipop里,能够经过GooglePlay来更新Chromium内核。chrome
可是问题来了:json
显然,即使是有了Lollipop的解决方案,但问题依然不少。咱们仍是须要一个替代方案,来保证咱们在全部的Android手机上表现一致。
这个方案就是在应用中集成Chromium。
因为本身编译Chromium的难度较大,因而转而寻找编译好的Chromium库来使用。
须要声明的是:Chromium内核只能在Android 4.0以上才能使用,以后提到的全部Chromium库都只能在4.0以上平台使用。app
最初在寻找替代方案的时候,应该是2013年10月左右,找到了两个Chromium库:
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,做者仍是很用心的。
android-chromium
这个库总体稳定,不存在上面的bug。用它做为编辑器差很少一年,没有出现什么问题。
但在今年六、7月的时候,忽然间发如今三星新出的几款平板上(搭载了Kitkat)表现为花屏,屏幕上出现了各类颜色的横条,没法进行编辑。其余搭载了Kitkat的手机当时没有发现过什么问题。
这里说一下这个库,自从做者看到Kitkat使用Chromium后,做者就声明再也不更新了,其实差很少一年前就已经不更新了。
这个库使用起来比较麻烦,须要本身再进行封装,甚至连onPageFinished都须要本身来作。
能够看到,上面的替代方案,到今年六、7月,实际上已经没法使用。
并且非组织维护的代码,一般都有些不可靠的意味。
因而不得不继续寻找替代方案。终于在Google I/O上看到了但愿 —— Crosswalk
上面的连接能够看到Crosswalk的介绍,Crosswalk种种吹牛逼的描述我就不写了。
写一下个人使用感觉:
最新稳定版Crosswalk基于Chromium38编译。
注:此库也能够配合Cordova(PhoneGap)使用。
OK,感觉说完,上教程。
在清单文件中写入下列权限
<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。
和其余自定义控件同样。
<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); } } }
上面的代码中其实已经剧透了,使用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.
对应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); } });
不像WebView同样获取setting设置setJavaScriptEnabled为true才能执行。
Crosswalk能够直接执行js。
mXWalkView.load("javascript:document.body.contentEditable=true;", null);
固然,按照Kitkat引入的方式,使用evaluateJavascript方法也是能够的。(大神们推荐)
定义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;
XWalkView设置JavaScript可用且绑定对象
//绑定 mXWalkView.addJavascriptInterface(new JsInterface(), "NativeInterface");
调用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时记得关闭。
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);
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,你的编辑器就能够实现了,这里再也不啰嗦了。