2012年8月31日 星期五

iOS 開發筆記 - 使用 Interface Builder 建立專屬的 UITableViewCell

IBStudy--


雖然內建的 UITableViewController 已經有不少還不錯用的 Cell Style (UITableViewCellStyleDefault, UITableViewCellStyleValue1, UITableViewCellStyleValue2, UITableViewCellStyleSubtitle),但客製化自己 Cell 還是一種必學的技能。然而,客製化自己的 layout 大概可以分成兩種方式,一種是純程式碼的撰寫(覆寫 layoutSubViews 或對 contentView 加東西),例如把 ImageView 擺在哪個 (x,y) 座標並設定大小為 (w,h) 等,另一種則是使用 Interface Builder 對元件的拖拉,再跟 Class 進行相對應的設定。由於之前比較熱愛 Coding ,所以一直沒用 Interface Builder,這次就來摸個幾把美美的 UI 操作吧 *誤*


使用 Interface Builder 仍需搭配 Class 物件來使用。例如建立自己的 MyTableViewCell(繼承UITableViewCell) 排版畫面 MyCell.xib 後,需搭配物件使用,當物件初始化時,可以指定採用 MyCell.xib 來初始化,整個過程等同於跟系統要了記憶體來使用,所以有一些人認為透過 Iterface Builder 所建立的 layout 不太好進行資源管理,並且當程式不穩時,很難除錯。


以 UITalbViewController 為例,建立自己的 MyTalbeViewCell,其粗略的使用方式:



  1. 建立 XIB (MyCell.xib),規劃 UI 呈現部分

  2. 建立對應的 UIView Class (MyTableViewCell 並繼承 UITableViewCell),並新增所需的 IBOutlet

  3. 更新 XIB 的 Custom Class 及對 XIB 上的元件進行連結 (outlets)

  4. 在 UITableViewController - (void)viewDidLoad 中,進行 XIB 的註冊

  5. 在 UITableViewController - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 中,進行 cell 的初始化及設定使用

  6. 在 UITableViewController -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 中,回傳 Cell 的高度


用法:[New File] -> [User Interface] -> 選 View or Empty 都行 -> 選擇 iPhone or iPad -> 取名 MyCell (此名稱跟之後XIB使用有關)


IBStudy00


點一下 MyCell.xib 開啟設定頁面,接著可在右下角 Object Library 之下方 Search 欄位中,填入想要的元件並拖拉到畫面進行 layout 安排,如使用 UITableViewCell、UILabel、UIImageView、UITextView 等元件


IBStudy01


IBStudy03


接著建立自己的 UITableViewCell 物件,此例為 MyTableViewCell,並將 XIB 中用到的元件進行宣告及實作(透過 Objective-C @property 語法可以請編譯器處理實作)


@interface MyTableViewCell : UITableViewCell


@property (nonatomic, strong) IBOutlet UIImageView *mImageView;
@property (nonatomic, strong) IBOutlet UILabel *mTitle;
@property (nonatomic, strong) IBOutlet UILabel *mSubtitle;
@property (nonatomic, strong) IBOutlet UILabel *mDescription;


@end


接著再切回 MyCell.XIB 進行元件的連結,先點選左邊 Objects 下的 UITableViewCell 後,在右邊選單上,切到 Identity inspector 可以進行 Custom Class 的更名,請更改成剛剛建立的 Class name (MyTableViewCell),如此一來切到 Connections inspector 可以進行 Outlets 連結,並可以看到 mImageView、mTitle、mSubtitle 和 mDescription 等項目。


IBStudy04 IBStudy05


將滑鼠移至項目旁邊的圓形框中,可以看到 + 號,點選按著後,拖到左邊畫面上的對應元件,放開後則完成連結。


IBStudy06


另外也可以在左邊 Objects 下,按著 Ctrl 點一下 My Table View Cell 後,也會出現 Connections 清單,其連結方式一樣。


IBStudy08


一切的 Connections 設定完後,即可存檔關閉,在此僅 demo 呈現的部份,若有其他點擊互動則需要設定 IBAction。此外,點選 MyTableViewCell.h 中,可以看到左邊都可以看 IBOutlet 都有圓點的連結圖示,亦可用來 debug 觀看自己有沒有漏掉什麼連結。


