星期五, 12月 07, 2012

.NET SortedList 存放同樣 key 的物件

今天遇到使用 SortedList 時產生例外, 抱怨 key 相同, 這時我才注意到, 原來 SortedList 不能存放相同 key 的物件。不過其實嚴格來說, 並不是 key 相同就不能存放, 而是預設的比較器是很正規的比較, 當兩個比較對象相同時, 就會傳回 0, 而 SortedList 就會抱怨 key 相同, 產生例外。

根據上述, 解決方案其實很簡單, 就是自己實作一個只會傳回 1 與 -1 的比較器, 在建構 SortedList 時傳入, 這樣即使 key 相同也可以存入。比較器的實作非常簡單, 可參考這一篇討論串中一樓的解答


class MyComparer : IComparer
{

  public int Compare(int x, int y)
  {
    if (x < y)
      return -1;
    else return 1;
  }
}
非常簡單。

星期三, 12月 05, 2012

修改 Visual Studio 專案檔的儲存資料夾

我因為常常製作一些功能相近的小程式, 所以都會拿現有的專案複製之後修改, 不過這樣一來, 會遺留許多就專案的痕跡, 自己看了也很不舒服。其中, 有許多設定, 像是專案名稱、組件名稱、命名空間等等都可以在 Visual Studio 裡頭直接修改, 只有儲存的資料夾必須自己手動修改。

對於專案最外層的資料夾, 也就是儲存有 .sln 檔的資料夾, 可以隨意更改名稱, 但是裡層的資料夾 (預設與外層同名), 就必須在修改名稱之後, 再以文字編輯器開啟 .sln 檔, 將其中的資料夾對應到新的名稱才行。例如我有一個叫做 WinTCPTest 的專案:


若想更改紅色框的資料夾名稱, 就要使用文字編輯器修改 WinTcpTest.sln 檔:

只要將 Project 段落中的資料夾名稱改為新的資料夾即可。

星期六, 12月 01, 2012

Xcode 4.5 中 UIDatePicker 在重新開啟專案後寬度莫名其妙變成 480

自從 Xcode 4.5 預設啟用 autolayout 功能後, 許多地方若是沒注意, 就會出現靈異現象。今天我自己遇到的狀況就是在準備送印一本日文翻譯書之前, 整理要附贈的範例光碟, 發現有個介紹 UIDatePicker 的範例開啟後, UIDatePicker 的寬度就莫名其妙變成 480, 超出了 iPhone 預設的 320。

於是我做了個簡單的實驗, 建立一個 Single Page 樣板的專案, 在 Storyboard 中放置一個 UIDatePicker, 如下:
這時即使甚麼事情都不做, 立刻關閉專案, 之後再重新開啟專案, 就會看到 storyboard 的內容變成這樣:
你可以看到 UIDatePicker 突然變寬了, 如果從尺寸檢視面板查看, 會發現寬度變成了 480。不過如果你執行這個程式, 看起來又很正常:
如果一開始建好專案時, 就先把 autolayout 功能取消, 就不會有這樣的問題。目前我也還不知道到底是哪裡出錯?即使啟用 autolayout, 我看了 UIDatePicker 的 constrain 也都沒有甚麼問題, 使用 Xcode 4.5.1、4.5.2 (以及朋友在 4.6) 上都會有一樣的情況, 若您知道問題的癥結, 也請告訴我, 謝謝。

PS:之前我也遇到過 UIView 動畫在 autolayout 啟用時, 使用 center 屬性變更畫面上元件的位置會跑到錯的位置上, 顯然 autolayout 真是值得大家好好研究一番啊!

星期二, 11月 27, 2012

Android 模擬器 Skin 設成 WXGA720 就跑不起來?

Android 模擬器一直是大家詬病的工具, 除了跑得很慢以外, 還回到一些奇奇怪怪的問題, 像是網路上許多人都發生過, 只要把 Android 模擬器設定為 Skin 使用 WXGA720, 像是這樣:

結果一起動模擬器就會出現配置記憶體錯誤, 終止執行。看了各路人馬的補救方案後, 我自己最後要這樣才能跑來, 一是改為自訂解析度 720X1280, 另外則是要很無俚頭的把 Hardware 中 Device ram size 的 1024 加上 mb 字尾, 像是這樣:
就可以執行了。至於原因嘛, 我真的不懂, 但至少這樣可以測試程式了。

星期三, 11月 14, 2012

