[筆記] Android 使用httpclient對self-signed certificate網站進行SSL連線

Android SDK 在進行 https 連線時,對於自簽署的憑證是會拒絕連線的,會得到 Not trusted server certificate 的例外。如果使用 HttpsURLConnection 來連線,網路上可以找到一些破解方法,在此不多談。使用 apache httpclient 其實執行效率比較差一點,但是他最大的好處就是有內建的機制儲存cookie,並且也可以跟隨 server 作自動轉址。網路上資料比較多的是 httpclient 3.x版,Android 使用 httpclient 4 (而且還有些實作被拿掉) 唯一找到比較可信的來源是 apache httpclient 官方的 example。節錄重點段落如下:

        KeyStore trustStore  = KeyStore.getInstance(KeyStore.getDefaultType());
        FileInputStream instream = new FileInputStream(new File("my.keystore"));
        try {
            trustStore.load(instream, "nopassword".toCharArray());
        } finally {
            instream.close();
        }

        SSLSocketFactory socketFactory = new SSLSocketFactory(trustStore);
        Scheme sch = new Scheme("https", socketFactory, 443);
        httpclient.getConnectionManager().getSchemeRegistry().register(sch);

直接把這段拿去用當然只有一個死字。本來看討論以為是要製造一個假的空憑證騙過 httpclient,從 Android 檔案系統有點微妙開始改來改去,一連串不同的例外或直接crash就不多談了。解決了檔案路徑,到底有沒有建立等等方面的問題之後才終於發現,假憑證是不行的…

1. 所以首先,開啟你PC或Mac上的瀏覽器連上目標網站,從憑證管理的地方把該網站的憑證匯出。每個瀏覽器做法都不同,請各位發揮正常工程師的水準做完這件事。以Firefox為例,找到site名後選匯出,多半是會得到一個檔名為 your_site_name.crt 的 X509(PEM) 憑證檔。為了之後使用方便,先把這檔案複製一份並把檔名改為 your_site_name.pem。

2. 將這個憑證格式從 PEM 轉為 BKS 格式。 這是關鍵性的一步啊。

        KeyStore trustStore  = KeyStore.getInstance(KeyStore.getDefaultType());

這行中的 getDefaultType 到底會 get到什麼 type 呢?答案:在 Android 中是 BKS。有 J2ME 經驗的人會想說,我看多半是跟 J2ME 一樣的 Sun JKS 格式吧,而且,我不要用 getDefaultType 就好了,我可以自己指定為 PEM, JKS格式啊。是的,你可以。只是你會得到錯誤訊息說 KeyStore JKS implementation not found。測了半天,看起來 Android 的 httpclient 只吃 BKS 就對了,其他都沒實作。

3. 那格式要怎麼轉?根據網路上找到的資料,可以使用 KeyTool IUI 這個Java工具。打開後從介面選 create → KeyStore,格式選 BKS,自己命名一下,要不要設密碼都可。接著選 import → Keystore’s entry → Trusted certificate → Regular certificate。 Source 的部分選 PEM 並選到剛剛瀏覽器得到的那個檔,Target 當然選 BKS 和剛 create 出來的檔。按OK之後會跳出Entries視窗,下方提示你enter new alias,隨便取個名字後ok連打,順利的話你應該就會有一個 your_keystore.bks 之類的檔案了。

4. 要把憑證檔放在 Android app 能讀到的地方,做法有兩種,放在 SD card 中,或直接放在 resources 中。

[方案一] 放在 SD card 的情形:首先模擬器要掛上SD card,這在 Eclipse 用 AVD 工具產生 avd 時就可以順便指定SD了。如果習慣用指令的話,也可以用如下指令產生 image 並 mount 上:

$ mksdcard 512M my_sdcard.so
$ emulator -sdcard ./my_sdcard.so

再來要把憑證檔 copy 到 SD card 中,目前只知道指令的做法:

$ adb push file_path/your_keystore.bks /sdcard

