好久沒有更新博客了.最近一直陷身在項目中難以有時間抽身梳理總結.關於博客確實不少想寫的主題.節前大概草草 的梳理一下大概就有十幾個主題.只能趁着放假的時間來逐漸把這批文章力所能及系統的更新出來. 主要涉及到咱們團隊如今Windows phone 項目開發中實際碰到一些問題和對應解決方案.若是想關注即時瞭解天天動態信息能夠直接在Sina微博@chenkaiHome 溝通交流.html
在開始更新這批博文前.一直在顧慮先更新那個主題爲好.回頭一想索性就說說這半個月有些苦惱的Windows phone中處理 WebBrowser在咱們項目中表現出來問題.git
話說去年.技術團隊提出要優化產品在各個平臺[IOS/Android/WP7/QT]客戶端開發業務流程.提出這個問題主要是爲了把原來通用業務邏輯流程封裝到能力更大的服務器端來作.各個客戶端在經過WebView統一的形式調用.這樣作目的.主要是解決原來各個客戶端在業務升級後更新客戶端版本時減小從新開發量.這樣一來把核心的業務邏輯變更所有集中服務器端.需求變化自上而下傳遞過程當中.在各個平臺之間能夠複用. 在每次更迭客戶端版本時提高開發團隊效率.github
最開始咱們採用的方案焦點主要是考慮到WebView封裝通用的業務邏輯流程涉及到與對應平臺原生應用程序的交互問題上.因此也就理所固然有人提出跨平臺移動框架PhoneGap[平臺交互]+HTML 5[UI呈現]的處理方案. 原來咱們設想真的很簡單.也天真的認爲PhoneGap+HTML 5搭配會把通用業務流程問題迎刃而解.web
首先.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(「」);app
方法把頁面交互數據經過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:
<
Grid
x:Name
="ContentPanel"
Grid.
Row
="1"
Margin
="12,0,12,0"
>
3:
<
StackPanel
>
4:
<
phone:WebBrowser
x:Name
="ComponentContent_WB"
Height
="450"
/>
5:
<
Button
x:Name
="ExcuteScript_BT"
Content
="Excute JavaScript"
Margin
="0,50,0,0"
Click
="ExcuteScript_BT_Click"
></
Button
>
6:
</
StackPanel
>
7:
</
Grid
>
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
定義一個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:
private
void 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: }
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
調用通用網易站點.在加載頁面完成後經過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: }
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
注意InvokeScript方法在執行帶有JAvaScript參數時. 參數傳遞是以String[]數組方式傳遞給JAvaScript函數.調用:
1:
private
void ExcuteScript_BT_Click(
object sender, RoutedEventArgs e)
2: {
3:
//Button Client Event Excute InvokeJavaScript Method
4:
try
5: {
6:
this.ComponentContent_WB.InvokeScript(
"NTESAutoComplete",
new
string[]{
"NoExistElement",
"NoExistStringArgument"});
7: }
8:
catch (Exception se)
9: {
10: MessageBox.Show(
"Excute JavaScript Have Exception:" + se.Message);
11: }
12: }
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
執行效果以下:
執行過程當中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 }
private
void MainPage_MouseLeftButtonDown(
object sender, MouseButtonEventArgs e) { Wb.InvokeScript(
"eval",
"document.forms[0].submit();");
// Works }
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
能夠發現.若是在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);
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
加載頁面效果:
另一種方式.則是加載必定定製好靜態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:
<
a
href
="http://chenkai.cnblogs.com"
>Test
</
a
>
12:
</
body
>
13:
</
head
>
14:
</
html
>
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
在執行第一步須要把該CreateProduct.html頁面添加解決方案.設置引用資源爲Content.須要向獨立存儲中添加存儲靜態文件.:
1:
private
void 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:
private
void 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: }
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }把須要展現的靜態HTML頁面在調用前須要存儲到獨立存儲中.調用以下:
1:
void MainPage_Loaded(
object sender, RoutedEventArgs e)
2: {
3: SaveFilesToIsoStore();
4: ComponentContent_WB.Navigate(
new Uri(
"CreateProduct.html", UriKind.Relative));
5: }
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }成功加載的頁面:
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }針對在Windows phone WEbBrowser中於JavaScript交付問題的問題出現的異常.在後臺代碼上處理是很是弱的.首先在CodeBehind中沒有成行JS調試工具支持.這對不熟悉前段JavaSCript代碼的開發人員來講是一個挑戰. 另一個問題就是一旦調用JavaScript出現異常狀況.很難確認問題源頭.這也大大影響開發效率.
固然在WEbBroser還涉及到頁面加載控制. 新窗口打開. 控制WEbBrowser頁面縮放等問題.這裏就再也不一一贅述.
關於本片源碼詳見:https://github.com/chenkai/WebBrowser-Case-Windows-phone-Sample
×××:/Files/chenkai/WebBrowserWP7Demo.rar
若有問題能夠Weibo上溝通交流:http://weibo.com/chenkaihome