利用C#開發移動跨平臺Hybrid App(一):從Native端聊Hybrid的實現

0x00 前言

前一段時間分別讀了兩篇博客,分別是葉小釵兄的《淺談Hybrid技術的設計與實現》以及徐磊哥的《從技術經理的角度算一算,如何能夠多快好省的作個app》。受到了不少啓發,同時也有本身的一些見解。由於目前三大平臺(雖然wp的份額相對於iOS以及android來講十分可憐)的開發語言分別是Objective-C(swift)、Java以及C#,先不論平臺的其餘特性如何,單單是各個平臺開發語言就已經不一樣了。而每每一個App要同時登錄不一樣的多個平臺,那麼如何可以快速的開發跨平臺的App,儘可能使得代碼複用便成了一個開發者不得不面對的問題。javascript

正如葉小釵兄在他的那篇博文中所說的:「這個時候使用IOS&Andriod開發一個APP彷佛成本有點太高了,而H5的低成本、高效率、跨平臺等特性立刻被利用起來造成了一種新的開發模式:Hybrid APP。」——利用Web配合Native開發的Hybrid APP出現了。因此在正式介紹C#開發跨平臺App以前,我以爲有必要先從不使用C#語言進行開發的另外兩個平臺(iOS~OC、Android~Java)的角度來聊聊Hybrid。須要提醒各位注意的是本文中我所使用的一些web端代碼來自葉小釵兄所分享的web端「簡單Hybrid框的實現」的代碼 。html

本文所使用的iOS端Hybrid的代碼能夠在這裏查看:前端

https://github.com/chenjd/iOS-Hybrid-Samplejava

0x01 你好,WebView

因爲Hybrid開發的Web端本質上是一些網絡頁面,所以在使用App的相關功能時至關於利用原平生臺去訪問一些網頁。正是由於內容來源於網頁而不是依賴於平臺,所以也就變相的實現了跨平臺。那麼對於iOS和Android來講,它們須要在Native端提供一個用來訪問頁面的容器——WebView。android

iOS的UIWebView

在iOS平臺咱們會使用UIKit.framework的UIWebView來訪問頁面。利用UIWebView控件,iOS平臺既能夠獲取網絡資源也能夠加載本地的HTML代碼。這裏主要會使用的是如下三個方法:git

UIWebView控件中加載資源的方法
- (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)encodingName baseURL:(NSURL *)baseURL
- (void)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL
- (void)loadRequest:(NSURLRequest *)request

 

 

下面咱們就利用這些方法,在iOS平臺上使用UIWebView控件來訪問葉小釵兄所提供的web頁面。github

//ViewController.h
@interface ViewController : UIViewController
{
    UIWebView *webView;
}

//ViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];
    webView = [[UIWebView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    
    //load yexiaochai's html
    NSBundle *thisBundle = [NSBundle mainBundle];
    NSString *path = [thisBundle pathForResource:@"webapp/hotel/index" ofType:@"html"];
    NSURL *baseURL = [NSURL fileURLWithPath:path];
    NSURLRequest *urlReq = [NSURLRequest requestWithURL:baseURL];
    [self.view addSubview: webView];
    [webView loadRequest:urlReq];

在iOS模擬器中運行,咱們能夠看到在Native端經過UIWebView已經加載出了Web端的內容。web

固然,此時咱們只是邁出了第一步,即在Native端渲染出Web端的內容。可是尚未涉及到Native和Web之間的交互。既然說道了Native和Web交互,那麼便不得不提的是另外兩個重要的方法:json

UIWebView控件中涉及Native和Web交互的方法swift

- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType

 

 

除此以外,iOS的UIWebView還有一些頁面可否前進後退的屬性以及控制它們前進後退的方法:

UIWebView控件中關於頁面前進後退的屬性和方法

canGoBack
canGoForward
- (void)goBack
- (void)goForward

 

初步介紹完iOS的UIWebView以後,再讓咱們來看一看Android平臺上的Web頁面的容器——WebView。

Android的WebView

在Android的部分,咱們首先須要知道的是在Andorid平臺上咱們使用的是android.webkit下的WebView來訪問頁面。和iOS平臺相似,Andorid平臺是經過利用WebView來實現獲取網絡資源或是加載本地的HTML代碼的。須要提醒各位注意的一點是,爲了使Activity可以正確的鏈接上網絡,在使用WebView加載遠程資源時務必確保在AndroidManifest文件中開啓INTERNET權限:

<uses-permission android:name="android.permission.INTERNET" />

而WebView加載資源的方法主要包括如下幾個:

WebView加載資源的方法
public void loadUrl(String url);
public void loadUrl(String url, Map<String, String> additionalHttpHeaders);
public void loadData(String data, String mimeType, String encoding);
public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl);

下面咱們就利用這些方法,在Andorid平臺上使用WebView控件來訪問葉小釵兄所提供的web頁面。

首先咱們要在Native端添加一個WebView控件,經過在layout文件中添加<WebView>就能夠十分容易的實現。

<?xml version="1.0" encoding="utf-8"?>
<WebView  xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/webview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
/>

固然,除了在layout中添加<WebView>元素以外,咱們能夠直接經過代碼在Activity設置爲WebView,

WebView webview = new WebView(this);
setContentView(webview);

不過下文仍是假設咱們在layout中添加了<WebView>元素。
此時,咱們在Native端已經準備好了盛放Web頁面的容器。接下來即是加載Html資源了。

WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.loadUrl("file:///android_asset/webapp/hotel/index.html");

到這裏咱們便再次將葉小釵兄的Web頁面渲染了出來。
和上文描述iOS相似,此時咱們也僅僅是邁出了第一步,即在Native端渲染出Web端的內容。可是尚未涉及到Native和Web之間的交互。不過Android平臺和iOS平臺固然也存在着區別,例如默認狀態下Andorid的WebView是不支持js的,因此若是咱們僅僅是將一個web頁面做爲一個沒有交互的靜態UI是能夠的,可是若是想要使用js進而實現Native和Web的交互,那麼還須要多設置一些內容。這裏咱們須要獲取WebView的WebSettings對象,在WebSettings中開啓對js的支持。

WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);

好了,開啓了對js的支持,也就是開啓了對Native和Web交互的支持。而Android平臺主要經過如下方法來實現Native調用web以及web調用Native的:

  • public void evaluateJavaScript(String script, ValueCallback<String> resultCallback);方法能夠在Native端調用JS代碼,固然這裏存在一個Android版本的問題,下文還會詳細說明。
  • 另外咱們須要使用JavascriptInterface.java,經過這個interface來完成Web調用Native的實現。

除此以外,Android的WebView固然還有和iOS的UIWebView相似的用來控制頁面行爲的一些方法:

  1. canGoBack();
  2. goBack();
  3. canGoForward();
  4. goForward();

0x02 Native端調用JS

在上一小節中我分別向你們介紹了iOS和Android在Native端是如何作到既可以訪問Native本地資源,又可以訪問遠程資源的。這樣在App開發的過程當中將部分經常須要更新的模塊以Web的形式建立,不只僅避免了用戶升級App整包替換的狀況,又可以比較快速的實現多平臺更新迭代

而這一切的背後都離不開Native和Web之間的交互。這種交互提及來無非是Native調用Web的方法,或是Web調用Native的方法。那麼在本小節,我就和大夥分別聊聊iOS和Android是如何調用Web中的方法的。

iOS Native調用JS

//ViewController.h
@interface ViewController : UIViewController
{
    UIWebView *webView;
    NSMutableString *msg;
    UITextField *msgText;
    UIButton *nativeCallJsBtn;
}

//ViewController.m
- (void)createNativeCallJsSample{
    [webView loadHTMLString:@"<html><head><script language = 'JavaScript'>function msg(text){alert(text);}</script></head><body style=\"background-color: #0ff000; color: #FFFFFF; font-family: Helvetica; font-size: 10pt; width: 300px; word-wrap: break-word;\"><button type='button' onclick=\"msg('Js調用')\" style=\"margin:30 auto;width:100;height:25;\">web button</button></body></html>" baseURL:nil];
    //建立一個UITextField,用來在native調用js時向js的函數傳送參數
    [self createNativeTextField];
    //建立一個按鈕,用來演示Native調用js
    [self createNativeCallJsButton];
}
...
//native 調用 js
- (void)btnClickMsg{
    [msg setString:@"msg('"];
    [msg appendString:@"native調用js:"];
    [msg appendString:[msgText text]];
    [msg appendString:@"')"];
    [webView stringByEvaluatingJavaScriptFromString:msg];
}