在 Windows Forms 程式中取得組建檔的版本編號

剛剛想要在視窗的標題列顯示程式的版本號碼 (設定在組建檔的版本編號), 找了許久, 最簡單的應該就是從這裡看到的解答:
this.GetType().Assembly.GetName().Version.ToString ();
當然, 從 GetName() 傳回的物件還可以取得其他與組件檔案相關的資訊可供使用, 需要的可以自己看一下文件囉。

星期四, 11月 01, 2012

Mac 上的免費螢幕抓圖軟體 InstantShot!

因為最近在處理一本 iOS 程式設計的書籍, 因此常常會需要抓取 Mac 螢幕畫面, 經過搜尋與試用後, 發現 InstantShot! 應該是我所能找到的免費軟體中最好用的。主要功能如下:

  • 支援存檔格式:tiff, jpg,png。
  • 擷取模式:全螢幕、區域、視窗 (可指定抓取視窗上的元件, 像是單獨的按鈕等等)。
  • 可完全自定快捷按鍵。
  • 有定時擷取功能, 可以設定等候指定秒數後才抓圖畫面, 也可以以固定時間間隔重複抓取多次畫面。
  • 可以抓取滑鼠游標。
  • 可以自訂儲存圖檔的資料夾, 客製化檔名。
其他還有一些我沒用到的細部功能, 就不贅述, 可自行參考網頁上的說明。雖然在啟動截圖時, 螢幕上會顯示說明, 不過我建議還是好好讀過網頁上的說明會比較好, 不然可能有些功能一開始會不知道該怎麼操作。

星期四, 10月 18, 2012

callback 到底應該怎麼翻?

由於現在越來越多程式語言或是框架 (framework) 強調非同步技術, 使得 callback 躍登熱門詞彙, 不過這個詞倒底要怎麼翻成中文, 可有點傷腦筋。過去有的人不翻, 有的翻為「回呼函數」, 這我個人認為無法達意;我也看過有作者翻成「回撥函數」, 這取材自回撥電話的意思 , 意思是到了, 但是在程式設計的語句中唸起來就是怪怪的。前幾天讀何孟翰先生寫的《超強圖解 前進 App Store!iOS6 SDK 實戰演練》一書, 他譯為「回應函數」, 我覺得真不錯, 既能達意, 夾在文句中唸起來也不會繞口, 趕緊記下來, 往後就用這個說法。

星期五, 10月 12, 2012

IBOutlet 與 IBAction 到底是什麼?

在學習 iOS 開發的時候, 一定會遇到的就是 IBOutlet 與 IBAction, 這兩個其實是定義在 UIKit 的 UINibDeclarations.h 中的 macro, 定義如下:
#ifndef IBOutlet
#define IBOutlet
#endif 
#ifndef IBAction
#define IBAction void
#endif
從定義可以看出來, IBOutlet 與 IBAction 對於程式碼沒有實質的意義, 存在的作用只是像是標籤一樣, 標示了程式碼中的某個屬性可以用來對應到介面上的某個同樣類別的元件, 或是某個方法可以用來回應規格相符的事件。

星期四, 10月 11, 2012

Xcode 4.5 自動產生 IBOutlet 屬性的改變

前天想說編輯的書最後還是應該用 Xcode 4.5 校稿才對, 結果一升級之後今天測試了一下, 發現了一個討厭的問題, 就是在自動產生 IBOutlet 的屬性時, 在對應的成員變數的命名上不一樣了, 這樣一來, 不就得要改書中的內文以及程式了?

以前如果在 Interface Builder 上從一個 UILabel 產生 IBOutlet 屬性, 假設取名為 myLabel, Xcode 會在 ViewController.h 檔中產生以下這行 @property 指令:
@property (weak, nonatomic) IBOutlet UILabel *myLabel;
同時在 ViewController.m 檔中產生對應的 @synthesize 指令:
@synthesize myLabel;
這樣在 ViewController.m 中就可以直接以 myLabel 操作對應的元件。但是在 Xcode 4.5 中, 已經不會產生 @synthesize 指令, 這會導致編譯時由編譯器自動以如下的 @synthesize 敘述幫你合成 getter 與 setter 方法:
@synthesize myLabel = _myLabel;
也就是將對應的成員變數以你指定的屬性名稱加上 "_" 為字首命名, 使得屬性名稱與對應的成員變數名稱變成不一樣了, 因此無法以 myLabel 操作元件, 而必須加上 self  以 self.myLabel 才能操作對應的元件。當然, 也可以直接使用變數, 但這樣除了違背封裝的意圖, 也會使用到程式中沒有出現過得變數。這下子, 書中原本的程式以及解說的內容都得一併修改, 真是麻煩了。

