天天看點

Windows phone 應用開發[14]-調用WebBrowser

很久沒有更新部落格了.最近一直陷身在項目中難以有時間抽身梳理總結.關于部落格确實很多想寫的主題.節前大概草草 的梳理一下大概就有十幾個主題.隻能趁着放假的時間來逐漸把這批文章力所能及系統的更新出來. 主要涉及到我們團隊現在Windows phone 項目開發中實際碰到一些問題和對應解決方案.如果想關注即時了解每天動态資訊可以直接在Sina微網誌@chenkaiHome 溝通交流.

在開始更新這批博文前.一直在顧慮先更新那個主題為好.回頭一想索性就說說這半個月有些苦惱的Windows phone中處理 WebBrowser在我們項目中表現出來問題.

話說去年.技術團隊提出要優化産品在各個平台[IOS/Android/WP7/QT]用戶端開發業務流程.提出這個問題主要是為了把原來通用業務邏輯流程封裝到能力更大的伺服器端來做.各個用戶端在通過WebView統一的形式調用.這樣做目的.主要是解決原來各個用戶端在業務更新後更新用戶端版本時減少重新開發量.這樣一來把核心的業務邏輯變動全部集中伺服器端.需求變化自上而下傳遞過程中.在各個平台之間可以複用. 在每次更疊用戶端版本時提升開發團隊效率.

最開始我們采用的方案焦點主要是考慮到WebView封裝通用的業務邏輯流程涉及到與對應平台原生應用程式的互動問題上.是以也就理所當然有人提出跨平台移動架構PhoneGap[平台互動]+HTML 5[UI呈現]的處理方案. 原來我們設想真的很簡單.也天真的認為PhoneGap+HTML 5搭配會把通用業務流程問題迎刃而解.

首先.PhoneGap[PS參考:Windows phone 應用開發[8]-體驗PhoneGap]作為移動跨平台架構.着重解決的問題是通過JavaScript實作跨平台API互動.并沒有UI.頁面還需要借助HTML 5效果.即使如此.也是難以和原生應用程式界面相媲美的.這就需要在使用過程中.要犧牲掉大量原生應用程式互動細節.使用者體驗上打了一個折扣.當然這和我們解決的核心問題做出犧牲還是值得.但問題各個平台相容性需要調整各個平台适配問題大大出乎我們預估.而在性能差異上更是難以兼顧保證的.而對于初次使用PhoneGap團隊解決這些問題所耗費的開發周期時間.卻遠大于開發Native Application原生應用時間還要長.這完全和使用初衷相背離. 其實問題隻是換了一種形式存在. 我們隻是從一個熟悉能夠預估量的泥潭跳到另外完全未知泥潭中.不斷嘗試掙紮…

談到這.說一個細節.類似在WebBrowser浏覽器控件中.打開一個新視窗的問題.Android平台可以采用LoadUrl方法直接打開一個新窗體實作Js控制的頁面跳轉. 而目前Windows phone WebBrowser情況不支援在目前頁打開新窗體.雖然可以通過InvokeJavaScript()放在LoadComplated方法中注入Js控制方法替換打開方式來實作. 但在實際調試中會發現.開發人員在C# 背景代碼中調試JavaScript來說是一個挑戰. 一來WebBrowser在注入和執行Js過程傳回的錯誤或異常都是簡單的代碼80開頭Code.而沒有具體的堆棧資訊. 這對找錯和确認問題照成很大障礙.另外在C#背景代碼處理JS缺乏有效的調試工具支援.這對PhoneGap封裝出來頁面複雜的Js調用或資料互動操作.照成一定難題.

說白了.在PhoenGap中通過JS實作Windows phone應用平台互動主要展現在兩個點上.第一就是通過在Js中調用:

【JAvaScript:】

window.external.Notify(“”);