咱們首先使用UIWebView的loadHTMLString方法,直接加載了一段Html代碼。其中的JS部分定義了一個函數function msg(text){alert(text);},即調用alert方法。而在Native端,咱們使用UIWebView的stringByEvaluatingJavaScriptFromString方法來實現對JS的調用。

最左邊的截圖是直接點擊Web頁面中HTML代碼生成的Button以後觸發的JS的msg方法。

而中間和右邊的截圖,則是經過Native的UITextField控件得到用戶的輸入以後再經過Native的按鈕控件調用btnClickMsg方法,進而調用stringByEvaluatingJavaScriptFromString方法以後,從Native端執行了JS的msg方法。

Android Native調用JS

Andorid平臺與iOS在調用JS的部分十分相似。不過須要提醒各位注意兩點,其一就是咱們在上文提過的,Android的WebView默認狀態下沒有開啓和JS交互的功能。這裏咱們須要獲取WebView的WebSettings對象,在WebSettings中開啓對js的支持。第二點須要注意的是Android的版本問題。在Kitkat(4.4)以後,咱們可使用evaluateJavaScript方法來調用JS,而以前的版本咱們直接調用WebView的loadUrl的方法,將JS代碼做爲參數傳入。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    WebView myWebView = (WebView) findViewById(R.id.webview);
    WebSettings webSettings = myWebView.getSettings();
    //開啓js支持
    webSettings.setJavaScriptEnabled(true);
    myWebView.loadData("<html><head><script language = 'JavaScript'>function msg(text){alert(text);}</script></head><body><button type='button' onclick= 'msg()'>web button</button></body></html>", "text/html", "utf-8");

    evaluateJavascript(myWebView);
}

....
//Native調用JS
public static void evaluateJavascript(WebView mWebview) {
    //判斷版本調用不一樣的方法
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        mWebview.evaluateJavascript("msg('native call js')", null);
    } else {
        mWebview.loadUrl("msg('native call js')");
    }
}
View Code

0x03 Web調用Native

Hybrid交互設計

我想行文至此,各位應該已經明白了兩大平臺(iOS和Android)的Native端是如何調用JS代碼的了。那麼本小節則主要來聊一聊Web是如何調用Native的。Web和Native之間交互的第一步即是要約定好格式。因爲我直接使用了葉小釵兄的Web部分,所以按照他設計的Web請求Native的模型:

requestHybrid({
  //建立一個新的webview對話框窗口
  tagname: 'hybridapi',
  //請求參數,會被Native使用
  param: {},
  //Native處理成功後回調前端的方法
  callback: function (data) {
  }
});

//index.html中的實際例子
requestHybrid({
    tagname: 'forward',
    param: {
        topage: 'webapp/flight/index',
        type: 'webview'
    }
});

而Native端會收到一個URL,例如:

hybrid://forward?t=1447949881120&param=%7B%22topage%22%3A%22webapp%2Fflight%2Findex%22%2C%22type%22%3A%22webview%22%7D
十分清楚,這裏咱們交互使用的schema是hybrid://,在Native部分會監控Webview發出的全部schema://請求,而後分發到「控制器」hybridapi處理程序,Native控制器處理時會須要param提供的參數。下面就讓咱們來分別看一下iOS和Android是如何使用Web調用Native的吧。(這裏我只是簡單的實現了和葉小釵兄web交互的iOS部分,Android部分演示的是一個簡單的Web調用Native的例子)。

iOS Web調用Native

在iOS平臺,咱們會用到shoudStartLoadWithRequest這個方法來捕獲Web所發出的請求。下面咱們就來看一段簡單的演示吧。

