2015年1月6日 星期二

如何用 FireMonkey 的 TWebBrowser 取回 JSON 資料

緣起

從 Delphi XE2 開始, FireMonkey就包含了跨平台的TWebBrowser元件(Windows版本跟行動版本是分開的, 我記得Windows版本的 TWebBrowser 元件, 是 VCL 版本的 ActiveX),但從 XE2 開始到 XE6, 每一版的 TWebBrowser 都少了點東西.

XE2到XE4都沒有Android版本的WebBrowser, 到 XE5 總算行動平台的 WebBrowser 都具備了, 但在網頁當中的 Form 輸入文字, 卻有選擇了文字無法傳送到文字框的缺失, 這個問題在 XE6 被解決掉了,XE6 的 WebBrowser 又有了效能不夠好的問題。

到了 XE7,終於大多數問題都被解決掉了,也加入了強制執行 Javascript 的功能,可謂十全八美 (按:iOS執行 Javascript之後,可以把執行後的字串當成回傳值讓 UIWebBrowserDelegate 處理,Delphi XE7 目前還不行)。

另外,也還無法直接 access WebBrowser 裡面的內容,在以往我們使用 VCL 版本的 TWebBrowser 時,我們至少還可以透過 ActiveX 介面取得 innerHTML 或者 innerText,然而目前在行動裝置上,FireMonkey 並沒有可以讓我們可以取得內容的途徑。

由A到B 由B到C 由C到D的轉折

俗話說「山不轉路轉」,TWebBrowser沒有可以 access 內容的途徑,但是Javascript有。Javascript 不能把字串直接傳給 Delphi 程式碼,但是可以把字串當成Javascript 的變數。TWebBrowser 不能直接取得內容,但可以取得網址。

所以,筆者兜兜轉轉,拼湊出了這麼一套方法:
1. 先確定進入了我們需要的內容網址,筆者以自己寫的 Google+ 登入網頁作為範例。
http://www.moveinpocket.com/demo/GoogleLogin_API/index2.php (登入)
https://www.googleapis.com/oauth2/v1/userinfo?access_token= (登入成功頁, 會以 JSON 格式回傳使用者的資料)
2. 檢查是否進入了登入成功頁,如果進入了,就讓 WebBrowser 執行Javascript,把 innerText 抓出來當字串變數。
if (Pos('https://www.googleapis.com/oauth2/v1/userinfo?access_token=',
        self.WebBrowser1.URL) = 1) then begin

            js := 'var markup = document.documentElement.innerText;' + #13 + #10
                + 'var newURL = "http://1.1.1.1/" + markup;' + #13 + #10 +
                'window.location = newURL;';
            self.WebBrowser1.EvaluateJavaScript(js);
    end;
3. 用 Javascript 的 redirect 方法:windows.location,轉到一個不存在的網址,把innerText 當成該網址的參數,不用名字,直接接在網址後面就行了。
4. 透過 TWebBrowser 的 ShouldStartLoadURL, 就可以抓到這串字了。
if (Pos('http://1.1.1.1/', URL) = 1) then begin
        self.WebBrowser1.Height := 1;
        jsonStr := URL;
        Fetch(jsonStr, 'http://1.1.1.1/');

        jsonStr := TIdURI.URLDecode(jsonStr, IndyTextEncoding_UTF8);

        ShowMessage('got it');
        self.Memo1.Lines.Text := jsonStr;
        self.TabControl1.ActiveTab := self.TabItemResult;
    end;

也太費功夫了吧⋯⋯ 不過沒辦法,這方法還真的能抓到TWebBrowser的內容喔。

使用時機

大家可能會問我:為什麼不用IdHttp,或者 TRESTClient來抓資料,這麼費神幹嘛,你自己不是Indy的愛用者嗎?

是的,筆者愛用Indy是事實,也還寫了Indy的書沒有錯,但有時候還是會有需要用TWebBrowser的時候,像是如果需要用 Google+ 當作登入頁,試問除了TWebBrowser,還有什麼方式可以做到?

這時候千萬別回我「用IdHTTP」喔,在行動裝置上的IdHTTP,對https網址是無法載入的,不信可以試試看。所以認命點,這是你在全球唯一能找到的解決方法了,問問我怎麼這麼有自信?因為我才剛在寫這篇文章之前做過功課,從 stackoverflow,到 edn,到Marco cantu的 Blog,expert-exchange,KTOP我都找過了,到2015年一月,目前也只有這個解法了,請相信!

以下,附上範例程式碼,太長的內容我不保證一定成功喔,但JSON資料還OK啦。
本篇文章範例程式專案

沒有留言:

張貼留言