從簡書iOS客戶端,來談談Hybrid方案細節設計

halohily發表於2017-08-21

作為一位 iOS 開發人員,你應該已經敏感地發現,自己的工作涉及內容已經不止於 Native 的部分,因為 Hybrid App 和 ReactNative 等技術方案已經不僅僅是概念,越來越多的公司開始著手自己的 Hybrid 方案以及 ReactNative 本地化工作。

一、引言

介紹相關概念的優秀文章已經有許多,方案的實現原理你也應該已經或多或少有了一些理解。不瞭解也沒有關係,在這篇文章裡,我將用簡書 iOS 客戶端的有關特性,來探索一下 Hybrid 方案的技術細節。文章的目的是拋磚引玉,用一個具體的專案,大家很熟悉的簡書客戶端,來幫助大家認識 Hybrid 方案,然後親自實現它

從現在開始,不再著眼於某一個 feature ,你需要站在一個客戶端架構師的角度來看待問題。

二、我們用到的簡書客戶端特性

1.介面構成分析
  • 本文的主角是簡書 iOS 客戶端的文章展示頁面,這是我的一篇文章的展示頁面:
    文章展示頁面
    文章展示頁面
  • 如你所見,文章內容的展示是使用webview控制元件,具體是UIWebview還是WKWebview按下不表,這不是本文的關鍵。在我的demo中,我使用了UIWebview
  • 在簡書發現tab欄的內容頂部,還有一個熱門內容推薦的輪播圖。與它類似是一些app內的活動推介輪播圖,以及廣告頁面,它們的詳情頁內容展示多使用webview。在簡書中,這個輪播圖對應的下一級頁面也是文章展示頁面,特性基本一致。
    熱門內容推薦輪播圖
    熱門內容推薦輪播圖
  • 在 webview 的基礎上,新增了符合瀏覽器使用者習慣的導航欄按鈕。包括左側的返回關閉按鈕。以及右側的功能列表按鈕。
  • 頁面底部,是一個工具欄,提供了四個常用的操作。注意這裡的評論按鈕,它是我們下文的一個談論點。
2.介面特性分析
  • 一般各家客戶端的內容頁,都會有一些適於自己功能點的設計。簡書也不例外。比如,在文章內容區域點選作者的頭像(它本身也是網頁的一部分,暫且理解為對應一個連結),跳轉到了作者的個人主頁,注意,容易發現它是一個客戶端的原生頁面,也就是一個VC。
    作者個人主頁
    作者個人主頁
  • 點選底部工具欄中的評論按鈕(原生元件),頁面(web頁面)會滑動到評論區域,如圖
    點選評論按鈕,頁面滑動
    點選評論按鈕,頁面滑動
  • 對一篇文章寫下自己的評論(使用了原生元件),評論列表(網頁內容)進行更新。
  • 簡書對於展示內容作了內外站的區分。據我自己的簡要測試,來自簡書域名www.jianshu.com下的內容,在載入過程中,是沒有進度條的,使用者體驗非常接近原生頁面。而第三方的內容,則在載入過程中會出現一般瀏覽器中常見的載入進度條,如圖:
    第三方內容的載入進度條
    第三方內容的載入進度條
  • 對於簡書域名下的內容,不會出現叉號的關閉按鈕,這也是為了營造接近原生頁面的使用者體驗,讓使用者不會察覺到這是一個 web 介面。而第三方內容,則會出現符合瀏覽器使用習慣的關閉按鈕,如上圖。

三、我們需要的儲備知識

1.Hybrid相關
  • 在Hybrid架構中,原生介面和web頁面需要頻繁地溝通,並且是雙向的溝通。原生程式碼可以構建JavaScript語句,交由webview進行執行,從而在web頁面上實現需要的效果。而在web頁面的js檔案中,也可以呼叫原生的Objective-C方法,從而執行一些原生方法才能完成的操作。與此相關的庫有WebViewJavascriptBridge以及JavaScriptCore,有需要的同學可以自行了解。