IBStudy10


最後則是挑一個 UITableViewController 來把玩吧:


- (void)viewDidLoad
{
    [super viewDidLoad];


    [self.tableView registerNib:[UINib nibWithNibName:@"MyCell" bundle:nil]
forCellReuseIdentifier:@"MyCellIdentifier"]; // "MyCell" for MyCell.xib
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return 1;
}


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return 5;
}


-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 150; // 可在 XIB 檔案,點選 My Talbe View Cell 從 Size inspector 得知
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    //UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
    //if( cell == nil)
    // cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"Cell"] autorelease];
    //cell.imageView.image = [UIImage imageNamed:@"icon_chrome.png"];


    MyTableViewCell *cell = (MyTableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"MyCellIdentifier"];
    cell.mTitle.text = [NSString stringWithFormat:@"Row: %d", indexPath.row];
    cell.mSubtitle.text = [NSString stringWithFormat:@"section: %d", indexPath.section];
    cell.mDescription.text = @"My Description";
    switch ((indexPath.row%4)) {
        case 1:
            cell.mImageView.image = [UIImage imageNamed:@"icon_chrome.png"];
            break;
        case 2:
            cell.mImageView.image = [UIImage imageNamed:@"icon_firefox.png"];
            break;
        case 3:
            cell.mImageView.image = [UIImage imageNamed:@"icon_safari.png"];
            break;
         default:
            cell.mImageView.image = [UIImage imageNamed:@"icon_ie.png"];
            break;
    }
    return cell;
}


成果:


IBStudy99


2012年8月29日 星期三

iOS 開發筆記 - UITableViewController 之 dataSource 更新顯示

最近開始複習 iOS SDK,過去使用 UITableViewController 時,每次資料新增變動時,都是透過 [self.tableView reloadData] 來更新畫面,效果就是把整個 UITableView 都重畫一次,只是若碰到資料更新很頻繁時,就會發現有些 touch event 因過度 reloadData 而出現短暫不能被處理。最近看 UITableView 相關書籍時,才發現原來早在 iOS SDK 3.0 時,就可以指定某個 Section 的某個 Row 更新就好!這招當然就可以用在新增資料及更新資料,實在太讚了!不曉得是不是太早接觸 iPhone SDK 哩,真的是太晚發現了 Orz


From UITableView Class Reference



  • reloadData (iOS SDK 2.0)

  • reloadRowsAtIndexPaths:withRowAnimation: (iOS SDK 3.0)

  • reloadSections:withRowAnimation: (iOS SDK 3.0)

  • reloadSectionIndexTitles (iOS SDK 3.0)


早期寫法(iOS 2.0):



  1. 更新 dataSource

  2. 呼叫 [self.tableView reloadData]