當然, 這樣的變更主要是為了符合良好的程式撰寫習慣, 讓屬性與成員變數能夠明確地區分開來, 不過能不能有個選項讓我可以選擇要不要接受這樣的改變啊?

PS:如果需要, 還是可以自行把 @synthesize myLabel; 加回去, 只是多一道工, 而且對我來說, 變成還要額外解是為什麼要這樣做, 更是麻煩!

星期一, 10月 08, 2012

關掉 Mac 開機的『登』聲音

不知道有沒有人跟我一樣覺得很困擾, 不想要再 Mac 開機時聽到一聲『登』, 尤其是在特定的場合, 『登』一聲實在很尷尬。網路上查了一下, 最簡單的方法就是裝個小工具, 設定開機時不啟用音效, 或是靜音, 我自己試過在 10.7.4 上使用 StartNinja, 可以完美達成任務, 其他有些軟體可能因為 Mac OS 版本的關係, 無法成功。

除了安裝工具軟體外, 也可以參考這一篇問答中 Acid 的回覆, 使用 AppleScript 加上開機與關機的 script, 在關機時設定喇叭靜音, 並在開機完成後設定喇叭恢復原始音量, 這樣就可以在開機時不會聽到『登』的聲音, 但開機後一樣可以聽到正常的聲音了。

星期六, 10月 06, 2012

Objective-C 的協定 (Protocol)

前幾天在看 iOS 開發的翻譯書稿件, 注意到一件奇怪的事。在使用 UIAlertView 的時候, 有些書都沒有強調擔任 UIAlertView 的委派物件必須遵守 (conform) UIAlertViewDelegate 協定。因此, 如果是由 controller 擔負委派物件時, controller 的類別大概會是這樣:
@interface ViewController : UIViewController
    ....
- (void)alertView:(UIAlertView *)alertView
    clickedButtonAtIndex:(NSInteger)buttonIndex {
    .....
}
@end
事實證明, 這樣的確是可以運作, 連個編譯的錯誤訊息都沒有。當使用者點選了 UIAlertView 上的按鈕時, 的確會引發 clickedButtonAtIndex。

但若是使用 UIActionSheet 時, 書中卻又強調委派物件必須遵守 UIActionSheetDelegate 協定。因此, 如果一樣由 controller 擔任委派物件時, controller 類別大概會長這樣:
@interface ViewController :
        UIViewController<UIActionSheetDelegate>
.....
- (void)actionSheet:(UIActionSheet *)actionSheet
        clickedButtonAtIndex:(NSInteger)buttonIndex {
.....
}
@end
實在覺得很奇怪, 兩個元件的 pattern 其實是一樣的, 但為甚麼一個需要明確標示遵守協定, 另一個卻不用?經過 google 找到這一篇 Why do I not need to declare UIAlertViewDelegate in the header?, 看起來原因有兩個:
  1. 在建立 UIAlertView 的  initXXX 方法中, delegate: 的型態是 delegate:(id), 並沒有要求委派物件必須遵守任何協定。但是在建立 UIActionSheet 的 initXXX 方法中, delegate: 的型態卻是 delegate:(id < UIActionSheetDelegate >), 表示委派物件應該要遵守 UIActionSheetDelegate 協定。
  2. 在 Objective-C 中, 協定的角色似乎沒有其他種類物件導向語言中的界面 (Interface) 那樣嚴謹。事實上, 如果擔任 UIActionSheet 的委派物件並沒有遵守 UIActionSheetDelegate 協定,  只要實作有正確的 clickedButtonAtIndex: 方法, 程式一樣可以動, 只是編譯時會有警告訊息而已。而在 framework 的實作中, 看來也都是以透過 respondsToSelector:@selector(delegatedMethod:) 檢查委派物件是否具有指定的方法, 才會實際叫用該方法, 而不是依靠是否遵循特定的協定來判斷。
換句話說, 協定就只是給大家參考的規格書, 而不是像是其他語言中的界面那樣嚴格。 

星期四, 10月 04, 2012

為 Windows 7 中的 XP Mode 虛擬硬碟檔搬家

