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<String, Type> TYPE_MAP = new HashMap<String, Type>(); /** * 日后可以在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<Integer> outterAnds = new ArrayList<Integer>(); // {或者}标记,遇到{加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<String, String> paramMap = new HashMap<String, String>(); 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上的简单调用
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>h5调用原生</title> </head> <body> <a href="myappschema://myappschema?action=oneaction&info={}">测试</a> </body> </html>
测试的时候,html可以放在assets文件夹下面,或者放在服务器上