Android嵌入Vue实现H5应用
现在很多的App里都内置了Web网页(Hybrid App),比如说很多电商平台,淘宝、京东、聚划算等等。
他们的页面是由Android的WebView实现的,其中涉及到Android客户端与Web网页的交互。
Android WebView组件可以实现App中内置网页的功能。
WebView
是一个基于webkit
引擎、展现web
页面的控件。
Android的Webview在低版本和高版本采用了不同的webkit版本内核,4.4后直接使用了Chrome。
作用为:
- 显示和渲染Web页面
- 直接使用html文件(网络上或本地assets中)作布局
- 可和JavaScript交互调用
==参考WebView教程==:https://www.jianshu.com/p/3c94ae673e2a
AndroidManifest.xml添加访问网络权限
:
1
| <uses-permission android:name="android.permission.INTERNET"/>
|
WebViews只能加载HTTPS的站点,无法加载HTTP站点。当加载HTTP站点时,会报错net :: ERR_CLEARTEXT_NOT_PERMITTED
。
在AndroidManifest.xml的application
标签下配置:
1 2 3 4
| <application .... android:usesCleartextTraffic="true" ....>
|
activity_main.xml
添加WebView组件:
1 2 3 4
| <WebView android:id="@+id/webview" android:layout_width="match_parent" android:layout_height="match_parent" />
|
MainActivity
找到该组件:
1 2 3 4 5 6 7 8 9 10
| private WebView webView = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
webView = (WebView) findViewById(R.id.webview);
webView.loadUrl("https://xiaoshiapp.city/h5/index.html"); }
|
Webview常用的工具类:
- WebSettings类:对WebView进行配置和管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setPluginsEnabled(true);
webSettings.setUseWideViewPort(true); webSettings.setLoadWithOverviewMode(true);
webSettings.setSupportZoom(true); webSettings.setBuiltInZoomControls(true); webSettings.setDisplayZoomControls(false);
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
webSettings.setAllowFileAccess(true); webSettings.setJavaScriptCanOpenWindowsAutomatically(true); webSettings.setLoadsImagesAutomatically(true); webSettings.setDefaultTextEncodingName("utf-8");
|
- WebViewClient类:处理各种通知 & 请求事件
onPageStarted()
作用:开始载入页面调用的,我们可以设定一个loading的页面,告诉用户程序在等待网络响应。
1 2 3 4 5 6
| webView.setWebViewClient(new WebViewClient(){ @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { } });
|
onPageFinished()
作用:在页面加载结束时调用。我们可以关闭loading 条,切换程序动作。
1 2 3 4 5 6
| webView.setWebViewClient(new WebViewClient(){ @Override public void onPageFinished(WebView view, String url) { } });
|
- WebChromeClient类:辅助 WebView 处理 Javascript 的对话框,网站图标,网站标题等等
onProgressChanged()
作用:获得网页的加载进度并显示
1 2 3 4 5 6 7 8 9
| webview.setWebChromeClient(new WebChromeClient(){ @Override public void onProgressChanged(WebView view, int newProgress) { if (newProgress < 100) { String progress = newProgress + "%"; progress.setText(progress); } else { } });
|
WebView与JavaScript的交互
在WebView的开发中,经常需要用到本地函数与H5界面或者Js交互,传递数据等,但是Android本身自带的WebView与Js交互方式存在安全隐患问题,故不详细展开,需要可以看下面的教程链接。
Android与JS通过WebView互相调用方法,实际上是:
- Android去调用JS的代码
- JS去调用Android的代码
对于Android调用JS代码的方法有2种:
- 通过
WebView
的loadUrl()
- 通过
WebView
的evaluateJavascript()
对于JS调用Android代码的方法有3种:
- 通过
WebView
的addJavascriptInterface()
进行对象映射
- 通过
WebViewClient
的shouldOverrideUrlLoading ()
方法回调拦截 url
- 通过
WebChromeClient
的onJsAlert()
、onJsConfirm()
、onJsPrompt()
方法回调拦截JS对话框alert()
、confirm()
、prompt()
消息
==参考教程==:https://www.jianshu.com/p/345f4d8a5cfa
JsBridge实现交互
为解决Android原生方法的安全隐患问题,推荐使用WebViewJavascriptBridge。
GitHub地址:https://github.com/lzyzsd/JsBridge
Android和JS互调(调用相机拍照)
首先是Android写了一个拍照的方法openCamera
,使用bridge暴露出去,JS调用该方法,成功后返回给JSsuccess
。
拍照完成后Android调用JS暴露的方法picPath
将base64字符串传给JS。
Android配置
根目录下的build.gradle
(全局的项目构建配置):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| buildscript { repositories { google() jcenter() maven { url "https://jitpack.io" } } dependencies { classpath 'com.android.tools.build:gradle:3.5.2' } }
allprojects { repositories { google() jcenter() maven { url "https://jitpack.io" } } }
|
APP目录下的build.gradle
(app模块的gradle构建脚本,一般用来管理app包名、版本的以及添加和修改依赖库):
1 2 3
| dependencies { compile 'com.github.lzyzsd:jsbridge:1.0.4' }
|
使用:
xml界面添加组件:
1 2 3 4 5
| <com.github.lzyzsd.jsbridge.BridgeWebView android:id="@+id/activity_jsbridge_bridgewebview" android:layout_width="match_parent" android:layout_height="match_parent"> </com.github.lzyzsd.jsbridge.BridgeWebView>
|
Activity找到组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| private BridgeWebView bridgeWebView;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_js_bridge); initView(); }
private void initView() { bridgeWebView = findViewById(R.id.activity_jsbridge_bridgewebview); bridgeWebView.loadUrl("https://www.cdfgroup.xyz/app/");
WebSettings webSettings = bridgeWebView.getSettings(); webSettings.setJavaScriptEnabled(true);
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); webSettings.setUseWideViewPort(true); webSettings.setLoadWithOverviewMode(true); webSettings.setSupportZoom(true); webSettings.setBuiltInZoomControls(true); webSettings.setDisplayZoomControls(false); }
|
实现互调:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| bridgeWebView.registerHandler("openCamera", new BridgeHandler() { @Override public void handler(String data, CallBackFunction function) { Toast.makeText(JsBridgeActivity.this, data, Toast.LENGTH_LONG).show(); takePhoto(); function.onCallBack("success"); } });
bridgeWebView.callHandler("picPath", pic64, new CallBackFunction() { @Override public void onCallBack(String data) { Toast.makeText(JsBridgeActivity.this, data, Toast.LENGTH_LONG).show(); } });
|
JS配置
JS需要封装一个工具类jsBridge
(参考:JSbridge 在Vue的封装与交互):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| let isAndroid = navigator.userAgent.indexOf('Android') > -1 || navigator.userAgent.indexOf('Adr') > -1; let isiOS = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
function setupWebViewJavascriptBridge(callback) { if (isAndroid) { if (window.WebViewJavascriptBridge) { callback(window.WebViewJavascriptBridge); } else { document.addEventListener( 'WebViewJavascriptBridgeReady', () => { callback(window.WebViewJavascriptBridge); }, false ); } console.log('tag,android'); sessionStorage.phoneType = 'android'; }
if (isiOS) { if (window.WebViewJavascriptBridge) { return callback(window.WebViewJavascriptBridge); } if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); } window.WVJBCallbacks = [callback]; var WVJBIframe = document.createElement('iframe'); WVJBIframe.style.display = 'none'; WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'; document.documentElement.appendChild(WVJBIframe); setTimeout(() => { document.documentElement.removeChild(WVJBIframe); }, 0); console.log('tag', 'ios'); sessionStorage.phoneType = 'ios'; } }
setupWebViewJavascriptBridge((bridge) => { console.log('tag,setupWebViewJavascriptBridge'); if (isAndroid) { bridge.init((message, responseCallback) => { console.log('JS got a message', message); var data = { 'Javascript Responds': 'Wee!' }; responseCallback(data); }); } });
export default { callHandler(name, data, callback) { setupWebViewJavascriptBridge((bridge) => { bridge.callHandler(name, data, callback); }); }, registerHandler(name, callback) { setupWebViewJavascriptBridge((bridge) => { bridge.registerHandler(name, (data, responseCallback) => { callback(data, responseCallback); }); }); } };
|
之后在main.js
中引入:
1 2
| import jsBridge from './utils/jsBridge.js' Vue.prototype.$bridge = jsBridge;
|
使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| chooseImage() { console.log("调用拍照"); this.$bridge.callHandler( 'openCamera' , {'param': "hello,android!"} , (responseData) => { console.log("JS接收到:" + responseData); }); },
registerPicResultFunction() { this.$bridge.registerHandler("picPath", (data, responseCallback) => { console.log("JS接收到图片的base64编码:"); console.log(data); let base64 = data.replace(/\+/g, "%2B"); this.picList.push({url: "data:image/png;base64," + base64}); responseCallback("success"); }) },
|
常见问题
在开发中比较麻烦的问题就是调试了。比如写了一个地图定位程序,JS需要调用Android原生的定位方法。
写一个Android获取当前定位信息的接口
JS调用后显示当前位置在地图上
当JS还需要依赖该定位数据写一些复杂的逻辑时,调试很麻烦。
- JS程序逻辑编写
- JS程序打包
- Android更新打包后的JS程序
- Android Studio连接手机进行调试,观察控制台输出数据
发现问题后修改代码还得执行一次这个流程,真的心累。
目前尚未找到合适的解决方案。