2015年10月26日 星期一

解決 Android Studio 設定檔占用C磁碟之問題

Android Studio 的設定檔預設存放於 C 磁碟,包含 config 與 system 兩個資料夾,使用至今大約有230 MB,若你的 C 碟空間很保貴,可以考慮更改預設的儲存路徑。

這種問題多半在行時 Android Studio 會出現警告「Low disk space on a android studio system directory partition.」,若磁碟真的滿了,後續動作將無法正常執行。

依這篇文章「Android Studio使用问题小结」裡面提供了解決方法,但目前(2015/10)的版本似乎有些出入,但大致上是沒有問題的。

路徑設定步驟

  1. 找到 Android Studio 的安裝路徑,展開至 AndroidStudio\bin。
  2. 用文字編輯器開啟 idea.properties。
  3. 直接新增設定,包含 idea.config.path=D:/xxx/config 與 idea.system.path=D:/xxx/system。

請注意,原本的設定檔若放在 C 碟,可以直接把它搬到上面第3點指定的路徑,讓其能夠正常對應即可。

2015年10月18日 星期日

Cordova 安裝與開發流程

安裝Cordova

Cordova是個跨平台行動應用程式設計的解決方案,主要色是使用HTML, JavaScript, CSS等常見的Web技術來製作App。首先介紹如何安裝,這邊以官方提供的標準安裝方法來進行,依照以下步驟完成(適用Windows):
  1. 至Node.js官方 (https://nodejs.org/) 下載Node.js並安裝。
  2. 開啟「cmd」或「Node.js command prompt」,會出現命令列視窗。
  3. 輸入指令「npm install -g cordova」,將會自動完成Cordova的下載與安裝。

npm install -g cordova


開發前置準備


為使後續開發能順利進行,還需要進行以下設定,各平台可能不太一樣。這邊以Android為例,需先安裝Android SDK,並設定「環境變數」內的「系統變數」,包含:
  1. 新增ANDROID_HOME變數,值為Android SDK的安裝路徑,例如D:\Android\sdk,依你實際安裝路徑為主。
  2. 調整path變數,新增以下兩者,記得用分號「;」隔開:
%ANDROID_HOME%/tools
%ANDROID_HOME%/platform-tools


 建立Cordova專案

安裝好Cordova,即可利用指令的方式建立專案,其指令相當簡單,不需使用其他工具來完成。

  1. 開啟「cmd」或「Node.js command prompt」,利用「cd」指令移到你想建立專案的目錄下。
  2. 使用指令建立專案,參數ProjectName請自行代換為專案名稱,它會作為整個專案的資料夾,且建立在目前的位置上。
  3. cordova create PROJECT_DIR APP_ID APP_NAME
    
    這邊的 APP_ID 即對應到 Android 的 Package Name 與 iOS 的 Bundle Identifier。
  4. 使用指令移至剛才建立的專案目錄內。
  5. cd PROJECT_DIR
    
  6. 依你的需求,將專案加入平台,平台指的是你的App之後要執行在哪,以Android為例,使用指令完成。
  7. cordova platform add android 


開始撰寫程式

Cordova是個跨平台解決方案,還記得在前面我們用「cordova platform add」指令加入了Android平台嗎?你的App最終可能要執行在Android, iOS, WP8, BlackBerry等平台,既然是跨平台,當然希望「寫一次」程式就可以執行在各個平台上,為了實現這個需求,你可以編輯的資源僅限於「www」資料夾內的檔案。

那麼之前在platforms\android\assets\www中看到的檔案呢?若你直接異動它,再透過對應的IDE還是可以產出可執行的App,但無法兼顧其他平台,既然選擇Cordova,最好不要這樣處理。

另外有關App的基本資訊,Cordova為了兼顧多平台,統一將設定放在專案根目錄中的config.xml檔案,若欲調整App名稱與版本等資料請編輯該檔。


編譯與打包

在完成程式的新增與編輯之後(記得,僅存取\www內的資源),於Node.js command prompt執行以下指令即可,執行前請確認有完成「開發前置準備」裡面提到的環境變數設定。
cordova build

值得注意,這個build指令會自動把\www內的東西copy至個平台中的\assets\www,並且依各平台不同特性加入與修正必要檔案,為確保整個流程正確,這部分請不要手動完成。

但是!若你不想要使用這種命令方式來打包成apk檔案,可以借用其他IDE來完成,若你有這個打算,請不要直接執行build,而改用以下指令(不同平台請自行抽換參數):
cordova prepare android


prepare會把相關檔案copy至正確的地方,但不進行編譯與打包之動作。實際上執行前面說的build,它也是自動幫你在背後執行prepare與compile兩個指令。
 

使用Android Studio編譯與打包

基本上透過Cordova CLI可以完成整個開發流程,包括撰寫程式與編譯,但實際上,熟悉Android Studio者,或想以圖形操作介面包完成打包的人,可以將Cordova的Android專案以Android Studio來維護。

個人偏好以這樣的方式進行開發,可以直接執行到實機、觀察Log,而簽署apk時圖形化介面也比較友善。

若前面有將Cordova專案加入Android平台的話,在專案根目錄下,應會產生platforms\android,我們直接使用Android Studio開啟這個目錄,具體的操作方式:

開啟Android Studio,選擇「Import project (Eclipse ADT, Gradle, etc…)」把專案載入,請記得要載入的是Cordova專案中platforms\android目錄。


Δ 選擇「Import project (Eclipse ADT, Gradle, etc…)」把專案載入 

Δ 載入Cordova專案中platforms\android目錄

若Android Studio有提示Android Gradle plugin需要更新,請按下「Fix plugin version and sync project」讓它自動完成即可。

開啟後就依以往的方式把apk檔案打包出來,直接接上實機也可以執行,如同開發原生程式一般。


特別提醒!使用 Android Studio 可以方便的將程式執行到實機,但每次更改程式後,在透過 AS 執行前記得先執行 cordova prepare 指令,否則執行到裝置上的的 App 會是先前的版本


後記

Cordova版本持續更新,本文撰寫於2015/10,使用的Cordova版本為5.3.3。(要查版本可以下cordova -v指令)。未來新版本難保會有不同的處理流程,屆時請大家自行注意。

本文皆以個人經驗撰寫,部分資料來自官方文件,若讀者有更好的解決方案歡迎提供!

下期預告「安裝Cordova Plugin」。


延伸閱讀

  1. APACHE CORDOVA: The Command-Line Interface,
    http://cordova.apache.org/docs/en/5.1.1/guide/cli/index.html
  2. Tiger-Workshop Blog: PhoneGap 與 Cordova 的實際差異,
    http://blog.tiger-workshop.com/difference-between-phonegap-and-cordova/

2015年10月10日 星期六

用程式碼角度談河內塔(Hannoi)

河內塔一直是個經典範例,在資料結構與演算法的相關課程中幾乎是固定班底。本文從「程式碼」開始說起,有些人知道這種問題可以使用遞迴(Recursive)來解,隨手Google一下也都有多種程式語言所實踐的例子,今天直接由程式碼起步,說明原理,希望透過這種方式可幫助大家更易理解。

閱讀前最好要具備的知識:
  1. 河內塔是什麼,本文不再重述,請Google一下。
  2. 參數傳遞與存取,你必須能夠辨別目前存取的變數是在哪裡傳進去的。
  3. 遞回與推疊之間的關係。

河內塔關鍵演算


這邊以Java語言為例,變數宣告建議用小寫,只是本文會反覆提到ABC三個字母,為了閱讀方便以大寫標示。

public void move(int n, char A, char B, char C) {
    if (n > 0){
        move(n-1, A, C, B);
        System.out.println("將第" + n + "個盤子由" + A + " 移至 " + C );
        move(n-1, B, A, C);
    }
}

有什麼方法可以快速容易理解這段程式呢?其實不用想太多,觀察一下可以發現傳入的參數A B C進行了一些奇怪的置換,遞迴呼叫時一下是A C B,一下又是B A C。

其實這種交換只是為了迎合描述當初歸納的規則(後面再提),有看到System.out.println( )嗎?它抓取變數A與C,並套入其他字詞顯示盤子如何移動。因此,在A B C三者中,請先關注A與C位置 (即第一、第三個位置,中間先不理他),目的是要讓輸出的語句能夠符合規則。

不同個數的河內塔


程式碼看完了,接著要理解規則,先考慮三種情況:

1. 只有一個盤子:直接由A柱 → C柱。


2. 兩個盤子:A柱 → B柱,A柱 → C柱,B柱 → C柱。


3. 三個盤子:太長了,直接看圖。

若暫時不看最大的盤子(藍色),是否就相似「兩個盤子」的情況呢?我們針對「兩個盤子」的情況,用最白話的方式來描述盤子是如何移動的:
  1. 把A柱上的小盤子,移到B柱。 (A→B)
  2. 把A柱上的中盤子,移到C柱。 (A→C)
  3. 把B柱上的小盤子,移到C柱。 (B→C)

到這邊,再回頭看看程式碼,是否可以發現:

move(n-1, A, C, B);
System.out.println("將第" + n + "個盤子由" + A + " 移至 " + C );
move(n-1, B, A, C);
  1. 第一行:move呼叫傳入的參數是「A」、「C」、「B」。
  2. 第二行:直接print出第一個變數 → 第三個變數的語句。
  3. 第三行:move呼叫傳入的參數是「B」、「A」、「C」。


這時冷靜一下,想想第一次呼叫 (也就是在外面,例如在main裡面呼叫) move時,傳入的是按照順序的A B C。所以,第二行的print會抓到誰呢?第一個變數是A,第三個變數是C,於是印出「A → C」,但是這行不會馬上印出來,因為在print前還有一個move呼叫!現在回來看看第一行,第一個傳入A,第三個傳入B,可預期的它會印出「A → B」。同理,第三行,第一個傳入B,第三個傳入C,預期會印出「B → C」。看到這邊,你可以發現,這剛好滿足了前面分析的規則順序,即A→B,A→C,B→C。

再來談談「遞迴」,你可以發現move裡面有move,這種自己呼叫自己的行為就是遞迴了,但是一路呼叫沒完沒了,所以要加個條件讓它能有「停下來的機會」,於是有個 n > 0的判斷式,隨著每次呼叫move時傳入的 n – 1,可預期未來會碰上n = 0的情況,從而使遞迴呼叫結束。

最後我們要把這一切串起來!把頭腦當成電腦,開始執行程式吧,還是以「兩個盤子」為例:

  1. move被呼叫了,第一次傳進去的參數是A B C。
  2. 第一行又呼叫了一次move,傳入了A C B (就是把step 1傳入的順序交換一下再傳給下個move)、同時也把 n - 1後傳入。
  3. 注意,現在人在第2個move裡了,嗯?你說第1個move跑去哪了嗎?請暫且忽略它,記得你目前在遞迴,先不要去管第1個move,你只需要一直執行下去。檢查n是否 > 0,若是,再呼叫一次move (第3次),又傳入n – 1與交換過的順序的3個參數。
  4. 現在人在第3個move裡了……

以下請靠想像,再寫下去你看得更亂。你需要知道的重點是,在move裡面,只有System.out.println( )會輸出結果,因此無論傳入的參數怎麼換,它就只會印出第1個 →  第3個這種描述。

那前面被我們無視的「第1次move」呢?現在假設遞回呼叫結束了 (遇上 n = 0),將開始彈出(pop)堆疊,觸發各次呼叫move裡面的print,print完再呼叫第3行的move,又是一連串的遞迴。

而為何使用 n = 0作為遞回結束的條件呢?n在這邊表示盤子的總數,前面一直舉的例子「兩個盤子」即是n = 2。細心的你可能已經發現,第一次呼叫傳入的n是2,但河內塔不是要先移動最上面的盤子嗎?注意在此是以遞回的方式來求解,想想前面「無視第1次move」時,是不是馬上進入第2次的move呼叫了,第1次move還無法進行print,最後在彈出堆疊時,print的執行順序是反過來的,即n = 1印完,再印n = 2 (n=0被排除了,不會印出東西)。

你也可以用比較直觀的方式思考,假設有3個盤子,要移動大盤子(3),是不是要先移動中盤子(2),要移動中盤子(2),是不是要先移動小盤子(1),這就是 n – 1的概念。而一路減到最後,移動是最小、最上面的盤子,因為它沒有阻礙了,於是直接A → C,而因為小盤(1)移走了,所以中盤(2)也沒有阻礙了,於是直接A→B,這不就接回我們前面談的步驟了嗎?

其實在仔細想想,你還會發覺在n =2的情況下,第一步是 A → B。在n = 3的情況下,第一步卻是A → C,但演算法都沒有變,一樣都是第一步,為何會有兩種走法?這就是這個演算法的神奇之處,欲知詳細請見後記。


後記

  1. 以個人的經驗而言,最通盤的理解方法就是透過紙筆來模擬,把整個堆疊的push與pop動作跑一次,並注意每次呼叫move的傳入參數,了解他們是如何互換順序的。
  2. 如果你多試著解4、5、6…n個盤子的問題,可以發現單數盤子第一步是A→C,雙數盤子第一步是A→B,在遞迴呼叫配合ABC交換的情況下,也恰好滿足這種規律。
  3. 還是有障礙嗎?那麼該搬出這句名言了:「遞迴只應天上有,凡人應當用迴圈」。





2015年9月12日 星期六

Code::Blocks 設定專案標準為 C99

步驟一:在 Project 上按右鍵,執行 Properties...


步驟二:切換至 Build targets 頁籤,點選 Build options ...


步驟三:在 Compiler settings 頁籤下找到 Other options 子頁籤,並手動填入「-std=c99」,按下OK即可。




2015年9月7日 星期一

程式開發觀念:減少重複

本篇並不談論深入的內容,主要為多少有接觸程式開發,但尚無法利用物件觀念來簡化程式的人寫所寫。

基本功:提取重複敘述

在開始之前,必須先確定你有這種能力,你可以把重複的程式包裝為方法(method),在不同的概念下也有人稱為function、副程式、函數等。

這樣好處有很多,大概列出幾項:
1.    以後要使用直接呼叫,不用再寫一次。
2.    多處的處理都會參照到一個方法,日後若需修正,僅需改一個地方(即修改方法的內容)。
3.    防止你忘了處理某些細節,因為相關的動作被包起來了,一次完成。

包裝成方法多半需要傳入參數與制定傳回值,這部分務必弄清楚,不同語言可能有不同的規範,請自行參閱各語言的書籍,這邊不再贅述。

若你對傳入/傳回沒有概念,這邊提供簡短的說明。舉例來說,你需要在Android內顯示Dialog,你可以編寫如下的method:

private void showDialog(String msg){
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setTitle(getString(R.string.strTitle));
    builder.setMessage(msg);
    //……
    builder.create().show();
}

在這個例子當中,會變動的只有Message(訊息)內容,其他的動作都是不變的,若你想讓Title也可以變動,那麼可以在showDialog後方的括號內,再加上第2個輸入參數,例如showDialog(String msg, String title)並在method內將其應用即可。

那麼傳回值呢?這個例子的傳回值是void,意即沒有傳回值,假設你需要在Dialog顯示後傳回True或False,那請把void修正為boolean,並於method內適當的地方加入return true; 或 return false; (視你的目的而定)。

但會這種包裝是不夠用的,在一些情況下你必須跨類別執行相同(或相似)的行為,這是可以利用「物件」的概念來處理。

善用物件

我們也可以透過物件導向的觀念來提升程式的重用。在物件導向領域中應該多少聽過封裝、繼承、多型,三者都非常重要(且深奧),本篇不探討細節內容,網路上已有不少大師提出看法與建議,值得大家參考。

就先談談最基本的封裝吧,簡單說就是要把一些處理過程包裝起來(最終包成class),而為什麼要這麼做呢?舉幾點好處:
1.    為了自己方便,在日後的開發過程呼叫已經包裝好的東西即可。
2.    為了同伴方便,要實現相同功能只要呼叫你包裝好的即可。
3.    安全性考量,畢免日後忘了進行某些處理。

而繼承可以讓你更方便的擴充,多型可以讓你的擴充更有彈性 (這是非常簡短的說法,詳細內請自行參考相關資料,不然真的談不完),善用這些觀念,可使程式更易維護,更白話點,好處即是程式碼可以方便的重複使用與管理。

來看看封裝的例子,你可以把網路連線的系列動作包裝成一個類別「網路存取.class」,其下有一些相關的屬性(Property),像是解析HTML使用的固定Tag、定義回傳狀態Tag、連線目標(URL)等,以及一些方法(method),如「檔案下載」與「檔案上載」。這邊的method如同本文前半段所提,那在這邊為何要再說一次呢?

因為這邊的method是寫於「網路存取.class」裡面,這個class聚合一些相關的方法與成員,專門設計來處理網路的相關事項,今後你若有相近的需求,基本上僅需這麼處理:

網路存取 i = new 網路存取();
i.檔案下載("http://hefetech.blogspot.tw/");

那繼承與多型呢?以本例而言不見得會使用到,而且相關的例子也不適合在這邊舉出,先跳過這個部分。

思考如何建立類別

有不少人在接觸物件導向時,難以將現實體驗轉化為虛擬的類別,若你也有這種問題,多練習還是最適當的方法,你可以試試以下例題,想想如何把它轉化為類別(class),思考這個類別裡面可能有什麼屬性與方法。
1.    鍵盤
2.    計算機
3.    滑鼠
4.    西瓜
5.    畢業證書
6.    智慧型手機
7.    魔術方塊
8.    行動電源
9.    鏡子
10.    山豬

你說怎麼沒有汽車嗎?倒是有個山豬可以讓你試著使用萬年動物「梗」,像是跑()、叫()…。這些都是現實世界的實體,概念鮮明,容易學習,但其通常與你寫的程式無關,你真正需要的是培養將較抽象概念轉為類別的能力,在這種情況下你很可能要「無中生有」,自己規劃類別中的內容,跟據實際需求進行定義。

在實際規劃上,以下這些例題不一定只產生單一類別,很可能會再拆分,但那屬於更進階的部分,不在本文討論的範圍。:

1.    會員
2.    HTTP連線管理器
3.    身分證字號驗證器


2015年8月29日 星期六

Google USB Driver驅動程式安裝與相關技巧

至今於Android SDK內提供的模擬器執行速度仍然偏慢,雖有其他替代方案,但多數App還是需要執行在真機(實體的手機)上的。有些品牌的手機使用廠商提供的區動即可,但有些要另外處理。

依照以下步驟檢查與安裝,應可順利完成。目前已實測Win 7、Win 8與Win 8.1,而Win 10的部份仍有些例外情況待排除。

環境確認

我們用最直觀的方式來檢查,首先打開Android Studio,在任何一個專案中點選下方「Android Monitor」若有出現裝置名稱,以及一堆Log資訊即表示手機已與ADB連線,在這種情況下你應該可以直接執行。


同時,請確定手機的連線模式為「媒體裝置連線」且「已啟用USB偵錯」。


有些品牌的手機在安裝完原廠提供的驅動程式後即可 (裝完你就可以看到一堆Log出現於LogCat中),但也有例外的情況,這時我們可以試試Google提供的USB Driver (見下一步驟)。

準備Google USB Driver

首先必須確定電腦內已備有相關檔案,Google USB Driver可藉由SDK Manager取得,打開SDK Manager,確認Extras下的Google USB Driver有無下載,若無,請把它下載下來。


請留意SDK Manager上方的路徑,這些檔案屬於Android SDK,自然集中在某個資料夾,已下載的驅動也會放在這邊,我們稍後會用到它,以本圖為例,路徑即為「E:\Users\user\AppData\Local\Android\android-sdk」。

為了後續安裝方便,我們直接在檔案總管內一步一步展開路徑,到達android-sdk後,再依序開啟extras → google → usb_driver,並把目前這一長串完整路徑複製下來。




為行動裝置更新驅動程式

開啟「裝置管理員」,找到目前連接至電腦的行動裝置,在上面按下右鍵→更新驅動程式軟體。



  選擇「瀏覽電腦上的驅動程式軟體(R)」。



選擇「讓我從電腦上的裝置驅動程式清單中挑選(L)」。



在此對話框填入前面複製的Driver路徑,並按下確定。




直接下一步完成安裝即可,中途可能出現一些警告。



至此已完成Google USB Driver的安裝,可以使用本文上方提供的方式,確認手機是否已連線至ADB,並於LogCat介面中顯示Log。






2015年8月24日 星期一

Android開發:如何找到錯誤

許多初學者初次開發 App,在經過一番努力後,出現的竟是「很抱歉,OOO已停止」的訊息,同時不知問題出在哪?錯誤訊息在哪?也不知道該如何詢問。

今次介紹如何利用 Android Studio (其他IDE也可) 提供的功能,協助我們找到問題點,將著重在Log的查閱與追蹤。

程式錯在哪?

會出現「很抱歉,OOO已停止」基本上都屬於嚴重的錯誤,應用程式無法繼續執行,此時應立即查閱 LogCat 介面的資訊,為了方便閱讀可以將 Log level 定為 Error在 Android Studio 中,展開下方的「Android Monitor」並切換至「logcat」即可


如上圖的紅字部分,即是目前需要解決的問題。Log 中藍色字樣是可以連結的,點按後將自動跳至出問題的程式列,以本例而言,MainActivity.java 的第 34 行有問題,點按後將直接跳至該處。

錯誤訊息要出現在 LogCat 有個條件,即 App 的執行環境必須與 ADB(Android Debug Bridge) 連結,簡單來說,有兩種可能:

1.  在 Android Studio (或其他IDE)直接將程式執行於已連結至電腦的行動裝置。
2.  將 App 執行於電腦中的模擬器,若你的模擬器是藉由 IDE 提供的按鈕啟動,應會自動連接。

找到錯誤訊息了,接下來呢?

在本例中,於 MainActivity.java 檔案中的第34行發生 OutOfMemoryError

一旦確認錯誤的地方,你有幾個選擇,除了自身經驗外,相信找 Google 是個便捷、快速的方法,Android 相關議題至今已在網路上累積相當可觀的資料量,除非是隨著 IDE 更新而引起的新問題,多數的問題都是能找到解決方案的。

當然,直接詢問有經驗者也可以,亦需給予必要的資訊(錯誤訊息),在此要提的是,所謂錯誤訊息並不是「很抱歉,OOO已停止」這只是個提醒,看不出錯在哪,以這些字句來詢問是得不到答案的。


後紀

測試、除錯一直是軟體開發的重要環節,也是個困難的問題,本文僅提供概略的方向,讓初學者有些參考依據,不致於在發生錯誤時停滯不前。

筆者曾協助解決一些應用開發的問題,但他們遇到的問題往往屬於 Java 的範疇,尚未牽扯至 Android 的特殊議題。欲使用 Java 開發原生程式的開發者,務必加強 Java 觀念,有些看似枯燥的環節反而是重點,稍不留神可能會讓你陷入瘋狂除錯的迴圈。

2015年8月21日 星期五

Android Log系列方法使用教學(配合Android Studio)

Hello!各位,這是家象首篇行動應用開發的技術文章,未來將規劃為一系列單元,不定期於FB專頁與此處連載。

本次的主題是如何在Android開發中使用Log,並以Android Studio作為開發環境,善用相關功能讓開發過程更為順利。

在開發過程中,可能會想透過類似 console 的介面閱覽變數值,或是在特定程式區段中輸出字串,以便判斷是否有執行,雖然這些事可以透過 debug 模式完成,但Log直覺、易用的特性仍是值得大家試試的!

主要使用的Log家族共約有5種,皆為static method,要使用前必須先 import android.util.Log

Log.v("Tag", "Value");
Log.d("Tag", "Value");
Log.i("Tag", "Value");
Log.w("Tag", "Value");
Log.e("Tag", "Value");
Log.a("Tag", "Value");

註:此處的wtf為What a Terrible Failure之意。

上述method可對應至Android Studio的logcat面板之Log level,方便檢視內容,但不是單一對應,分別有:
Verbose:列出所有Log。
Debug:列出d, I, w, e, wtf。
Info:列出I, w, e, wtf。
Warn:列出w, e, wtf。
Error:列出e, wtf。
Assert:列出wtf。

依上述變化調整level,可達過濾之用,亦可於搜尋框輸入關鍵字搜尋,通常填入「標籤值」。


進階延伸:神秘的第3參數
呼叫Log.d( )時,可以看到有3參數的多載方法,第3參數型別為Throwable,可供傳入Exception 或 Error物件,使程式執行至此除了印出 Log 也一併產生 Exception 或 Error 物件。