如果有顯示類似 ftp 傳輸速度之類的訊息應該就是成功了。
接著把 android 程式碼中檔案部分改一改

        FileInputStream instream = new FileInputStream(new File("/sdcard/your_keystore.bks"));
        ...
            trustStore.load(instream, null);

如果沒設密碼,可以把 keystore load 的第二個密碼參數改成 null,有設的話當然就把 “nopassword” 改成你的密碼。
基本上這樣應該就大功告成了。

[方案二] 再講講憑證放在 resources 的方法。首先把檔案複製到專案下的 res/raw/your_keystore.bks 。Eclipse 沒有錯誤的話就 ok,否則多半是你檔名不合規格,稍微改改。接著取用方法是把 android 程式碼改成:

        InputStream instream = getResources().openRawResource(R.raw.your_keystore);

其它同SD card。

5. 還有一個地方要注意,就是 SSL port。

        Scheme sch = new Scheme("https", socketFactory, 443);

如果你要連的網站不是用 port 443,這邊請記得改掉。

6. 另外如果要使用檔案放在 resources 的做法,getResources() 應該只能在 Activity class 中執行,如果另外包裝連線類別的話請不要直接服用上面的程式碼,要自行從 context 抓到資源再傳過去。

看起來不太複雜的事情我其實還鬼打牆了滿久才解決,希望對各位有點幫助。

[筆記]Android 的 Activity Lifecycle

Activity Lifecycle實驗:1 = Root Activity, 2 = Sub Activity started by 1

Scenario A

  1. 先測單一Activity時,程式啟動,進入主畫面
    onCreate 1
    onStart 1
    onResume 1
  2. 按 BACK,螢幕跑回桌面
    onPause 1
    onStop 1
    onDestroy 1
  3. 選程式icon再進入主畫面
    onCreate 1
    onStart 1
    onResume 1

結論:在 Root 按 BACK 等於結束程式。

Scenario B

  1. 程式啟動進入主畫面
    onCreate 1
    onStart 1
    onResume 1
  2. 按 HOME,螢幕跑回桌面
    onSaveInstanceState 1
    onPause 1
    onStop 1
  3. 選程式icon再進入主畫面
    onRestart 1
    onStart 1
    onResume 1

結論:在 Root 按 HOME 只是把程式放到背景,重新進入後不會執行 onCreate。

Scenario C

  1. 程式啟動進入主畫面
    onCreate 1
    onStart 1
    onResume 1
  2. 按下按鈕,進入畫面2
    onSaveInstanceState 1
    onPause 1
    onCreate 2
    onStart 2
    onResume 2
    onStop 1
  3. 按BACK 返回主畫面
    onPause 2
    onRestart 1
    onStart 1
    onResume 1
    onStop 2
    onDestroy 2

結論:在 Sub 按 BACK,Sub活動會消滅,另外看起來Android滿仔細的,停掉2重開1的流程是有巧妙的安排。

Scenario D

  1. 程式啟動進入主畫面
    onCreate 1
    onStart 1
    onResume 1
  2. 按下按鈕,進入畫面2
    onSaveInstanceState 1
    onPause 1
    onCreate 2
    onStart 2
    onResume 2
    onStop 1
  3. 在畫面2,按HOME 返回桌面
    onSaveInstanceState 2
    onPause 2
    onStop 2
  4. 選程式icon,會直接進入畫面2
    onRestart 2
    onStart 2
    onResume 2

結論:在 Sub活動按 HOME 會暫停Sub (Root早就暫停了),再回到程式是直接 Reactivate Sub畫面

Scenario E

  1. 補充:在 textbox 打字到一半,按POWER鎖住螢幕
    onSaveInstanceState x
    onPause x
  2. 解除鎖定,會直接回app畫面,並且之前的輸入到一半的文字不會消失
    onResume x

Scenario F

  1. 補充:打字到一半,有電話進來
    onSaveInstanceState x
    onPause x
    onStop x
  2. 掛斷電話,畫面及游標回到原來的 textbox,輸入的文字還在
    onRestart x
    onStart x
    onResume x

