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,你的編輯器就能夠實現了,這裏再也不囉嗦了。