详解android与HTML混合开发总结
现在很多的 app中会嵌套html5的页面,比如经常变化的等等,有一部分页面需要原生java与html5中的js进行交互操作,下面介绍一下android中html5的使用:
1、关于html5种cookie
网页中可能会用到 用户信息等很多参数,可以提前把这些信息放到cookie中,可以采用以下方法:
public static void addcookies(context context, webview webview, string url) { string url=“https://www.xxxx.com/xx/xx/” string protocol = ""; string authority = ""; try { url urlobj = new url(url); protocol = urlobj.getprotocol(); authority = urlobj.getauthority(); } catch (exception e) { e.printstacktrace(); } string ua = webview.getsettings().getuseragentstring(); webview.getsettings().setuseragentstring(constant.project_name + "/" + paramhandler.getversion(context) + "(" + ua + "; hfwsh)"); if (!textutils.isempty(url) && !textutils.isempty(protocol) && !textutils.isempty(authority)) { if (protocol.equals("https") && authority.indexof("liepin.com") > -1) { cookiesyncmanager.createinstance(context); cookiemanager cookiemanager = cookiemanager.getinstance(); cookiemanager.setacceptcookie(true); try { list<string> data = getcookiesstring(); if (!listutils.isempty(data)) { for (string value : data) { cookiemanager.setcookie(url, value); } } cookiemanager.setcookie(url, "client_id=" + constant.client_id + ";path=/;domain=.xxxx.com"); cookiemanager.setcookie(url, "appversion=" + constant .version + ";path=/;domain=.xxxx.com"); cookiesyncmanager.getinstance().sync(); } catch (exception e) { logutils.e("exception:" + e.getmessage()); } } } }
public list<string> getcookiesstring() { arraylist data = new arraylist(); this.clearexpired(); collection values = this.mcookies.values(); iterator var3 = values.iterator(); while(var3.hasnext()) { swiftcookie c = (swiftcookie)var3.next(); data.add(c.tocookiestring()); } return data; }
在 mwebview.loadurl(url)之前添加cookie,网页就可以通过cookie取到相应的参数值了。
2、关于js的安全问题
js在4.2以前有漏洞
通过javascript,可以访问当前设备的sd卡上面的任何东西,甚至是联系人信息,短信等。好,我们一起来看看是怎么出现这样的错误的。
1,webview添加了javascript对象,并且当前应用具有读写sdcard的权限,也就是:android.permission.write_external_storage
2,js中可以遍历window对象,找到存在“getclass”方法的对象的对象,然后再通过反射的机制,得到runtime对象,然后调用静态方法来执行一些命令,比如访问文件的命令.
3,再从执行命令后返回的输入流中得到字符串,就可以得到文件名的信息了。然后想干什么就干什么,好危险。核心js代码如下:
function execute(cmdargs) { for (var obj in window) { if ("getclass" in window[obj]) { alert(obj); return window[obj].getclass().forname("java.lang.runtime") .getmethod("getruntime",null).invoke(null,null).exec(cmdargs); } } }
解决方案:
1,android 4.2以上的系统
在android 4.2以上的,google作了修正,通过在java的远程方法上面声明一个@javascriptinterface,如下面代码:
class jsobject { @javascriptinterface public string tostring() { return "injectedobject"; } } webview.addjavascriptinterface(new jsobject(), "injectedobject"); webview.loaddata("", "text/html", null); webview.loadurl("javascript:alert(injectedobject.tostring())");
2,android 4.2以下的系统
这个问题比较难解决,但也不是不能解决。
首先,我们肯定不能再调用addjavascriptinterface方法了。关于这个问题,最核心的就是要知道js事件这一个动作,js与java进行交互我们知道,有以下几种,比prompt, alert等,
这样的动作都会对应到webchromeclient类中相应的方法,对于prompt,它对应的方法是onjsprompt方法,这个方法的声明如下:
public boolean onjsprompt(webview view, string url, string message, string defaultvalue, jspromptresult result)
通过这个方法,js能把信息(文本)传递到java,而java也能把信息(文本)传递到js中,通知这个思路我们能不能找到解决方案呢?
经过一番尝试与分析,找到一种比较可行的方案,请看下面几个小点:
【1】让js调用一个javascript方法,这个方法中是调用prompt方法,通过prompt把js中的信息传递过来,这些信息应该是我们组合成的一段有意义的文本,可能包含:特定标识,方法名称,参数等。
在onjsprompt方法中,我们去解析传递过来的文本,得到方法名,参数等,再通过反射机制,调用指定的方法,从而调用到java对象的方法。
【2】关于返回值,可以通过prompt返回回去,这样就可以把java中方法的处理结果返回到js中。
【3】我们需要动态生成一段声明javascript方法的js脚本,通过loadurl来加载它,从而注册到html页面中,具体的代码如下:
javascript:(function jsaddjavascriptinterface_(){ if (typeof(window.jsinterface)!='undefined') { console.log('window.jsinterface_js_interface_name is exist!!');} else { window.jsinterface = { onbuttonclick:function(arg0) { return prompt('myapp:'+json.stringify({obj:'jsinterface',func:'onbuttonclick',args:[arg0]})); }, onimageclick:function(arg0,arg1,arg2) { prompt('myapp:'+json.stringify({obj:'jsinterface',func:'onimageclick',args:[arg0,arg1,arg2]})); }, }; } } )()
说明:
1,上面代码中的jsinterface就是要注册的对象名,它注册了两个方法,onbuttonclick(arg0)和onimageclick(arg0, arg1, arg2),如果有返回值,就添加上return。
2,prompt中是我们约定的字符串,它包含特定的标识符myapp:,后面包含了一串json字符串,它包含了方法名,参数,对象名等。
3,当js调用onbuttonclick或onimageclick时,就会回调到java层中的onjsprompt方法,我们再解析出方法名,参数,对象名,再反射调用方法。
4,window.jsinterface这表示在window上声明了一个js对象,声明方法的形式是:方法名:function(参数1,参数2)
3、在html5中进行java和js的交互
1)、方法一:
mwebview.getsettings().setjavascriptenabled(true); mwebview.addjavascriptinterface(this, "xxx");
然后在当前类中实现以下方法:
@javascriptinterface public void callbackfromh5(final string j) { //todo }
callbackfromh5的名字必须和网页中的js方法名一样
java调用js方法:
mwebview.loadurl(string.format("javascript:java2js(0)"));//这里是java端调用webview的js
js方法名需要和网页端一直
2)方法二:
jsbridge方法(https://github.com/lzyzsd/jsbridge)
android jsbridge 就是用来在 android app的原生 java 代码与 javascript 代码中架设通信(调用)桥梁的辅助工具
1 将jsbridge.jar引入到我们的工程
android studio:
repositories { // ... maven { url "https://jitpack.io" } } dependencies { compile 'com.github.lzyzsd:jsbridge:1.0.4' }
2、布局文件
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <!-- button 演示java调用web --> <button android:id="@+id/button" android:layout_width="match_parent" android:text="@string/button_name" android:layout_height="dp" /> <!-- webview 演示web调用java --> <com.github.lzyzsd.jsbridge.bridgewebview android:id="@+id/webview" android:layout_width="match_parent" android:layout_height="match_parent" > </com.github.lzyzsd.jsbridge.bridgewebview> </linearlayout>
3、java代码
//加载服务器网页 webview.loadurl("https://www.baidu.com"); //必须和js同名函数。 webview.registerhandler("submitfromweb", new bridgehandler() { @override public void handler(string data, callbackfunction function) { string str ="html返回给java的数据:" + data; maketext(mainactivity.this, str, length_short).show(); log.i(tag, "handler = submitfromweb, data from web = " + data); function.oncallback( str + ",java经过处理后:"+ str.substring(,)); } }); //模拟用户获取本地位置 user user = new user(); location location = new location(); location.address = "xxx"; user.location = location; user.name = "bruce"; webview.callhandler("functioninjs", new gson().tojson(user), new callbackfunction() { @override public void oncallback(string data) { maketext(mainactivity.this, "网页在获取你的信息", length_short).show(); } }); webview.send("hello");
webview.callhandler("functioninjs", "data from java", new callbackfunction() { @override public void oncallback(string data) { // todo auto-generated method stub log.i(tag, "reponse data from js " + data); } });
js调用
var str1 = document.getelementbyid("text1").value; var str2 = document.getelementbyid("text2").value; //调用本地java方法 window.webviewjavascriptbridge.callhandler( 'submitfromweb' , {'param': str} , function(responsedata) { document.getelementbyid("show").innerhtml = "send get responsedata from java, data = " + responsedata } ); //注册事件监听 document.addeventlistener( 'webviewjavascriptbridgeready' , function() { callback(webviewjavascriptbridge) }, false ); //注册回调函数,第一次连接时调用 初始化函数 connectwebviewjavascriptbridge(function(bridge) { bridge.init(function(message, responsecallback) { console.log('js got a message', message); var data = { 'javascript responds': 'wee!' }; console.log('js responding with', data); responsecallback(data); }); bridge.registerhandler("functioninjs", function(data, responsecallback) { document.getelementbyid("show").innerhtml = ("data from java: = " + data); var responsedata = "javascript says right back aka!"; responsecallback(responsedata); }); })
4、关于webview的优化
1、设置webview 缓存模式
private void initwebview() { mwebview.getsettings().setjavascriptenabled(true); mwebview.getsettings().setrenderpriority(renderpriority.high); mwebview.getsettings().setcachemode(websettings.load_default); //设置 缓存模式 // 开启 dom storage api 功能 mwebview.getsettings().setdomstorageenabled(true); //开启 database storage api 功能 mwebview.getsettings().setdatabaseenabled(true); string cachedirpath = getfilesdir().getabsolutepath()+app_cacahe_dirname; // string cachedirpath = getcachedir().getabsolutepath()+constant.app_db_dirname; log.i(tag, "cachedirpath="+cachedirpath); //设置数据库缓存路径 mwebview.getsettings().setdatabasepath(cachedirpath); //设置 application caches 缓存目录 mwebview.getsettings().setappcachepath(cachedirpath); //开启 application caches 功能 mwebview.getsettings().setappcacheenabled(true);
2、清除缓存
/** * 清除webview缓存 */ public void clearwebviewcache(){ //清理webview缓存数据库 try { deletedatabase("webview.db"); deletedatabase("webviewcache.db"); } catch (exception e) { e.printstacktrace(); } //webview 缓存文件 file appcachedir = new file(getfilesdir().getabsolutepath()+app_cacahe_dirname); log.e(tag, "appcachedir path="+appcachedir.getabsolutepath()); file webviewcachedir = new file(getcachedir().getabsolutepath()+"/webviewcache"); log.e(tag, "webviewcachedir path="+webviewcachedir.getabsolutepath()); //删除webview 缓存目录 if(webviewcachedir.exists()){ deletefile(webviewcachedir); } //删除webview 缓存 缓存目录 if(appcachedir.exists()){ deletefile(appcachedir); } }
3、在使用webview加载网页的时候,有一些固定的资源文件如js/css/图片等资源会比较大,如果直接从网络加载会导致页面加载的比较慢,而且会消耗比较多的流量。所以这些文件应该放在assets里面同app打包。
解决这个问题用到api 11(honeycomb)提供的shouldinterceptrequest(webview view, string url) 函数来加载本地资源。
api 21又将这个方法弃用了,是重载一个新的shouldinterceptrequest,需要的参数中将url替换成了成了request。
比如有一个图片xxxxx.png,这个图片已经放在了assets中,现在加载了一个外部html,就需要直接把assets里面的图片拿出来加载而不需要重新从网络获取。当然可以在html里面将图片链接换成file:///android_asset/xxxxx.png,
但是这样这个html就不能在android ,ios,wap中公用了。
webview.setwebviewclient(new webviewclient() { @override public webresourceresponse shouldinterceptrequest(webview view, string url) { webresourceresponse response = null; if (build.version.sdk_int >= build.version_codes.honeycomb){ response = super.shouldinterceptrequest(view,url); if (url.contains("xxxxx.png")){ try { response = new webresourceresponse("image/png","utf-8",getassets().open("xxxxx.png")); } catch (ioexception e) { e.printstacktrace(); } } } // return super.shouldinterceptrequest(view, url); return response; } @targetapi(build.version_codes.lollipop) @override public webresourceresponse shouldinterceptrequest(webview view, webresourcerequest request) { webresourceresponse response = null; response = super.shouldinterceptrequest(view, request); if (url.contains("xxxxx.png")){ try { response = new webresourceresponse("image/png","utf-",getassets().open("xxxxx.png")); } catch (ioexception e) { e.printstacktrace(); } } return response; } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读
-
Android Mms之:对话与联系人关联的总结详解
-
Android开发笔记之:消息循环与Looper的详解
-
Android开发笔记之:Handler Runnable与Thread的区别详解
-
Android开发中方向传感器定义与用法详解【附指南针实现方法】
-
Android开发之图形图像与动画(五)LayoutAnimationController详解
-
Android Mms之:对话与联系人关联的总结详解
-
Android开发笔记之:消息循环与Looper的详解
-
Android开发教程之Fragment定义、创建与使用方法详解【包含Activity通讯,事务执行等】
-
浅谈html5与APP混合开发遇到的问题总结
-
Android开发笔记之:Handler Runnable与Thread的区别详解