前幾天因為公司的電腦硬碟不大, 結果 C 碟空間太少, 連想裝個 Visual Stdio 2012 Express for Desktop 測試都不夠, 仔細檢查了 C 碟的空間, 怎麼算都覺得至少還可以多個 6GB 左右, 但是就是找不到這個空間被誰戰掉了。巡視一遍後,終於找到原來是 XP Mode 的虛擬硬碟檔案就佔了 6 GB 多, 直覺認為, 不就把它搬到其他磁碟就搞定了!

不過第一個遇到的問題是 XP Mode 都是以休眠的方式暫停, 而且在開始功能表中只有『登出』沒有『關機』, 不能關機就沒法更動虛擬硬碟檔案。還有 Virtual PC 功能表上有 Ctrl+Alt+Del 功能, 可以讓 XP 顯示『Windows 安全性』交談窗, 就可以把 XP 關機了。

接著第二個遇到的問題是, XP Mode 的硬碟虛擬檔不是單一檔案, 而是由一個基本硬碟虛擬檔延伸而來, 經過 Google 大神幫助, 找到了這一篇 Transferring/Moving a VHD from Windows Virtual PC (Windows XP Mode) to another computer , Virtual PC 本身就提供把分開的兩個硬碟虛擬檔合併的功能, 只要在硬碟設定的地方按『變更』, 就有合併虛擬硬碟的功能, 而且還可以直接將合併的結果儲存到別的位置, 這樣就可以把虛擬硬碟檔搬走了。

就這樣, 我就讓 C 碟空出了許多空間, 又可以亂裝軟體惡搞了。

星期二, 9月 25, 2012

Eclipse 中文化後變回英文介面

大部分的人都知道可以到 Eclipse 的 Babel 專案頁面中下載中文化的語言包, 讓 Eclipse 變成中文介面。不過 Eclipse 預設會根據作業系統目前的語系設定, 使用對應的語言包。也就是說, 一旦你安裝了繁體中文的語言包, 那麼預設就是中文的介面, 而這在 Eclipse 中並設定畫面可以更改。

如果想將 Eclipse 改回英文介面, 網路上大部分人的作法都是建議在執行 Eclipse 時, 加上參數指定語系為英文, 例如:
eclipse.exe -vmargs   -Duser.language=en” 
為了方便起見, 你可以建立不同的捷徑, 在不同語系之間轉換。不過實際上會想換回英文, 大概也都不需要在不同語系間切換了, 這時可以把上述的參數加到在 Eclipse 資料夾下的 eclipse.ini 檔案中, 例如:
-startup
plugins/org.eclipse.equinox.launcher_1.3.0.v20120522-1813.jar

...............................
-vmargs
-Dosgi.requiredJavaVersion=1.5
-Dhelp.lucene.tokenizer=standard
-Xms40m
-Xmx384m
-Duser.language=en”
這樣執行 eclipse 時就會強制改用英文語系了。

星期六, 9月 22, 2012

錯誤訊息:Missing styles. Is the correct theme chosen for this layout?

Android 開發採用 Eclipse 作為整合開發環境, 不過這 Eclipse 真的是有許多狀況, 今天同事給我一個很簡單的專案檔案, 說怎麼跑都會出錯, 程式當掉。我看了之後, 雖然發現了程式撰寫上的問題, 但還看到一個怪怪的畫面, 我打開 layout 檔案後看到的是這樣:


首先注意到的是怎麼不管是 TextView 或是 Button 的樣子都很怪, 有些字還疊在一起。然後看到下方顯示了 "Missing styles. Is the correct theme chosen for this layout?" 的錯誤訊息, 但明明使用的就是建立專案後預設的 AppTheme 啊?檢查了 styles.xml、AndroidManifest.xml 的內容, 都沒有改過, AppTheme 的定義都沒有錯誤。如果從上方選取 theme 的功能表拉下來看, 會發現原來根本就誤以為專案中沒有定義 AppTheme 這個主題:


顯然一定是資源的相關資料亂掉了, 只好死馬當活馬醫, 先隨便換個現在系統認得的主題:


果然改個主題之後, 畫面看起來就正常多了:


但是下方還有個錯誤訊息, 這次變成找不到 @string/app_name 這個字串資源了, 所以畫面的標題無法顯示 App 的名稱。明明剛剛都沒問題, 也沒去動過 strings.xml 檔, 根據經驗, 當 Eclipse 頭腦不清時, 最有用的作法就是重新啟動 Eclipse, 重新啟動之後果然一切正常, 而且也認得專案中有定義 AppTheme 主題, 就算再選回 AppTheme 也沒有問題:


之後專案就完全正常了, 接著將之前提到有問題的程式碼改正後, 執行起來也都正確無誤。Eclipse, 我真是被你打敗了! (可以來個 Visual Studio for Android 嗎?)

Android Virtual Device 模擬器的儲存位置

我自己的習慣都是把資料單獨儲存在一個分割區中, 與作業系統分開, 不過因為 Windows 預設的使用者資料夾就是在 C, 所以在建立 Android 模擬器時, 預設也都會儲存到 C 下, 經過 Google 一下, 只要設定一個環境變數 ANDROID_SDK_HOME, 指定希望儲存的目的資料夾, 就可以讓 Android Virtual Device Manager 把新建立的模擬器儲存到指定位置了。


這同時也可以解決當使用者名稱有中文時, 無法從包含名稱有中文的資料夾啟動模擬器的問題。

讓 jQuery Mobile 頁面上的固定式頁首與頁尾工具列不會因碰觸畫面而隱藏

有朋友今天傳了一個 jQuery Mobile 的頁面給我, 程式如下:
<!DOCTYPE html> 
<html>
<head>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1"> 
 <title>jQuery Mobile Demo</title> 
 <link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.css" />
 <script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
 <script type="text/javascript">
 $(document).bind("mobileinit", function(){
  $("[data-role=header],[data-role=footer]").fixedtoolbar({ tapToggle:false });
 });
 </script>
 <script src="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.js"></script>
</head> 

<body> 
<div data-role="page">
 <div data-role="header" data-position="fixed" data-fullscreen="true">
  <h1>全螢幕標頭</h1>
 </div>
 <div data-role="content">
  <img src="mycat.jpg" /> 
 </div>
</div>
</body>
</html>
這個程式是希望能在初始化的階段透過第 11 行的程式設定固定在頁首與頁尾的工具列不會因為使用者碰觸畫面而自動隱藏或重現, 不過實際執行之後, 發現設定失效, 工具列還是會以預設的方式自動隱藏或重現。經過查詢 jQuery Mobile 文件以及 Google 爬文後, 發現 mobileinit 事件發生時, 其實後續的 HTML 內容並未載入, 因此事件處理方法中的 $("[data-role=header],[data-role=footer]") 根本就抓不到元素, 自然無法讓期望的設定生效。為了解決這個問題, 只要改成處理 pageinit 事件, 也就是在後續的 HTML 內容生成後再進行設定, 就可以正確執行了。修改的程式如下:
$(document).bind("pageinit", function(){
 $("[data-role=header],[data-role=footer]").fixedtoolbar({ tapToggle:false });
});
你也可以在事件處理的程式中多加上一行 alert() 呼叫, 就可以觀察實際抓取到的元素內容了:
$(document).bind("pageinit", function(){
 $("[data-role=header],[data-role=footer]").fixedtoolbar({ tapToggle:false });
 alert($("[data-role=header],[data-role=footer]").text());
});

星期四, 9月 20, 2012

免費雙窗格檔案總管軟體的測試

之前因為一直會出現『Windows 檔案總管 已經停止運作』的錯誤, 很是困擾, 所以嘗試改用一些替代品, 而我習慣會開多個檔案總管, 所以找來試用的就是類似 Total Commander 這樣可以有兩個窗格並列的軟體來試用, 先把這幾天試用過的軟體缺點寫下來, 以後就不用再重試了:
  • Multi Commander: 使用起來還不錯, 可是我在複製資料夾或是從 7Zip 拖拉資料夾到某一個窗格中的資料夾時, 會有複製不完全的狀況, 不知道是什麼原因, 可是這狀況很恐怖, 萬一我誤以為資料都已複製完成, 把來源資料刪除, 那就慘了!
  • Free Commander:這軟體小巧輕盈, 但是在複製檔案等長時間動作時, 就會整個 hang 住, 無法進行其他操作, 這可是大問題, 所以也放棄了。
  • FreeCommander XE:這是 Free Commander 的下一版, 不過目前還處在 Beta 階段, 但試用過後真的很不賴, 像是前面提過複製檔案等動作時, 已經不會 block 住 UI, 並且這個版本除了雙面板以外, 每一個面板都有活頁功能, 所以可以再單一面板配置多頁, 每一頁顯示不同的資料夾, 非常方便, 現在正列為日常使用工具長期測試中。