2.UIWebview的相關特性
  • UIWebviewDelegate
    webview的代理方法大家想必非常熟悉,我們可以在頁面載入前、開始載入時、載入完成時以及失敗時進行需要的操作。這裡我們需要用到的是這一條代理方法:
    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
    webview根據它的返回結果來決定是否進行載入。
  • 執行JS語句的方法:
    - (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
    我們可以自行構建一條JS語句,通過這個方法交由webview執行
  • goback相關
    UIWebview擁有布林型別的canGoBackloading等屬性,通過監測它們的值我們可以知道當前頁面是否可以進行回退,以及頁面是否正在載入。
    與之對應,擁有- (void)goBack;等方法,呼叫之頁面會進行返回,就像我們在瀏覽器中常見的那樣。

四、相關特性的模仿實現

對於上文中提到的相關特性,我出寫了一個demo,對它們進行了簡要的模仿實現。當然簡書官方的實現會考慮到方方面面,而我的demo僅是從Hybrid架構的思想出發,盼能夠拋磚引玉。
這是demo中對該頁面的模仿實現:

demo頁面
demo頁面

1.頁面初始化

在demo中,使用一條web頁面的URL來初始化VC:- (instancetype)initWithURL:(NSURL *)URL;這條URL對應文章的連結。
頂部導航欄和底部工具欄都是系統原生的UINavigationBarUIToolBar,按鈕素材使用阿里巴巴的iconfont字型。

2.點選作者頭像進入個人主頁

關於這個特性的實現,如果按照 Hybrid 架構的思想,屬於 Web 頁面呼叫原生方法,進入一個原生的VC。點選頭像,JS指令碼執行相關程式碼,呼叫原生方法暴露出來的介面,執行原生方法。
我在這裡用一種簡要的方法實現:原生程式碼利用之前提到的代理方法,在使用者點選頭像後,攔截該URL,分析URL為頭像部分,直接執行原生方法跳轉到個人主頁VC。
通過分析簡書文章頁面的網頁原始碼,我發現使用者頭像對應的URL中的Query部分,有一個引數為utm_medium=note-author-link。據此,在- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType代理方法中加以判斷,若是頭像連結,則跳轉到個人主頁VC。下面是相關程式碼:

NSURL *destinationURL = request.URL;
    NSString *URLQuery = destinationURL.query;
//    簡書點選文章中頭像時跳轉至原生頁面。此處利用頭像連結中的一個引數作判斷
    if ([URLQuery containsString:@"utm_medium=note-author-link"])
    {
        NSLog(@"我跳轉到個人主頁啦");
        AvatorViewController *avatorVC = [[AvatorViewController alloc] init];
        [self.navigationController pushViewController:avatorVC animated:YES];
        return NO;
    }複製程式碼

最後返回NO是因為若是頭像連結,該web頁面是不需要做跳轉操作的。

這裡順便講一個小tips:如果想要在Mac端檢視移動端的網頁原始碼,那麼你只需要在Safari中輸入該頁面,並且在開發選項下的使用者代理中,選擇iOS系統下的Safari作為代理,這時再使用原始碼檢視,看到的就是移動端的網頁原始碼了。

3.點選評論按鈕,頁面滑動到評論區域

這個特性的實現方式和上面類似,點選評論按鈕,原生程式碼構建一條JS語句,交由- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;方法進行執行,由web頁面執行滑動操作。程式碼如下:

- (void)scrollToCommentField
{
    [self stringByEvaluatingJavaScriptFromString:@"scrollTo(0,20500)"];
}複製程式碼

這裡的JS語句非常簡單,由於筆者的前端知識還有所欠缺,沒有想到可以精確滑動的評論區域的JS語句,所以簡要實現,點到為止。

4.原生元件寫評論,web頁面更新

這裡首先需要貼一下文章頁面的網頁原始碼:

<!-- 評論列表 -->
  <div data-vcomp="comments-list" data-lazy="3">
    <script type="application/json">
      {"likedNote":true,"commentable":true,"publicCommentsCount":3,"noteId":2491941,"likesCount":43}
    </script>
  </div>複製程式碼

可以看到,頁面的評論內容是非同步載入的。所以這個功能的實現,我
認為比較合理的邏輯是原生元件向伺服器提交一條新的評論,收到成功回撥之後,原生元件和web頁面進行互動,執行更新並載入評論列表的JS程式碼,從而看到自己發的新評論。

5.內外站頁面的區分

這裡和點選頭像的實現方法類似,通過攔截連結的URL,區分內部連結和第三方連結,從而在開始載入的時候採用不同的載入介面,或者對於第三方連結單獨開啟一個第三方VC。
demo中關於第三方連結的關閉按鈕的顯示邏輯,做出了相應的處理。

五、demo中的不足

看到這裡大家應該就會發現,對於提到Hybrid我們就會想到的Bridge、Router等模組,我並沒有做明顯的限定。這樣也是為了方便大家用一種更接近以往原生程式碼編寫的思維,來理解Hybrid模式。
同時,demo中較多涉及了原生程式碼對web頁面做出的溝通操作。而沒有JS程式碼對原生程式碼的呼叫,這是因為一來站在一個簡書客戶端的使用者和iOS開發的角度,對於JS端執行的操作,有些力不能及,這本是和你共同工作的前端夥伴的任務,二來對於一篇幫助大家入門Hybrid的文章來說,從這個單方面的互動來入手,管中窺豹,已是足夠。

六、一些感悟

其實,寫了這麼多,我覺得收穫到一些感悟是最重要的,下面的要講的,可能是我覺得更為重要的思想性的東西。

1.未來的趨勢之一,便是大前端團隊進行客戶端開發。
  • 看到這裡你發現,如果你們的團隊想要採用Hybrid模式進行產品的開發,光靠iOS或者是安卓的客戶端工程師是不可能完成的。在客戶端框架的開發過程中,需要和前端的工程師溝通具體的技術細節。比如怎樣設計介面能夠更好地兼顧客戶端和前端特點,對於某個問題,如何能把握全域性而不是單單從客戶端的角度來看待。這些可能是普通的iOS開發工程師和大牛的差距所在之一。
  • 越來越多的客戶端工程師招聘要求中,出現了熟悉前端語言的要求。如果你能在精通客戶端開發之餘,對前端語言也遊刃有餘,那麼在接下來的發展趨勢中,就會有更多的可能性。所以,請開始你的前端學習吧~。
2.在Hybrid模式下,如何進行產品技術方案的取捨
  • 如前文所見,簡書客戶端對於內部域名的內容和第三方內容,在展示方式上是有明顯不同的。在閱讀簡書的文章時,讓使用者發現不了自己是在一個瀏覽器上進行閱讀,這在方方面面就極大改善了使用者體驗。為了做到這一點,我推測簡書首先需要對自己的內容進行非常良好的CDN加速,以保證內容載入時不會耗時過長,同時採取一些預載入策略,二是在內容載入時,採用與原生介面部分相同的loading介面,去掉進度條,模擬原生介面的載入過程。而對於第三方的連結,採用進度條+返回、關閉按鈕的設計,則更符合使用者在瀏覽器中進行閱讀的習慣,也可以和自己的內容進行直觀區分,這也改善了使用者體驗。
  • 對於某些原生和Web頁面都可以實現的特性如何取捨,這也是需要考慮的問題。比如,點選評論按鈕頁面滑動,這個功能使用web頁面的滑動而非原生的控制顯然更為自然也更符合使用者習慣。而對於撰寫評論的功能,使用原生的鍵盤、編輯器元件,當然就比使用web頁面的鍵入更加穩定可控了。

七、文章的demo

對於文章內容,我寫了一個demo,這是demo地址
為了便於理解,我為程式碼寫了詳盡的註釋。
如果覺得它對你有幫助,不妨在github上為我點一個star~非常感謝!

相關文章