尼克大說,對Julian的十大IO問題之一應該是"你是自己花錢去的嗎?“, 今年的確自費出國參加了幾個活動, 本以為今年也無緣IO了, 不過最終還是成行了, 不用繼續怨念, 這都得感謝很多人呀…首先是Tiffanie的幫忙牽線, 以及Elvis的慷慨讓票(老實說他大可以拿去賣黃牛票賺一筆,卻原價轉賣給我), 還有wuman, 不曉得幫我跟他朋友煩過幾次, 後來聽到尼克大也差點幫我弄到票, 實在非常的感動….還得感謝Arthur, 我這次就可以不用自費出去了(還得間接感謝這時候離職的Johnny :P , 還有也有幫我問的mifong), 反正終於不用在台灣枯坐在電腦前熬夜看轉播, 雖然去了美國還是熬著夜被公司這邊要東西(不是有人拿著保障名額出去的?), 好吧….總歸還是很高興….很感謝這麼多的人….

好了, 感謝完了, 進入正題, 猶豫半天, 還是把這篇當半遊記來寫, 反正好像還在Jet lag (呃, 都回來三四天了), 還睡不著, 適合胡言亂語, 寫寫流水帳

這次的Google I/O是第一次一口氣舉辦了三天, 大概越來越熱門了, 才會讓Google這次乾脆延長了一天, 不過票價也提高到了九百鎂, 這大概也已經成為每年developer最大的活動之一了, 老實說, 第一次來的我也有點興奮過頭了, 早上"六點"不到就到了Moscone Center, 本以為我已經夠瘋了, 結果:

DSC03266

好吧, 早來其實沒特別好處,  除了可以早點來吃早餐, 吃完早餐後看到長長一列的排隊人潮就發現, 完全沒佔到便宜, 一堆人排隊等著上樓聽Keynote呀! 我這時候才排, 已經是落後了, 事實上, Keynote開始時, 我還被卡在外面呢….

不過排隊排的後面也不完全差, 不知道怎回事, 我就這麼被擠到這位子:

DSC03293

雖然在邊邊, 但這是前十排的位置呀….中間的位置好像是媒體

不意外的, 今年Android也是一大主題, 這幾年, Android, Chrome似乎就已經佔去Google話題的大半, 當然, Glasses還有跳傘噱頭也是今年的一大亮點, 不過, 畢竟還不算是現實的東西

會場很多比較東西都跟Android比較相關 (機器人比較好做話題吧, 總不會要Chrome拿來當神奇寶貝球):

DSC03323DSC03318DSC03320DSC03317

今年雖然增加了三天, 但Keynote還是維持跟往年一樣就只有兩天, Keynote內容基本上也跟去年差不多, 第一天著重在Android, 第二天著重在Chrome, 只是第一天這次插入了Glasses和G+, 第二天則是加入了Drive, Cloud…有老面孔也有新面孔…

老賈上天堂後, Vic是我覺得現在Keynote講的最好的一個, 條理分明, 速度也控制得宜, 不像Cook的那種拖長長還不知道他表達什麼, 現場聽Keynote的感覺是…. "比較熱鬧”…台上的3D投影也很炫…不過就是少了跟朋友同步聊天的樂趣, 在台灣看還可以同時用Skype, Facebook, Plurk, 現場人多混亂, 認識的朋友也早不知道被擠到哪去了, 上網也很不穩定, Wifi, 3G都不是很OK呀….

大老遠跑去現場跟在家看轉播錄影有啥不同?除了說可以去一些沒有轉播的場次外, 跟外面一堆攤位的互動, 也是在家沒辦法辦到的…. 本來很認真的邊聽Keynote邊排好後面想聽的東西, 不過後來想想就沒照著跑了, 大部分時間同時在多個場次跑來跑去, 還有在外面的攤位逛逛, 主要是覺得, 大老遠跑去, 如果只是去聽那些會有錄影轉播的場次, 其實挺浪費的, 因為那些回去還可以反覆複習, 不用去到裡面打瞌睡, 打游擊, 然後回去再複習錄影, 收穫可能比較多, 而且, 外面還是有些寶可以挖呀

今年跟往年一樣, 也有大放送一堆東西, 首先第一天是Android developer三寶 - Tablet (Nexus 7), Phone (Galaxy Nexus), 和Nexus Q, 第二天則是Chromebox

DSC03347

