android 學習之路h5混合開發專案經歷
首先需求是要做一個h5呼叫原生去傳送udp mqtt 儲存 tcp 相機之類的功能
第一步在mainActivity 註冊監聽
private void initWebLoad(String url) { Util.synCookies(getApplicationContext(), url, SharePreferenceUtils.getPrefString(getApplicationContext(), SharePreferenceUtils.WEB_VIEW_COOKIE, "")); //第一個引數為使用者名稱,第二個引數為密碼 final String basic = Credentials.basic("zhangsan", "123456"); Map<String, String> heads=new HashMap<>(); heads.put("Authorization",basic); bridgeWebView2.loadUrl(url,heads); ShakeService shakeService=ARouter.getInstance().navigation(ShakeService.class); if(shakeService!=null){ shakeService.handleShake(this); //除錯 if(Build.VERSION.SDK_INT> Build.VERSION_CODES.KITKAT) { bridgeWebView2.setWebContentsDebuggingEnabled(true); } } getPresenter().registerH5CallNative(bridgeWebView2); }
第二步 在MainPresenter p層做registerH5CallNative方法的實現
/** * H5呼叫原生 * @param bridgeWebView */ public void registerH5CallNative(final WVJBWebView bridgeWebView) { bridgeWebView.registerHandler(REGISTER_HANDLE_NAME, new WVJBWebView.WVJBHandler() { @Override public void handler(Object dataobj, WVJBWebView.WVJBResponseCallback function) { String data=dataobj.toString(); functionMap.put(function.toString(),function); try { Timber.e(TAG, "##H5CallNative 收到: " + data); String service = Util.getService(data); Timber.e(TAG, service); String action = Util.getAction(data); Timber.e(TAG,action); LinkedTreeMap messageTreeMap = GsonUtils.getJsonLinkedTree(data); String message = ""; if (messageTreeMap != null) { message = GsonUtils.getJsonString(messageTreeMap.get("data")); } Timber.e(TAG,message); switch (service){ case SERVICE_HTTP: case SERVICE_FILE: if(httpService==null) { httpService = ARouter.getInstance().navigation(HttpService.class); } if(httpService!=null){ httpService.handleData(function.toString(),getActivity(),action,message); } break; case SERVICE_UDP: if(udpService==null) { udpService = ARouter.getInstance().navigation(UdpService.class); } if(udpService!=null){ udpService.handleData(action,message); } break; case SERVICE_MQTT: if(mqttService==null){ mqttService=ARouter.getInstance().navigation(MqttService.class); } if(mqttService!=null){ mqttService.handleData(function.toString(),action,message); } break; //資料儲存 case SERVICE_DATA: case SERVICE_DATA_BASE: if(databaseService==null){ databaseService=ARouter.getInstance().navigation(DatabaseService.class); } if(databaseService!=null){ databaseService.handleData(function.toString(),action,message); } break; case SERVICE_SYSTEM: if(systemService==null){ systemService=ARouter.getInstance().navigation(SystemService.class); } if(systemService!=null){ systemService.handleData(function.toString(),getActivity(),action,message); } break; case SERVICE_SMARTLINK: if(smartLinkService==null){ smartLinkService=ARouter.getInstance().navigation(SmartLinkService.class); } if(smartLinkService!=null){ smartLinkService.handleData(function.toString(),action,message); } break; case SERVICE_TCP: if(tcpService==null){ tcpService=ARouter.getInstance().navigation(TcpService.class); } if(tcpService!=null){ tcpService.handleData(function.toString(),action,message); } break; case SERVICE_CAMERA: if(cameraService==null){ cameraService=ARouter.getInstance().navigation(CameraService.class); } if(cameraService!=null){ cameraService.handleData(function.toString(),getActivity(),action,message); } break; case SERVICE_APPUPGRADE: if(upgradeService==null){ upgradeService=ARouter.getInstance().navigation(UpgradeService.class); } if(upgradeService!=null){ upgradeService.handleData(getActivity(),function.toString(),action,message); } break; case SERVICE_MAP: if(mapService==null){ mapService=ARouter.getInstance().navigation(MapService.class); } if(mapService!=null){ mapService.handleData(function.toString(),getActivity(),action,message); } break; case SERVICE_BLE: if(bleService==null){ bleService=ARouter.getInstance().navigation(BleService.class); } if(bleService!=null){ bleService.handleData(function.toString(),action,message); } break; case SERVICE_CLIENT: if(ipcService==null){ ipcService=ARouter.getInstance().navigation(IpcService.class); } if(ipcService!=null){ if(ACTION_TO_START.equals(action) ){ if(getView()!=null){ getView().addVideoFragment(message); } } ipcService.handleData(getActivity(),function.toString(),service,action,message); } break; case SERVICESD_CARD: if(ipcService==null){ ipcService=ARouter.getInstance().navigation(IpcService.class); } if(ipcService!=null){ ipcService.handleData(getActivity(),function.toString(),service,action,message); } break; case SERVICE_STATUSBAR: final LinkedTreeMap linkedTreeMap =GsonUtils.getJsonLinkedTree(message); switch (action){ case ACTION_ROTATE: //1豎屏,0橫屏 int orientation = -1; if (linkedTreeMap != null) { if (linkedTreeMap.containsKey("orientation")) { orientation = (int) Double.parseDouble(linkedTreeMap.get("orientation").toString()); if(orientation==0){ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);//橫屏 callH5Callback(function.toString(),"{\"code\":200}"); }else if(orientation==1){ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);//豎屏 callH5Callback(function.toString(),"{\"code\":200}"); } } } break; case ACTION_STATE: //1顯示,0隱藏 int isVisible = -1; if (linkedTreeMap != null) { if (linkedTreeMap.containsKey("isVisible")) { isVisible = (int) Double.parseDouble(linkedTreeMap.get("isVisible").toString()); if(isVisible==0){ getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); //隱藏狀態列 callH5Callback(function.toString(),"{\"code\":200}"); }else if(isVisible==1){ getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); //顯示狀態列 callH5Callback(function.toString(),"{\"code\":200}"); } } } break; case ACTION_STYLE: //1黑色,0白色 int setStyle = -1; if (linkedTreeMap != null) { if (linkedTreeMap.containsKey("setStyle")) { setStyle = (int) Double.parseDouble(linkedTreeMap.get("setStyle").toString()); if(setStyle==0){ StatusBarUtil.setLightMode(getActivity()); callH5Callback(function.toString(),"{\"code\":200}"); }else if(setStyle==1){ StatusBarUtil.setDarkMode(getActivity()); callH5Callback(function.toString(),"{\"code\":200}"); } } } break; } break; case SERVICE_EXTERNAL: if(externalService==null){ externalService=ARouter.getInstance().navigation(ExternalService.class); } if(externalService!=null){ externalService.handleData(getActivity(),function.toString(),action,message); } break; } }catch (Exception e){ e.printStackTrace(); } } }); }
第三步 在對應的service serviceImpl中實現其方法,舉例說udpservice udpserviceImpl
public interface UdpService extends IProvider {
void handleData(String action, String data);
}
udpserviceImpl去實現handleData方法
public class UdpServiceImpl implements UdpService { private static final String TAG = "UdpServiceImpl"; private Context context; private static final String ACTION_SEND="send"; public static final String ACTION_LISTEN="listen"; public static final String ACTION_CLOSE="close"; private static final String H5_MODULE_UDP="UDP"; private static final String H5_ACTION_PUSH_MESSAGE = "pushMessage"; @Override public void handleData( String action, String data) { // Log.e(TAG,action+"------data:"+data); switch (action){ case ACTION_SEND: final LinkedTreeMap linkedTreeMap = GsonUtils.getJsonLinkedTree(data); String ip = ""; int port = 0; String msg = ""; if (linkedTreeMap != null) { ip = linkedTreeMap.get("ip").toString(); if(linkedTreeMap.containsKey("port")) port = (int) Double.parseDouble(linkedTreeMap.get("port").toString()); msg = GsonUtils.getJsonString(linkedTreeMap.get("message")); LinkedTreeMap t = GsonUtils.getJsonLinkedTree(msg); String method = t.get("method").toString(); if (method.equals("devDiscoveryReq")) { UDPManager.getInstance().ScanDevice(context, port, msg, new UdpResponseListener() { @Override public void onResponse(final String m) { EventBus.getDefault().post(new JsCallH5ByNativeEvent(H5_MODULE_UDP,H5_ACTION_PUSH_MESSAGE,m)); } @Override public void onTimeOut() { } @Override public void onError(Exception e) { } }); } else { int timeout; if (method.equals("wifiListReq")) { timeout=20000; }else{ timeout=-1; } UDPManager.getInstance().sendAsyn(context, ip, port,timeout, msg, new UdpResponseListener() { @Override public void onResponse(final String m) { EventBus.getDefault().post(new JsCallH5ByNativeEvent(H5_MODULE_UDP,H5_ACTION_PUSH_MESSAGE,m)); } @Override public void onTimeOut() { } @Override public void onError(Exception e) { } }); } } break; case ACTION_LISTEN: final LinkedTreeMap linkedTreeMap2 = GsonUtils.getJsonLinkedTree(data); int port2 = 0; if (linkedTreeMap2 != null) { port2 = (int) Double.parseDouble(linkedTreeMap2.get("port").toString()); //listenAsyn UDPManager.getInstance().nioListen( port2, new UdpResponseListener() { @Override public void onResponse(String message) { EventBus.getDefault().post(new JsCallH5ByNativeEvent(H5_MODULE_UDP, H5_ACTION_PUSH_MESSAGE, message)); } @Override public void onTimeOut() { } @Override public void onError(Exception e) { } }); } break; case ACTION_CLOSE: // UDPManager.getInstance().StopListen(); UDPManager.getInstance().stopNioListen(); break; } } @Override public void init(Context context) { this.context=context; // Log.e(TAG, "init: " ); } }
將h5傳過來的json轉換成map 通過對應key取出來
final LinkedTreeMap linkedTreeMap = GsonUtils.getJsonLinkedTree(data);
public static LinkedTreeMap getJsonLinkedTree(String jsonData) {
if (TextUtils.isEmpty(jsonData)) {
return null;
}
Gson gson = new GsonBuilder()
.disableHtmlEscaping().create();
LinkedTreeMap map = gson.fromJson(jsonData, LinkedTreeMap.class);
return map;
}
根據不同的引數呼叫udp的不同方法 這邊舉例講send
UDPManager.getInstance().sendAsyn(context, ip, port,timeout, msg, new UdpResponseListener() {
@Override
public void onResponse(final String m) {
EventBus.getDefault().post(new JsCallH5ByNativeEvent(H5_MODULE_UDP,H5_ACTION_PUSH_MESSAGE,m));
}
@Override
public void onTimeOut() {
}
@Override
public void onError(Exception e) {
}
}
第四步 在UDPManager中去實現send方法 因為防止執行緒阻塞 非同步傳送
/**
* 非同步傳送
*/
public void sendAsyn(final Context context, final String ip, final int port, final int timeout, final String message, final UdpResponseListener responseListener) {
this.context=context;
thread = null;
thread = new Thread(new Runnable() {
@Override
public void run() {
try {
send(ip, port,timeout, message, responseListener);
} catch (IOException e) {
e.printStackTrace();
}
}
});
thread.start();
}
具體實現udp傳送資料及收到資料方法 new ds一個DatagramSocket物件 用來發送udp new 兩個DatagramPacket 一個dp_send用來儲藏傳送資料,一個dp_receive用來接收資料,
private void send(String ip, int port, int timeout, String message, UdpResponseListener responseListener) throws IOException {
// Log.e(TAG, "send 傳送udp資料: " );
byte[] buf = new byte[10240];
DatagramSocket ds = new DatagramSocket();
InetAddress loc = InetAddress.getLocalHost();
try {
if (TextUtils.isEmpty(ip)) {
loc = InetAddress.getByName("255.255.255.255");
} else {
loc = getAddressByString(ip);
}
} catch (UnknownHostException e) {
e.printStackTrace();
}
//定義用來發送資料的DatagramPacket例項
DatagramPacket dp_send = new DatagramPacket(message.getBytes(), message.length(), loc, port);
//定義用來接收資料的DatagramPacket例項
DatagramPacket dp_receive = new DatagramPacket(buf, 10240);
//資料發向本地36877埠
if(timeout<0) {
ds.setSoTimeout(TIMEOUT); //設定接收資料時阻塞的最長時間
}else{
ds.setSoTimeout(timeout);
}
int tries = 0; //重發資料的次數
boolean receivedResponse = false; //是否接收到資料的標誌位
//直到接收到資料,或者重發次數達到預定值,則退出迴圈
while (!receivedResponse && tries < MAXNUM) {
//傳送資料
ds.send(dp_send);
try {
//接收從服務端傳送回來的資料
ds.receive(dp_receive);
String strMsg=new String(dp_receive.getData()).trim();
// Log.e("##########", dp_receive.getAddress()
// .getHostAddress().toString()+dp_receive.getPort()
// + ":" +strMsg );
//如果接收到的資料不是來自目標地址,則丟擲異常
// if (!"/255.255.255.255".equals(loc.toString()) && !dp_receive.getAddress().equals(loc)) {
// throw new IOException("Received packet from an umknown source" + loc.toString() + " receiver Address:" + dp_receive.getAddress());
// }
//如果接收到資料。則將receivedResponse標誌位改為true,從而退出迴圈
receivedResponse = true;
} catch (InterruptedIOException e) {
e.printStackTrace();
//如果接收資料時阻塞超時,重發並減少一次重發的次數
tries += 1;
// Log.e("duyucheng", "Time out," + (MAXNUM - tries) + " more tries...");
}
}
if (receivedResponse) {
//如果收到資料,則打印出來
// Log.e("duyucheng", "client received data from server:");
String str_receive = new String(dp_receive.getData(), 0, dp_receive.getLength());
String device_ip=dp_receive.getAddress().getHostAddress().toString();
int rec_port=dp_receive.getPort();
try {
JSONObject jsonObject1=new JSONObject(str_receive);
JSONObject paloadObject=jsonObject1.getJSONObject("payload");
paloadObject.put("ip",device_ip);
paloadObject.put("port",rec_port);
str_receive =jsonObject1.toString();
}catch (Exception e){
e.printStackTrace();
}
//由於dp_receive在接收了資料之後,其內部訊息長度值會變為實際接收的訊息的位元組數,
//所以這裡要將dp_receive的內部訊息長度重新置為1024
dp_receive.setLength(1024);
if (responseListener != null) {
responseListener.onResponse(str_receive);
}
} else {
//如果重發MAXNUM次資料後,仍未獲得伺服器傳送回來的資料,則列印如下資訊
// Log.e("duyucheng", "No response -- give up.");
if (responseListener != null) {
responseListener.onError(null);
}
}
ds.close();
}
第五步 在第三步的send方法 成功用eventbus 將資料傳送出來,自己先new一個事件類JsCallH5ByNativeEvent,並在mainActivity中註冊 EventBus.getDefault().register(this);
UDPManager.getInstance().sendAsyn(context, ip, port,timeout, msg, new UdpResponseListener() {
@Override
public void onResponse(final String m) {
EventBus.getDefault().post(new JsCallH5ByNativeEvent(H5_MODULE_UDP,H5_ACTION_PUSH_MESSAGE,m));
}
@Override
public void onTimeOut() {
}
@Override
public void onError(Exception e) {
}
}
public class JsCallH5ByNativeEvent {
private String service;
private String action;
private String data;
public JsCallH5ByNativeEvent(String service,String action,String data){
this.service=service;
this.action=action;
this.data=data;
}
public String getService() {
return service;
}
public void setService(String service) {
this.service = service;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
EventBus.getDefault().register(this);
第六步 在mainActivity中監聽eventBus傳送的資料
@Subscribe(threadMode=ThreadMode.MAIN)
public void onJsCallH5ByNative(JsCallH5ByNativeEvent event){
if (event != null&&getPresenter()!=null) {
getPresenter().callH5ByNavite(bridgeWebView2,event.getService(),event.getAction(),event.getData());
}
}
第七步 將資料轉換成json傳送給h5端
/**
* 原生髮資料到H5
* @param service
* @param action
* @param data
*/
public void callH5ByNavite(final WVJBWebView bridgeWebView, String service, String action, String data) {
JsonObject jsonBean = null;
if (!TextUtils.isEmpty(data)) {
try {
jsonBean = new JsonParser().parse(data).getAsJsonObject();
}catch (Exception e){
e.printStackTrace();
}
}
H5NativeBean h5NativeBean = new H5NativeBean();
h5NativeBean.setService(service);
h5NativeBean.setAction(action);
h5NativeBean.setData(jsonBean);
final String message = GsonUtils.getJsonString(h5NativeBean);
Timber.e("%%callH5ByNavite 發到H5: " + message);
if(getActivity()!=null&&bridgeWebView!=null){
bridgeWebView.callHandler(HANDLER_CALL_NATIVE, message);
}
}
搞定 !!!
涉及到的知識點有
1 service介面+serviceImpl實現類 "高擴充套件性”。
2 mvp模式
Activity 和Fragment 視為View層,負責處理 UI。
Presenter 為業務處理層,既能呼叫UI邏輯,又能請求資料,該層為純Java類,不涉及任何Android API。
Model 層中包含著具體的資料請求,資料來源。
3 Handler
4 廣播
5 非同步
6 udp
7 eventBus