在Android開發(fā)的過程中兄墅,很多活動頁面都使用H5進行快速開發(fā)并隨時更新,這樣的話H5與原生的交互就不可避免
H5相關除了Webview類以外诈泼,在使用的過程中還需要用到別的類:
1.WebSettings 從WebView中get膝迎,進行一些設置攘须,比如開啟js支持摧扇,viewport支持(該配置幫助H5進行不同屏幕分辨率的適配)圣贸,使用網頁自身縮放,dom存儲扛稽,數據庫吁峻,多窗口等
2.WebViewClient 主要功能組件,頁面加載完成庇绽,頁面結束锡搜,頁面錯誤等回調橙困,最主要的是通過shouldOverrideUrlLoading方法對url進行攔截以進行客戶端需要的一些操作瞧掺,也在該方法中進行scheme的調用,另外還有其他的資源攔截方法可以用來緩存
3.WebChromeClient 處理關于外層的一些事物凡傅,如網站標題辟狈,Icon,加載進度夏跷,定位權限請求等
在H5與原生的交互中哼转,原生調用H5比較容易,直接使用以下的代碼即可
WebView.loadUrl("javascript:XXX")
對于H5調用來說一般有兩種方法槽华,一個是通過webview.addJavascriptInterface(Object,String)和@JavascriptInterface注解使H5可以直接調用原生的某些方法壹蔓,但這樣做的局限性比較大,不太推薦
另一種方法就是使用scheme:
Android Scheme主要是用來H5與原生頁面的交互猫态,而且這種使用url的交互也可以用于原生佣蓉,后端返回不同的鏈接客戶端做出不同的操作披摄,非常靈活,在信鴿的推送點擊中也很好用
對于這個需求主要分為以下幾個步驟:
1.監(jiān)聽webview發(fā)出的請求
2.對該請求進行解析
3.對解析結果進行相應的操作
1.監(jiān)聽并攔截webview的請求
對于webview請求相關的回調都存在于WebViewClient中
需要完成H5調用需要H5發(fā)起一個url請求勇凭,客戶端在WebViewClient.shouldOverrideUrlLoading()方法中進行攔截
該方法分為兩個版本疚膊,如下:
@RequiresApi(Build.VERSION_CODES.N)//這個注解只是個提示,起不到過濾版本的作用虾标,還是需要進行版本判斷寓盗,否則高版本會兩個方法都調用,N以下版本沒有這個方法
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val url = request.url.toString()
if (url.startsWith(HTTP_URL_PREFIX)) {//不攔截http請求璧函,這里不攔截的話會在同一個webview中打開新的頁面傀蚌,如有需求可以進行攔截并使其打開新的頁面
return super.shouldOverrideUrlLoading(view, request)
}
return SchemeUtil.openScheme(activity!!, url, fragment)//處理其他請求
}
return false//返回false不攔截
}
@Suppress("deprecation")
override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
if (url.startsWith(HTTP_URL_PREFIX)) {
return super.shouldOverrideUrlLoading(view, url)
}
return SchemeUtil.openScheme(activity!!, url, fragment)
}
return false
}
2.解析請求
第一步里過濾了http請求不進行scheme的相關流程
所以需要進行scheme調用的就前端和客戶端約定一套schme,可以使用 項目名://+方法名+.項目名?json_params={} 格式柳譬,加兩個項目名來過濾以防錯誤調用(保險的話可以用更復雜的名字或者加密喳张,一般不太需要)
然后就是對某個具體的scheme進行解析:
//第一步解析url獲取參數,protocolStr為完成的scheme鏈接美澳,fragment為發(fā)起請求的fragment销部,對于webview來說可以在創(chuàng)建webviewclient時將activity和fragment傳進來方便后續(xù)使用
//這個方法就是將scheme解析為{String方法名}和{JSONObject參數}
fun openScheme(context: Context,protocolStr:String,fragment: Fragment?){
//防抖
if(System.currentTimeMillis() - lastTime < GAP_OF_SAME_SCHEME
&& TextUtils.isEmpty(protocolStr)
&& protocolStr == lastHost)return
lastTime = System.currentTimeMillis()
lastHost = protocolStr
/**
* foo://example.com:8042/over/there?name=ferret&psw=123#nose
* \_/ \______________/\_________/ \_________________/ \__/
* | | | | |
* scheme authority path query fragment
*
*
*/
try {
val uri = URI(protocolStr)
val host = uri.authority
val query = protocolStr.substring(protocolStr.indexOf('?') + 1)
var paramObj:JSONObject? = null
if(query!=null) {
val paramStr = query.split("&")
val paramMap = HashMap<String, String>()
for (pairStr in paramStr) {
val pair = pairStr.split(Regex("="),2)
if (pair.size == 2) {
paramMap[pair[0]] = StringUtil.decodeString(pair[1])
}
}
// 獲取json_params參數
val json = paramMap[mSchemeKeyParams]
paramObj = if (json == null) null else JSONObject(json)
}
var type:String? = null
if (host!=null && host.indexOf(".") != -1){
type = host.substring(0,host.indexOf("."))
}
openScheme(context,type,paramObj,fragment)
}catch (e: Exception){
e.printStackTrace()
}
}
3.對解析結果進行處理
這里拿到方法名和參數以后就可以進行對應的操作
有很多方法可以進行,最直接的方法可以使用switch-case在原地進行處理制跟,但這樣就是造成一個類過于龐大導致可讀性可維護性下降舅桩,而且即使調用別的類的方法也需要每次添加scheme都在這里修改,不符合開閉原則
這里推薦使用反射雨膨,具體操作如下:
1.新建一個scheme處理接口擂涛,比如:
interface ISchemeFilter {
fun process(context: Context, paramObj: JSONObject?, fragment: Fragment?)
}
2.創(chuàng)建一個上面接口的實現類,比如:
class OpenXXXFilter:ISchemeFilter {
override fun process(context: Context, paramObj: JSONObject?, fragment: Fragment?) {
XXXActivity.open(context)
//如果簡單的操作在這里可以完成則到此結束聊记,如果不行則使用context和fragment進行相應方法的調用
}
}
3.建立一個Map撒妈,以方法名為鍵,具體實現類的類名 //這個可以不用排监,可以直接使用方法名作為類名狰右,但是不太靈活
4.通過方法名查詢到對應的類名,并使用該類名創(chuàng)建實例并調用其process方法舆床,具體代碼如下:
val schemeFilterClassName = schemeMap[type]
schemeFilterClass = Class.forName(schemeFilterClassName!!)
val constructor = schemeFilterClass!!.getConstructor()
val schemeFilter = constructor.newInstance() as ISchemeFilter
schemeFilter.process(context, paramObj, fragment)
//注意判空與try-catch