大老遠跑過去, 雖然不完全是為了這些贈品, 但說不想拿這些就太虛偽了 :P 開箱時忘了拍照片(其實是懶得拍), 所以就不放照片了, 由於今年Android的主題是新版的4.1 (Jelly bean), 因此Tablet和Phone都已經升級到最新, 馬上可以把玩了

除了這些, 還有T-shirts外, 如果跑對門路, 還有別的東西, 比如說我和Wuman跑去ADK這個場次, 賭的就是去年這topic有送ADK, 今年應該也會, 果然這也是有送的場次, 此外第二天Sony在場外(離會場很遠)也舉辦了活動, 有送前五百名參加者Google TV, 結果我們一行人, 冒著冷風排了兩個多小時, 排倒數20名內, 成功的進去讓Sony請喝酒拿Google TV

雖然Google排了三天的內容, 但第三天其實就是垃圾時間了, office hour也都收了, 這天想問問題根本就白跑了(本來想去問問G+ history API的說), 連便當都只剩下盒餐, 只好跟Tiffanie搜刮一下桌上的Jelly bean

555768_3977770399215_694333072_n

 

這次的一些重點內容心得(大略想到的, 以後有機會再補充);

Android Jelly Bean: 實際上的感覺真的是非常的順暢, 感覺下了很多功夫在performance上面呀, 雖然整個Usability還不夠好, 但也有了不錯的進步, 整體流暢度可以幹掉iPhone了, 第一次拿到裝了JB的Galaxy Nexus後, 就有點愛不釋手了, Google Now雖然不像是直接針對Siri來的, 但感覺像是一個以Search為中心整合的一個產品, 不過, 我實際試用, 不覺得它有台上展示的那麼聰明, 我拿著它的建議搭車, 老要我搭下一班, 不過JB真很令人惶恐, 尤其時Android手機製造商呀, 快被Google打成純製造商了吧….

Nexus 7: 拿在手上, 還是擺脫不了廉價感, 不過就199, 能要求啥? 一打開到桌面, 濃濃的Play store味, 很明顯的感覺Google的重點不是放在硬體跟OS, 而是內容, 擺明的就是跟Kindle Fire嗆聲, 不過, 雖然電子書也是它的其中一個重點, 但實際上看, 覺得這螢幕看電子書, 不舒服

Nexus Q: Google進軍客廳的一個"實驗", 為啥說是"實驗"? 因為這東西功能實在不多, 也不特別突出, 就是上Play store下載下來播放, 某種程度上跟Apple TV近似, 但卻又賣超貴(比起Apple TV), 而且, 進客廳, 跟原本Google TV的重疊度太過於高了~~設定程式必須跑在JB的裝置上, 現在JB裝置也就那兩個, 所以到時候它開賣時, 想買它的人搞不好還得同時搬個Nexus 7回家(假如Galaxy Nexus那時候還沒升級)

Google+: 在Keynote提的Event等於就是Facebook Event的升級版了! 看來它是試著去解決Facebook event碰到的問題, party mode是一個不錯的設計, 雖然我之前就有想要做類似的東西了, 至於這次也同樣發表的history api, 一整個看起來就是Facebook open graph的複製品, Google+有在進步, mobile client有做更好, 但其他的, 似乎太跟Facebook走了, 這不見得是好事

Glasses: 目前噱頭大於實際用途的東西, 感覺上現有的功能不多, 從keynote和會場的聊天聽來, 沒多少功能, 拿來Hangout還OK, 預購價格1500還算很貴, 不過預購的人不少, 功能不齊全, 明年才能拿到, 電池續航力都是個疑問

Chrome: 今年的Keynote主要集中在不同裝置間的Sync, keynote上太陽馬戲團用CSS3D, WebRTC做出來的東西還挺不錯的, 會場的攤位則是有展示各種不同以HTML5 API做的東西, 還有以Native client打造的遊戲(如古墓奇兵) , 至於Chromebox…用了之後的感想是, ChromeOS是個爛東西 = =“

其他: Google Drive API應該值得看一下(Drive整個好像Dropbox + evernote + google doc), Mobile Youtube API , 至於GCE, 細節似乎還不太多, 不過以後應該有機會來跟AWS做競爭

以上, 懶得寫了, 該睡了…

最後,

跟著老朋友新朋友, 一起參加這活動, 是意義非凡的一件事呀

DSC03316

看來script injection也不算是啥旁門左道了, 在Android 4.0 ICS上的WebView也使用了同樣的技巧了(在Gingerbread上並未看到這樣的codes)

