前段时间有点android与h5交互的需求开发,考虑到自己的需求,所以选择webView作为中间层,实现双方通讯,下面是在实践过程中关于JNIBridge WebView的一些总结
首先我们知道android(Java)与js属于不同种类的语言,所以两种平台语言的通讯必须借助外力,例如我们最初学的桥接模式,或者适配器模式,都是通过中间层去转化而实现不同模式的适配,再例如现在很火的语言中间层,thrift,protobuf,或者是grpc的中间层,可以使不同语言之间进行交互。这里WebView是为了支持h5与android原生进行交互的Bridge,当然底层也是jni的调用,详细的就不介绍了下面直接上代码。
[1] 在Activity中定义WebView
private WebView webView; //定义webview
[2] 初始化WebView,针对以下封装方法,后面会有详细的介绍。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
captureManager = new ImageCaptureManager(MainActivity.this);
initWebView(); //初始化WebView组件
initWebViewSetting(); //初始化WebView配置
initWebChromeClient(); //初始化Web浏览器设置
initWebViewClient(); //初始化客户端配置
initListener(); //初始化事件监听器
loadWebView(); //加载webView视图
}
[2.1] 初始化并绑定webView组件 ,具体如下:
/**
* 初始化视图
*/
public void initWebView() {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
webView = new WebView(getApplicationContext());
this.addContentView(webView, params);
}
[2.2] 初始化webViewSetting,具体如下:
@SuppressLint("SetJavaScriptEnabled")
public void initWebViewSetting() {
webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true); //设置android能否使用js
//设置自适应屏幕
webSettings.setUseWideViewPort(true); //将图片调整到适合webView的大小
webSettings.setLoadWithOverviewMode(true); //缩放至屏幕大小
//缩放操作
webSettings.setSupportZoom(true); //支持缩放默认为true
webSettings.setBuiltInZoomControls(true); //设置内置缩放控件。若为false,则该webView不可缩放
webSettings.setDisplayZoomControls(true); //隐藏原生的缩放控件
if (isNetworkAvailable(getApplicationContext())) {
//有网络连接,设置默认缓存模式
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
} ele {
//无网络连接,设置本地缓存模式缓存模式
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
}
webSettings.setAllowFileAccess(true); //设置允许访问文件
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);//支持通过js打开新的弹窗
webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
//设置缓存目录名
webSettings.setAppCacheMaxSize(1024 * 1024 * 8);//存储的最大容量
String appCachePath = getApplicationContext().getCacheDir().getAbsolutePath() + "rainbow";
webSettings.setAppCachePath(appCachePath); //设置 Application Caches 缓存目录
//缓存设置
webSettings.setDomStorageEnabled(true);
webSettings.setDatabaseEnabled(true);
webSettings.setAllowFileAccess(true);
webSettings.setAppCacheEnabled(true);
webSettings.setDefaultTextEncodingName("utf-8"); //设置文本默认编码格式
}
[2.2.1] 检测网络是否可用,根据网络状况,设置不同的缓存策略,具体如下:
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager connectivity = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity != null) {
NetworkInfo info = connectivity.getActiveNetworkInfo();
// 当前处于网络环境
if (info != null && info.isConnected()) {
// 网络环境可用,主要防止App未授权网络
if (info.getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
}
return false;
}
[3] 初始化客户端浏览器,具体如下:
public void initWebChromeClient() {
//不直接启动浏览器去加载,而是通过webView去加载页面
webView.setWebChromeClient(new WebChromeClient() {
/**
* 设置js弹窗
* @param view 视图
* @param url 路径
* @param message 弹窗内容
* @param result 结果
* @return boolean
*/
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
AlertDialog.Builder b = new AlertDialog.Builder(MainActivity.this);
// b.setTitle("Alert");
b.setMessage(message);
b.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm());
b.setCancelable(false);
b.create().show();
return super.onJsAlert(view, url, message, result);
}
/**
* 设置响应js Confirm函数回调
* @param view 视图
* @param url 路径
* @param message 弹窗内容
* @param result 结果
* @return
*/
@Override
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
AlertDialog.Builder b = new AlertDialog.Builder(MainActivity.this);
b.setTitle("Confirm");
b.setMessage(message);
b.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm());
b.setNegativeButton(android.R.string.cancel, (dialog, which) -> result.cancel());
b.create().show();
return true;
}
/**
* 设置响应js 函数回调
* @param view 视图
* @param url 路径
* @param message 弹窗内容
* @param result 结果
* @return
*/
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) {
final View v = View.inflate(MainActivity.this, R.layout.prompt_dialog, null);
((TextView) v.findViewById(R.id.prompt_message_text)).setText(message);
((EditText) v.findViewById(R.id.prompt_input_field)).setText(defaultValue);
AlertDialog.Builder b = new AlertDialog.Builder(MainActivity.this);
b.setTitle("Prompt");
b.setView(v);
b.setPositiveButton(android.R.string.ok, (dialog, which) -> {
String value = ((EditText) v.findViewById(R.id.prompt_input_field)).getText().toString();
result.confirm(value);
});
b.setNegativeButton(android.R.string.cancel, (dialog, which) -> result.cancel());
b.create().show();
return true;
}
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
mUploadCallbackAboveL = filePathCallback;
take();
return true;
}
@SuppressWarnings("unused")
//<3.0
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
mUploadMessage = uploadMsg;
take();
}
@SuppressWarnings("unused")
//>3.0+
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
mUploadMessage = uploadMsg;
take();
}
@SuppressWarnings("unused")
//>4.1.1
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
mUploadMessage = uploadMsg;
take();
}
});
}
[4] 初始化WebView客户端,具体如下:
public void initWebViewClient() {
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
/**这里是进行android调用js的关键
*tips:
*
* 1:如果android需要调用js组件,需要在回调函数onPageFinished中执行,否则调用无效
*2、android调用js的两种手段
* 2.1、通过webView.evaluateJavaScript("javascript:MyJsMethod()")
* 2.2、通过webView.loadUrl("javascript:MyJsMethod()");
*/
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
webView.post(() -> {
});
}
@Override
public void onLoadResource(WebView view, String url) {
super.onLoadResource(view, url);
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
switch (error.getErrorCode()) {
case 404:
view.loadUrl("错误界面");
}
}
});
}
[5] 监听手机回退事件及解决手机back键直接退出的问题,具体如下:
public void initListener() {
webView.setOnKeyListener((v, keyCode, event) -> {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
webView.goBack();
return true; //已处理
}
}
return false;
});
}
/**
* 手机回退事件处理
*
* @param keyCode 回退事件码
* @param event 事件
* @return boolean
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KEYCODE_BACK && webView.canGoBack()) {
webView.goBack();
}
return super.onKeyDown(keyCode, event);
}
[6] 通过webView加载html资源,具体如下:
/**
* 加载WebView
*/
public void loadWebView() {
webView.clearCache(true); //清除页面缓存
webView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); //取消右侧滑动栏
String url="http://localhost:8080/xx/xx"; //通过网络加载
String url="/asset/abc.html";//通过本地加载
webView.loadUrl(url);
webView.addJavascriptInterface(new CallMedia(), "callMedia");
}
[7] webView内存泄露处理 ,具体如下:
/**
* 当Activity停止时
*/
@Override
protected void onStop() {
webSettings.setJavaScriptEnabled(false);
super.onStop();
}
/**
* 当Activity恢复时
*/
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onResume() {
webSettings.setJavaScriptEnabled(true);
super.onResume();
}
/**
* Activity销毁,防止webview内存泄露
*/
@Override
protected void onDestroy() {
if (webView != null) {
// webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
// webView.clearHistory();
((ViewGroup) webView.getParent()).removeView(webView);
webView.destroy();
webView = null;
}
super.onDestroy();
}
[8] javascript调用android客户端SA层服务代码逻辑,主要是通过 @JavascriptInterface注解实现,具体如下:
//声明暴露类
public class CallMedia {
public CallMedia() {
}
//声明暴露方法
@JavascriptInterface
public void callAndroidMedia(String token, String name, String data, int uid, int type) {
if (type != MediaParamsType.VIDEO.getState() &&
type != MediaParamsType.AUDIO.getState())
return;
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString(MediaRoomConstants.MEDIA_ROOM_TOKEN, token);
bundle.putString(MediaRoomConstants.MEDIA_ROOM_NAME, name);
bundle.putString(MediaRoomConstants.MEDIA_ROOM_DATA, data);
bundle.putInt(MediaRoomConstants.MEDIA_ROOM_ID, uid);
intent.putExtra("media", bundle);
if (MediaParamsType.VIDEO.getState() == type) { //视频
intent.setClass(MainActivity.this, VideoChatViewActivity.class);
} else if (type == MediaParamsType.AUDIO.getState()) {//音频
intent.setClass(MainActivity.this, VoiceChatViewActivity.class);
}
startActivity(intent, bundle);
}
}
[8.1] 暴露android原生接口给javascript调用,具体如下:
webView.addJavascriptInterface(new CallMedia(), "callMedia");
[8.2] js调用android端已暴露方法,具体如下:
callMedia对应有 webView.addJavascriptInterface(new CallMedia(), "callMedia");的callMedia接口对象。
执行代码:
callMedia.callAndroidMedia(token, name, data, uid,type)