//按照和web端規定的格式,獲取數據
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSURL * url = [request URL];
    if ([[url scheme] isEqualToString:@"hybrid"]) {
        NSString *actionType = request.URL.host;
        //從url中獲取web傳來的參數
        NSDictionary *actionDict = [self getDicFromUrl : url];
        //根據web的指示,native端相應的作出迴應
        [self doActionType:actionType : actionDict];
        return NO;
    }
    return YES;
}

//從url中獲取web傳來的參數
- (NSDictionary *) getDicFromUrl : (NSURL *)url{
    NSArray* paramArray = [[url query] componentsSeparatedByString:@"param="];
    NSString* paramStr = paramArray[1];
    NSString *jsonDictString = [paramStr stringByRemovingPercentEncoding];
    NSData *jsonData = [jsonDictString dataUsingEncoding:NSUTF8StringEncoding];
    NSError *e;
    NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:nil error:&e];
    return dict;
}

//根據web的指示,native端相應的作出迴應
-(void) doActionType : (NSString*) type : (NSDictionary*) dict{
    
    if ([type isEqualToString:@"forward"]) {
        [webView goForward];
    }
    //打開一個新的Web
    if([dict[@"type"] isEqualToString: @"webview"]){
        [self web2Web: dict[@"topage"]];
    }
    //打開一個Native頁面(我簡化爲了控件)
    else if ([dict[@"type"] isEqualToString: @"native"]){
        [self web2Native];
    }
}

 最左邊的圖是最開始的Web頁面,點擊「新開webview...」按鈕以後,Native端又打開了一個新的Web頁面,即中間的圖所示。

最右邊的圖演示的是點擊「跳轉native...」按鈕以後,Native端建立了一個Native的UI控件。

Android Web調用Native

Android平臺上Web調用Native要比iOS稍微複雜一些。由於在Android平臺上咱們須要藉助JavaScriptInterface來實現Web調用Native的功能。

下面就讓咱們演示一個小例子,不讓Web調用JS的alert,相反使用Web調用Native的Dialog來顯示提示吧。

首先咱們要在Android應用中引入JavaScriptInterface。

...
...
import android.webkit.JavascriptInterface;

public class WebAppInterface {
    Context mContext;

    WebAppInterface(Context c) {
        mContext = c;
    }

    @JavascriptInterface
    public void showToast(String toast) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
    }
}

以後,咱們使用WebView的 addJavascriptInterface()方法將這個類和JS代碼綁定,做爲Web調用Native的橋樑。

WebView webView = (WebView) findViewById(R.id.webview);
webView.addJavascriptInterface(new WebAppInterface(this), "Android");

最後,在Web端也作相應的實現。點擊Web中的button會調用Native的showToast方法。

<input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" />

<script type="text/javascript">
    function showAndroidToast(toast) {
        Android.showToast(toast);
    }
</script>

 

0x04 讓Web的歸Web,Native的歸Native

到此,在沒有使用C#做爲開發語言的兩大平臺上使用Hybrid的方式令Web和Native交互的內容就介紹完了。可是,不知道各位讀者是否和我有相同的感受呢?那就web和native之間的界線仍然十分明顯。

事實上我認爲Hybrid的最大價值其實並不在於跨平臺。相反,在一些經常變更的模塊的熱更新的問題上使用這種將Web和Native結合的Hybrid方式是十分適合的。

這是由於它並無從本質上解決所謂的跨平臺的問題,而是採用了一個十分討巧的方式或者說是逃避問題的方式超脫於平臺的範疇。而這也致使了Hybrid的缺點,好比Hybrid體驗和Native相比是有距離的。所以,Hybrid是一種優秀的熱更新技術,同時也是一個不完美的跨平臺方案。

至於解決跨平臺問題更好的方案,就交給Native端本身去想辦法吧。好比三大移動平臺都使用同一種語言甚至是同一個IDE進行Native部分的開發,看上去彷佛就是一個很美妙的方案。若是再加上利用Hybrid方式帶來的熱更新加持,結果就更棒了。

因此,就讓Web的歸Web,Native的歸Native吧。

未完待續~~~~~

相關文章
相關標籤/搜索