0%

Android嵌入Vue实现H5应用

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 webSettings = webView.getSettings();

// 如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
webSettings.setJavaScriptEnabled(true);
// 若加载的 html 里有JS 在执行动画等操作,会造成资源浪费(CPU、电量)
// 在 onStop 和 onResume 里分别把 setJavaScriptEnabled() 给设置成 false 和 true 即可

//支持插件
webSettings.setPluginsEnabled(true);

//设置自适应屏幕,两者结合使用
webSettings.setUseWideViewPort(true); // 将图片调整到适合webview的大小
webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小

//缩放操作
webSettings.setSupportZoom(true); // 支持缩放,默认为true。是下面操作的前提,结合使用
webSettings.setBuiltInZoomControls(true); // 设置内置的缩放控件。若为false,则该WebView不可缩放
webSettings.setDisplayZoomControls(false); // 隐藏原生的缩放控件

// 缓存操作
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
//缓存模式如下:
//LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
//LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
//LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
//LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。

//其他细节操作
webSettings.setAllowFileAccess(true); // 设置可以访问文件
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); // 支持通过JS打开新窗口
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种:

  1. 通过WebViewloadUrl()
  2. 通过WebViewevaluateJavascript()

对于JS调用Android代码的方法有3种:

  1. 通过WebViewaddJavascriptInterface()进行对象映射
  2. 通过 WebViewClientshouldOverrideUrlLoading ()方法回调拦截 url
  3. 通过 WebChromeClientonJsAlert()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。

image-20211207214936885

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" } // 添加依赖源,JitPack.io 是一个 GitHub 开源代码库的便捷发布渠道
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

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();
}

/**
* 初始化View
*/
private void initView() {
bridgeWebView = findViewById(R.id.activity_jsbridge_bridgewebview);
bridgeWebView.loadUrl("https://www.cdfgroup.xyz/app/");

// BridgeWebView继承了WebView,所以WebWiew的工具类都可以正常使用
WebSettings webSettings = bridgeWebView.getSettings();
webSettings.setJavaScriptEnabled(true); // 开启JavaScript

// 缓存设置
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); // 不使用缓存
// 自适应屏幕设置
webSettings.setUseWideViewPort(true); // 将图片调整到适合webview的大小
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
//注册函数,使JS能通过JSBridge调用Android
bridgeWebView.registerHandler("openCamera", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
//JS传递给Android
Toast.makeText(JsBridgeActivity.this, data, Toast.LENGTH_LONG).show();
takePhoto(); // 拍照
//Android返回给JS的消息
function.onCallBack("success");
}
});

// Android调用JS的方法(这里放在takePhoto()方法里面)
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) {
//Android使用
if (isAndroid) {
//如果该对象已存在则直接执行
if (window.WebViewJavascriptBridge) {
callback(window.WebViewJavascriptBridge);
} else {
//否则添加事件监听再执行
document.addEventListener(
'WebViewJavascriptBridgeReady',
() => {
callback(window.WebViewJavascriptBridge);
},
false
);
}
console.log('tag,android');
sessionStorage.phoneType = 'android';
}

//iOS使用
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';
}
}

//注册回调函数,第一次连接时调用(进入H5页面) 初始化函数(android需要初始化,ios不用)
setupWebViewJavascriptBridge((bridge) => {
console.log('tag,setupWebViewJavascriptBridge');
if (isAndroid) {
//初始化 一个无参数的handler供Android调用
bridge.init((message, responseCallback) => {
console.log('JS got a message', message);
var data = {
'Javascript Responds': 'Wee!'
};
responseCallback(data);
});
}
});

export default {
// js调APP方法 (参数分别为:app提供的方法名 传给app的数据 回调)
callHandler(name, data, callback) {
setupWebViewJavascriptBridge((bridge) => {
bridge.callHandler(name, data, callback);
});
},
// APP调js方法 (参数分别为:js提供的方法名 回调)
registerHandler(name, callback) {
setupWebViewJavascriptBridge((bridge) => {
bridge.registerHandler(name, (data, responseCallback) => {
callback(data, responseCallback);
});
});
}
};

之后在main.js中引入:

1
2
import jsBridge from './utils/jsBridge.js'  // h5和安卓原生联调
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
// 调用Android的方法(JS调用Android)
chooseImage() {
console.log("调用拍照");
this.$bridge.callHandler(
'openCamera'
, {'param': "hello,android!"}
, (responseData) => {
console.log("JS接收到:" + responseData);
});
},

// 注册接收图片路径的回调方法(Android调用JS)
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原生的定位方法。

  1. 写一个Android获取当前定位信息的接口

  2. JS调用后显示当前位置在地图上

当JS还需要依赖该定位数据写一些复杂的逻辑时,调试很麻烦。

  1. JS程序逻辑编写
  2. JS程序打包
  3. Android更新打包后的JS程序
  4. Android Studio连接手机进行调试,观察控制台输出数据

发现问题后修改代码还得执行一次这个流程,真的心累。

目前尚未找到合适的解决方案。