浅谈混合开发与Android,JS数据交互

Posted 沉睡的雄狮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈混合开发与Android,JS数据交互相关的知识,希望对你有一定的参考价值。

本文是作者原创,如转载请注明出处!

一.概论

  现在时代已经走过了移动互联网的超级火爆阶段,市场上移动开发人员已经趋于饱和,显然,只会原生APP的开发已不能满足市场的需求,随着H5的兴起与火爆,H5在原生APP中的使用越来越广泛,也就是我们常说的混合开发(Hybrid APP).最新很火的微信小程序相信大家都是知道的,实际上微信小程序加载的界面就是一个html5的界面,HTML5界面在一些电商类的APP中主要承担展示数据的作用,但是他的作用并不仅限于此,最起码js调用原生方法和原生调用js的方法是必须的,我们看到的现在微信小程序中的膜拜单车入口,进入后的扫码功能,就是js调用安卓原生的摄像头进行扫码的,另外分享功能以及点击js中的一个按钮跳转到APP原生界面这些需求都是很常见的,所以js和原生方法的相互调用是必须会的.最近做HTML5相关的地图APP,正好用到了相关的知识,总结下项目中遇到的坑.

二.JS与Android交互

1.android如何加载一个H5界面到APP?
目前比较成熟的方案是采用Android封装好的中间件WebView.

加载方式

//创建一个WebView,也可以直接在布局中写WebView控件
WebView mWebView = new WebView(mContext);
//加载网络上的一个url
String url = "https://www.baidu.com";
mWebView.loadUrl(url);
//加载本地的一个url,asserts目录下的
mWebView.loadUrl("file:///android_asset/index.html");

WebView的一些重要设置

//先获取到WebSettings控制类
WebSettings settings = mWebView.getSettings();
//设置支持javascript
settings.setJavaScriptEnabled(true);
//设置为false表示将图片调整为适合WebView的大小
settings.setUseWideViewPort(false);
//设置可以访问文件
settings.setAllowFileAccess(true);
settings.setAllowContentAccess(true);
settings.setAllowFileAccessFromFileURLs(true);
//设置js支持数据库
settings.setDatabaseEnabled(true);
//设置js支持window.localStorage,如果不设置,js中获取的window和localStorage都是null,之前我就被坑了,项目中js界面用到了window.localStorage,然后我和js采用alert的方式调试的过程中,发现这个方法一直报错才找到的这个api.
settings.setDomStorageEnabled(true);
mWebView.loadUrl("file:///android_asset/index.html");
//添加了一个java和js交互的接口,android字符串相当于一个Brige桥梁的作用,安卓4.2以后增加了@JavascriptInterface接口,只有代码@JavascriptInterface注解的方法js才能调用,之前是被注入的类和从父类继承的所有的public的方法都能访问,这也是进一步保证APP的安全性.
mWebView.addJavascriptInterface(mSnMap, "android");
//设置WebView是否加载完成的监听
mWebView.setWebViewClient(new WebViewClient() {
            @Override
            //WebView加载完成会调用的方法
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);

            }

            @Override
            //WebView开始加载调用的方法
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                super.onPageStarted(view, url, favicon);

            @Override
            //WebView的界面可见时调用的方法
            public void onPageCommitVisible(WebView view, String url) {
                super.onPageCommitVisible(view, url);
            }

            @Override
            //是否要拦截js和java调用的方法
            public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
                return true;
            }
        });
        //该设置保证加载界面时会自动调用系统的浏览器,而不是提示给用户让用户去选择哪个浏览器
mWebView.setWebChromeClient(new WebChromeClient(){
            //对应js中的alert()方法,可以重写该方法完成与js的交互
            @Override
            public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
                return super.onJsAlert(view, url, message, result);
            }
            //对应js中promt(),可以重写该方法完成与js的交互
            @Override
            public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
                return super.onJsPrompt(view, url, message, defaultValue, result);
            }
            //对应js中的console.log方法,可以重写该方法完成与js的交互
            @Override
            public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
                return super.onConsoleMessage(consoleMessage);
            }
        });

java调用js的方法

//比如我要调用addline(jsonline)方法,这里给出我写的一个示例
//WebView规定java和js交互的时候前面的字段是javascript:+方法名
//注意:传变量的时候注意要给字符串加上两个单引号,传递方式就是我下面写的那种规范
public void addLine(Line line) {
        if (line != null) {
            jsonLine = mGson.toJson(line);
            mWebView.loadUrl("javascript:addLine(‘" + jsonLine + "‘)");
        }
    }

js调用java的方法

//这里介绍一种最简单的方式,当然也可以自己使用伪协议去封装
//1.添加一个交互的接口,mSnMap表示要注入的java类,android是桥接字符串,js调用的使用就使用这个字符串进行交互
mWebView.addJavascriptInterface(mSnMap, "android");
//2.在js中调用,比如要调用本地读取二进制文件的一个方法
@JavascriptInterface
    public String readshape(String mapshape) {
        Gson gson = new Gson();
        MapShape mapShape = gson.fromJson(mapshape, MapShape.class);
        String shapeBase64 = SEInterfacePvReader.getMapShape(mapShape);
        System.out.println(shapeBase64);
        return shapeBase64;
    }