Device-2012-02-17-171716

在ICS的Setting裡面"Accessiblity"裡有個設定叫"Install web script", 其實這東西應該沒使用者看的懂, 其實蠻怪的, 不過既然放了就有它的作用

ICS的WebView裡面有這樣一段codes:

int axsParameterValue = getAxsUrlParameterValue(url);

        if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED) {

            boolean onDeviceScriptInjectionEnabled = (Settings.Secure.getInt(mContext

                    .getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);

            if (onDeviceScriptInjectionEnabled) {

                ensureAccessibilityScriptInjectorInstance(false);

                // neither script injected nor script injection opted out => we inject

                loadUrl(ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT);

                // TODO: Set this flag after successfull script injection. Maybe upon injection

                // the chooser should update the meta tag and we check it to declare success

                mAccessibilityScriptInjected = true;

            } else {

                // injection disabled so we fallback to the basic built-in support

                ensureAccessibilityScriptInjectorInstance(true);

            }

        } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT) {

            // injection opted out so we fallback to the basic buil-in support

            ensureAccessibilityScriptInjectorInstance(true);

        } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED) {

            ensureAccessibilityScriptInjectorInstance(false);

            // the URL provides accessibility but we still need to add our generic script

            loadUrl(ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT);

        } else {

            Log.e(LOGTAG, “Unknown URL value for the "axs" URL parameter: ” + axsParameterValue);

        }

這功能啟動的條件是url裡有"axs=1"或是剛講的那個設定是enabled, 而這一整段code是在onPageFinished最後被呼叫到的, 也就是頁面載入完成之後
它主要做的只有:

 loadUrl(ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT);

