切換語言為:簡體

原生APP WebView 與 h5 通訊設計

  • 爱糖宝
  • 2024-09-08
  • 2051
  • 0
  • 0

一、背景

APP 套殼 webview h5, 兩者的通訊實現不難,如何設計的簡便,易於擴充套件方便的和 h5 的 開發框架結合相對比較重要。

二、IOS的實現

(一)、端程式碼實現

1、端實現配置 webview , 注入 js 指令碼
// 建立 WKWebViewConfiguration 物件,這是 WKWebView 的配置類,用於設定各種引數
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
// 建立 WKUserContentController 物件,這個控制器用於管理使用者指令碼和訊息處理
config.userContentController = [[WKUserContentController alloc] init];
// 註冊一個指令碼訊息處理器,這裏註冊了一個名為 AppModel 的處理器,用於處理 JavaScript 向原生髮送的訊息
// 當JS透過AppModel來呼叫時,我們可以在WKScriptMessageHandler代理中接收到
[config.userContentController addScriptMessageHandler:self name:@"AppModel"];
// 獲取應用的版本號,從應用的 Info.plist 檔案中讀取 CFBundleVersion 鍵的值
NSString *appBuild = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
// 定義要注入到 window 物件的 JavaScript 程式碼,這裏注入了一些全域性變數
NSString *jsString = [NSString stringWithFormat:@"window.__iosApp__ = true; window.iosBuildCode = %@",appBuild];
// 建立一個 WKUserScript 物件,用於將定義的 JavaScript 程式碼注入到網頁中
WKUserScript *noneSelectScript = [[WKUserScript alloc] initWithSource:jsString injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
// 將自定義的 JavaScript 指令碼新增到 WKUserContentController 中
[config.userContentController addUserScript:noneSelectScript];

2、實現 native 呼叫 h5 的方法

native 呼叫 h5 的實現思路是 透過呼叫一個方法,傳入事件和引數,在h5內在分發處理不同的事件,達到簡化程式碼的目的

- (void)channelMessage:(NSString *)event withData:(NSString *)data {
    NSLog(@"data: %@",data);
    NSString *jsCode = [NSString stringWithFormat:@"channelMessage('%@', '%@')", event, data];
    // 呼叫執行js方法
    [self.webView evaluateJavaScript: jsCode completionHandler:^(id _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"Error calling JavaScript: %@", error.localizedDescription);
        } else {
            if ([response isEqualToString:@"sucess"]) {
                NSLog(@"方法執行成功");
            }
        }
    }];
};

3、實現處理 h5 發來的事件請求

端上實現 h5的請求是透過 WKScriptMessageHandler協議方法實現的

 @interface ViewController ()<WKScriptMessageHandler>

實現WKScriptMessageHandler協議方法 實現 h5 傳送訊息呼叫 native 的方法, 透過接收物件,內部包含 message 和 其他需要的引數物件, 分發事件, 如下的呼叫購買、呼叫下載、 開啟聯絡人app