其實這類軟體還有很多, 有時間會再測試。


星期六, 9月 15, 2012

Ubuntu 奇怪的『不使用密碼登入』

今天手賤, 看到使用者帳號的設定中在密碼的地方有『不使用密碼登入』的選項:

我一向不喜歡一直打密碼, 想說這樣設定之後應該很方便, 沒想到設定後雖然登入確實不用密碼, 但所有需要權限認證的地方 (當然包含 su 指令) 卻都無法認證了, 不打密碼也不行、打了原本的密碼還是不行, 可是如果切到 Console 登入, 卻又一切正常。最後還是依靠 Google 大神搜尋, 解法就是開個終端機, 用 passwd 指令更改密碼就可以了。所以最後我還是用 passwd 設成的密碼, 一切就正常, 而使用者帳號設定的地方就又變回需要密碼才能登入。

星期六, 9月 08, 2012

Android 的 process、activity、task 與 back 鈕的關係

在 Android 系統中, 一個 App 主要是以畫面為單位來切分程式單元, 每個程式單元就稱為一個「活動 (Activity)」, 負責處理單一畫面的工作, 包含畫面本身的構成、顯示以及該畫面與使用者的互動。以 Gmail 內建 App 為例, 就具有信件清單、信件內容、編寫新郵件等不同的畫面, 每一個畫面就由一個活動來處理。活動一開始的工作, 就是將畫面顯示出來, 例如 Gmail 的信件清單畫面, 就要擺個能夠切換不同標籤信件的按鈕, 並且放置一個列表元件顯示信件清單。畫面顯示出來之後, 活動也必須負責像是使用者按了清單中的某一封信件時要跳轉到信件內容畫面的動作。在同一個 App 的所有活動之中, 必須指定其中一個為「主活動 (main activity)」, 也就是 App 一開始時要最先執行的活動。

由上可知, 當我們使用 Android 系統時, 畫面的跳轉實際上就是從一個活動跳到另一個活動, 系統會幫我們把活動跳轉的過程記錄下來, 當我們按了 Back 按鈕時, 系統就會依照跳轉的歷程, 結束目前的活動, 返回到前一個活動。舉例來說, 如果你在 Gmail 的信件清單中按了某一封信件, 這時處理信件清單畫面的活動就會跳轉到顯示信件內容的活動, 由該活動將信件內容顯示出來, 此時如果按了 Back 鈕, 就會結束顯示信件內容的活動, 回到前一個活動, 也就是顯示信件清單的活動。活動跳轉的歷程是紀錄在一個堆疊中, 稱為「返回堆疊 (back stack)」, 而返回堆疊裡的這一串活動, 就統稱為是一個「任務 (task)」, 任務中第一個推入到返回堆疊中的活動稱為「根活動 (root activity)」。

一個任務是由使用者在 Home 畫面或是應用程式列表中點選了某個 App 的圖示開始, 假設此 App 名稱為 A1, 此時 A1 的主活動就會推入返回堆疊中成為根活動, 隨著使用者的操作切換畫面, 並將對應的活動堆入返回堆疊。如果使用者按了 Back 鈕回到根活動, 並再次按下 Back 鈕從根活動回到 Home 畫面, 那麼整個任務就結束了。如果在任務尚未結束的情況下, 使用者按了 Home 鈕回到 Home 畫面, 並點選執行了另外一個 App, 假設名稱為 A2, 此時系統就會建立一個新的任務, 配置一個新的返回堆疊, 並以 A2 的主活動為根活動。這個時候, 系統中同時存在兩個任務, 如果長按 Home 鈕, 就可以在兩個任務之間切換。

要特別注意的是長按 Home 鈕時會看到的 App 圖示是以任務為單位, 而不是以活動為單位。舉例來說, 如果你執行了瀏覽器, 接著在瀏覽器的網頁瀏覽畫面中使用分享的功能進入 Gmail 編寫新郵件的畫面, 此時的任務是由瀏覽器開始, 因此若是長按 Home 鈕, 只會看到瀏覽器的圖示, 而不會看到 Gmail 的圖示。另外, 如果已經有以某個 App 開始的任務在執行, 那麼在 Home 畫面點選該 App 圖示時會直接切換到該任務, 而不會建立新的任務。