更有效率得寫法(iOS 3.0),新增資料(前頭):



  1. [dataSource insertObject:newItem atIndex:0];

  2. [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject: [NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];


更有效率得寫法(iOS 3.0),新增資料(後頭):



  1. [dataSource addObject:newItem];

  2. [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject: [NSIndexPath indexPathForRow:[dataSource count]-1 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];


如果,不幸一開始使用卻會蹦出 Exception:


Invalid update: invalid number of sections.  The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (0), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).


那可能跟我的情況很像,簡單的說,假設你的 numberOfSectionsInTableView 判斷是 return dataSource && [dataSource count] ? 1 : 0 ; 的話,那一開始因為沒資料而 sections 個數為 0,所以更新指定 section 時會出錯,解法有兩個,一個是一開始 numberOfSectionsInTableView 就會回傳固定數值(非0),這樣就不會出錯;另一個則是第一次新增資料時,呼叫 [self.tableView reloadData] 處理,之後才用 insertRowsAtIndexPaths 處理:


[dataSource addObject:item];
if( [dataSource count] == 1 )
        [self.tableView reloadData];
else
        [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject: [NSIndexPath indexPathForRow:[dataSource count]-1 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];


此外,在新增資料在前頭後,如果更新太頻繁時將會碰到 'NSInternalInconsistencyException', reason: 'Attempt to create two animations for cell' 而程式 crash,例如:


for( int i=0 ; i<50 ; i++ ) {
         [dataSource insertObject:[NSString stringWithFormat:@"%d",i] atIndex:0]; // 每次塞在 index = 0 的位置
         [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject: [NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic]; // 每次都對 index = 0 的 cell 更新
}


解法:


for( int i=0 ; i<50 ; i++ ) {
        [dataSource insertObject:[NSString stringWithFormat:@"%d",i] atIndex:0];
        [self.tableView beginUpdates];
        [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject: [NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
        [self.tableView endUpdates];
}


或統一更新:


NSMutableArray *indexPathSet = [[NSMutableArray alloc] init];
for( int i=0 ; i<50 ; i++ ) {
        [dataSource insertObject:[NSString stringWithFormat:@"%d",i] atIndex:0];
        [indexPathSet addObject:[NSIndexPath indexPathForRow:i inSection:0]]; // 依序累積 50 個 rows
}
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:indexPathSet withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
[indexPathSet release];


故比較好得方式就是在對 cell 處理更新特效時(除了reloadData用法外),一律採用 [self.tableView beginUpdates]; 和 [self.tableView endUpdates]; 包起來(有點lock味道),並且在目前很流行 Event-Driven 或 Async 架構更須如此。更多詳情,請參考 UITableView Class Reference 囉。


2012年8月24日 星期五

Android 開發筆記 - APP 的上架與更新

ExportSignedApplicationPackage


嘗試將小試身手的程式上架看看,初步的心得,覺得 Android app 上架比 iOS app 還要快,大概資料填妥後,約兩小時內就可以在 Google Play 搜尋到了!


上架前,需準備的資料:



  • keystore

  • Signed Application Package (apk)

  • ic_Launcher.png

  • 512x512 icon

  • 2 images


軟體更新需準備的資料:



  • 更新 AndroidManifest.xml 的 Version code 跟 Version name 

  • 上次使用的 keystore

  • Signed Application Package (apk)


所謂的 keystore 可以想成對 apk 進行簽名,透過這個 keystore ,可以驗證此 apk 是由同一人所送出的。特別在 Android Developer Console 中,可允許多位開發人員,假設有兩個團隊分別上架兩個 app 時,這時每個團隊各自用自己的 keystore 維護,除了可避免內鬥被人亂換掉 apk 外,也是種不錯的保護機制。當然,如果連 keystore 弄丟的話,似乎…只好砍掉重來了 XD


過去產生 keystone 時,都是透過 command line 來進行,現在 Eclipse+ADT 的環境已經可以透過視窗介面進行啦!並且產生完 keystore 後,連續動作還可以順便產生可以上架的 apk 


在 Eclipse 之 Package Explorer 裡點選 Your App -> 按右鍵選擇 Android Tools -> 選 Export Signed Application -> Create new keystone -> 輸入相關資料及儲存位置 -> 順便輸出簽證後的 apk


CreateKeystore1


CreateKeystore2


接著回到 Android app 上架流程,拿著剛簽好的 apk 往 Android Developer Console 上去後,會蹦出一些表單資料的填寫,其中需要的額外資料:



  • 至少 2 張圖片

  • 一張 512x512 圖片

  • 程式顯示名稱(在Google Play上)

  • 程式簡介

  • 程式分類

  • 程式類型


如此填好後,就可以按儲存或是發佈了,十分快速。至於兩張使用圖片,可以用 Eclipse 之 DDMS 去拍兩張程式在裝置上的使用截圖即可,而 512x512 基本上就是 App icon 的圖,所以做 App icon 時,別忘了先做張 512x512 的圖,之後縮成 72x72 來當 ic_Launcher.png 使用即可。


如果要更新已上架的軟體,姑且不論程式碼有沒更新,至少需更新 AndroidManifest.xml 的 Version code 跟 Version name 即可,其中 Version code 是流水號,讓 Google Play 系統判別用途,而 Version name 是顯示給使用者的版本,如 1.0, 1.0.1, 1.1 等用途。接著一樣用 Android Tools -> Export Signed Application -> 選擇 keystone path -> 輸入密碼 -> 選擇 Alias 及密碼後 -> 輸出成功


UseExistingKeystore1


UseExistingKeystore2


最後到 Android Developer console 上,選擇要更新的 app 後,切換到 APK 分頁,點選 "上載APK" 後,再把前一版本停用,啟用新版本,就算完成更新,更細膩的部份則是切換到"產品詳細資料"裡,填寫此版本更新的項目資訊。約莫兩小時就可以在 Google Play 上搜尋到新版資訊。


免費申請鄧白氏環球編碼(D-U-N-S Number)

最近正在幫公司申請 iOS Developer Program for Company 版本,結果有一欄是 D-U-N-S Number,詢問一些前輩才發現這是今年五月底或六月初才新增的資料欄位,可能是要提高申請 Company 的門檻補充 Company 資料吧,看到要多填的資料時,大多都會想改用 Individual 版本吧。


順便一提的,鄧白氏環球編碼(D-U-N-S Number)常用在貿易公司,鄧白氏大概算公認的第三方評鑑公司,故申請到D-U-N-S Number可促進貿易過程的流暢。總之,為了符合 Apple 的要求,著手進行申請吧!就跑去逛 www.dnb.com 後,發現該網站免費申請的表格是僅限美國地區的(或是與美國政府單位有來往的才可以申請),就輾轉逛到 www.dnb.com.tw 後,發現沒有所謂的免費,申請一組就要價一萬六(年費) 並且在 CocoaChina 看到一堆人哀號 XD  大多都花錢買了一組(人民幣1500以上吧),正當自己意志力薄弱時,就順手播電話給 Apple Developer 台灣區客服(0800-022-237),得知是可以免費申請的。


首先在 https://developer.apple.com/ios/enroll/dunsLookupForm.action 填好公司資料進行查詢(D-U-N-S Number申請前都是先查詢),查沒資料就可以按 Submit 去申請啦!大概一到兩天,就會收到 dnb.com 寄來的 D-U-N-S Number Request/Update Confirmation 信件,上頭會顯示大概約一個月左右才會完成申請,過程將會有專人聯絡。然而,這次的經驗是在7天內就收到台灣區的電話確認,確認完資料後,大概一天內就會收到 D-U-N-S Number Request/Update Completed,也就獲取到一組 D-U-N-S Number 啦。


D-U-N-S Profile Lookup


Legal Entity Name: YourCompanyName Inc.
Tradestyle or DBA: (可空白)


Headquarters Address (公司地址)
Street Address:
City: 
State/Province:
Postal Code:
Phone Number:
Country:Taiwan


Mailing Address (同上)


Work Information
Full Name: 負責人名字
Job Title: 負責人職稱
Phone Number: 負責人電話
Work Email: 負責人信箱


不過,在 https://developer.apple.com/support/D-U-N-S/ 網站上有提到,仍須 14 個工作天 Apple 才能跟 DNB 同步資料,所以還須等等囉。


補充一下:當初沒透過 https://developer.apple.com/ios/enroll/dunsLookupForm.action 申請的原因是...底下的 captcha image 根本看不到...當然也就沒試了,所幸寄信給 Apple 後,就可以使用囉。


2012年8月16日 星期四

Android 開發筆記 - 更新 ListView 之 AndroidRuntime FATAL EXCEPTION 之 UI Thread 問題

一直以來都知道更新 UI 時,要使用 UI Thread 來進行,有幾種方式:



  • 使用 Handler.post

  • 使用 YourActivity.this.runOnUiThread

  • 使用 mListView.post 


然而今天練習時卻一直蹦出錯誤訊息:


AndroidRuntime FATAL EXCEPTION: main
java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread.


因為我在背景跑了 Thread 來更新資料,更新完資料在請 Handler 或 runOnUiThread 來更新 ListView,卻依舊碰到上述的錯誤訊息,最後才發現...那就是 ListView 的 Adapter 一開始在初始化時,有指定一個 ListAdapter 給他,那時有綁定一個 List/ArrayList 結構來記錄要顯示的資料(在此簡稱 mItemList),我一直以為只要 mSimpleAdapter.notifyDataSetChanged 、 mListView.requestLayout 在 UIThread 做就好,沒想到那個 mItemList 的增減也必須在 UIThread 做才行。這個 bug 不是每次都會出現,但出現的比例不低啦 :P


總之,解法就把 mItemList 的更新也都在 UIThread 做就行了 :P


申請個人或公司型 Android Developer 開發者帳號

Android Developer Console


摸了 Android app 大概有快一年半吧,一直以來都沒接觸過 Google Play (Android Market) 這塊東西,如今有幸幫公司處理,就來筆記一下啦!購買開發者帳號需要一張有效的信用卡,並透過 Google Wallet 來使用。首先從 Android Developer 官網連入,從右上角選單可以看到 Google Play Developer Console ,點選後進行登入,如果還未註冊為 Android 開發者時,就會顯示註冊畫面,輸入完"開發人員名稱/公司名稱"、"電子郵件地址"、"網站網址"、"電話號碼" 後,就可以點選繼續進行。


Android Market 開發人員發佈協議


接著同意一下條款後,接著就會導到 Google wallet 來進行付款的部份。


註冊成為開發人員


如果要報公司帳,應該要用公司信用卡才行?接著填一填信用卡資料後送出後,驗證完信用卡就算完成吧,並且可以查看 Google wallet 的購買清單狀態或是直接切回 Google Play Developer Console,而 Google Play Developer Console 一開始會顯示資料處理中,但似乎不影響軟體上架的設定:


進行中


過一陣子後,就會顯示已完成的狀態


處理完


如此一來就可以慢慢把玩啦!接著就從右下角的 Upload Application 後,就可以上傳 APK 檔案,並進行後續的設定囉(似乎離軟體上架還有段距離要設定...)。


至於怎樣算是公司型開發者帳號呢,其實就只要修改 Profile 把顯示的名稱改成  XXX Inc. 就差不多了 XD 比 iOS 公司型開發者要容易許多啊啊,並且可以新增無限多的帳號來使用開發者權限。


最後一提,如果公司沒有信用卡並使用個人信用卡去綁公司指定的 Gmail 帳號的話,那用完後可以連 Google Wallet 帳號,在付款方式那邊可以刪掉已綁定的信用卡唷,如此一來就不用擔心自己的信用卡一直綁在公司帳號啦。


2012年8月13日 星期一

[Android] Galaxy Nexus 刷機筆記(重返 Google 親生子?) @ Mac OS X 10.8

七月多推薦周邊親人買 Galaxy Nexus 來用,那時洽逢 SAMSUNG 與 Apple 智慧財產官司的關係,花了好大一把功夫才買到一台(空機價約13.5k附近),雖然不是最便宜的(美國官網公訂價$399美金,台灣有機會可以找到 13k 一台),但看在缺貨情況已經很滿意了。使用上除了拍照有聲音外,還都不錯用,不致於想要 root 它啦,不過,讓我想更新它的主因是...直到 2012-08-11 時,尚未接收到 Android 4.1 OTA 更新通知,查了一下原來這隻手機似乎是港台機?代號是 yakjuzs 並且屬於 SAMSUNG 系列的?也就是系統更新是 SAMSUNG 在維護的,與其等到 SAMSUNG  推出 Android 4.1 更新檔,還不如自己更新吧 :P 於是就下海刷一下吧!


雖然過去工作常編 AOSP 甚至把一些手機 root 研究,但本身使用 Android 手機時,並不太想要 root 或刷機的 :P 例如刷機刷用的檔案有沒被加料等等。故這次更新我主要採用 Google 官方提供的 Factory Images for Nexus Devices 來源,這也是我專挑 Google Nexus 原生手機的主因之一,可以方便地處理一些事務。


刷機有風險,需自行承擔手機失去保固或變成磚頭


手上這隻手機在 "設定" > "關於手機" 可以看到 "基頻版本" 為 I9250XXK1 和 "版本號碼" 有 "ITL41F.I9250ZSKKB" 字樣,並且透過 Android-SDK 來取得 adb/fastboot 等指令來驗證系統,並把手機接上後,也將手機開啟開發者設定,使用 adb devices 和 adb shell 確認手機狀態:


$ adb devices
XXXXXXX

$ adb shell cat /system/build.prop
...
ro.build.id=ITL41F
ro.build.display.id=ITL41F.I9250ZSKKB
ro.build.version.incremental=I9250ZSKKB
...
ro.build.version.release=4.0.1
...
ro.product.model=Galaxy Nexus
ro.product.brand=samsung
ro.product.name=yakjuzs
ro.product.device=maguro
... 


進入刷機前,請先手動備份需要的資料,實在是過程會清掉系統資料,就像重灌電腦一樣。備份資料嘛,例如手機內的照片、簡訊、鈴聲等等,其中簡訊用 SMS Backup & Restore 這套 Android app 軟體,可以輕鬆地把簡訊匯出 XML 檔案,並支援匯入(若有草稿需先清掉才能正常備份)。


接著,把手機關機後,同時按 "Volume + 鍵" 、 "Volume - 鍵" 和 "Powert 鍵" 一會兒,手機就會進入 fastboot mode:


fastboot_mode


透過指令進行 bootloader unlock (此步將清除手機資料,請詳看 unlock bootloader 訊息),打完就可以看到手機畫面切換為詢問 unlock bootloader ,並透過 Volume +/- 鍵進行選擇切換,並用 Power 鍵確定:


unlock_bootloader


$ fastboot oem unlock
...
OKAY [ 23.924s]
finished. total time: 23.924s


選 YES 後,則將進行 unlock bootloader 並且在 fastboot mode 最下一行顯示紅色的 "LOCK STATE - UNLOCKED" ,代表可以自行安裝(刷機)指定的 OS 啦:


fastboot_unlock


接著就將下載好的 Factory Images for Nexus Devices 解壓縮並擺好位置(在此使用 Factory Images "yakju" for Galaxy Nexus "maguro" (GSM/HSPA+) - 4.1.1 (JRO03C) ,請確認自己手機的狀況),透過一連串的 fastboot 指令進行更新:


$ fastboot flash bootloader /path/yakju-jro03c/bootloader-maguro-primelc03.img
sending 'bootloader' (2308 KB)...
OKAY [ 0.315s]
writing 'bootloader'...
OKAY [ 0.300s]
finished. total time: 0.615s
$ fastboot reboot-bootloader
rebooting into bootloader...
OKAY [ 0.007s]
finished. total time: 0.007s
$ fastboot flash radio /path/yakju-jro03c/radio-maguro-i9250xxlf1.img
sending 'radio' (12288 KB)...
OKAY [ 1.720s]
writing 'radio'...
OKAY [ 1.369s]
finished. total time: 3.088s
$ fastboot reboot-bootloader
rebooting into bootloader...
OKAY [ 0.007s]
finished. total time: 0.007s
$ fastboot -w update /path/yakju-jro03c/image-yakju-jro03c.zip
archive does not contain 'boot.sig'
archive does not contain 'recovery.sig'
archive does not contain 'system.sig'
--------------------------------------------
Bootloader Version...: PRIMELC03
Baseband Version.....: I9250XXLF1
Serial Number........: #################
--------------------------------------------
checking product...
OKAY [ 0.007s]
checking version-bootloader...
OKAY [ 0.008s]
checking version-baseband...
OKAY [ 0.008s]
sending 'boot' (4366 KB)...
OKAY [ 0.589s]
writing 'boot'...
OKAY [ 0.431s]
sending 'recovery' (4708 KB)...
OKAY [ 0.629s]
writing 'recovery'...
OKAY [ 0.500s]
sending 'system' (396675 KB)...
OKAY [ 43.758s]
writing 'system'...
OKAY [ 38.104s]
erasing 'userdata'...
OKAY [ 0.292s]
formatting 'userdata' partition...
Creating filesystem with parameters:
        Size: 14539534336
        Block size: 4096
        Blocks per group: 32768
        Inodes per group: 8144
        Inode size: 256
        Journal blocks: 32768
        Label:
        Blocks: 3549691
        Block groups: 109
        Reserved block group size: 871
Created filesystem with 11/887696 inodes and 97200/3549691 blocks
sending 'userdata' (137559 KB)...
writing 'userdata'...
OKAY [ 28.911s]
erasing 'cache'...
OKAY [ 0.013s]
formatting 'cache' partition...
Creating filesystem with parameters:
Size: 452984832
Block size: 4096
Blocks per group: 32768
Inodes per group: 6912
Inode size: 256
Journal blocks: 1728
Label:
Blocks: 110592
Block groups: 4
Reserved block group size: 31
Created filesystem with 11/27648 inodes and 3566/110592 blocks
sending 'cache' (8832 KB)...
writing 'cache'...
OKAY [ 2.715s]
rebooting...


finished. total time: 116.000s


如此一來,手機重開機後,就會是 Android 4.1.1 囉!先測試一下打電話、接電話的基本功能吧!


最後,我還是習慣把手機的 bootloader lock 起來 XD 一樣關機後,同時按住 "Volume + 鍵" 、 "Volume - 鍵" 和 "Powert 鍵" 一會兒,進入 fastboot mode 後,透過指令進行:


$ fastboot oem lock
...
OKAY [ 0.165s]
finished. total time: 0.165s


收工。


一開始只是單純想要手動升到 Android 4.1.1 ,後來發現連拍照聲音也都解決了 XD 只能說剛好買到的 Galaxy Nexus 可能要拿去賣給亞洲(日本?)吧,所以有些地區有規定手機拍照一定要有聲音(防偷拍)。透過這次的系統更新,之後應該可以等 Google 第一波作業系統就升級了吧,不再需要等 SAMSUNG 囉~


2012年8月10日 星期五

[CSS] 使用 CSS Multi-column Layout Module 實作自動分頁

CSS Multi-column Layout Module (html pagination)


兩年前在 Monocle EPUB Reader 看到的作法,今天被朋友問了一下分頁作法,我一時之間也想不起來 XD 只好翻翻以前寫的 HTML EPUB Reader,順便做個筆記吧!


概念是透過 CSS Multi-column Layout Module http://www.w3.org/TR/css3-multicol/ 實作的,假設定一個 DIV 並在裡頭塞滿字,透過設定 [-moz- | -webkit-]column-width = 150px 話,假設瀏覽器視窗為 600px ,那就會一頁看到 4 個 columns 囉。


於是,分頁的作法就是在指定的顯示範圍內,切換指定的 column 而已。


範例:


<html>
       <head>
              <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
              <style type="text/css">
                     #show {
                            position:absolute;
                            height:100%;width:100%;
                            margin: 0;
                            z-index:100;
                            overflow: hidden;
                     }
                     #content {
                            position:absolute;
                            font-size:20px;
                            -webkit-column-gap: 0pt; -webkit-column-width: 1000px;
                            -moz-column-gap: 0pt; -moz-column-width: 1000px;
                            z-index:100;
                     }
              </style>
              <script type="text/javascript" src="http://code.jquery.com/jquery-1.8.0.min.js"></script>
              <script type="text/javascript">
                     function windowResize() {
                            var window_width = $(window).width() - 20;
                            var window_height = $(window).height() - 50;
                            window_width += 'px';
                            window_height += 'px';
                            $( '#show' ).width( window_width).height(window_height);
                            $( '#content' ).width(window_width).height(window_height)
                            .css( '-moz-column-width' , window_width ).css( '-webkit-column-width' , window_width );
                     }
                     $( window ).resize( function() {
                            windowResize();
                     } );
                     $( document ).ready( function() {
                            windowResize();
                            $('#prev').click( function() {
                                   //console.log('prev');
                                   var obj = document.getElementById('show');
                                   if( obj.scrollLeft > 0 )
                                          obj.scrollLeft -= $('#show').width();
                                   else
                                          obj.scrollLeft = 0;
                            } );
                            $('#next').click( function() {
                                   //console.log('next');
                                   var obj = document.getElementById('show');
                                   if( obj.scrollLeft < $('#show').width() )
                                          obj.scrollLeft += $('#show').width();
                                   } );
                                   setTimeout(function() { window.scrollTo(0, 1) }, 100);
                     } );
              </script>
       </head>
       <body>
              <button id="prev">Prev</button><button id="next">Next</button>
              <div id="show">
                     <div id="content">
                            <p>大量文字</p>
                     </div>
              </div>
       </body>
</html>


[Linux] 使用 Dropbox Command Line @ Ubuntu 12.04 server

今天看到一篇文章談論關於 team 合作的模式,其中提及 Dropbox 的地位,例如大家組隊參加比賽,在比賽過程中有不少工作的委派跟追蹤,這時候該如何進行?其中有一個微小卻很重要的就是檔案的交換,這時候使用 dropbox 共享某個目錄就顯得很方便,甚至大家可以自定 filename 或 dirname 當做一個 notification,經由 dropbox 同步功能、通知功能就方便達成啦!


講了那麼多,我只是要用在虛擬機器上 XDD 這樣機器掛了,至少一些工作的檔案或記錄,可以有多幾版本 sync 到 dropbox 啦,以免哪天機器突然下線欲哭無淚。申請 dropbox 免費帳號 2GB 空間實在很夠用了!更可每一台虛擬機器申請一個帳號(可避免關鍵帳密擺在虛擬機器上),接著常用帳號分享目錄給各台的功能。此外,搭配 GMail 又可以放大絕(YourAccount+VM1@gmail.com)。另外,在工作站上,又可以直接開很多個 user 跑 dropbox 喔。


設定 Dropbox @ Ubuntu 12.04 64bit server:


$ wget -O dropbox.x86.tar.gz "http://www.dropbox.com/download/?plat=lnx.x86"
$ tar -xvf dropbox.x86.tar.gz
$ mv .dropbox-dist/ ~/
$ ~/.dropbox-dist/dropboxd
/home/user/.dropbox-dist/dropboxd: 10: exec: /home/user/.dropbox-dist/dropbox: not found
$ file ~/.dropbox-dist/dropbox
/home/user/.dropbox-dist/dropbox: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.8, stripped
$ sudo apt-get install ia32-libs
$ ~/.dropbox-dist/dropboxd
This client is not linked to any account...
Please visit https://www.dropbox.com/cli_link?host_id=xxxxxxxxxxxxxxxxx&cl=en_US to link this machine.



Client successfully linked, Welcome Bbs!


如果 OS 環境也是 64Bit 的,那就跟我一樣安裝一下 ia32-libs 即可,當執行 ~/.dropbox-dist/dropbox 時,顯示 This client is not linked to any account... 並給予一支 url 時,就只需開瀏覽器去瀏覽那個 url ,並用你將綁定在機器的帳號登入即可,如此一來,成功後會顯示 Client successfully linked, Welcome Bbs! 訊息。


經由上述流程後,已經設定好 dropbox command line 的使用了,在家目錄可以看到 ~/Dropbox 跟 ~/.dropbox 兩個目錄,其中 ~/Dropbox 就是用來同步的目錄,而 ~/.dropbox 則是綁定帳號的相關資訊,如果要用其他帳號來綁定的話,請刪掉這兩個目錄吧。


設定一下開機自動啟動:


網路上一堆人參考 http://wiki.dropbox.com/TipsAndTricks/TextBasedLinuxInstall/UbuntuStartup 和 http://wiki.dropbox.com/TipsAndTricks/TextBasedLinuxInstall 等等,但我現在都連不上無法看原始的 script


故暫時參考 modified script from here http://wiki.dropbox.com/TipsAndTricks/TextBasedLinuxInstall/UbuntuStartup,這 script 做的事就是找出有哪些 user 的家目錄中有 ~/.dropbox-dist/dropbox ,找到後把它跑起來,如此而已。而這個修改的版本嘛,還滿通用的,只需設定一個 group 名為 dropbox ,並把機器上使用的帳號加入此 group 即可,無需修改 script 囉。


$ sudo cp dropbox-script /etc/init.d/dropbox
$ sudo chmod +x /etc/init.d/dropbox
$ sudo /etc/init.d/dropbox status
Status of dropbox ...
$ sudo update-rc.d dropbox defaults 
Adding system startup for /etc/init.d/dropbox ...
/etc/rc0.d/K20dropbox -> ../init.d/dropbox
/etc/rc1.d/K20dropbox -> ../init.d/dropbox
/etc/rc6.d/K20dropbox -> ../init.d/dropbox
/etc/rc2.d/S20dropbox -> ../init.d/dropbox
/etc/rc3.d/S20dropbox -> ../init.d/dropbox
/etc/rc4.d/S20dropbox -> ../init.d/dropbox
/etc/rc5.d/S20dropbox -> ../init.d/dropbox
$ sudo /etc/init.d/dropbox start
Starting dropbox...
$ sudo /etc/init.d/dropbox status
Status of dropbox ...
dropboxd for USER username: running (pid #####)


最後,如需要更豐富的 dropbox 運行資訊,可以試試 http://www.dropbox.com/download?dl=packages/dropbox.py 這隻程式吧。