Android富文本编辑器开发之旅

来源:互联网 发布:java伪静态页面 编辑:程序博客网 时间:2024/06/05 14:45

技术选型

从确定要开发富文本编辑器到正式立项,差不多只有一周的时间,在这周内我结合公司已有的业务调研了市面上富文本编辑器的实现方式。主要有以下两种:

  1. Android原生实现,主要通过EditText,Span等相关技术。优点:交互效果比较理想,绕过了WebView的兼容性等问题。
    缺点:不能与网页端文本编辑器实现互通,web端通过css等实现一些新样式,原生如果要兼容新样式,就需要发版本支持,不够灵活。
  2. WebView+javascript方式实现。
    优点:能够与网页端实现互通,减少了对客户端的依赖,网页端出新样式,只需要通过更新JS就可以支持。
    缺点:首先要建立JS与原生之间稳定的沟通渠道(可以通过JSSDK来提供支持)。交互效果没有原生的理想。需要兼容各个版本的WebView以及其他兼容性问题。

考虑到App端需要与网页端实现内容互通,选取第二种方式作为我们富文本编辑器的实现方式。大致思路如下:

  1. 请求服务端接口,查询是否有JS更新,下载最新的编辑器JS。
  2. 挂载编辑器JS和其他JS(JSSDK),实现Js与Java互相调用。
  3. 编辑器内容逻辑,比如文字换行,图片插入和删除,字体加黑等都通过JS来实现。编辑器的复杂逻辑,比如多线程图片上传,图片上传失败重试,草稿保存与恢复等都是通过js调用java方法来实现。

    编辑器最终实现效果在差强人意,但是还存在一些三方ROM兼容性缺陷。
    下面谈一下在开发编辑器的过程中的积累的一些经验。

JS的挂载

JS挂载是首先要解决的一个难题。AndroidWebView挂载JS有两种方式:

  1. 通过WebView.loadUrl()来挂载,这种方式在所有版本的系统上都支持。需要注意的是挂载之前要在js代码前面加上前缀:“javascript:”。完整的方式是:loadUrl(“javascript:”+jsStr)
  2. 通过WebView.executeJavaScript()调用。这个方法带返回值,可以获取JS执行后的结果。由于这个方法是在4.4才引入,所以只能在4.4及以上版本使用。

    掌握了如何挂载JS,还要把握JS的挂载时机。
    我们一般在WebViewClient.onPageFinished()方法回调时调用loadUrl或者executeJavaScript方法来挂载。

掌握以上两步,就可以将Js挂载到WebView。

Java与JS互调

这里分为:java调用Js和js调用java两部分。

  1. Java调用JS
    Java调用Js的方法与挂载JS的方法相同,但是有一点需要注意:在4.4以下手机无法获取Js执行后的返回值。为了解决不同版本WebView获取Js返回值的问题,统一在java调用js方法后,js再调用java方法,将返回值以参数传递给java方法。虽然增加了一层调用,但是可以有效解决问题。
    在查阅资料的的时候发现,网上也有人通过反射调用WebView的底层方法,从而实现在4.4以下手机获取Js执行的返回值。
  2. JS调用java。
    JS调用java通过在java代码中暴露一个方法给JS实现的。这里也有一个问题,JS与java沟通频繁,是不是需要每个调用都要暴露一个方法?如果是这样,会有什么问题呢?问题是:如果每个编辑器的操作都通过定义方法来实现,那么这些不够通用的方法就会散落在代码中,造成日后维护问题,并且不好管理,比如:我想查看每次js调用java的参数,我是不是需要在没个方法都加上:Log.i(TAG,Params)。这就需要引入java与js之间沟通的问题,有效的解决方案就是:JSSDK。关于JSSDK的内容,可以查看这个项目:JSBridge。

再谈loadUrl

在测试编辑器兼容性的时候,发现在4.4以上手机运行正常的编辑器在4.4以下手机上却出现 在输入文字时,键盘会自动关闭(落下)的现象。后来通过移除外部变量,Logcat,Debug等方式发现:当WebView调用loadUrl方法时,会自动让键盘关闭。为什么在4.4以上没有这个问题?因为在执行JS的时候,我对代码进行了一定的兼容性处理:在4.4及以上系统调用executeJavaScript,否则调用loadUrl。
通过谷歌,发现WebView的确有调用loadUrl方法会关闭软键盘的行为。关于规避这个行为有两种思路:

  1. loadUrl在执行过程中,会层层调用其他类的某些方法,找打让键盘落下的代码,使用反射直接调用底层代码避免调用让键盘落下的代码。这种方式不失为一种解决方案,但是我最终没有采取这种方式,原因如下:WebView作为重要的控件,google经常对其进行修复,而且很多厂商会对其实现进行改造,这里不能确定你要反射调用的方法在三方ROM或者某些版本下能否成功,处理不好,还有可能带来程序的crash。
  2. 第二种方法是改变之前与JS的某些调用逻辑,避免在输入文字的时候调用loadUrl方法,从而规避问题。

后来我选用第二种方式作为解决问题的方法,解决问题。

JS访问本地资源

由于JS需要频繁升级,所以在设计阶段,JS就设计为可以动态更新,而且Html标签都是通过JS动态扩展出来的。
在直接通过loadUrl加载线上JS时发现:在图片选取后,将本地图片地址直接给JS作为jmg标签的src属性,由于线上JS对本地图片没有访问权限导致不能加载图片。后来采用的方案是:通过接口获取最新的编辑器JS放在本地。在assets目录下放一个空白Html,通过loadUrl将Html加载进WebView,在onPageFinished方法时,将本地JS挂载进WebView,由于Html和JS都是本地的,可以对手机图片进行访问,从而解决远程JS无法访问本地图片的问题。

多线程图片上传

这里我通过 Excutors.newFixedThreadPool方式获取一个线程池,将图片上传抽象成一个任务,在有图片上传的时候,将任务丢给线程池,在上传时,对进度进行回传给Activity,进而传给JS。图片上传是通过OkHttp来实现的。这部分属于通用知识,请所搜线程池和OkHttp的相关知识。

2 0
原创粉丝点击