在 Android 系統中, 編譯好的 App 經過安裝後就存在檔案系統中, 當使用者點觸某個 App 的圖示執行時, 系統就會將 App 相關的程式碼載入到記憶體中執行, 在系統中執行的 App 就稱為是「行程 (Process)」, 系統會紀錄相關的資訊。當行程執行, 完成初始動作後, 就會執行主活動, 開始與使用者互動。


星期三, 9月 05, 2012

PhoneGap 問題又一樁:index.html 檔載入太慢

今天同事在測試 PhoneGap 做出來的 Android App 時, 又遇到了一個靈異現象, 他使用 Samsung SIII 測試可以正常執行的 App, 到了我的 LG P970 上一執行就出現以下的錯誤訊息:
Application Error - The connection to the server was unsuccessful.
(file:///android_asset/www/index.html) 
 查了 Google 之後才發現, 這個問題可能是因為手機速度或是連網速度太慢, 超過 20 秒還無法將 index .html 載入 (連同 index.html 中所連結的外部檔案, 此例就是用了外部的 jQuery 與 jQueryMobile 檔案), 就會出現這個訊息。馬上測試一下, 把我的手機改成用 WiFi 連網, 果然就成功了, 後來發現原來同事用的 Samsing SIII 一直都是用 WiFi, 所以都沒有遇到過同樣的問題。

為了解決這個問題, 網路上有聰明的人把 index.html 改名字成 main.html, 另外加了一個 index.html, 這個 index.html 其實只是個空殼子, 用來轉址到 main.html, 也就是真正包含 App 內容的網頁:
<!doctype html>
<html>
 <head>
  <title>tittle</title>
  <script>
   window.location='./main.html';
  </script>
 <body>  
 </body>
</html>
這樣問題就解決了, 提供給大家參考, 不然總不能叫用你 App 的使用者都要改成 WiFi 連線才行吧!

星期一, 9月 03, 2012

Dreamweaver CS6 的 PhoneGap Build 服務與中文路徑名稱水土不服?

因為同事在測試 Dreamweaver CS6 的 PhoneGap Build Service, 之前用英文版都很正常, 可是一改成中文版的 Dreamweaver CS6 之後, PhoneGap Build 服務就沒成功過, 一直看到錯誤。但如果自己打包上 PhoneGap Build 網站上又都正常無誤, 感覺一整個奇怪, 沒道理到了中文版同樣的功能就不能用!

為了測試, 我也裝了 Dreamweaver CS6, 也是先試看看英文版, 果然沒問題。換到中文版, 把剛剛在英文版建好的網站拿來測試, 用 PhoneGap Build 服務重新建置, 沒問題啊!但是念頭一想, 那我用中文版建個新的網站來試試, 一試之下果然出現錯誤!

我想了一下, 看看建好的網站, 發現英文版與中文版唯一的差別是預設的網站儲存路徑一個是「Unnamed Site XX」, 一個是「未命名網站 XX」(XX 是序號), 這下我想不會是因為中文資料夾的原因吧?趕緊試一下, 果然, 只要路徑沒有中文,PhoneGap Build 服務馬上又恢復正常了。

我自己推測, 應該是 PhoneGap Build 服務會把整個網站的內容壓成 Zip 檔之類的, 再上傳到 PhoneGap Build 網站進行建置的工作, 但是路徑有中文的時候可能這個幕後的 Zip 檔檔名會因為編碼的關係變成莫名其妙的檔名, 導致後續的動作出錯吧?

如果你也在測試 Dreamweaver CS6 PhoneGap Build 服務的功能, 可以參考一下!

星期四, 8月 16, 2012

OOXX, Eclipse 中 R.java 就是不更新!

根據我估狗的結果, 顯然許多人都遇過這個問題, 比方說你在 res/layout 中加個新的 layout 檔, 或是改了某個資源的 Id, 可是 R.java 中這個新的 layout 檔就是不出現, 更改的 Id 名稱也不會更改變數名稱, 真的是不知道背後發生什麼事!

遇到這種狀況, 可以試試看使用 Project/Clean 功能表指令清理一下專案, 或者是更暴力一點, 把 R.java 刪除, 這兩個舉動都會重新產生 R.java 檔, 一般來說, 這樣就會根據目前的資源內容產生正確的 R.java 了。

星期三, 8月 15, 2012

android:minSdkVersion、android:targetSdkVersion、Project Build Target 的區別

初學 Android 的人在 Eclipse 裡建專案時, 看到這個建立新專案的精靈視窗, 常常會搞不懂中間 Build SDK 與 Minimum Required SDK 這兩個欄位的意義:

簡單來說, Minimum Required SDK 是宣稱此專案至少要在哪一個版本的 Android 系統上才能跑, 這裡選擇的值會出現在專案中 AndroidManifest.xml 檔中的 uses-sdk  元素中:
<uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="15" />
其中 android:minSdkVersion 屬性值就是上圖中 Minimum Required SDK 欄位的設定值。要特別注意的是, 這裡 SDK 版本都是以所謂的 API Level 值來指定, 而不是版本號碼, 例如圖中 Android 2.2 的 API Level 就是 8, 所以設定的值是 8, 而不是 2.2。

要特別說明的是, 這個設定值會影響 Google Play 是否將此程式顯示在商店中, 如果使用者裝置的 Android 版本比程式中這一項設定值指定的版本低, 此程式就不會出現在  Google Play 商店中。實際在執行程式時, 系統也會檢查此項設定值, 若是系統版本比程式的設定值低, 也會禁止執行這個程式。不過要注意的是, 這個設定值是開發者所「宣稱」, 至於這個程式是不是真的可以在該版本的 Android 裝置上正確執行, 就是開發者要負責的事情。

那麼在上面精靈視窗中的 Build SDK 設定的又是什麼值呢?這指的是實際在編譯程式時要用那一個版本的程式庫。這就與你實際要使用的功能有關, 有些功能只有在特定版本才提供, 若程式要使用, 就必須指定 Build SDK 欄位值為該版本。這個設定值會記錄在專案中 project.properties檔案中:
# Project target.
target=android-10
 或者也可以透過專案的 Properties 視窗檢視:


前面提到 AndroidManifest.xml 檔時, 您可能有注意到在 uses-sdk 元素中除了 android:minSdkVersion 外還有一個屬性叫做 android:targetSdkVersion, 請特別留意, 這個 android:targetSdkVersion屬性值和 Build SDK 欄位值是兩個不同的值。android:targetSdkVersion 屬性值是開發者『宣稱』在特定版本平台上已測試過程式可以正確無誤的執行。你可能會覺得很疑惑, 那這個值和 android:minSdkVersion 屬性值有什麼差異?

這就要講到 Android 平台的演進了, 在 Android 的新版本中, 當遇到以舊版程式庫編譯的程式時, 可能會嘗試啟動某些相容性的處理, 以便在新系統的平台上不會因為版本的差異而讓程式出錯。舉例來說, 新版的 Android 因為支援大畫面的平板, 所以遇到舊版程式時, 有可能會自動做某些調整, 讓理論上是為小尺寸螢幕設計的程式顯示起來不會與大螢幕格格不入。當然這額外的處理有可能會影響到執行的效能, 如果開發者確認不用系統幫忙程式也不會有問題, 就可以設定 android:targetSdkVersion 屬性值, 強制系統就不要啟動相容性的處理。

綜合上述, 一般來說, android:targetSdkVersion >= Build SDK >= android:minSdkVersion, 這是因為既然宣稱至少要使用 android:minSdkVersion 設定的版本才能執行此程式, 那麼編譯時就會透過 Build SDK 使用 android:minSdkVersion設定版本或是更新版本的程式庫, 才能包含所需的功能;而這個程式有可能在往後新版的 Android 版本上正確執行, 所以 android:targetSdkVersion 設定的版本至少會是 Build SDK 指定的版本。

舉例來說, 若是 android:minSdkVersion 為 7、Build SDK 為 8、android:targetSdkVersion 為 10, 就表示這個程式至少要在 Android 2.1 的裝置上才能執行, 但程式編譯時是使用了 Android 2.2 的程式庫, 而且程式已經在 Android 2.3.3 上測試過顯示、執行皆正常。

android:targetSdkVersion 屬性值在建立新專案時並沒有欄位可以設定, 我自己測試的結果似乎預設值為最新版本的前一版, 例如目前是 Android 4.1, API Level 為 16, 所以預設值就會自動設為 15, 請務必依照實際測試狀況修改此屬性值。

從上述說明就衍生了一個重要的問題, 因為 Build SDK 設定值可能會大於 android:minSdkVersion 設定的版本, 因此開發者必需自行處理相容性的問題, 如果使用了較新版本的功能, 就必須在程式中檢查執行時的系統版本, 在比 Build SDK 版本低的系統中以其他的方式提供相同的功能, 或是甚至禁用該功能, 才不會讓舊版系統的使用者因為執行到系統不具備的功能而出錯。