這裡面有個更讓我在意的事情,就是我在兩個Activity都有overwrite onRestoreInstanceState,並加入message,但是從來都沒有被呼叫過。官方手冊在提到 onSaveInstance 的時候,也是說明這儲存的bundle是讓你在下次onCreate時使用,並沒有提到 onRestoreInstanceState。不論如何,這兩個method並不是在 Lifecycle 中必定會呼叫的程序,不要依賴它們來儲存使用者輸入到一半的資料,照設計目的看來比較像是記住上一次是看到第二頁之類的功能。之後如果有讀到相關說明再補充上來。

上面的實驗雖然 onPause 和 onStop 總是在一起,但根據手冊,Pause 狀態跟 Stop狀態確實是不同。Pause狀態下,使用者不能與這個活動互動,但是仍然看的見它。也就是說如果你產生了一個透明的或不占到全螢幕的子活動,則主活動有可能只會 Pause,而不會 Stop。而Pause狀態對系統來說仍然是alive,只有系統資源真的太低時才會kill它,而Stop的活動相對來說很有機會被殺掉。(不知道 Alert Dialog 與 Parent是不是這種關係)

關於保存狀態或傳遞資料方面,官方SDK手冊強調,如果需要回傳結果給主活動,不要在 onDestroy 加入 setResult function! ,之前被 google 到的某論壇文章騙了,說可以在 onDestroy setResult,怒。根據手冊說,想要做保存資料,例如 user 輸入之類的事情應該放在 onPause,onDestroy 應該只做釋放資源之類的動作,因為 oPause是唯一一個保證會在系統 kill process 之前呼叫並且等待其return的function。實際上測試的結果,確實在 onDestroy 試圖 setResult 是沒有用的,intent 應該是會隨著此sub Activity 被消滅,onActivityResult 的地方會得到 null intent。一般來說返回鍵表示要放棄現在的動作返回,不保留狀態還算合理,但看了一下Android的通訊錄app,按下返回鍵時,其實還是當作 submit 立刻儲存了打到一半的資料,這可能是為了防止編輯到一半電話進來,資料全被清空的滿腔怒火。另外已經在背景的Activity是有機會被kill process的。萬一發生了這事情,在kill process之前會先 onSaveInstanceState(Bundle),下次再 onCreate(Budle) 時就可以使用這 bundle 的動態暫存資料。

總之,儲存永久性資料的動作可以在 onPause做,onDestroy只能做資源回收,比如 connection close,之後有其他心得會再補充資料。

[筆記]Android 多國語言

官網的Hello, L10N,還有 ADT plug-in 都叫大家使用兩個字母的語言代碼,例如 zh, en, ja 來分字串語系檔。圖片則規定要使用語系+地區,例如 zh-rTW。這時候最大的疑惑立刻浮上心頭,那簡體跟繁體字串要怎麼辦(〃*`Д´)。網路上沒有搜到甚麼資料,乾脆自己惡搞看看,直接把/res/values-zh/strings.xml 複製修改成兩份,變成 /res/values-zh-rTW/strings.xml 跟 /res/values-zh-rTW/strings.xml 內容當然分別是簡體跟繁體的。喔! 沒有編譯錯誤,很好。接著開始切換模擬器語系…おおお!還真的會分簡體跟繁體啊!教學幹嘛不寫啊!(`・ω・´)總之姊妹們,有需要的話語系分下去就對了(*´∀`*)
ps. 版本的話,SDK 1.1的模擬器只有德文和英文懶的測,SDK 1.5 和 SDK 2.01 是測試OK的,推論應該是只要手機有支援相關語系都可以。

[筆記]Android的返回鍵

從Main Activity 使用 startActivity 或 startActivityForResult 產生並切換到 sub Activity之後,如果按下返回鍵,會發生甚麼事? 本來很擔心整個sub Activity只是看不見,但仍然蹲在角落畫圈圈,隨著Main Activity一次一次的再度 startActivity 而產生撿不完的屍體。Google之後再測試一下發現好家在,按下返回鍵系統就會呼叫 onDestroy(),結束掉這個 sub Activity。上以前被寫得很爛的J2ME Midlet 嚇過,真的是很害怕屍體山啊,為了愛護大自然大家一定要做好資源回收喔。∑(´д`*)

