h5原生混合开发已经有些年头了,说实在的h5调用原生最好用的还是在h5发起新链接的时候拦截下来,然后原生做出反应。
思路是自定义一套原生h5交互协议,用户在h5页面点击的时候,h5发起一个跳转(location=“”),客户端拦截这个跳转,如果符合协议的跳转,就交给客户端进行操作
否则返回给浏览器,进行常规的跳转
定义:我们把h5发起让原生处理的请求定义为action
代码示例:
public class MyWebViewClient extends WebViewClient {
private MyWebView mWebView;
public MyWebViewClient(MyWebView webView) {
super();
mWebView = webView;
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (NativeInvokeFilter.filter(url, mWebView)) {
return true;
} else {
return super.shouldOverrideUrlLoading(view, url);
}
}
}
自定义webview,统一使用该webview来处理h5调用原生的需求
package com.example.base.basetemplate.widget.MyWebView;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.webkit.WebSettings;
import android.webkit.WebView;
/**
* Created by cgx on 2017-6-15.
*/
public class MyWebView extends WebView {
public MyWebView(Context context) {
super(context);
init();
}
public MyWebView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
@SuppressLint("SetJavaScriptEnabled")
private void init() {
setWebViewClient(new MyWebViewClient(this));
WebSettings settings = getSettings();
if (settings != null) {
settings.setJavaScriptEnabled(true);
settings.setDomStorageEnabled(true);
settings.setAllowContentAccess(true);
settings.setTextSize(WebSettings.TextSize.NORMAL);
settings.setAllowFileAccess(false);
settings.setSavePassword(false);
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
settings.setBlockNetworkImage(false);
// TODO: SDK修改成21后再设置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
// settings.setAppCacheEnabled(true);
// settings.setAppCacheMaxSize(1024 * 1024 * 8);
// String appCacheDir = getContext().getDir("cache", Context.MODE_PRIVATE).getPath();
// settings.setAppCachePath(appCacheDir);
}
setHorizontalScrollBarEnabled(false);
}
}
至于自定义webview的调用,就不再啰嗦了
NativeInvokeFilter
处理action的核心类
package com.example.base.basetemplate.nativeinvokefilter;
import android.text.TextUtils;
import com.example.base.basetemplate.nativeinvokefilter.handler.OneActionHandler;
import com.example.base.basetemplate.widget.MyWebView.MyWebView;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* Created by cgx on 2017-6-15.
*/
public class NativeInvokeFilter {
//请求头部
public static final String SCHEMA = "myappschema://myappschema?";
//此handler和java系统的handler没有任何关系,易于扩展性体现在新加的action必须实现这个接口,统一放在同一个目录下
public interface NativeHandler {
void handle(NativeInvokeProtocol nip, MyWebView webView);
}
private static final Map TYPE_MAP = new HashMap();
/**
* 日后可以在Type里面添加新的action,首先要定义好action对应的字段(第一个参数)和对应的handler
*/
public enum Type {
UNKNOWN("unknown", null),
ONEACTION("oneaction",new OneActionHandler());
private NativeHandler mHandler;
Type(String key, NativeHandler handler) {
TYPE_MAP.put(key, this);
mHandler = handler;
}
public static Type parse(String key) {
if (TextUtils.isEmpty(key)) {
return UNKNOWN;
}
Type type = TYPE_MAP.get(key);
return type == null ? UNKNOWN : type;
}
public static NativeHandler getHandler(String key) {
return parse(key).mHandler;
}
}
public static boolean filter(String url, MyWebView webView) {
if (canParse(url)) {
NativeInvokeProtocol nip = NativeInvokeProtocol.fromParamString(
url.substring(SCHEMA.length()));
if (nip != null)
return handleInvoke(nip, webView);
}
return false;
}
/**
* 检测传递的url,如果以SCHEMA开头就可以执行action的操作
* @param url
* @return
*/
private static boolean canParse(String url) {
if (TextUtils.isEmpty(url)) {
return false;
}
return url.startsWith(SCHEMA);
}
/**
* 执行handler的方法,首先查找TYPE_MAP,通过action来查找到对应的handler,如果有找得到就执行handler的handle方法
* @param nip
* @param webView
* @return
*/
private static boolean handleInvoke(NativeInvokeProtocol nip, MyWebView webView) {
// 优先使用newAction字段
String realAction = null == nip.newAction || 0 == nip.newAction.length() ? nip.action : nip.newAction;
if (TextUtils.isEmpty(realAction))
return false;
NativeHandler handler = Type.getHandler(realAction);
if (handler != null) {
handler.handle(nip, webView);
return true;
} else
return false;
}
/**
* 解析action链接头部的类,重点的变量是info,action返回的信息体,通常是一个json字符串
* 变量action,对应具体的业务
*/
public static class NativeInvokeProtocol {
private static final String ACTION = "action";
private static final String NEW_ACTION = "newAction";
private static final String INFO = "info";
private static final String LEFT_BRACE = "{";
private static final String RIGHT_BRACE = "}";
private static final String EQUAL = "=";
private static final String AND = "&";
private static final String SHARP = "#";
private String action;
private String newAction;
private String url;
public String info;
public String getUrl() {
return url;
}
public String getAction() {
return action;
}
public static NativeInvokeProtocol fromParamString(String paramStr) {
NativeInvokeProtocol nip = new NativeInvokeProtocol();
if (TextUtils.isEmpty(paramStr)) {
return nip;
}
try {
paramStr = URLDecoder.decode(paramStr, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 循环扫描字符串,找出最外层的&(不在{}之间)所在位置,分隔字符串
int len = paramStr.length();
ArrayList outterAnds = new ArrayList();
// {或者}标记,遇到{加1,遇到}减1,等于0即表示当前扫描位置不在{}之间
int braceIndicator = 0;
for (int i = 0; i < len - 1; i++) {
String ch = paramStr.substring(i, i + 1);
if (ch.equals(AND) && braceIndicator == 0) {
outterAnds.add(i);
}
if (ch.equals(LEFT_BRACE)) {
braceIndicator++;
}
if (ch.equals(RIGHT_BRACE)) {
braceIndicator--;
}
}
// 加上字符串首尾位置
outterAnds.add(0, -1);
outterAnds.add(paramStr.length());
HashMap paramMap = new HashMap();
for (int i = 0; i < outterAnds.size() - 1; i++) {
String paramItemStr = paramStr.substring(outterAnds.get(i) + 1, outterAnds.get(i + 1));
int keyIndex = paramItemStr.indexOf(EQUAL);
String key = paramItemStr.substring(0, keyIndex);
String value = paramItemStr.substring(keyIndex + 1);
paramMap.put(key, value);
}
nip.action = paramMap.get(ACTION);
if(null!=nip.action && nip.action.length()>0 && nip.action.contains(SHARP))
nip.action = nip.action.split(SHARP)[0];
nip.info = paramMap.get(INFO);
nip.newAction = paramMap.get(NEW_ACTION);
return nip;
}
}
}
一个非常简洁的handler
public class OneActionHandler implements NativeHandler {
@Override
public void handle(NativeInvokeProtocol nip, MyWebView webView) {
Toast.makeText(MyEnvironment.activity(),nip.info,Toast.LENGTH_SHORT).show();
}
}
html上的简单调用
h5调用原生测试
测试的时候,html可以放在assets文件夹下面,或者放在服务器上