好久没有更新博客了.最近一直陷身在项目中难以有时间抽身梳理总结.关于博客确实不少想写的主题.节前大概草草 的梳理一下大概就有十几个主题.只能趁着放假的时间来逐渐把这批文章力所能及系统的更新出来. 主要涉及到咱们团队如今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