Android中JS與Java的極簡互動庫-SimpleJavaJsBridge

牛犇發表於2016-10-09

前言

最近接觸android中js與java互動的東西很多,當然它們之間的互動方式有幾種,但是我覺得這幾種互動方式都存在一定的不足,這是我決定編寫SimpleJavaJsBridge這個庫的關鍵原因。

我會按以下順序進行本文章:

  1. 現有js與java通訊方案及不足
  2. js與java完美通訊方案設計
  3. SimpleJavaJsBridge

現在進入正題

1. 現有js與java通訊方案及不足

先來說明一點js與java通訊,指的是js既可以給java傳送訊息,同時java也可以給js傳送訊息。那就來屢屢它們之間的通訊方案。

1.1 java給js傳送訊息
官方唯一指定方法是通過webview的loadUrl(String)方法進行的,看下的虛擬碼:

呼叫方法非常的簡單,”javascript:”+js方法的名字+方法的引數值拼接成一個字串就可以給js傳送訊息了,猶如是在直接呼叫js的方法。

1.2 js給java傳送訊息
js給java傳送訊息實際上只有2種方案,依次來分析下這2種方案。
1.2.1 官方方法
先看下虛擬碼:

這種方法其實是把一個物件注入到WebView中,js給java傳送訊息的方式是

這其實也猶如在java程式碼中呼叫java的方法,因為java提供給js的方法名,方法引數是啥,js在傳送訊息時,方法名與引數必須保持一致,這也是這些java程式碼不能進行混淆的原因。

但是這種方法存在一個嚴重的漏洞,雖然官方在android4.4的時候給出了相應的解決方案,但是android4.4以下的版本還得解決該漏洞,因此一些巨人們就開始琢磨著解決這個坑,第二種方法由此誕生。

1.2.2 js傳遞約定好的字串給java
這種方案的主要原理是:

  • 找到一個js可以給java傳送訊息的入口(這個入口有onJsPrompt,onJsAlert等等)
  • js通過入口把訊息按既定好的規則拼接成字串傳遞給java
  • java按照既定好的規則對字串進行解析
  • 根據解析資料,通過反射來呼叫自己相應方法

這種方法使用起來要比官方方法(第一種方法)麻煩。

1.3 存在的不足
上面介紹了js與java的通訊方法,那我就來分析下我認為存在的不足。
1.3.1 java給js傳送訊息方法和js給java傳送訊息的官方方法存在的不足
1.3.1.1 強依賴
java給js傳送訊息的方法,和js給java傳送訊息的官方方法都存在著強依賴的問題,都要高度依賴對方的方法名字,方法引數。強依賴發生於同一模組內,個人覺得不是問題甚至是高內聚的體現。但是java與js可以說是處於兩個不同的模組或者叫兩個不同世界,只要js提供給java的方法發生變化,java也得改動,同理java提供給js的方法也如此。處於兩個不同模組知道對方的細節越少越好,這樣耦合性就會降低,耦合性降低的好處就不用說了。

1.3.1.2 強依賴導致js需要相容不同的系統
先看段虛擬碼:

上面的程式碼展示的是js使用native的定位功能的程式碼,因為js在給不同的系統傳送訊息的方式不一樣,就會出現if else if 這樣的相容語句。當前js程式碼只被ios和android使用,假如還會被wp或pc來使用,那if else if豈不是要噁心死。產生該問題的主要原因是:js程式碼在針對不同的系統自己獨有的通訊方式進行通訊。

1.3.1.3 給不存在的介面傳送訊息沒反饋
java在給js的一個不存在介面傳送訊息時,java根本不知道該介面不存在,java只會傻傻的等待。同理js在給java的一個不存在介面傳送訊息時,js是可以通過捕獲異常來知道該介面不存在,但是這不是最好的解決方案。
給不存在介面傳送訊息沒反饋會導致js程式碼充斥著if else if語句,看段虛擬碼:

這是一段呼叫java進行定位的js程式碼,android app在版本1.1的時候才增加了定位的功能,因此對於1.1以下版本是不支援這功能的,因此js程式碼裡面非常有必要根據版本號進行判斷。這只是由於版本問題導致if else if的一個小小的縮影。還有一些其他情況導致if else if的產生比如一份js程式碼被多個業務使用。

1.3.2 js給java傳送訊息的第二種方法存在不足
上文提到的js給java傳送訊息的第二種方法,它解決了存在的漏洞,但是這種方法,使用起來要比第一種方法複雜,java會多做以下工作:

  • 解析js傳遞給java的字串,把呼叫的介面,引數解析出來
  • 把呼叫的介面,引數對映到相應的方法

不論js傳遞給java的字串是json格式還是其他格式,解析這樣的字串肯定是一件無趣的重複的體力勞動。

若想解決以上的問題,我們有必要設計一套完美的通訊方案。

2. js與java完美通訊方案設計

2.1 一套完美的js與java的通訊方案應滿足以下幾點:

  • js與java知道對方的細節越少越好,越少它們的耦合性越低。那到底多少為好呢?我個人覺得互相暴漏給對方一個介面足矣。這樣js與native的通訊就類似於通過一個管道通訊或者說類似於socket通訊(降低強依賴)
  • js與java之間通訊,需要定義好一套通訊協議或者叫通訊規則,在管道之間傳遞通訊協議。這樣它們之間的通訊是針對一套定義好的協議進行的,而不是針對每個系統自己獨有的通訊方式(好處js就不會出現相容不同的系統的if else if程式碼)
  • 主動傳送訊息給對方時,對方必須對該訊息予以反饋,即使主動傳送訊息者對反饋訊息不感興趣,(反饋資訊可以去掉由於版本相容等帶來的if else if相容程式碼)

