android web模块独立进程的实现

一、简述原理: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());
            }
        }
    }
}


猜你喜欢

转载自blog.csdn.net/u010577768/article/details/80423064
今日推荐