關於 BACK key,HOME key,官方Dev Guide 的 Application Fundamentals 的 Activities and Tasks 這個 session 講得滿清楚的。你的整個 App 是一個TASK,root Activity 和 sub Activity,包括中間呼叫系統地圖在內,跑在同一個 TASK process,一個 TASK 就是一個 STACK。主活動也就是程式載入的第一個活動一定是在堆疊底端,它產生子活動時,子活動就被加入堆疊。按下BACK key 時,子活動就從堆疊中被拿出來(丟掉)。子活動的子活動當然也是相同。所以BACK key做的事情就是把現在這個活動拿去丟:P (各位兄弟姊妹們應該沒有忘記STACK是LIFO吧) 而HOME key又會做甚麼? 他不丟東西,他會把這整個TASK / STACK 放到背景。等你在別人家玩一圈,再度按下app icon,背景的那整包 STACK / TASK 就被放回前景。如果剛在是從某個子活動按下HOME,現在應該還是看到剛才的那個子活動才對。BACK key則還是負責殺掉目前的子活動,並不會回到你剛玩過的其他app。謎之聲:不知道為什麼忽然開始覺得好險沒有Forward Key。另外補充一點,就是如果離開去外面玩太久,系統會把TASK中的子活動都殺光,只剩下主活動,下次切回來時看起來就好像新開的應用程式一樣。

上面說的是系統預設行為,TASK 和 Activity 的行為是可以由一些參數設定加以改變的,這邊就不深入討論了。有興趣可以繼續看完上面給的連結。

私にとって、シャントット帝国最大の陰謀は…

クローン・タルタル部隊はフォモルだヽ(`Д´)ノ

這次更新之後先去解了超壯闊超感人的塔魯史詩完全召喚篇,接著看山多軍又一次學不乖沒帶解毒藥死掉,再勉為其難的拿了一下巴斯D3棍,之後當然就是衝S斯拉S女王的劇情了。解到萬歲牌塔魯部隊時,時間有些晚應該沒有野團shout了,就先去收集星屑順便開希望看有沒有現地團。這種時間…好吧雖然是米人勸誘就打看看吧。組到最後只有隊長是米人( ´Д`)=3 還是很沒耐性的米人(話說有幾個米人有耐性了) 我說看這編成沒黑,要回去換黑他不想等,當然沒兩下就全滅出來了。沒想到隊長居然畏罪潛逃,剩下五個『日人』,反正就先收星屑吧。後來T塔魯找了Y大來一起打,總之最後打贏了,跟日人也聊的滿愉快的,恩,這其實是前言。
其實因為上班後比較懶神兵勳章都被拔了兩階了,本來打算打完黑白香拖拖就再度封印,不過既然換了からくり士穿的加速褲子總要去感受一下。就這麼吃了一堆自主海老,升了好幾點沒力波後某一天,忽然想到詩跟召都40了還沒去拿AF1。就開著赤掛忍去震動的迴廊秒了一隻眼怪,想說沒練習過solo召喚獸,就又打了個土召換。開赤忍沒有D1也沒teleport可回家,就直接掏出了塔布戒。既然都飛到地下壕了,就去禮拜堂拜拜一下吧。跟fomor無冤無仇的我進了禮拜堂的fomor區當然是不會補消音,正在對書架上下其手找料理書的時候消音掉了。一瞬間忽然聽到fomor唱忍術的唱忍術,轟魔法的轟魔法,完全不知道發生甚麼事就看到本塔魯屁屁朝天了,而且周圍都是fomor,雖然掛了RR也不敢爬起來只好D4。゚(゚´Д`゚)゚。 我…我沒有殺fomor阿哪來的怨み…震驚之餘努力回想了一兩分鐘,忽然腦中浮出一整排黑黑的塔魯高唱香脫脫萬歲的場景(´・ω・`)

『ドルチェ.シャントット!』
『ドルチェ.シャントット!』

グローン.タルタル