2.2 那我們就開始設計js與java之間的通訊方案
2.2.1 互相暴漏給對方一個介面

  • js為java提供一個唯一的介面,這個介面可以是在java端寫死的,也可以是js傳遞過來的(這樣更靈活)。所有傳送給js的訊息(請求訊息和反饋訊息)都通過該介面
  • java為js提供的一個唯一的介面,因為官方的方法存在漏洞,我們採用在onJsPrompt方法中接收js傳送的所有訊息,當然大家還可以選擇其他方法來接收js的訊息,這不是重點。

2.2.2 js與java之間通訊協議的制定

js與java之間的通訊特別類似於網路請求,主動發起訊息的行為可以稱為request(請求訊息),對該訊息的反饋可以稱為response(響應訊息)。

request
一個request封裝了請求對方的哪個介面,以及該介面所需要的引數。

response
一個response封裝了狀態資訊(可以知道處理的結果是成功還是失敗)和處理結果。

如何接收對方傳送的response訊息?

大家都應該都會想到,在傳送訊息的時候傳遞一個回撥介面就行了,但是因為js與java之間是跨語言的,尤其是java是不可能把回撥介面傳遞給js,js雖然可以傳遞過來但是會有問題,所以這時候有一種解決辦法:

  • 在給對方傳送request訊息時,為回撥介面生成一個唯一的id值,把id值存入request中發出。
  • 同時把回撥介面快取起來。
  • 在接收到response時,從response解析這個id值,根據id值查詢到回撥介面。

因此request和response中還得包含回撥id這個值。

通訊協議的格式
request資料格式:

response資料格式:

到此通訊協議就已經定義好了。

2.2.3 讓繁瑣的無趣的重複的苦力活兒不再有
大家可以看到通訊協議request和response都是json格式,從json中解析資料或者把資料封裝為json都是重複的苦力活兒。
這也是我一直想著力解決的痛點,解決之道是從retrofit中獲得啟發的,應用註解來解決以上問題。

關於js與java完美通訊的設計思想到此為止,這也是SimpleJavaJsBridge這個庫的核心思想,那我們就來看下SimpleJavaJsBridge。

3. SimpleJavaJsBridge

SimpleJavaJsBridge我為什麼要起一個這樣的名字,首先它解決了上文中提到的讓繁瑣的無趣的重複的苦力活兒不再有的問題,對於不管是從json中解析資料還是把資料封裝成json,使用者都不需要關心,讓使用者很省心;並且它使用起來也非常的簡單,在稍後的例子中大家會體會到,所以用了simple這個詞兒。通過它java可以給js傳送訊息,並且接收js的響應訊息;同時js也可以給java傳送訊息,同樣接收java的響應訊息。因此它是java與js之間通訊的橋樑,因此它的名字叫為SimpleJavaJsBridge。

3.1 如何解決繁瑣的無趣的重複的苦力活兒?
解決這個問題思路來自於鼎鼎有名的Retrofit,Retrofit通過註解的方式解決了構建request和解析response的問題,因此註解也可以解決我現在遇到的問題。那我們就來認識下這些註解。
InvokeJSInterface
用來標註java給js傳送訊息的方法,它的value值代表js提供的功能的介面名字
JavaCallback4JS
用來標註java提供給js的回撥方法的
JavaInterface4JS
用來標註java提供給js的介面,它的value值代表功能的介面名字
Param
用來標註引數或者類的例項屬性,它的value值代表引數被存入json中的key值,它的needConvert代表當前的引數是否需要進行轉換,因為通過JsonObject類往json中存放的資料是有要求的,JsonObject中只能存放基本資料和JsonObject和JsonArray這些資料型別,對於其他的型別就得進行轉換了。因此只要是不能直接通過JsonObject存放的型別該值必須為true
ParamCallback
用來標註回撥型別的引數,比如傳送request給js的方法中,需要有一個回撥引數,那這個引數必須用它來標註
ParamResponseStatus
用來標註響應狀態型別的引數,比如:statusCode,StatusMsg這些引數,它的value值是json中的key值。

3.2 SimpleJavaJsBridge使用
3.2.1 構建一個SimpleJavaJsBridge例項

通過SimpleJavaJsBridge.Builder來構建一個SimpleJavaJsBridge物件,

  • addJavaInterface4JS用來新增java提供給js的介面
  • setWebView 設定WebView這是必須進行設定的
  • setJSMethodName4Java 設定js為java唯一暴漏的方法名字
  • setProtocol設定協議欄位,這也是必須的,這個欄位主要是為了ios而設定的

當然還可以呼叫其他的一些方法對SimpleJavaJsBridge進行設定

3.2.2 java給js提供介面
java給js提供一個無參的介面

java給js提供帶有引數的介面

3.2.3 給js傳送訊息

總結

SimpleJavaJsBridge庫在js與java的通訊中帶來以下優點:

  • js程式碼中不再有由於系統或者app版本甚至業務原因產生的if else if的相容語句
  • java不需要再關心資料封裝為json或者從json中解析資料的繁瑣工作
  • 讓js與java之間的通訊更簡單

若你動心了可以下載來試用下:SimpleJavaJsBridge

參考:大頭鬼的https://github.com/niuxiaowei/JsBridge.git

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式

Android中JS與Java的極簡互動庫-SimpleJavaJsBridge Android中JS與Java的極簡互動庫-SimpleJavaJsBridge

相關文章