這邊並不是強制去載入一個新的URL, 其實他做的就是script injection, ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT的內容就是:

    // JavaScript to inject the script chooser which will

    // pick the right script for the current URL

    private static final String ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT =

        “javascript:(function() {” +

        “    var chooser = document.createElement(‘script’);” +

        “    chooser.type = 'text/javascript’;” +

        “    chooser.src = 'https://ssl.gstatic.com/accessibility/javascript/android/AndroidScriptChooser.user.js’;” +

        “    document.getElementsByTagName('head’)[0].appendChild(chooser);” +

        “  })();”;

它就是在最後把https://ssl.gstatic.com/accessibility/javascript/android/AndroidScriptChooser.user.js給inject到page

還沒去仔細看js裡面的內容, 似乎都是一些基本的東西的樣子, 還不太知道他的用意, 不過應該跟加速(?) Google本身的頁面有關係, 不然其他web site應該也沒用到這些東西

上次寫了一篇"startActivityForResult and callback in WebView“, 本篇則是上次這篇的延伸應用, 這是有人問我如何inject一整個javascript file到一個web page內(剛剛回顧了一下自己這篇, 發現我把它叫做javascript injection)

其實原理是一樣的, 在載入完原本的web page之後, 一樣透過URL來插入script:

mWebView.loadUrl("javascript:var js = document.createElement(‘script’);js.type = 'text/javascript’;js.src = http://my_host/1.js;document.getElementsByTagName('head’)[0].appendChild(js);”);

一樣是透過“javascript:”來inject, 不一樣的只是, 這次我要插入的是一整個js檔, 所以這串javascript的目的就是要建立一個新的script element, 並將它插入head裡, 這樣任務就達成了

但這方法的缺點是, 來源必須是一個url, 也就是要把script file放在server才可以, 如果script是來自應用程式本身, 比如說放在應用程式apk裡面, 或是放在data partition就不行了

在Honeycomb之前的版本, 我還沒想到一個比較好的解法, 但Honeycomb (API level 11, 含11)之後就有一個比較簡單的解法了

作法就是overwrite WebViewClient的shouldInterceptRequest,這似乎就是為了類似的用途而生的呀~~

這邊我將來自於apk asset目錄裡的檔案的url定義成"asset://“, 因此, 想當然耳, 要導向的url就是這種, 作法也很簡單, 將asset的input stream包裝成WebResourceResponse就可以了, 這樣只要"js.src="後面的url是"asset://xxx.js”, 這js的來源就是apk裡的asset

缺點是, 這方法只適用API level 11之後

延伸應用? 其實應該可以利用這個API做出一個lightweight版本的client side serlvet (這樣叫好像也不是很貼切, 反正就是不需要透過http去存取), 不過因為資訊只有url可以使用, 因此不能implement “POST”…

題外話, 這個我是在Ice cream sandwich的emulator上測試的, 不過真的要小抱怨一下, 開個emulator要開很久, 看篇漫畫結束後還沒跑完, 如果叫developer完全用emulator開發, 真的會抓狂吧….這樣開發者的開發意願也會降低吧…. = =“

Android 4.0 Ice Cream Sandwich 有一個新功能是, 使用者可以停用(Disable)系統上預載的應用程式, 以往系統預載的應用程式是不能被刪除的, 現在, 新的版本多了一個按鈕讓你可以停用它:

Device-2011-10-22-001443
當你把預載應用程式的icon拖到App Info就可以看到一個Disable的按鈕, 按下去後, 你就不會在程式啟動介面上看到他了

這是中國人所謂的…“眼不見為淨"嗎?

被Disable掉的應用程式基本上並沒被移除, 它還是在你手機裡面, 並不會因此多出一些可使用空間, 只是你看他不爽, 以後就可以不用再看到他了….(呃, 不爽用就不要用不就好了)

這是新功能嗎? 對這介面上來說…這按鈕…是新的

PackageManager裡面有個叫做setApplicationEnabledSetting , 這用途就是用來作這種事的, 所以做這樣一個功能到底多複雜呢?

PackageManager pm = getPackageManager(); pm.setApplicationEnabledSetting("com.geekyouup.paug.awesomepager”, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);

就差不多上面那樣

當然啦, 是沒辦法隨便寫一個軟體去disable人家的應用程式的, 如果可以, 不就天下大亂了, 這API只能用在跟你的應用程式相同的uid的package

不過對於系統應用程式來說, 就沒這限制了吧

至於這API啥時有的?        Since API level “1”

Ok, So…..

We got a new feature……….

想半天標題不知道怎下, 有點不是很貼切, 內容也寫的有點懶 :P 有可能會看不懂吧

最近開始想要一個禮拜想一個東西來實踐一下(不知道可以持續多久:P), 這禮拜想到的是這個: 從WebView內的javascript去叫起一個Activity, 然後把這Activity回傳的結果回傳給WebView內的javascript

具體的假想應用範例: import contact information, 從Javascript內叫contact picker, 並把所選的contact資訊匯入WebView內的form中

首先, 呼叫contact picker的範例如下:

public void launch()

{
    Intent intent = new Intent(Intent.ACTION_PICK);
    intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
    startActivityForResult(intent, PICK_CONTACT);
}

基本上, 是要使用startActivityForResult, 這樣才能把所選定的contact給回傳給我們的Activity

但如何讓這lauch被javascript call到呢? 我們必須把這個method封裝到一個class內, 我用一個名叫ContactLauncher的class來做封裝, 並且將這個interface指定給WebView:

webView.addJavascriptInterface(new ContactLauncher(), “contactPicker”);

這樣一來, 我們在javascript內就有一個contactPicker可以供呼叫了, javascript的範例如下:

function launchPicker()
{
    if(contactPicker) {
         //register callback
          window.activitycallback = function(response) {
                  $(’#name’).val(response.name);
          };

          //call picker
         contactPicker.launch();
    } else { //no picker }
}

我們可以在javascript內透過"contactPicker.launch()“來叫起Contact picker, 這個就會直接呼叫ContactLauncher裡的lauch()

那在這之前的程式碼是幹啥用的呢? 由於launch()並不是一個blocking call, 並不會等到結果回傳後才結束, 而我們又要startActivityForResult回傳回來的資料, 因此我們需要一個callback來接收回傳的資料

那怎讓javascript可以接收回傳回來後的資料呢, 這邊我利用一個類似javascript injection的方式來做(原理是利用WebView可以接收"javascript:"這種形態的url)

以下是onActivityResult的實作 (這邊偷懶只取一個Display name):

protected void onActivityResult(int requestCode, int resultCode, Intent data) { if(requestCode == PICK_CONTACT) { Cursor c = managedQuery(data.getData(), null, null, null, null); c.moveToNext(); String name = c.getString(c.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME));  String json = "{name:'"+name+"'}"; webView.loadUrl("javascript:window.activitycallback("+json+");"); } }

 

因為在WebView內的那個javascript page我已經先安插了一個activitycallback, 所以就利用loadUrl來呼叫它, 這樣就大功告成了!

這只是一個簡單的範例, 還可以做的更generic一點, 比如說把傳入的資料轉化成json這段, 這招應該還可以做一些應用才對