// 3、實現WKScriptMessageHandler協議方法  實現 h5 傳送訊息呼叫 native 的方法 
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController
      didReceiveScriptMessage:(WKScriptMessage *)message {
    if ([message.name isEqualToString:@"AppModel"]) {
        // 假設message.body是一個字串,首先將其轉換為NSDictionary
          NSDictionary *messageData = [NSJSONSerialization JSONObjectWithData:[((NSString *)message.body) dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
       
        // 現在你可以訪問param1和param2了
           NSString *message = messageData[@"message"];
         
        // h5 呼叫native 購買的方法
        if ([message isKindOfClass:[NSString class]] && [message isEqualToString:@"handleBuy"]) {
                NSString *productId = messageData[@"productId"];
               [self buyClickWithProductID:productId];
        }
        
        // h5 呼叫native 下載的方法
        if ([message isKindOfClass:[NSString class]] && [message isEqualToString:@"downLoadVideo"]) {
                NSString *videoUrl = messageData[@"videoUrl"];
                [self downLoadVideo:videoUrl];
        }
        
        // h5 呼叫 native 開啟聯絡人app
        if([message isKindOfClass:[NSString class]] && [message isEqualToString:@"sharedApplication"]) {
            NSURL *url = [NSURL URLWithString:messageData[@"url"]];
            NSURL *appStoreUrl = [NSURL URLWithString:messageData[@"appStoreUrl"]];
            // 檢查應用是否可開啟
            if ([[UIApplication sharedApplication] canOpenURL:url]) {
                // 應用已安裝,開啟應用
                [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
            } else {
                [[UIApplication sharedApplication] openURL:appStoreUrl options:@{} completionHandler:nil];
            }
        }
    
        NSLog(@"message: %@",message);
    }
}

(二)、H5 實現通訊

1、h5 實現主要是通 channelMessage 接收 端發過來的訊息 ( 端上設定呼叫這個函式 )
 window.channelMessage = function channelMessage(event: ListenerNativeMessageKey, message?: string) {
      // 接收處理訊息
      return "success";
 };

2、透過 window.webkit.messageHandlers.AppModel.postMessage 向端傳送訊息
  if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.AppModel) {
    window.webkit.messageHandlers.AppModel.postMessage(messageDataStr);
  } else {
    console.log("The native context does not exist.");
  }

3、透過釋出訂閱模式整合到專案中

爲了更好的後續開發,透過釋出訂閱模式,管理事件的傳送與接收,透過ts 定義好不同的型別,方便檢視和使用時的提示等

// 匯入釋出訂閱模式
import iosPubSub from "./iosPubSub";

//...
// 初始化呼叫下面程式碼
function sendMessageToNative(message, params = {}) {
  const messageData = {
    ...params,
    message,
  };
  const messageDataStr = JSON.stringify(messageData);
  if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.AppModel) {
    window.webkit.messageHandlers.AppModel.postMessage(messageDataStr);
  } else {
    console.log("The native context does not exist.");
  }
}

// native 傳送 h5 訊息
iosPubSub.subscribe(messageKey.sendToNative, sendMessageToNative);

// 監聽 native 傳送來的訊息 event 為訊息型別
window.channelMessage = function channelMessage(event: ListenerNativeMessageKey, message?: string) {
  iosPubSub.publish(event, message);
  return "success";
};
//...

解釋: 在初始化時,訂閱 messageKey.sendToNative 這個事件,繫結一個函式,傳入要傳送的事件名和引數物件。 在使用的時候如下

// 呼叫 native 的購買, 傳遞事件,和引數
iosBridge.pubSub.publish(messageKey.sendToNative, messageKey.handleBuy, { productId: productId });

透過 window.channelMessage 接收端傳送的訊息, 在需要用到的地方 訂閱, 比如訂閱 messageKey.messageNotification 接收端傳送的訊息, 在銷燬時取消此事件的訂閱

  useEffect(() => {
    iosBridge.pubSub.subscribe(messageKey.messageNotification, messageNotification);
    return () => {
      iosBridge.pubSub.unsubscribe(messageKey.messageNotification, messageNotification);
    };
  }, []);

透過 messageKey 的ts 物件定義內部的事件及型別傳參,方便管理不同的事件

三、Android 的實現

(一)、端程式碼實現

1、實現webview的配置

透過 WebAppInterface(this),實現 JavaScript 可以呼叫的方法,在 JavaScript 透過"Android"中用於訪問這個物件的介面名稱

webView.addJavascriptInterface(WebAppInterface(this), "Android")

2、實現 native 呼叫 h5 方法
fun callJsFromAndroid(funName: String, eventType: String, message:String?="") {
    // 確保在主執行緒中呼叫evaluateJavascript
    if (Looper.myLooper() == Looper.getMainLooper()) {
        handleEvaluateJs(funName, eventType,  message)
    } else {
        // 如果不在主執行緒,使用Handler切換到主執行緒
        Handler(Looper.getMainLooper()).post {
handleEvaluateJs(funName, eventType, message)
        }
}
}
fun handleEvaluateJs (funName: String, eventType: String, message:String?){
    webView.evaluateJavascript("javascript:$funName('$eventType', '$message')") {
    value ->
    // 處理 JavaScript 函式返回的結果
        if (value != null) {
            // 如果返回值不為空,執行一些操作
            println("JavaScript 返回值: $value")
        }
}
}

⚠️ 在 webview 的通訊中, 要確保呼叫js 是在主執行緒進行,仍然是在呼叫 js 時將事件型別和引數進行傳遞

3、實現接收 h5 發來的事件
inner class WebAppInterface(private val activity:ComponentActivity) {
    /**
* js 呼叫native的購買 傳入對是物件字串 透過import org.json.JSONObject解構物件
*/
    @JavascriptInterface
    fun handleBuy(productAndUser: String) {
        // 將字串轉換為 JSONObject
        val jsonObj = JSONObject(productAndUser)
        val productId = jsonObj.getString("productId")
        val mainSecId = jsonObj.getString("mainSecId")
        toGooglePay(productId, mainSecId)
    }
}

安卓端接收事件 是透過 WebAppInterface 類實現的 安全考慮配置 @JavascriptInterface 註解

(二)、H5 實現通訊

1、h5 依然是透過 window物件上掛在的 channelMessage 接收 native 傳送來的訊息, 透過publish 觸發事件
 window.channelMessage = function channelMessage(event: ListenerNativeMessageKey, message?: string) {
      androidPubSub.publish(event, message);
      return "success";
  };

在用到的頁面訂閱接收

function showToast(message: string) {
    Taro.showToast({
      title: t(message),
    });
  }
  //...
  useEffect(() => {
    androidBridge.pubSub.subscribe(androidBridgeMessage.showToast, showToast);
    return () => {
      androidBridge.pubSub.unsubscribe(androidBridgeMessage.showToast, showToast);
    };
  }, []);

2、透過 window.Android[messageType]?.(paramsStr) 向 native 傳送事件
 function sendMessageToNative(messageType, params = {}) {
      const paramsStr = JSON.stringify(params);
      if (window.Android) {
        try {
          window.Android[messageType]?.(paramsStr);
        } catch (error) {}
      } else {
        console.log("The native context does not exist.");
      }
    }
    // native 傳送 h5 訊息
    androidPubSub.subscribe(messageKey.sendToNative, sendMessageToNative);

依然是透過釋出訂閱模式與專案進行整合, 在使用的時候 透過 呼叫 messageKey.sendToNative 向native 傳送事件和引數

三、總結

APP 的端內webview與 內嵌的 H5 方案的通訊除了各自透過 介面實現外, 還需要我們有一套更好的方式管理兩端的通訊, 尤其注意webview的通訊要在主執行緒,否則會遇到傳送給h5可以收到,h5 再返回無法收到訊息的情況。 另外就是怎麼能簡化方便的管理事件方法, 與移動端框架更好的結合,也就是訊息驅動檢視更新。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.