方法把頁面互動資料通過WebBrowser控件SCriptNotify事件接收傳遞給原生應用程式. 另外一個點.就是可以在在Windows phone應用程式直接調用WEbBrowser控件InvokeScript()方法來調用JavaScript函數. 這兩個方法.實作了PhoneGap資料傳遞和互動整個過程.

那在Windows phone應用程式使用WebBrowser有哪些常見需要解決的問題?

[1]異常處理.

well.這裡不得不首先說在WebBrowser調用JavaSCript時需要處理的異常問題.Windows Phone 提供一個基于桌面版本的 Silverlight 的 WebBrowser 控件,也就是說WindowsPhone 目前的WEbBrowser控件是基于Silverlight桌面版本的WebBrowser控件而來,但仍然有幾處不同[WEbBrowser與Silverlight版本不同地方].其中兩個版本在開發最大不同主要有如下幾點:

Windows phone WebBrowser控件與Silverlight 桌面版本的不同:

[1]Windows phone 版本相對Silverlight版本具有直接使用 IsolateStroage獨立存儲的權限

[2]相對Silverlight在執行InvokeScript()方法時限制了執行範圍必須是XAP 程式包相同的站點中加載的腳本.而Windows phone 解除該限制.

[3]在Windows phone版本時從獨立存儲加載的内容或使用 NavigateToString(String) 方法加載的内容沒有跨站點通路限制。

那麼在Windows phone WebBrowser中調用JavaScript常見的可能出現的異常主要有兩個,如下重制這兩個異常情況.首先建立一個Windows Phone Application 應用程式. Mainpage.CS:

1: <!--ContentPanel - place additional content here-->      
2: <Gridx:Name="ContentPanel"Grid.Row="1"Margin="12,0,12,0">      
3: <StackPanel>      
4: <phone:WebBrowserx:Name="ComponentContent_WB"Height="450"/>      
5: <Buttonx:Name="ExcuteScript_BT"Content="Excute JavaScript"Margin="0,50,0,0"Click="ExcuteScript_BT_Click"></Button>      
6: </StackPanel>      
7: </Grid>      

定義一個WebBrowser和Button按鈕用來在頁面加載完成後執行JavaSCripti函數事件.當然在執行JavaSCript需要設定WEbbrowser可以調用JS的. 設定IsScriptEnabled="True" BehindCode 如下:

1: // Constructor      
2: public MainPage()      
3:  {      
4:  InitializeComponent();      
5: this.Loaded += new RoutedEventHandler(MainPage_Loaded);      
6:  }      
7:       
8: void MainPage_Loaded(object sender, RoutedEventArgs e)      
9:  {      
10: string navigateUrl = @"http://www.163.com";      
11: this.ComponentContent_WB.Navigate(new Uri(navigateUrl, UriKind.RelativeOrAbsolute));      
12:  }      
13:       
14: privatevoid ExcuteScript_BT_Click(object sender, RoutedEventArgs e)      
15:  {      
16: //Button Client Event Excute InvokeJavaScript Method      
17: try      
18:  {      
19: this.ComponentContent_WB.InvokeScript("DefineNoExistJSMethod");      
20:  }      
21: catch (Exception se)      
22:  {      
23:  MessageBox.Show("Excute JavaScript Have Exception:" + se.Message);      
24:  }      
25:  }      

調用通用網易站點.在加載頁面完成後通過Button按鈕執行一個不來就不存在JavaScript函數.執行效果如下":

因這個JavaScript函數不存在是以執行肯定報錯.注意這裡報錯資訊是以80020006為開頭的UnKnowError如下:

可見在堆棧的異常資訊一欄中對JavaScripit提供的資訊非常有限.這個Message代碼為80020006.其實就是在目前應用程式執行範圍找不到該JavaScript方法.另外一種情況恰恰相反.在執行已經定義JavaScript Function 函數出現的異常. 類似找到163.com站點中一個任意JAvaScript函數在背景方法調用:

1: function NTESAutoComplete ( inputElem, nextElem ) {      
2: var t = this;      
3:  t._inputElem = inputElem;      
4:  t._nextElem = nextElem;      
5:  t._idName = "login_auto_list";      
6:  t._className = "login-auto-list";      
7:  }      

注意InvokeScript方法在執行帶有JAvaScript參數時. 參數傳遞是以String[]數組方式傳遞給JAvaScript函數.調用:

1: privatevoid ExcuteScript_BT_Click(object sender, RoutedEventArgs e)      
2:  {      
3: //Button Client Event Excute InvokeJavaScript Method      
4: try      
5:  {      
6: this.ComponentContent_WB.InvokeScript("NTESAutoComplete",newstring[]{"NoExistElement",      
"NoExistStringArgument"});      
7:  }      
8: catch (Exception se)      
9:  {      
10:  MessageBox.Show("Excute JavaScript Have Exception:" + se.Message);      
11:  }      
12:  }      

執行效果如下:

執行過程中InvokeScript得到異常 "An unknown error has occurred. Error: 80020101".而這個異常是在往往執行過程JavaScript内部錯誤引起.因在背景代碼沒有有效的工具.支援.是以對于JavaScript的錯誤是很難查找确認問題具體在那. 這個問題出現一般會有兩種大概原因.

第一點.在調用InvokeScript()是WebBrowser控件事件執行順序.其實針對WEbBrowser控件.除了從FrameworkElement類和Control類繼承了通用了UIElement屬性和方法外.WEbBrowser重點擴充自身導航操作.類似其中三個比較中重要的方法.Navigating、Navigated 和 LoadCompleted事件.

那麼說到這 這個三個事件在實際操作執行順序是?

WebBrowser導航事件的執行順序:

Navigating > Navigated > LoadCompleted

Navigating是執行Navigate方法表示目前WEbBrowser正在執行加載URL操作、Navigated事件WebBrowser 控件成功導航後發生 和 LoadCompleted事件在 WebBrowser 控件成功加載内容後發生.

如果在這種情況下.即使我們發現我們Codebehind中InvokeScript()調用JS沒有問題.同時HTML JavaScript函數測試也沒有問題.這就導緻我們始終無法通過程式測試找到JS 報錯80020101異常在那. 這是在背景代碼調試JAvaScript最讓人痛苦的地方.比如我們在如下方法掉用如上JavaScript函數:

void Wb_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)      
{      
Wb.InvokeScript("eval", "document.forms[0].submit();"); // Throws 80020101      
}      
privatevoid MainPage_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)      
{      
Wb.InvokeScript("eval", "document.forms[0].submit();"); // Works      
}      

可以發現.如果在Navigated事件觸發時.即使我們背景代碼調用和JavaScript函數都沒有錯誤.依然還是爆出80020101的異常.這主要是因為DOM對象操作在頁面觸發Navigated事件還沒有完全初始化.導緻調用頁面執行時出現異常.

第二點.則是比較常見的即使需要對JavaSCript做一定修改.確定JS函數在執行時不會出錯. 則這個80020101異常一般都會在如上兩種情況下出現.

[2]現實靜态頁面.

在WEbBrowser中.可能需要在沒有網絡情況下.需要在某一些情況下通過背景應用程式操作HTML頁面. 而在Windows phone提供兩種方式來加載本地靜态的HTML頁面.

在Windows phone 中WEbBrowser中提供NavigateToString方法将 HTML 字元串置于 Web 浏覽器控件中以便進行呈現.操作也是簡單的:

1: string defineHtmlStr = @"<html>      
2:  <head>      
3:  <script>      
4:  function DefineExistFun(elementStr)      
5:  {      
6:  var getElems=document.getElementByTag(elementStr);      
7:  alert(elementStr);      
8:  }      
9:  </script>      
10:  <body>      
11:  <a href=" + "http://chenkai.cnblogs.com" + ">Test</a>"      
12:  + "</body>"      
13:  + "</head></html>";      
14: this.ComponentContent_WB.NavigateToString(defineHtmlStr);      