//3.js调用并获取返回值,另外解释一下,js调用java的方法是在WebView的一个独立的后台线程中,另外这里js调用Android的方法时传递参数只能传一个,而且java与js交互的方法是不支持二进制流交互的,只支持简单的基本数据类型,比如int,boolean,String,数组也是不支持的,数组可以采用json字符串的形式
var bufferData = android.readshape(json);

三.数据结构,文件读取

文件读取这块内容,因为矢量数据的存储结构不是我们公司的人设计的,是和我们公司合作的人设计的,他们的服务器代码采用的是C++结合lua脚本语言共同编写的,涉及到数据结构的具体详情不变透漏,主要说下C++和java数据结构的差别以及读取数据时选择的API.

1.C++中的char和java中的char
C++中的char与ios object-c中的char一样,都是占一个byte
java中的char占2个byte
所以在数据读取过程中c++经常创建一个vector buf就等价于java中读取文件时创建的byte数组.这里用到了java中int转byte数组的工具类.

//具体使用高位到低位还是低位到高位根据具体数据结构而定
  public static byte[] intToByteArray(int i) {
        byte[] result = new byte[4];
        //由高位到低位
//        result[0] = (byte)((i >> 24) & 0xFF);
//        result[1] = (byte)((i >> 16) & 0xFF);
//        result[2] = (byte)((i >> 8) & 0xFF);
//        result[3] = (byte)(i & 0xFF);
        //低位在前
        //由低位到高位
        result[0] = (byte) (i & 0xFF);
        result[1] = (byte) ((i >> 8) & 0xFF);
        result[2] = (byte) ((i >> 16) & 0xFF);
        result[3] = (byte) ((i >> 24) & 0xFF);
        return result;
    }
    /**
     * byte[] 转化为int[] ,低位在前的方式
     * @param title
     * @return
     */
    public static int[] byteArrayToIntArray(byte[] title) {
        int[] array = new int[title.length / 4];
        byte[] temp = new byte[4];
        int k = 0;
        for (int i = 0; i < title.length; i++) {
            temp[i % 4] = title[i];
            if (i > 0 && (i+1) % 4 == 0) {
                array[k] = byteArrayToInt2(temp);
                k++;
            }
        }
        return array;
    }
     /**
     * byte数组转化为int值的工具类,低位在前
     *
     * @param b
     * @return byte[]转化为的int数
     */
    public static int byteArrayToInt2(byte[] b) {
        int MASK = 0xFF;
        int result = 0;
        result = b[0] & MASK;
        result = result + ((b[1] & MASK) << 8);
        result = result + ((b[2] & MASK) << 16);
        result = result + ((b[3] & MASK) << 24);
        return result;
    }

2.文件的读取可以选择RandomAccessFile类和FileInputStream类
两个类都能实现读取二进制文件的效果,同时两个类中都有定位到文件的某个位置的方法,但是他们是有区别的.
FileInputStream的skip()方法是相对于当前位置定位的
RandomAccessFile的seek()方法则可以定位到文件的任意位置
下面用代码举个例子

fis.skip(10);
fis.skip(5);
//执行完这两行代码后读取的输入流FileInputStream定位到文件的第15个字节处
raf.seek(10);
raf.seek(5);
//这两行代码的最终结果是RandomAccessFile最终定位到文件的第5个字节处.

//另外需要注意的是:RandomAccessFile是继承于Object的,只是他实现了DataOutput, DataInput,所以可以读文件也可以写文件.而FileInputStream是继承于InputStream的,只能读文件,而我在项目中读文件采用的是InputStream读,ByteArrayOutputStream作为输出流存储数据.

java和js如何交互文件
java和js是不支持直接的二进制流的交互,所以我们就换了一种方式进行交互,将二进制数据读取后使用Base64算法转化为字符串,将字符串传递给js,js再使用Base64算法反编码为二进制数据流.当然除了这种方式外,还有其他的思路,比如移动端开发一个本地的服务器给js发请求,完成数据的交互.

四.项目总结

目前来说,原生与H5的混合开发相对比较成熟,但是现在没有任何一个工具能够在js与java之前完成debug调试,所以项目上遇到问题只能采用日志和alert弹框的方式一步一步排查代码中的错误,调试起来复杂又繁琐,而且java和js在数据交互上依赖浏览器内核,性能上也存在问题,希望不久的将来,技术上能够突破这些瓶颈.地图的开发还是使用原生调用c++的OpenGL绘制矢量地图比较靠谱,之前研究过1个月的OpenGL开发,因为太难以及项目进度比较赶的原因没有继续下去,接下来会抽出更多的时间去完成OpenGL的底层绘制地图,后续的博客也会分享OpenGL的学习之路.

奉上我很喜欢的一句话,与君共勉:比你聪明的人比你还要努力,你又有什么理由不努力呢?

如有不足之处请指正评论,谢谢!










以上是关于浅谈混合开发与Android,JS数据交互的主要内容,如果未能解决你的问题,请参考以下文章

错误记录Flutter 混合开发获取 BinaryMessenger 报错 ( FlutterActivityAndFragmentDelegate.getFlutterEngine() )(代码片段

Android与HTML+JS交互入门

android中webview与js交互

JS与APP原生控件交互

Android WebView实现原生与JS的交互

Android WebView实现原生与JS的交互