前言
最近一直在学习Hybrid开发,如何在H5页面调用Android原生接口,并返回值,以及回调。学习了一段时间,总算是有点收获,效果也做出来了。于是写下这篇博客,记录一下。
本文中我以2个接口示例,来进行讲解。第1个示例很简单,就是调用接口,返回登录Token;第2个示例是H5调用接口,弹出Android原生界面进行图片选择,选择完之后返回选择图片的Base64格式的字符串。然后H5页面接收返回的字符串,回调进行图片显示。
Base64
首先,讲解一下Base64。图片也是一个文件,可以通过Base64返回这个文件的Base64字符串,这个字符串可以直接放到H5的img标签进行显示。
下面做个示例,方便大家了解。
新建一个Html文件,编写代码如下:
1 | <html> |
可以看到img标签的src属性跟一串字符。”data:image/jpg;base64“表示数据的类型,之后的一大长串就是Base64串。直接浏览器打开,显示如下:
可以看到图片正常显示了。
有了这个基础之后,后面的思路就很清楚了:Android层选择好图片后,返回图片的Base64格式的String对象到H5,H5回调拿来显示即可。
实践
本次例子采用三方库safe-java-js-webview-bridge进行Android、JS互调,PhotoPicker进行图片选择。
AS新建项目,添加gradle依赖。PhotoPicker不支持gradle依赖,直接将代码下载下来后导入library Module。
添加asserts目录,将H5页面,js、css文件添加进去。这里我直接将项目中的联调页面扔进去了,以求简便。
根据safe-java-js-webview-bridge三方库的使用说明,新建MyBridge类。这个类就是定义Android、JS互调接口的类。
1 | public class MyBridge { |
这个类需要根据H5页面那边调用接口的格式来进行编写,示例中的接口调用是这样子的。
1 | function connectWebViewJavascriptBridge(callback) { |
所以我的MyBridge类只定义了一个send方法,在其内部接收Json数据,根据cmd参数判断到底要执行哪个方法。我把方法写在引用WebView的Activity中了,所以添加了一个init方法,进行初始化,保持WebViewActivity对象,以便调用。
下面编写WebViewActivity类。
1 | public class WebViewActivity extends Activity { |
可以看到MyBridge中的send方法最终会调用到WebViewActivity中的getToken方法与getPictures方法。
getToken方法很简单,仅仅是返回一个”Hello world“。getPictures方法则是打开PhotoPicker三方库中的Activity。在选完图片之后,利用onActivityResult接收返回的图片路径。然后利用Base64进行处理,得到我们需要的String对象,返回给JsCallback进行回调。
在JS中处理回调参数的代码是var imgs = JSON.parse(responseData).data.imgs;
,所以我们返回的String对象也需要时JSON格式,并且包含data、imgs属性。类似这样:{“data”:{“imgs”:[“string1”, “string2”]}}。所以我在onActivityResult中进行了拼接处理。
最后运行程序,得到的效果大致是这样的。
可以看到,在我们的H5页面确实显示了图片。
题外话
话题1
在引入PhotoPicker的时候,若在选择图片碰到很长的图片,例如微博长图,400*8000px这样的图,会导致在时容易出现OOM。
在原来库中进行压缩的inSampleSize是这样计算的:
1 | private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { |
使用这种方式,计算得到的inSampleSize值,在压缩这种长图后得到的Bitmap仍然会很大,导致OOM。
改成如下则不会OOM了,但是图片会变得模糊一些(不可避免的,总要舍弃一些东西,微信也是这样)。
1 | private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { |
这个问题我已经跟作者反应,并且已经改正了。
话题2
拼接好String字符串,准备返回给JsCallback进行回调时会出错。Log信息:
1 | Log:I/chromium: [INFO:CONSOLE(1)] "Uncaught SyntaxError: Unexpected identifier", source: (1) |
后面我看到JsCallback里的apply函数:
1 | public void apply (Object... args) throws JsCallbackException { |
他会在参数的前后加上双引号。因为返回的String对象是类似Json格式,里面也会包含双引号,这就会导致传递出错。
这个问题我也已经提了issue给作者,但目前还没有什么回应。
碰到问题需要解决,在作者回应之前只能自己改咯。
将代码下载下来,丢到工程中,不采用gradle依赖的方式了。也就三个类:InjectedChromeClient、JsCallback、JsCallJava。手动将apply中的代码改成如下:
1 | public void apply (Object... args) throws JsCallbackException { |
双引号改成单引号就行了。
但是只有又会引发新的问题:返回的String不能包含单引号。这确实比较蛋疼了,只能根据需求来吧,能解决当前问题的就行。延伸到既包含双引号又包含单引号的String对象又该如何传递呢?这里抛个疑问,大家可以共同探讨。
补充一点:通过Base64返回的String串来显示图片只适合小图片,太大的图片需要压缩后再返回,不然也会OOM。