一、简述原理:html -> web进程 -> app主进程 -> 回调到web进程
通过广播,也可以直接 app主进程 -> web进程
1、html->web进程,这一步不用说了,webview.addJavascriptInterface(obj, "webview");
2、web进程 -> app主进程 通过aidl实现
3、app主进程 -> 回调到web进程 也是通过aidl接口实现
4、广播本身跨进程的
5、为了让组件中供web调用的 JsInterface 与业务分离,增加了一个H5Bridge.getInstance().register(JsInterfaceImpl.class)
H5Bridge内部解析JsInterfaceImpl.class的所有方法,缓存到map里,web进程接收到web请求时,把web请求的数据通过aidl传到主进程,然后主进程中根据web传回的数据,从H5Bridge的map缓存中找到对应的方法,通过反射调用执行,执行后通过传入AidlCallback接口回调到子进程。
6、web端的调用
webview.jsFunc("methodName", "{'key':'value', 'key2':'value2'}")
webview是addJavascriptInterface(, "webview")中定义的别名
jsFunc()是中转接口中@JavascriptInterface注解的统一接收web调用的函数名
methodName是客户端与web端协商的调用的方法名,第二个参数是该方法调用需要的参数
二、具体实现:
1、首先要定义两个aidl接口(注意aidl文件所在包名)
//主进程的服务返回给子进程的Binder管理器,该管理器本身也是Binder,通过它可以获取对应业务的Binder
IBinderManager.aidl
//用于h5子进程连接主进程成功后,由主进程返回给子进程的Binder,子进程可通过该binder来调用主进程中的方法
IWebBinder.aidl
//用于h5子进程调用的主进程方法执行完毕时,回调给子进程,让子进程做相应处理的Binder
IWebBinderCallback.aidl
2、编写aidl的实现
/**
* Binder管理器
*/
public class BinderManager extends IBinderManager.Stub {
public static final int BINDER_WEB_AIDL = 1;//h5进程请求主进程
private Context context;
public BinderManager(Context context) {
this.context = context;
}
@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
switch (binderCode) {
case BINDER_WEB_AIDL: {
binder = new WebBinderInterface(context);
break;
}
default:
break;
}
return binder;
}
}
/**
* 主进程中,封装了给h5进程调用的接口
*/
public class WebBinderInterface extends IWebBinder.Stub {
private Context context;
public WebBinderInterface(Context context) {
this.context = context;
}
@Override //处理web进程中h5页面穿过来的事件
public void handleJsFunction(String methodName, String params, IWebBinderCallback callback) throws RemoteException {
int pid = android.os.Process.myPid();
Logger.d(WebRemoteControl.TAG , String.format("=======WebBinderInterface.handleJsFunction(methodName:(%s) params:(%s)", pid, methodName, params));
try {
H5Bridge.getInstance().callJava(methodName, params, callback);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3、编写主进程的服务
/**
* 主进程的服务端,子进程连接该服务,返回Binder,用于在子进程调用主进程的api
*/
public class MainRemoteService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new BinderManager(this);
}
}
4、编写子进程请求连接主进程的工具类
/**
* 用于web进程向mainprocess发起连接,获取binder
*/
public class WebBinderClient {
private IBinderManager mBinderManager;
private static volatile WebBinderClient mInstance;
private CountDownLatch mCountDownLatch;
private ServiceConnectImpl mConnect;
private WebBinderClient() {}
public static WebBinderClient getInstance() {
if (mInstance == null) {
synchronized (WebBinderClient.class) {
if (mInstance == null) {
mInstance = new WebBinderClient();
}
}
}
return mInstance;
}
/**
* 启动服务、连接主进程服务端
*/
public synchronized void bindMainService(Context context) {
mCountDownLatch = new CountDownLatch(1);//共享锁
Intent service = new Intent(context, MainRemoteService.class);
if (mConnect == null) {
mConnect = new ServiceConnectImpl(context);
}
context.bindService(service, mConnect, Context.BIND_AUTO_CREATE);
try {
mCountDownLatch.await();//阻塞当前线程(webview子进程的子线程),等待连接完成
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 退出h5时解绑服务
*/
public synchronized void unbindMainService(Context context) {
if (mConnect != null) {
context.unbindService(mConnect);
}
}
private class ServiceConnectImpl implements ServiceConnection {
private Context mContext;
public ServiceConnectImpl(Context context) {
mContext = context;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderManager = IBinderManager.Stub.asInterface(service);
final int pid = android.os.Process.myPid();
Logger.d(WebRemoteControl.TAG , "=======onServiceConnected: 进程ID:"+ pid);
try {
mBinderManager.asBinder().linkToDeath(new IBinder.DeathRecipient() {
@Override
public void binderDied() {//子进程的主线程中监听binder的死亡通知
Logger.d(WebRemoteControl.TAG , "=======binderDied: 进程ID:"+ pid);
mBinderManager.asBinder().unlinkToDeath(this, 0);
mBinderManager = null;
bindMainService(mContext);//binder死了就再次去启动服务连接主进程
}
}, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
mCountDownLatch.countDown();//共享锁释放锁,子进程启动服务的线程唤醒继续往下执行
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
/**
* 根据binderCode获取Binder
* @param binderCode 一个标识而已 {@link BinderManager}
*/
public IBinder queryBinder(int binderCode) {
IBinder binder = null;
try {
if (mBinderManager != null) {
binder = mBinderManager.queryBinder(binderCode);
}
} catch (RemoteException e) {
e.printStackTrace();
}
return binder;
}
}
5、编写web调用native的中转接口
/**
* 真正被webview.addJavascriptInterface(xxx)添加的
*/
public final class BaseRemoteJsInterface {
private final Handler mHandler = new Handler();
private JsFunctionCallback mCallback;
@JavascriptInterface
public void jsFunc(final String methodName, final String param) {
mHandler.post(new Runnable() {
@Override
public void run() {
try {
if (mCallback != null) {
mCallback.execute(methodName, param);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public void setCallback(JsFunctionCallback callback) {
this.mCallback = callback;
}
public interface JsFunctionCallback {
void execute(String methodName, String params);
}
}
6、编写web模块独立进程的操作入口类
/**
* web模块独立进程的操作入口类
*/
public class WebRemoteControl implements BaseRemoteJsInterface.JsFunctionCallback{
public static final String TAG = "webremote";
protected BaseRemoteJsInterface mJsInterface;
private IServiceConnectCallback mCallback;
private WebView mWebView;
private Activity mActivity;
protected IWebBinder mWebBinder;
public WebRemoteControl(Activity activity) {
mActivity = activity;
mJsInterface = new BaseRemoteJsInterface();
mJsInterface.setCallback(this);
}
/**
* 添加web端调用接口,连接主进程服务
*/
public void setWebView(WebView webView) {
mWebView = webView;
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(mJsInterface, WebConst.JS_INTERFACE);
bindService(mActivity);
}
/**
* 启动服务,与主进程连接
*/
protected void bindService(Activity activity) {
ThreadPoolFactory.instance().fixExecutor(new Runnable() {
@Override
public void run() {
WebBinderClient webClient = WebBinderClient.getInstance();
webClient.bindMainService(activity);//子线程启动服务,启动会该线程会休眠,等待连接成功后才唤醒
IBinder iBinder = webClient.queryBinder(BinderManager.BINDER_WEB_AIDL);
mWebBinder = IWebBinder.Stub.asInterface(iBinder);//服务端(主进程)返回的binder
if (mCallback != null) {
mCallback.onServiceConnected();
}
}
});
}
@Override
public void execute(String methodName, String params) {//h5调用native方法时回调
if (mWebBinder == null) {
Logger.e(TAG, "mWebBinder == null");
return;
}
handleJsFunc(methodName, params);
}
/**
* 处理h5调用native的操作
*/
protected void handleJsFunc(String action, String params) {
try {//handleJsFunction()是在主进程中,回调通过aidl回到了子进程中
mWebBinder.handleJsFunction(action, params, new IWebBinderCallback.Stub() {
@Override
public void onResult(int msgType, String message) throws RemoteException {
Logger.d(TAG, "=======handleJsFunction.onResult() message:"+ message);
resolveResult(msgType, message);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private void resolveResult(int msgType, String message) {
switch (msgType) {
case WebConst.MSG_TYPE_JS:
mWebView.loadUrl(message);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mWebView.evaluateJavascript(message, null);
} else {
mWebView.loadUrl(message);
}
break;
// TODO: 2018/5/23 回调消息处理
}
}
public void setServiceConnectListener(IServiceConnectCallback callback) {
mCallback = callback;
}
}
7、编写web调用native的中转接口与真正接口之间的桥接类
/**
* 解析给web调用的接口类,根据web调用传过来的方法名、参数等信息,调用这里解析出来的方法
* 这种实现是为了不需要改动底层真正给web调用的接口{@link com.silvrr.common.module.h5.interfaces.BaseRemoteJsInterface}
* 只需要上层通过H5Bridge.getInstance().register(H5Interface.class)
*/
public class H5Bridge {
//key:方法名 value:方法对象Method
private static Map<String, Method> mMethodMp = new HashMap<>();
private static volatile H5Bridge mInstance;
public static H5Bridge getInstance() {
if (mInstance == null) {
synchronized (H5Bridge.class) {
if (mInstance == null) {
mInstance = new H5Bridge();
}
}
}
return mInstance;
}
/**
* 解析提供给h5调用的方法所在的类,把该类的所有方法解析,并以键值对的形式放到 mMethodMp 中
* 1、该类中的方法不允许重名,否则后面的方法会覆盖前面的方法
* 2、方法的参数为JSONObject
* @param clazz 提供给h5调用的方法所在的类
*/
public void register(Class clazz) {
try {
parseMethods(clazz);
} catch (Exception e) {
e.printStackTrace();
}
}
private void parseMethods(Class injectedCls) throws Exception {
Method[] methods = injectedCls.getDeclaredMethods();
Method[] methods2 = injectedCls.getSuperclass().getDeclaredMethods();
appendMethodsToMap(methods, mMethodMp);
appendMethodsToMap(methods2, mMethodMp);
}
private void appendMethodsToMap(Method[] methods, Map<String, Method> mMethodsMap) {
for (Method method : methods) {
String name = method.getName();
if (name == null) {
continue;
}
Class[] parameters = method.getParameterTypes();
if (null != parameters) {
if (parameters[0] == JSONObject.class) {
mMethodsMap.put(name, method);
}
}
}
}
/**
* 根据网页调用的方法名、参数,通过反射调用注册的客户端方法
*/
public void callJava(String methodName, String param, IWebBinderCallback callback) {
if (mMethodMp.containsKey(methodName)) {
Method method = mMethodMp.get(methodName);
if (method != null) {
try {
method.invoke(null, new JSONObject(param), callback);
} catch (Exception e) {
Logger.d(WebRemoteControl.TAG, "执行异常,请检查传入参数是否有误!");
}
} else {
Logger.d(WebRemoteControl.TAG, "Android侧没有定义该方法,请检查接口参数名称是否有误!");
}
} else {
Logger.d(WebRemoteControl.TAG, "Android侧没有定义接口[" + methodName + "],请检查接口参数名称是否有误!");
}
}
}
8、编写子进程广播,接收主进程消息(可有可无的,写不写都行)
/**
* 用于接收主进程的消息
*/
public class WebRemoteReciver extends BroadcastReceiver {
public static final String WEB_REMOTE_ACTION = "web_remote_action";
public static final String REMOTE_MESSAGE_KEY = "remote_message_key";
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) {
return;
}
String action = intent.getAction();
if (!WEB_REMOTE_ACTION.equals(action)) {
return;
}
RemoteMessage msg = intent.getParcelableExtra(REMOTE_MESSAGE_KEY);
if (msg == null) {
return;
}
switch (msg.mMsgType) {
case RemoteMessage.MSG_TYPE_KILL://让子进程自杀
Process.killProcess(Process.myPid());
break;
}
LoggerUtils.d(WebRemoteControl.TAG, "========接收到主进程的广播消息");
}
}
9、在WebViewActivity中的调用
public void onCreate(...) {
//开始连接主进程
WebRemoteControl remoteControl = new WebRemoteControl(this);
remoteControl.setWebView(mWebView);
remoteControl.setServiceConnectListener(new IServiceConnectCallback() {
@Override
public void onServiceConnected() {
runOnUiThread(new Runnable() {
@Override
public void run() {//连接主进程成功后,加载url
mWebView.loadUrl(getWebPresenter().getUrl());
}
});
}
});
//完成连接主进程
}
10、Manifest中的注册
<application>
<activity
android:name=".xxx.WebViewActivity"
android:screenOrientation="portrait"
android:process=":webview"/>
<service android:name="xxx.MainRemoteService"/>
<receiver android:name=".xxx.WebRemoteReciver"
android:process=":webview">
<intent-filter>
<action android:name="web_remote_action"/>
</intent-filter>
</receiver>
</application>
11、最后在Application中注册真正要给web调用的接口
public void onCreate() {
ThreadPoolFactory.getInstance().execute(new Runnable() {
@Override
public void run() {
H5Bridge.getInstance().register(JsNativeInterface.class);
}
});
}
最后,给web调用的真正方法是这么写的,仅供参考
/**
* 给web端调用的方法
*/
public class JsNativeInterface {
public static void jsTestFunction(JSONObject param, IWebBinderCallback callback) {
//操作主进程UI
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(MarketApplication.getInstance(), param.toString(), Toast.LENGTH_SHORT).show();
}
});
//回调给子进程调用js
if (callback != null) {
try {
callback.onResult(0, "javascript:alert('CallBack');");
} catch (RemoteException e) {
e.printStackTrace();
Logger.d(WebRemoteControl.TAG,"=======JsNativeInterface.callback.onResult() exception:" + e.getMessage());
}
}
}
}