做移動開發大多數的時候跟手機介面打交道,也就是說你只能在電腦上開發寫程式碼,最終效果是確是在另外一個終端看到的。雖然各種瀏覽器為開發者提供了很多模擬手機裝置的功能,這些功能總體來說基本可以滿足我們對於除錯移動裝置的需求,但是模擬畢竟是模擬,它不能真正做到實現真機一樣的效果。經常遇到在chrome模擬器上樣式顯示正常,在手機上卻出現樣式錯誤的情況,在PC端模擬器執行正常,在真機上卻報錯的情況。所以,必須在想辦法在真實的機子上測試我們的程式碼最終的執行效果。移動端真機除錯有幾個剛需:看log、看error資訊、看網路請求。其它的如實時修改html/css/javascipt程式碼, 檢視timeline,cookie,localstorage,網路資源和斷點除錯就不是那麼頻繁。可以沿著兩條思路解決這個問題:第一,自己寫一個簡單的除錯工具,第二:尋找專業的除錯工具。
自己寫工具
先介紹一下容易理解的移動端除錯方法吧,比如自己寫一個列印輸出的資訊框。
/* * 移動端列印 * */ function debug(msg) { msg=new printTree().dump(msg); var div = document.querySelector("#logField"); if (div) { div.innerHTML += msg; } else { div = document.createElement('div'); div.id = "logField"; div.classList.add('debug'); // div.style['position'] = 'fixed'; // div.style['top'] = 0; // div.style['left'] = 0; div.style['width'] = '100%'; div.style['background-color'] = 'rgba(0,0,0,.8)'; div.style['font-size'] = '1.4rem'; div.style['color'] = 'yellow'; div.style['word-break'] = 'break-all'; div.style['line-height'] = '1.3'; div.style['padding'] = '10px 20px'; div.innerHTML = msg; var first=document.body.firstChild; document.body.insertBefore(div,first); // document.body.appendChild(div); } }
printTree原始碼如下所示:
1 ;(function(window){ 2 3 // Constructor 4 printTree = function (config){ 5 6 config=config ? config : {}; 7 8 this.tabKey= config.tabKey ? config.tabKey : " "; 9 10 }; 11 12 // Prototype 13 printTree.prototype = { 14 15 /** 16 * 17 * Private methods 18 * 19 */ 20 21 /** 22 * [對函式進行格式化處理] 23 * @param {[型別不確定]} obj [資料內容] 24 * @param {[Number]} indent [縮排Tab鍵數量] 25 * @param {[Boolean]} addComma [是否需要在行尾加逗號] 26 * @param {[Boolean]} isArray [是不是陣列] 27 * @param {[Boolean]} isPropertyContent [是不是屬性內容] 28 * @return {[String]} print [格式化後的物件字串] 29 */ 30 _printObject:function(obj, indent, addComma, isArray, isPropertyContent){ 31 32 var print = ""; 33 34 // 是否需要加逗號 35 var comma = (addComma) ? "," : ""; 36 37 // 判斷物件型別 38 var type = Object.prototype.toString.call(obj); 39 40 // 陣列的處理 41 if( type == "[object Array]"){ 42 43 // 44 if(obj.length == 0){ 45 // isPropertyContent如果為真,說明是屬性內容,不用加tab鍵,否則要在前面加tab鍵 46 print += this._getRow(indent, "[ ]"+comma, isPropertyContent); 47 48 }else{ 49 50 // 列印陣列前面的中括號 51 print += this._getRow(indent, "[", isPropertyContent); 52 53 for(var i = 0; i < obj.length; i++){ 54 // _printObject 陣列中可能巢狀這其它物件 55 // indent + 1 每遞迴一次,加一個縮排符 56 // i < (obj.length - 1) 不是陣列的最後一個元素,都要加逗號 57 print += this._printObject(obj[i], indent + 1, i < (obj.length - 1), true, false); 58 59 } 60 61 // 列印陣列後面的中括號 62 print += this._getRow(indent, "]"+comma); 63 64 } 65 66 }else if(type == "[object Object]"){ 67 68 if(obj == null){ 69 print += this._formatLiteral("null", "", comma, indent, isArray); 70 } 71 else{ 72 73 var numProps = 0; 74 75 for(var prop in obj) numProps++; 76 77 if(numProps == 0){ 78 79 print += this._getRow(indent, "{ }"+comma, isPropertyContent); 80 81 }else{ 82 83 print += this._getRow(indent, "{", isPropertyContent); 84 85 var j = 0; 86 87 for(var prop in obj){ 88 89 print += this._getRow(indent + 1, prop+':'+this._printObject(obj[prop], indent + 1, ++j < numProps, false, true)); 90 91 } 92 93 print += this._getRow(indent, "}"+comma); 94 95 } 96 } 97 98 }else if(type == "[object Number]"){ 99 100 print += this._formatLiteral(obj,comma,indent, isArray); 101 102 }else if(type == "[object Boolean]"){ 103 104 print += this._formatLiteral(obj, comma, indent, isArray); 105 106 }else if(type == "[object Function]"){ 107 108 obj = this._formatFunction(obj); 109 110 print += this._formatLiteral(obj, comma, indent, isArray); 111 112 }else if(type == "[object Undefined]"){ 113 114 print += this._formatLiteral("undefined",comma, indent, isArray); 115 116 }else{ 117 118 print += this._formatLiteral(obj, comma, indent, isArray); 119 120 } 121 122 return print; 123 124 }, 125 126 /** 127 * [對文字內容進行格式化處理] 128 * @param {[String]} literal [字元內容] 129 * @param {[String]} comma [是否加逗號] 130 * @param {[Number]} indent [縮排的Tab鍵數量] 131 * @param {[Boolean]} isArray [是否為陣列] 132 * @return {[String]} str [格式化後的函式字串] 133 */ 134 _formatLiteral:function(literal, comma, indent, isArray){ 135 136 var str=literal+comma; 137 138 if(isArray){ 139 str = this._getRow(indent, str); 140 } 141 142 return str; 143 144 }, 145 146 /** 147 * [對函式進行格式化處理] 148 * @param {[Function]} obj [函式內容] 149 * @return {[String]} str [格式化後的函式字串] 150 */ 151 _formatFunction:function(obj){ 152 153 154 var str = ""; 155 // 以換行符對每行進行分割 156 var funcStrArray = obj.toString().split("\n"); 157 158 if(funcStrArray.length){ 159 160 // 重組每行內容,除末尾行之外,每行加上換行符 161 for(var i = 0; i < funcStrArray.length-1; i++){ 162 str += funcStrArray[i] + "\n"; 163 } 164 return str+funcStrArray[i]; 165 }else{ 166 return str; 167 } 168 169 }, 170 171 172 /** 173 * [給每行加上縮排和換行] 174 * @param {[Number]} indent [每行需要縮排的TAB數量] 175 * @param {[String]} data [屬性名] 176 * @param {[Boolean]} isPropertyContent [是否為屬性內容] 177 * @return {[String]} tabs+data [格式化後的行內容] 178 */ 179 _getRow:function(indent, data, isPropertyContent){ 180 181 var tabs = ""; 182 183 // 計算屬性名稱前面的tab鍵數量 184 for(var i = 0; i < indent && !isPropertyContent; i++){ 185 tabs += this.tabKey; 186 } 187 188 // 給屬性名加上換行(內容不為空且末尾不含換行符) 189 if(data != null && data.length > 0 && data.charAt(data.length-1) != "\n"){ 190 data = data+"\n"; 191 } 192 193 return tabs+data; 194 195 }, 196 /** 197 * 198 * Public methods 199 * 200 */ 201 dump:function(object){ 202 203 try{ 204 return this._printObject(object, 0, false, false, false) ; 205 }catch(e){ 206 alert("object語法錯誤,不能格式化,錯誤資訊:\n"+e.message); 207 } 208 209 }, 210 211 }; 212 213 if (typeof exports !== 'undefined'){ 214 exports.printTree = printTree; 215 }else{ 216 window.printTree = printTree; 217 } 218 219 })(window);
這個方法有個缺點是無法列印[HTML DOM Element],
比如一個實際的DOM 元素為
<input type="email" name="email" id="email" placeholder="請輸入企業郵箱賬號">
通過上述方式列印出來的效果為圖2所示。類似的工具還有vconsole
安裝方法
npm install vconsole
使用方法---在需要除錯的頁面引入下面的js檔案
<script src="path/to/vconsole.min.js"></script>
<script>
console.log('Hello world');
</script>
這個工具的實現原理,是很簡單的函式劫持。其大概原理是建立一個和現有函式同名的函式(當然首先要把原來的函式給儲存下來),以覆蓋掉他原本的引用,然後在函式體內先針對引數做一些自己想要實現的功能,最後再呼叫之前儲存的原函式,實現原本的功能。
vconsole工具重寫了window.XMLHttpRequest和window.console方法
使用專業的工具
使用 Weinre 除錯
weinre全稱是web insperctor remote,是一種遠端除錯工具,可以在PC上除錯執行在移動裝置上的遠端頁面。
Weinre 是一個相當簡單好用的除錯工具。它會在你本地建立一個監聽伺服器,並提供一個 JavaScript指令碼,你只需要在需要測試的頁面中載入這段 JS,就可以被 Weinre 監聽到,在 inspect 皮膚中除錯你這個頁面。
weinre工作原理
如上圖, Weinre由三部分組成,第一部分是執行在PC上的Debug Server, 它會與其他兩部分互動,在測試頁引入的那個target.js檔案就存在於這個Server裡
第二部分是Debug Client, 這個就是我們上面一直在使用的執行在chrome中的除錯客戶端,它與Debug Server進行連線,並提供除錯介面給使用者。
第三部分是Debug Target, 也就是執行在我們遠端裝置瀏覽器中的target.js, 它通過XHR與Server連線互動,將我們的程式碼暴露給Server, 來實現DOM Inspection與修改。
在命令列啟動weinre就開啟了Debug Server, 然後在瀏覽器中輸入http://ip:weinre埠就開啟了Debug Client, 在除錯頁面中嵌入target.js程式碼, 在手機中開啟頁面, 就開啟Debug Target。
weinre 的具體使用方法如下:
首先全域性安裝 weinre:
npm install -g weinre
安裝完成之後,在命令列下啟動weinre監聽伺服器,啟動監聽伺服器之前,需要獲取本機的區域網地址:
- Mac 在終端執行
ipconfig getifaddr en0
命令。 - Win 在命令列執行
ipconfig
命令。
這時候拿到本機IP,比如我的機器IP 為 192.168.201.54,這時候執行:
weinre --boundHost 192.168.201.54 --httpPort 10000
weinre支援的引數有:
--help : 顯示Weinre的Help
--httpPort [portNumber] : 設定Weinre使用的埠號, 預設是8080
--boundHost [hostname | ip address | -all-] : 預設是'localhost', 這個引數是為了限制可以訪問Weinre Server的裝置, 設定為-all-或者指定ip, 那麼任何裝置都可以訪問Weinre Server。
--verbose [true | false] : 如果想看到更多的關於Weinre執行情況的輸出, 那麼可以設定這個選項為true, 預設為false;
--debug [true | false] : 這個選項與--verbose類似, 會輸出更多的資訊。預設為false。
--readTimeout [seconds] : Server傳送資訊到Target/Client的超時時間, 預設為5s。
--deathTimeout [seconds] : 預設為3倍的readTimeout, 如果頁面超過這個時間都沒有任何響應, 那麼就會斷開連線。
開啟本地監聽伺服器
複製http://192.168.201.54:10000,貼上到瀏覽器位址列,開啟伺服器網址,
複製監聽指令碼到需要被監聽頁面
當我們有真機訪問被除錯頁面的時候,被除錯頁面就會出現在監聽列表中,如果有多個網頁,你可以從列表中選擇一個。然後就可以使用後面的 Elements、Console 等皮膚來進行除錯操作了:
首頁RemoteTab由三部分組成, Targets是註冊的遠端裝置列表, 當前我們還沒有訪問測試頁面, 所以Targets列表為none, Clients是Weinre客戶端, 也即開啟這個Weinre頁面的裝置列表。ServerProperties就是我們執行Weinre時的一些配置項。
weinre 非常靈活,跨平臺(Android、iOS 、Window Phone 都支援),跨瀏覽器(chrome,safari,國內各種瀏覽器都可以用), 可以讓我們在電腦上直接除錯執行在手機上的遠端頁面。在除錯移動裝置時需要在本地搭建一個區域網 IP 的伺服器,將裝置與本機網路連線成一個區域網,用移動裝置訪問這個網頁即可。當然 Weinre 也不是萬能的,相比 Chrome 的除錯工具,它缺少 JavaScript 斷點以及 Profiles 等常用功能,但是它相容性強,可以實現基礎除錯功能。
https頁面除錯
weinre相容性挺強,而且能支援微信端頁面的除錯,到此為止,如果頁面請求使用的是http,那weinre已經可以解決除錯問題了。
但是如果要除錯https請求的頁面,僅僅使用weinre無法解決,因為在頁面中需要引入除錯的js檔案,weinre啟動的是http服務,於是使用反向代理軟體ngrok,它可以做地址對映,並支援http/https/tcp等,使用也比較簡單:
這是官網下載地址:https://ngrok.com/download,或者可以直接使用npm下載:
npm install -g ngrok
然後啟動
ngrok http 192.168.201.54:9999
啟動後可以開啟 http://127.0.0.1:4040 檢視連線資訊:
在html中引入下面的js檔案
注意:
1.繫結埠一定不能與本地環境已監聽的埠衝突。本地我已監聽了8080埠,所以我繫結的是10000埠。
2.boundHost預設為localhost,只能本地PC上用http://localhost:8080來訪問,將localhost換做本地ip就無法開啟Weinre除錯工具,為了能在其他裝置以及本地裝置用ip開啟Weinre除錯工具,需要設定boundHost為"-all-"或者ip.
3.監聽https頁面時,要先啟動weinre,再啟動ngrok.
使用JSconsole
JSConsole相當於一個簡化版的weinre,專注於console功能,它相對於Weinre的優點就是提供了現成了線上Debug Server與Debug Client, 無需使用者在PC本地執行Debug服務, 只要在需要除錯的頁面像Weinre一樣加入一個target庫, 就可以在JSConsole官網上除錯這個頁面了。JSConsole的網址為https://jsconsole.com/。
接著在開啟的網頁輸入 :listen
,將會得到一串 GUID 以及一對帶有 src 屬性的 Javascript 標籤,如下圖:
將這個 Javascript 指令碼插入到需要除錯的 html 頁面中,比如這樣:
複製程式碼<script src="http://jsconsole.com/remote.js?BDA15940-A201-4EAB-9482-941CD41742EC"></script>
<script>
var a = 1
, b = 2;
console.log(a + b);
</script>
然後重新整理你本地需要除錯的頁面(PC端或者移動端),如果是第一次開啟的話,會彈出下圖內容,大概意思就是告訴你現在引入了 JSConsole 的一段 js 進行除錯,記得在上線時將它移除。
在開啟 JSConsole 的頁面便會輸出 console 的內容;如果頁面 JS 報錯,一般情況下也能在 JSConsole 中進行定位。
需要注意的是,重新整理的是本地頁面,而並不是 JSConsole 的頁面,一旦重新整理 JSConsole 的頁面,便會生成一個新的 GUID,這樣之前生成的就沒用了,除錯也就失效了
使用spy-debugger除錯
1、一站式頁面除錯工具,遠端除錯任何手機瀏覽器頁面,任何手機移動端webview(如:微信,HybirdApp,手機瀏覽器等)。 2、spy-debugger內部整合了weinre。 3、同時支援HTTP/HTTPS頁面的除錯。
安裝
Windows 下
npm install spy-debugger -g
Mac 下
sudo npm install spy-debugger -g
安裝後在命令列下輸入 spy-debugger,啟動代理伺服器
設定手機代理
https頁面除錯
如果要監聽https頁面,你還需要做如下操作:
第一步:生成證照
spy-debugger initCA
// 證照生成在使用者根目錄的node-mitmproxy資料夾下的
// 如: /Users/wuchangming/node-mitmproxy
第二步:安裝證照
把node-mitmproxy資料夾下的 node-mitmproxy.ca.crt 傳到手機上,點選安裝即可。
使用fiddler除錯
Fiddler是最強大最好用的Web除錯工具之一,它能記錄所有客戶端和伺服器的http和https請求,分析請求資料、設定斷點、除錯web應用、修改請求的資料,甚至可以修改伺服器返回的資料. 使用Fiddler無論對開發還是測試來說,都有很大的幫助。
Fiddler的工作原理
Fiddler 是以代理web伺服器的形式工作的,它是在web server 和 client 之間搭了一層 proxy,所有的請求都會經過它。在開啟它的那一瞬間,它就已經設定好了瀏覽器的代理了。它使用代理地址:127.0.0.1, 埠:8888.當你關閉的時候,它會自動登出代理服務,不過如果Fiddler非正常退出,會造成網頁無法訪問。 這是因為Fiddler沒有自動登出,解決的辦法是重新啟動下Fiddler..
下載fiddler
Fiddler的基本介面
fiddler圖示介紹
每種圖示代表不同的相應型別,具體的型別包括:
Inspectors皮膚
Inspectors tab下有很多檢視Request或者Response的訊息。分為上下兩個部分,上半部分是請求頭部分,下半部分是響應頭部分。對於每一部分,提供了多種不同格式檢視每個請求和響應的內容。
JPG 格式使用 ImageView 就可以看到圖片,
HTML/JS/CSS 使用 TextView 可以看到響應的內容。
其中Raw Tab可以檢視完整的訊息,
Headers tab 只檢視訊息中的header.
Auth則可以檢視授權Proxy-Authorization 和 Authorization的相關資訊。
Cookies標籤可以看到請求的cookie和響應的set-cookie頭資訊。
2.配置fiddler,讓fiddler可以抓取https協議
預設情況下,fiddler是不會捕獲https會話的,所以需要自行設定下。啟動軟體,點選tools->Fiddler Options,在彈出框選擇“HTTPS”,如下頁面,將捕獲HTTPS連線這一項前面全打鉤,點選ok即可操作成功。
Tools --> Telerik Fiddler Options --> 選擇https,這個皮膚的配置項有:
Capture HTTPS CONNECTs:捕獲https連線
Decrypt HTTPS traffic:解密HTTPS通訊
Ignore servercertificate errors:忽略伺服器證照錯誤
Fiddler中設定斷點修改Request
Fiddler最強大的功能莫過於設定斷點了,設定好斷點後,你可以修改httpRequest 的任何資訊包括host, cookie或者表單中的資料。設定斷點有兩種方法
第一種:開啟Fiddler 點選Rules-> Automatic Breakpoint ->Before Requests(這種方法會中斷所有的會話)
如何消除命令呢? 點選Rules-> Automatic Breakpoint ->Disabled
第二種: 在命令列中輸入命令: bpu www.baidu.com (這種方法只會中斷www.baidu.com)
如何消除命令呢? 在命令列中輸入命令 bpu
Fiddler中設定斷點修改Response
當然Fiddler中也能修改Response
第一種:開啟Fiddler 點選Rules-> Automatic Breakpoint ->After Response (這種方法會中斷所有的會話)
如何消除命令呢? 點選Rules-> Automatic Breakpoint ->Disabled
第二種: 在命令列中輸入命令: bpafter www.baidu.com (這種方法只會中斷www.baidu.com)
如何消除命令呢? 在命令列中輸入命令 bpafter,
AutoResponder皮膚
Fiddler比較重要且比較強大的功能之一。可用於攔截某一請求,並重定向到本地的資源,或者使用Fiddler的內建響應。可用於除錯伺服器端程式碼而無需修改伺服器端的程式碼和配置,因為攔截和重定向後,實際上訪問的是本地的檔案或者得到的是Fiddler的內建響應。當勾選allow autoresponser 並設定相應的規則後(本例中的規則是將http://blog.csdn.net/ohmygirl的請求攔截到本地的檔案layout.html),如下圖所示
因此,如果要除錯伺服器的某個指令碼檔案,可以將該指令碼攔截到本地,在本地修改完指令碼之後,再修改伺服器端的內容,這可以保證,儘量在真實的環境下去除錯,從而最大限度的減少bug發生的可能性。
不僅是單個url,Fiddler支援多種url匹配的方式:
I. 字元匹配
如 example可以匹配 http://www.example.com和http://example.com.cn
II. 完全匹配
以EXACT開頭表示完全匹配,如上邊的例子
EXACT:http://blog.csdn.net/ohmygirl
III. 正規表示式匹配
以regex: 開頭,使用正規表示式來匹配URL
如:regex:(?insx).*\.(css|js|php)$ 表示匹配所有以css,js,php結尾的請求url
Composer(構建請求)
老版本的fiddler中叫request-builder.顧名思義,可以構建相應的請求,有兩種常用的方式構建請求:
(1)Parsed 輸入請求的url之後executed即可,也可以修改相應的頭資訊(如新增常用的accept, host, referrer, cookie,cache-control等頭部)後execute.
這個功能的常見應用是重新整理頁面的訪問量
(2)Raw。使用HTTP頭部資訊構建http請求。與上類似。不多敘述
QuickExec命令列的使用
Fiddler的左下角有一個命令列工具叫做QuickExec,允許你直接輸入命令。
常見得命令有
help 開啟官方的使用頁面介紹,所有的命令都會列出來 http://docs.telerik.com/fiddler/knowledgebase/quickexec
cls 清屏 (Ctrl+x 也可以清屏) select 選擇會話的命令 ?.png 用來選擇png字尾的圖片 bpu 截獲request
Fiddler的HTTP統計檢視
通過陳列出所有的HTTP通訊量,Fiddler可以很容易的向您展示哪些檔案生成了您當前請求的頁面。使用Statistics頁籤,使用者可以通過選擇多個會話來得來這幾個會話的總的資訊統計,比如多個請求和傳輸的位元組數。
選擇第一個請求和最後一個請求,可獲得整個頁面載入所消耗的總體時間。從條形圖表中還可以分別出哪些請求耗時最多,從而對頁面的訪問進行訪問速度優化
Filter皮膚
主機過濾規則;
第一個選項zone有三種選擇:no zone;僅顯示區域網主機;僅顯示網際網路主機;
第二個選項Host有四種選擇,可以在下面文字框輸入內容,根據輸入內容顯示或不顯示相應的主機;
“No Host Filter”不設定hosts過濾
“Hide The Following Hosts”隱藏過濾到的域名
“Show Only The Following Hosts”只顯示過濾到的域名
“Flag The Following Hosts”標記過濾到的域名
Action按鈕可以儲存過濾規則集,載入過濾規則集等;
客戶端處理過濾規則;
可以選擇僅顯示來自某些程式的流量;僅顯示chrome流量;
請求頭過濾;可以選擇
僅顯示包含某些字元的URL;隱藏包含某些字元的URL;顯示頭部包含某些標誌的包;
還可以刪除和設定請求頭;此功能尚未用過;待用過之後再發文詳述;
斷點設定;
在post時中斷請求;
在帶查詢引數的GET請求時中斷;
在非同步請求時中斷;
獲取到某種型別的響應時中斷;
根據響應碼過濾;
隱藏成功的包;
隱藏不成功的包;
2字頭的狀態碼,代表請求已成功被伺服器接收、理解、並接受;
隱藏需要使用者驗證的請求;
隱藏重定向請求;
響應型別和尺寸過濾;
預設顯示全部型別;其他還有image,html,texe/css,scripts等型別;
響應頭過濾;
捕獲設定了cookie的響應頭;
捕獲響應頭帶某些字串的包等;
HTTP抓包分析工具有比較多,如wireshark, FireBug,HttpWatch,Tcpdump,PAW(mac)等。在做移動端開發時,找到一款合適自己的能進行移動裝置HTTP抓包的工具也是非常重要的。正所謂,工欲善其事必先利其器。
個人非常喜歡Fiddler和spy-debugger,功能強大,簡單易用。