记一次使用 android 自带 WebView 做富文本编辑器之API、机型的兼容及各种奇葩bug的解决
转载请声明出处(http://www.cnblogs.com/linguanh/)
目录
1,测试设备介绍
2,开源项目richeditor及CrossWalk的选择
3,遇到的bug及其解决方法
4,附加功能彩蛋。
1,测试设备介绍----------------------
测试的机型有 魅蓝note2-api 22,小米2A-api 16,三星galaxy I9152-API 17.
上述机型均通过测试,针对它们各自产生的bug我会在第二大点处介绍。
2,开源项目richeditor及CrossWalk的比较---------------------------
关于richeditor,它是一个算是很不错的webView富文本编辑器,git链接:https://github.com/wasabeef/richeditor-android
优点:
1,是轻量级,功能较丰富
2,丰富的功能:
前进、返回、粗体、斜体、字号修改、背景颜色、字体颜色、图片及超链接插入,其中图片不含有其它功能,例如没有带有点击看大图,删除等。
3,接口丰富,嵌入和调用极其方便。
缺点:
兼容性差、bug多、二次开发极难!体现在:
1,在上面所列机型里面都有一个共同的bug,插入图片后,如果通过 javaScript 设置点击事件,在第一次进入该页面的时候,所有webView图片的点击都能响应,此时如果用户点击返回,finish当前页面,再次进入该页面后,所有图片点击事件失效,这个bug我无法解决,诡异地毫无人性,尝试过注销jsResult,但是无效,手动销毁webView及撤销等所有缓存设置都没效。是么时候再有效?退出当前APP,重新进入就有效,然后往事重现。
2,在小米2A-api 16上测试,无法删除通过软键盘删除键删除图片标签,这个问题很粗!还一个是,如果你需要在接口 OnTextChange 里面loadUrl的话,那么就会,每输入一次键值,每输入一个字符,软键盘隐藏一次,点击再弹起,输入一个字符又隐藏,简直毁三观。
3,因为它的所有实现,几乎都是javaScript 注入,你要改,必须要会点javaScript,可能会一点还不够。
接下来是CrossWalk,它和上面的不同,它不是一个仅仅只是重写一个 WebView 那么简单,它是独立出来的一个浏览器,下载等所有在他们官网:https://crosswalk-project.org/ ,看到这,你或许心里默想,这明明讲的是文本编辑器,突然变成浏览器了?留意我上面说到 richeditor 所产生到的一些bug,richeditor 是基于android自带浏览器上面搞的,早期版本内核是webkit,后来是 Chrome,bug的产生有可能就是内核搞得鬼,或者是手机生产商自己改的sdk结的果,而 CrossWalk 统一了它们而不失兼容型,所以,值得一试。
使用方法很简单,我们只需要把 richeditor 里面继承的 WebView 改为 CrossWalk 的XWalkView 即可,修改下对应的函数。
优点:
1,流畅度明显提高,javaScript 兼容提高;
2,自动修复了 小米2A-api 16 无法删除图片标签的问题;
3,自动修复了 小米2A-api 16 ,如果在onTextChange处loudUrl,每输入一次键值,每输入一个字符,软键盘隐藏一次的问题;
4,使用简单,只需要引入下载好的 library
缺点:
1,太臃肿,官网的稳定版本,整个library 20多M,或者更大,编译后APK 40多M (致命点);
2, 和richeditor 第一点bug相同,进入,退出,再进入,所有点击事件"扑街"。
3,这个更是奇葩,导致我直接放弃使用它。无法嵌套在 ScrollView 里面,只能设置固定高度,而且超过后,无法滚动。
4,因为也是使用 js,这个就不说了,要改你得会。
3,遇到的bug及其解决方法---------------------------
所有遇到的bug,请看上面第二点。它们解决,待我喝口水,详细道来......
richeditor 的bug解决
1,richeditor 在所上面三种机子上面体现出的,在第一次进入该编辑页面的时候,所有webView图片的点击都能响应,此 时如果用户点击返回,finish当前页面,再次进入该页面后,所有点击事件失效。
解决:
放弃javaScript 的点击注入,重写webView的onTouch实现完美点击,代码示例如下:
RE.insertImage = function(url, alt,ran, w, h) {
//var html = '<img src="' + url + '" alt="' + alt + '" id="' + ran +'"width="'+w+'"'+'height="'+h+'"'+ 'onclick="RE.showDialog('+ran+')" />'; //这是js点击注入
var html = '<img src="' + url + '" alt="' + alt + '" id="' + ran +'"width="'+w+'"'+'height="'+h+'" />';
RE.insertHTML(html);
}
上面使用如果使用 js 注入的点击,第一次进入页面点击便响应 RE.showDialog(),退出再进入就再也没效了。
mEditor.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
WebView temp = (WebView) v;
WebView.HitTestResult mResult = temp.getHitTestResult();
if(mResult==null){
Log.d("zzzzz","mResult==null");
if(mEditor.getHitTestResult()==null){
Log.d("zzzzz","mEditor mResult==null");
}
}else {
final int type = mResult.getType();
switch (type) {
case WebView.HitTestResult.ANCHOR_TYPE:
case WebView.HitTestResult.SRC_ANCHOR_TYPE:
//点击的是链接
break;
case WebView.HitTestResult.IMAGE_TYPE:
case WebView.HitTestResult.IMAGE_ANCHOR_TYPE:
case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
Log.d("zzzzz", "hit image");
String strUrl = mResult.getExtra(); // 获取该 img标签里面的 src
// Class s = mResult.getClass();
if(strUrl==null){
Log.d("zzzzz", "image src is null");
}else{
Log.d("zzzzz", "fucking success "+strUrl);
showDeleteDialog(strUrl); //
}
//点击的是图片
temp.clearFocus();
mEditor.clearFocusEditor();
return true;
default:
Log.d("zzzzz", "hit white");
//点击的是空白处
break;
}
}
Log.d("zzzzz","show me the fucking info");
return false;
}
});
上面的折叠代码是我的onTouch 重写例子,注意里面的注释。这样就能捕获webView 里面的图片点击事件,并获取对应的 图片的url。
2,在小米2a-api 16上面,在onTextChange借口处loudUrl(),每输入一次键值,每输入一个字符,软键盘隐藏一次的问题。
解决:
使用java大招------反射,因为这个是在是难,源码在我解决这些东西的过程中是肯定有看的了,百度也不能停,顺便分享个 android 源码的链接,在线查看 http://grepcode.com/project/repository.grepcode.com/java/ext/com.google.android/android/
引入我下面的这个类,然后使用。
public class PrivateApiBridgeMode {
private static final int EXECUTE_JS = 194;
Method handler;
Object webViewCore;
boolean initFailed;
@SuppressWarnings("rawtypes")
private void initReflection(WebView webView) {
Object webViewObject = webView;
Class webViewClass = WebView.class;
try {
Field f = webViewClass.getDeclaredField("mProvider"); // mProvider 是WebView的一个借口成员,我们找到它
f.setAccessible(true); // 设置可访问
webViewObject = f.get(webView); // 拿到对象
webViewClass = webViewObject.getClass(); // webView 类
} catch (Throwable e) {
Log.d("zzzzz","initReflection Throwable "+e.toString() );
}
try {
Field f = webViewClass.getDeclaredField("mWebViewCore");
f.setAccessible(true);
webViewCore = f.get(webViewObject);
if (webViewCore != null) {
handler = webViewCore.getClass().getDeclaredMethod("sendMessage", Message.class);
handler.setAccessible(true);
}
} catch (Throwable e) {
initFailed = true;
}
}
public void onNativeToJsMessageAvailable(WebView webView,String js) {
if (handler == null && !initFailed) {
initReflection(webView);
}
if (handler != null) {
Message execJsMessage = Message.obtain(null, EXECUTE_JS, js); // 阻断
try {
handler.invoke(webViewCore, execJsMessage);
} catch (Throwable e) {
Log.d("zzzzz","onNativeToJsMessageAvailable Throwable "+e.toString() );
}
}
}
}
使用对比,webView.loadUrl(String)
换为 new PrivateApiBridgeMode ().onNativeToJsMessageAvailable(this,String);
3,小米2A-api 16上测试,无法删除通过软键盘删除键删除图片标签
请看下面的第四点,彩蛋....
CrossWalk的bug解决
1,太臃肿的问题,这个只能这样搞:http://blog.csdn.net/recall2012/article/details/47319653
2,进入,退出,再进入,所有图片点击事件失效的解决方法同 richeditor。
3,无法嵌套在 ScrollView 里面,只能设置固定高度,而且超过后,无法滚动。
本人不才,遇到这个bug的时候,我已心力交瘁,直接放弃它了,建议,能不套 ScrollView的,就别套吧.....
4,附加功能彩蛋---------------------------
彩蛋一:上面我讲到了,点击图片显示大图,因为能拿到 src,你还可以保存,发送,上传,等等。唯有一个不行,此乃便是删除图片,如果它不是有进入,返回,再进入会导致图片 img 的 onClick 功能失效的情况,那么我就可能通过为img 标签设置 id,来对应删除。
例如:
<img src="'" id="id" onclick=" RE.delete(插入时就加入个整数作为id) " />
我上面的例子是可能通过在 js 注入的时候为标签添加参数的,那么我完全可以添加个 id(大一点的随机数),删除的时候就执行下面的 js
RE.deleteImage = function(id) {
// obj.parentNode.removeChild(obj);
document.getElementById(id).parentNode.removeChild(document.getElementById(id));
}
上面的代码结合,用户点击就相应 RE.delete() 然后 回调 java接口,java接口获取到 id,再传入到 RE.deleteImage() 成功删除图片。你不用怀疑,因为我成功过。
由于,进入,返回,再进入会导致图片 img 的 onClick 功能失效,所以我们可以这样做:
利用临时文件夹的方法,使用图片路径对应id来对应查找删除,这里要注意了,图片路径对应id,我们可以采用 HashMap<String,Integer>,String是图片路径,Integer是我上面说到的id, 如果你懂了,那么到这里,你是会发现一个问题的,那么就是,用户加入同一张图片多次就完了,无法一一对应,String作为key直接被覆盖,那么为了防止用户可能输入同一张图片多次,就是路径相同的情况,所以我们要建临时文件,退出再删除,占用了点 CPU 时间,那么怎么建文件呢。例子如下:
int ran = (int) (1 + Math.random() * (1000000 - 1 + 1)); // 随机数id
if(text.contains("file:///storage/sdcard0/Tencent/QQ_Images/8012cccb6d78121.jpg")){ // 同一张图片出现了
String path = "/storage/sdcard0/Tencent/QQ_Images/8012cccb6d78121.jpg"; // 重复的图片的路径
if(copyfile( // 复制到临时文件夹
new File(path),
new File("/storage/sdcard0/bcImageCache/"), // 临时文件夹
"/temp"+ran+".jpg" // 该重复图片的临时名字
)){
Log.d("zzzzz","copy success");
}else{
Log.d("zzzzz","copy faild");
}
hs.put("file:///storage/sdcard0/bcImageCache/"+"temp"+ran+".jpg",ran); // hashMap 加数据
mEditor.insertImage("file:///storage/sdcard0/bcImageCache/"+"temp"+ran+".jpg", "wrong",ran, 100, 100);
// 插入的是临时复制的图片
}else{ // 没重复,正常插入
hs.put("file:///storage/sdcard0/Tencent/QQ_Images/8012cccb6d78121.jpg",ran);
mEditor.insertImage("file:///storage/sdcard0/Tencent/QQ_Images/8012cccb6d78121.jpg", "wrong",ran, 100, 100);
}
彩蛋二:监听 WebView 在被编辑的时候的所有按键事件,然后做你的事情(我曾试过通过它监听删除键来删除图片,faild)。
// 重写该函数
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
return new myInputConnection(super.onCreateInputConnection(outAttrs),true);
} private class myInputConnection extends InputConnectionWrapper { public myInputConnection(InputConnection target, boolean mutable) {
super(target, mutable);
} @Override
public boolean sendKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
if (deleteKeyListener != null) {
deleteKeyListener.onDeleteClick(); // 执行删除键的事件接口
return true;
}
}else{
Log.d("zzzzz","fuck key");
}
return super.sendKeyEvent(event);
} @Override
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
if (beforeLength == 1 && afterLength == 0) {
return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_DEL))
&& sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
KeyEvent.KEYCODE_DEL));
}
return super.deleteSurroundingText(beforeLength, afterLength);
}
} private OnDeleteKeytListener deleteKeyListener; public void OnDeleteKeytListener(OnDeleteKeytListener delKeyEventListener) {
this.deleteKeyListener = deleteKeyListener;
} public interface OnDeleteKeytListener {
void onDeleteClick();
}
好了,就这么多,喜欢就点个顶吧,一次总结这么多,不容易,让更多人看到,开发不易啊!
记一次使用 android 自带 WebView 做富文本编辑器之API、机型的兼容及各种奇葩bug的解决的更多相关文章
- Android 各个版本WebView
转载请注明出处 http://blog.csdn.net/typename/ powered by miechal zhao : miechalzhao@gmail.com 前言: 根据Googl ...
- Android 各个版本号WebView
转载请注明出处 http://blog.csdn.net/typename/ powered by miechal zhao : miechalzhao@gmail.com 前言: 依据Googl ...
- 放弃WebView,使用Crosswalk做富文本编辑器
版权声明: 欢迎转载,但请保留文章原始出处 作者:GavinCT 出处:http://www.cnblogs.com/ct2011/p/4100132.html 为什么放弃WebView Androi ...
- Android中的webview的进度条
<application android:icon="@drawable/hunqin" android:label="@string/app_name" ...
- android学习笔记WebView的基本使用
WebView可以看做是一个浏览器,它使用了WebKit渲染引擎加载显示网页. WebView的使用需要掌握下面几点, 1,首先需要开启应用网络访问权限 在AndroidMinafest.xml中添加 ...
- Android:让WebView支持<input type=”file”…>元素
最近在做一个活动页面:用户上传一张图片进行缩放.旋转后点击下一步填写内容后生成图片! 做好后经过各种测试是没有问题的,基本没有什么明显BUG,流程都能走通,但是嵌入到APP后,问题就来了! 在IOS上 ...
- Android中脱离WebView使用WebSocket实现群聊和推送功能
WebSocket是Web2.0时代的新产物,用于弥补HTTP协议的某些不足,不过他们之间真实的关系是兄弟关系,都是对socket的进一步封装,其目前最直观的表现就是服务器推送和聊天功能.更多知识参考 ...
- HTML5学习总结-10 Android 控件WebView显示网页
WebView可以使得网页轻松的内嵌到app里,还可以直接跟js相互调用. webview有两个方法:setWebChromeClient 和 setWebClient 1)setWebClient: ...
- Android 中的 WebView实现 Html5 标签网页加载
自Android 4.4起,Android中的WebView开始基于Chromium(谷歌浏览器)支持浏览器的一系列功能,webkit解析网页各个节点,这个改变,使得WebView的性能大幅度提升,并 ...
随机推荐
- 深入理解JS 执行细节
javascript从定义到执行,JS引擎在实现层做了很多初始化工作,因此在学习JS引擎工作机制之前,我们需要引入几个相关的概念:执行环境栈.全局对象.执行环境.变量对象.活动对象.作用域和作用域链等 ...
- 在离线环境中使用.NET Core
在离线环境中使用.NET Core 0x00 写在开始 很早开始就对.NET Core比较关注,一改微软之前给人的印象,变得轻量.开源.跨平台.最近打算试着在工作中使用.但工作是在与互联网完全隔离的网 ...
- excel 日期/数字格式不生效需要但双击才会生效的解决办法
原因: Excel2007设置过单元格格式后,并不能立即生效必须挨个双击单元格,才能生效.数据行很多.效率太低. 原因:主要是一些从网上拷贝过来的日期或数字excel默认为文本格式或特殊-中文数字格式 ...
- 利用Oracle RUEI+EM12c进行应用的“端到端”性能诊断
概述 我们知道,影响一个B/S应用性能的因素,粗略地说,有以下几个大的环节: 1. 客户端环节 2. 网络环节(可能包括WAN和LAN) 3. 应用及中间层环节 4. 数据库层环节 能够对各个环节的问 ...
- Java多态性——分派
一.基本概念 Java是一门面向对象的程序设计语言,因为Java具备面向对象的三个基本特征:封装.继承和多态.这三个特征并不是各自独立的,从一定角度上看,封装和继承几乎都是为多态而准备的.多态性主要体 ...
- 【C#公共帮助类】 Utils 10年代码,最全的系统帮助类
为大家分享一下个人的一个Utils系统帮助类,可能有些现在有新的技术替代,自行修改哈~ 这个帮助类主要包含:对象转换处理 .分割字符串.截取字符串.删除最后结尾的一个逗号. 删除最后结尾的指定字符后的 ...
- 封装集合(Encapsulate Collection)
封装就是将相关的方法或者属性抽象成为一个对象. 封装的意义: 对外隐藏内部实现,接口不变,内部实现自由修改. 只返回需要的数据和方法. 提供一种方式防止数据被修改. 更好的代码复用. 当一个类的属性类 ...
- C# 序列化与反序列化几种格式的转换
这里介绍了几种方式之间的序列化与反序列化之间的转换 首先介绍的如何序列化,将object对象序列化常见的两种方式即string和xml对象; 第一种将object转换为string对象,这种比较简单没 ...
- iOS开源项目周报1222
由OpenDigg 出品的iOS开源项目周报第二期来啦.我们的iOS开源周报集合了OpenDigg一周来新收录的优质的iOS开发方面的开源项目,方便iOS开发人员便捷的找到自己需要的项目工具等. io ...
- x01.os.22: ubuntu 常用设置
新组装了个 64 位电脑,i5 CPU,进入 ubuntu 后,又是一通搜索设置,整理如下,以备后用. 安装 .dep 包 sudo dpkg -i [filename.dep] 在 ubuntu 中 ...