加載頁面效果:

另外一種方式.則是加載一定定制好靜态HTML頁面.使用 WebBrowser 控件在應用程式中顯示已設定格式的靜态内容。例如,開發人員可能希望在應用程式包中包含幫助文本,以便使用者可以随時通路.建立一個靜态HTML界面:

1: <html>      
2: <head>      
3: <script>      
4: function DefineExistFun(elementStr)      
5:  {      
6: var getElems=document.getElementByTag(elementStr);      
7:  alert(elementStr);      
8:  }      
9: </script>      
10: <body>      
11: <ahref="http://chenkai.cnblogs.com">Test</a>      
12: </body>      
13: </head>      
14: </html>      

在執行第一步需要把該CreateProduct.html頁面添加解決方案.設定引用資源為Content.需要向獨立存儲中添加存儲靜态檔案.:

1: privatevoid SaveFilesToIsoStore()      
2:  {      
3: //These files must match what is included in the application package,      
4: //or BinaryStream.Dispose below will throw an exception.      
5: string[] files = {      
6: "CreateProduct.html"      
7:  };      
8:       
9:  IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();      
10:       
11: if (false == isoStore.FileExists(files[0]))      
12:  {      
13: foreach (string f in files)      
14:  {      
15:  StreamResourceInfo sr = Application.GetResourceStream(new Uri(f, UriKind.Relative));      
16: using (BinaryReader br = new BinaryReader(sr.Stream))      
17:  {      
18: byte[] data = br.ReadBytes((int)sr.Stream.Length);      
19:  SaveToIsoStore(f, data);      
20:  }      
21:  }      
22:  }      
23:  }      
24:       
25: privatevoid SaveToIsoStore(string fileName, byte[] data)      
26:  {      
27: string strBaseDir = string.Empty;      
28: string delimStr = "/";      
29: char[] delimiter = delimStr.ToCharArray();      
30: string[] dirsPath = fileName.Split(delimiter);      
31:       
32: //Get the IsoStore.      
33:  IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();      
34:       
35: //Re-create the directory structure.      
36: for (int i = 0; i < dirsPath.Length - 1; i++)      
37:  {      
38:  strBaseDir = System.IO.Path.Combine(strBaseDir, dirsPath[i]);      
39:  isoStore.CreateDirectory(strBaseDir);      
40:  }      
41:       
42: //Remove the existing file.      
43: if (isoStore.FileExists(fileName))      
44:  {      
45:  isoStore.DeleteFile(fileName);      
46:  }      
47:       
48: //Write the file.      
49: using (BinaryWriter bw = new BinaryWriter(isoStore.CreateFile(fileName)))      
50:  {      
51:  bw.Write(data);      
52:  bw.Close();      
53:  }      
54:  }      

把需要展示的靜态HTML頁面在調用前需要存儲到獨立存儲中.調用如下:

1: void MainPage_Loaded(object sender, RoutedEventArgs e)      
2:  {       
3:  SaveFilesToIsoStore();      
4:  ComponentContent_WB.Navigate(new Uri("CreateProduct.html", UriKind.Relative));       
5:  }      

成功加載的頁面:

針對在Windows phone WEbBrowser中于JavaScript傳遞問題的問題出現的異常.在背景代碼上處理是非常弱的.首先在CodeBehind中沒有成行JS調試工具支援.這對不熟悉前段JavaSCript代碼的開發人員來說是一個挑戰. 另外一個問題就是一旦調用JavaScript出現異常情況.很難确認問題源頭.這也大大影響開發效率.

當然在WEbBroser還涉及到頁面加載控制. 新視窗打開. 控制WEbBrowser頁面縮放等問題.這裡就不再一一贅述.

關于本片源碼詳見:https://github.com/chenkai/WebBrowser-Case-Windows-phone-Sample

繼續閱讀