2010年12月28日 星期二

Android 開發筆記 - 關於反組譯 Android 程式,還真的不用 30 秒就搞定!

先前聽說許多關於 Android 程式很容易被逆向工程,我也抱持這樣的觀點去戳 Android 開發者 XDD 直到今天開完會,前輩跟我說 Android 產生出的 dex file 很難閱讀,因此我就去找一下相關資料,找到一篇 2007/11/28 How to decompile .dex file on Android 文章,上頭也是敘述要去解讀 dex 有點不好讀,但隨後找到 2009/08/08 decompiling DEX into Java sourcecode 這篇文章,細看一下原來還有 dex2jar 的方式!這下可好了...


所以,咱們來試試吧!我在 Ubuntu 10.04 的環境下測試


準備工具:



  • dex2jar

    • A tool for converting Android's .dex format to Java's .class format



  • JD-GUI

    • JD-GUI is a standalone graphical utility that displays Java source codes of “.class” files. 



  • 一份編譯好的 apk 檔案



接著就把 dex2jar 解壓縮出來,並執行


$ cd dex2jar-0.0.7.7-SNAPSHOT
$ sh dex2jar.sh MyWidget.apk


然後就會看到一個對應的 jar 檔:MyWidget.apk.dex2jar.jar


解壓縮 JD-GUI 去開啟 MyWidget.apk.dex2jar.jar 檔案,不囉唆,程式碼就可以看了!整個過程不用 30 秒吧!打快一點可能 3 秒喔 XD


dex2jar2JD-GUI


這...還真的叫我難以投入 Android 開發啊 *誤* 不過仔細一看,之前在 MyWidget 寫的程式,有些地方式用 R.layout.main 的地方,已經直接被換成數值了,但我想這樣也不會太難閱讀,畢竟整個 source code 都丟在你眼前了!更別提 dex2jar 仍然很活躍地進行維護及開發,真的開感謝一下對岸,第一次看到簡體中文有那種莫名的感動 XD 太多東西都嘛是英文先衝啦


至於 dex2jar 的原理,可以參考這邊:介绍dex2jar的核心思想,有原理才會有相對應的解法喔!


順便筆記一下 iPhone 的:


arm-elf-objcopy -I binary -O elf32-littlearm iPhoneApp out.elf && arm-elf-objdump -marm9 -D out.elf


但組語嘛,雖然學過,但我想我應該看不下去 XD


最後,關於逆向工程的事情,如果程式碼非常冗長,那一樣不好啃啊!所以,玩玩就好。


2010年12月26日 星期日

Mac mini 拆殼 + 更換記憶體

關於更換 Mac mini 裝備的事宜,可以參考很多網路上的資料,最好也看一些影片!這整個過程不是痛苦兩字可道盡 XDD 畢竟一台 Mac mini ,可是花了 19990 購買的 :P 心臟真的要大顆一點...


這個由來是因為最近 DDR3 1066 2GB 的記憶體一條才 650 元,所以心一橫就買了兩條,幫自己的 Mac mini 更新啦!在官方的 spec 中,敘述是最大支援到 4GB (兩條RAM),但有一些影片跟文章說明只需更新 EFI 可達到支援 8GB (一條插 4GB),思考了良久,覺得用不到 8GB 這麼多,再加上買兩條 4GB 的記憶體增加花費,故最後只敗了兩條 2GB 的金士頓囉!至於更換硬體設備,有錢的話可以去找蘋果店家,花錢請他們更換囉。這篇純粹個人記錄用,不見得適用於其他人 :P 請留意!


列一下參考資料:



回到主題,在此僅記錄個人的拆機過程,請先多看看其他人的拆機過程,少說看個三篇吧!我是 2010/04 買的 Mac mini ,現在已經算是舊版的,版本應該是 2009 年初版,比 2010 年中的版本還要厚喔。


基本工具


準備工具,我使用一隻水果刀 + 鐵尺,做為開路先鋒!用水果刀從細縫插入(記得刀鋒朝外),硬撐出可以讓鐵尺插入的空間,此後就把水果刀拔掉了。開殼方式類是上述影片 "How to open mac mini..." 裡頭的過程,但不一樣的是我使用寬度短小的鐵尺!所以十分不方便。請小心使用水果刀跟鐵尺,不要以為鐵尺不會傷人,在擠壓過程中,我被鐵尺割到了 :P 所幸只是很小的皮肉傷,也讓我之後移動鐵尺時都會包一張衛生紙。至於插入到真的把整個底盤弄出來,大概花了 15 分鐘吧 XD 一直不斷地"輕輕弄" ,最後被傷到後獸性大發,就比較敢用力!只是我用得是寬度小的鐵尺,必須在一旁移動和扳開,導致 Mac mini 的底部有明顯被磨擦的痕跡。


插入鐵尺+拔掉水果刀
先透過水果刀撐出較大的縫隙,再用鐵尺插入


右邊
終於把右邊都先敲開了,已經可以看到磨損的樣貌!原先想要把一根根"輕輕地"扳開,但搞到最後啥都不管,直接在中間移動嘗試,硬把鐵尺彎大力一點直到整個底盤被弄出來,這真的是經驗論!心臟要大顆啊。只要一邊被撬開後,其他兩邊都會很自然地弄好 XD


底部

左邊

快拆完
等到右、底、左都被撬開後,接著可以把他反過來,從面板那邊慢慢地扳開囉 !


拆完殼
直到最後殼與機子分開了,接著要把機子上方的板子移開,但移開前要先把左邊兩處跟右上方的土金色移開,好像是天線的樣子。左邊兩處就只要慢慢往上移就行了,右上方那個要先去把底部的卡榫輕壓,就會彈跳出來。


天線跟彈簧
左下位置的天線與彈簧,都要保存好


注意有條線被膠帶貼住
左下的天線有被膠帶黏起來,我是有把它輕輕撕開,把天線移開,這樣才方便把機殼上方移掉


底部的樣貌
機殼上方有一顆硬碟,除了剛剛提到的 3 個天線外,此殼還有 4 處的小螺絲要拆,以及與機殼連線的排線喔!等到這些都清光後,再慢慢地把殼子往上移開來,由於底部還有像 PCI 卡的連接方式,所以需要慢慢地往上,這些細節請多看一下影片。


更換完記憶體
終於移開機子上方的部分,可以看到記憶體的擺置位置,我也順手換掉兩條原裝的 SAMSUNG 記憶體啦!


最後再慢慢把東西還原,收工!而成果嘛,如下圖:
兩條記憶體 + 白色屑屑 底部嚴重損傷
兩條被替換的記憶體、Mac mini 的損傷以及一台被更新成 4GB 的 Mac mini 啦!線都接好後,可以先開機檢查(左上角的[蘋果]->[關於這台Mac]->[更多資訊]->查看記憶體是不是顯是 4GB 囉),再把機殼蓋上,畢竟機殼對我這個新手來說真的太難拆了。


買了新螢幕

工作之後,錢花在 3C 上頭越來越不手軟!記得大學時期,第一次打工的錢,就花了 1500 買一張 AGP 8X 的顯示卡,彌補了一下前一年組的主機,當時為了省錢只用了一張 PCI 顯卡撐著,所以賺到錢就買上想到顯卡的升級。


去年底,主機掛了後,我也花了快兩萬元在主機身上, 今年四月,花了快兩萬五買了 Mac mini 跟周邊對應產品(這台可不是年中轟動的標錯價的 Server 啊),幾天前又花了五千多,買了一台 22" LCD 和 2 條 1066 的筆電記憶體。看來,今年稱得上花大錢的年代!3C 這種產品,沒事還是不要去留意,不然就會像我一樣不停敗下去 :p


呼,最近也有一些態度要改變,像是 3C 產品就是用來享受用的,但我總會有那種壞念頭,好比如花錢買了主機,就勢必要給它賺回來,例如大學那張顯卡,正是從事網頁相關的工作內容等。雖然成果也算是多少有賺回來,但這樣的心態反而給自己更多無所謂的壓力感。我想,我該試著多享受一下購物,而不是逼著自己賺錢啦!


忘了一提,這次購物的理由是...因為 Mac mini 轉 D-Sub 時的輸出,在我之前那台 19" LCD 1440x900 上,最多只能選擇 1280x768 的解析度,對於我這種愛看 code 的人,這種高度有點吃力。而 2GB DDR3 1066 的筆電記憶體,其實也是要拿來擴充 Mac mini 啦,思考了很久,最後還是看在最近的低價敗了。


2010年12月24日 星期五

驗證 GTalk 使用明文/加密文傳遞聊天內容

我一直以為 GTalk 是加密傳送聊天訊息的,直到今早一位在火燙公司的學弟,請我跟他用 GTalk 聊天時,意外發現 GTalk 原來不是完全的加密聊天的。


情境敘述:


目前共有三台電腦



  1. 第一台是 Ubuntu 10.04 安裝 Pidgin 2.6.6 版本,上頭標示支援 GTalk SSL/TLS 加密傳輸,使用 A 帳號

  2. 第二台是 Windows XP,安裝 GTalk 官方繁體中文版 1.0.0.105,此版本不提供加密服務,使用 B 帳號

  3. 第三台是 Windows 7,安裝 GTalk 官方英文版 1.0.0.104,此版本提供加密服務,使用 C 帳號


測試結果:



  • 當 A 跟 B 進行聊天時,發現兩者傳遞的內容都是明文的 XML 資料,其中 <body> 內記錄的就是聊天內容,除此之外,當然連帳號資訊都是未加密的

  • 當 A 跟 C 進行聊天時,此時兩端會使用加密傳遞

  • 當 B 跟 C 進行聊天時,此時兩端皆會使用不加密傳遞,結果跟 A 與 B 一樣


因此驗證了一件事,不要以為自己的 IM client 標榜加密,就等於跟人溝通都會加密的!還是會看對方情況來處理,例如 B 跟 C 都已是官方的軟體,只是一個是繁體版,另一個是英文版,但繁體版不加密,導致英文版最後也配合繁體版進行不加密的通訊。


查了一些文章,這篇說的跟我測的差不多:正面全裸的Gtalk 1.0.0.105,慎用!


所謂的加密部分,只能確保 gtalk client 端連到 google server 端的過程,但從 google server 連到對方那端時,不見得會加密(例如對方透過 http 在 GMail 上用)。除此之外,這次測到的一個大問題是:


就算你自己的 gtalk client 可以提供加密連線,但對方不提供時,結果自己連到 google server 這端也就不走加密


呼,還是萬試謹言慎行啊!還有,快去用英文版就好!


最後附上一些圖示:


gtalk 加密
GTalk 使用加密時的封包訊息


gtalk未加密
GTalk不使用加密時的封包,其中 <body> 儲存的就是聊天訊息,此例為 "Hello" 訊息,除此之外連同通訊雙方的帳密資訊也是曝光的,並使用可讀性佳的 XML 描述


2010年12月18日 星期六

Windows 7 常用免費軟體

回老家,順便重灌一下電腦,神清氣爽啦,順便記一下最近常用的免費軟體:



工作類:



Firefox Plugin:



Google Chrome Plugin:



筆記:


XMarks 可以把書籤資料紀錄在 Server 上,該 Server 可以是自己架的,也可以直接用 XMarks 上面的。而多個瀏覽器的好處,我可以把工作上的同步在 Google Chrome 上頭,然後把家裡用的同步在 Firefox 上頭,如此一來也可以分得很乾淨,另一種用法也可以用目錄分辨工作或家用。另外,我在 Firefox 上僅使用書籤工具列,好處是看到不錯的網頁就直接拖拉分頁進去記錄,而書籤工作列上只要目錄分的好,就不會有顯示上的問題,只會有操作上的便利。


2010年12月16日 星期四

Android 開發筆記 - 簡易的 Widget 實作

MyWidgetShow


除了要從選單點選來執行的應用程式外,還有另一種呈現的方式,那就 Widget 模式,也就是在廣告單上常常看到簡介某大廠 Android 手機時,其手機畫面正顯示的天氣資訊,有點像似待機時顯示在背景的資訊。這種程式實作上並沒有特別困難,但是我看了一些文章教學,反而很著重在介面設計,畢竟 Widget 的特色就是要美美的,結果對我這種介面沒經驗的人來說,往往第一步就卡住了,像是要用內建的 draw9patch 去弄圖片等,故在此就先完全不理會,連什麼背景框框都不要,純粹用來筆記實作 Widget 的流程。


實做 Widget 跟一般 project 的設定沒有差太多,但還是要多留意:



  1. 建立專案時,不見得要使用 Activity

  2. 編寫 AndroidManifest.xml 檔案

  3. 新增描述 Widget 的檔案

  4. 設定 Widget 排版

  5. 實作更新 Widget 的物件


建立一個 project


[Eclipse]->[File]->[New]->[Android Project]

Project name: MyWidget
Build Target: Android 2.2
Application name: MyWidget
Package name: com.test.widget
Min SDK Version: 8


留意的是在此不啟用 Create Activity,這並不是必須的選項。啟用 Activity 的好處,可以規劃使用者在新增 Widget 時,可以在過程中可以多加設定動作。在此僅學習建立 Widget 的流程,就不多提了。


MyWidgetStru


設定 AndroidManifest.xml 檔案


新增 <receiver> 等敘述標籤,別於以前的 <activity>。

<receiver android:name=".MyWidget" >
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
        android:resource="@xml/widget" />
</receiver>


在此 reciever 的 name 代表之後會建立的 Class 名稱,也就用更新 Widget 資訊的。而 meta-data 中所描述的 resource 則是下一步要新增 Widget 的描述檔


完整描述:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test.widget"
    android:versionCode="1"
    android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <receiver android:name=".MyWidget" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data android:name="android.appwidget.provider"
                android:resource="@xml/widget" />
        </receiver>

    </application>
    <uses-sdk android:minSdkVersion="8" />
</manifest>


建立 xml/widget.xml 檔案,用以描述 Widget 的資訊


在 res 目錄裡建立新的目錄,名為 xml,接著在 res/xml 目錄裡建立一個檔案,可從 xml 按右鍵->[News]->[Other]->[Android]->[Android XML File],並填寫 File 為 widget.xml 並勾選 AppWidget Provider 的型態,最後就可以按 Finish 結束,接著開啟 xml/widget.xml 檔案進行編輯


<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="294dp"
    android:minHeight="72dp"
    android:updatePeriodMillis="1800000"
    android:initialLayout="@layout/main"

>
</appwidget-provider>


在此 minWidth 和 minHeight 是設定 Widget 顯示的範圍,而 dp 的數值計算可參考官網的教學,在此不多談,而 updatePeriodMillis 代表此 Widget 更新的頻率,1000 代表 1 秒,所以 1800000 代表 30 分鐘更新一次,更新頻率不宜過高,容易使得手機沒電。然而,經測試發現,目前內定的情境,無論把更新頻率弄到多快,最少要等 30 分鐘才會更新一次,解決的方式也不是沒有,在此先不多談。


設定排版部份


在此使用預設的 main.xml 檔案,開啟 main.xml 檔案後,替已存在的 TextView 加上 android:id="@+id/now"、android:gravity="center"、android:textColor="@android:color/black" 和 android:textSize="18sp" ,另外,再把 layout_height 更新為 fill_parent 即可。


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:id="@+id/now"
    android:gravity="center"
    android:textColor="@android:color/black"
    android:textSize="18sp"
    android:layout_width="fill_parent"

    android:layout_height="fill_parent"
    android:text="@string/hello"
    />
</LinearLayout>


實做 Widget 的資訊更新


在 src/com.test.widget 裡,新增一個 Class 名為 MyWidget.java


package com.test.widget;

import java.text.SimpleDateFormat;
import java.util.Date;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;

public class MyWidget extends AppWidgetProvider {
    @Override
    public void onUpdate(Context context,AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        RemoteViews updateViews = new RemoteViews( context.getPackageName(), R.layout.main);
        updateViews.setTextViewText(R.id.now,  new SimpleDateFormat( "yyyy/MM/dd HH:mm:ss" ).format( new Date() ) );
        appWidgetManager.updateAppWidget(appWidgetIds, updateViews);
    }
}


最後,把程式 Run 起來,起初模擬器上不會顯示任何效果,因為 Widget 是要讓使用者自行設置的,需要透過模擬器鍵盤上的 [Menu]->[Add]->[Widgets] 就可以找到 MyWidget,點選下去才是真的安裝此 Widget 啦,成果就是螢幕上面多了一個時間囉!這也是 MyWidget.java 裡頭寫得東作,僅把 TextView 上的內容更新為時間。另外,若想要把已安裝的 Widget 刪除,僅需用滑鼠按住它,接著 Home 就會變成垃圾桶,拖進去就可以囉。


2010年12月15日 星期三

Android 開發教學筆記 - 使用 Regular Expression、Network Connection 和 Thread

getNews


打算寫一個稍微複雜的小程式,練習的項目:



  • 使用 Regular Expression

  • 使用 Network Connection

  • 使用 Thread


以三個項目為出發點,把以前的老題目拿出來:定期抓台灣 Yahoo 首頁的兩則焦點新聞,顯示在 Android 模擬上。因此,先想一下排版問題,大概就三個 TextView,分別為 更新時間 和 兩則新聞。


建立 project


[Eclipse]->[File]->[New]->[Android Project]

Project name: MyWeb
Build Target: Android 2.2
Application name: MyWeb
Package name: com.test.Web
Create Activity: MyWeb
Min SDK Version: 8


設定使用網路權限


點選 AndroidManifest.xml 檔案,在 <manifest> 裡增加 <uses-permission android:name="android.permission.INTERNET" />


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.test.web"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".MyWeb"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
    <uses-sdk android:minSdkVersion="8" />
    <uses-permission android:name="android.permission.INTERNET" />
</manifest>


設定排版


點選 main.xml 檔案,增加 3 個 TextView,分別為"更新時間"、"新聞1"和"新聞2"。


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello"
    />
<TextView android:id="@+id/UpdateDate" android:layout_width="fill_parent" android:layout_height="wrap_parent"></TextView>    
<TextView android:id="@+id/News1" android:layout_width="fill_parent" android:layout_height="wrap_content"></TextView>
<TextView android:id="@+id/News2" android:layout_width="fill_parent" android:layout_height="wrap_content"></TextView>

</LinearLayout>


MyWeb.java


package com.test.web;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;

import com.test.web.GetYahooNews;

public class MyWeb extends Activity {
    Handler jobs;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        jobs = new Handler();
        new Thread( new GetYahooNews( this )).start();
    }

    public void updateNews( final ArrayList<String> news )
    {
        jobs.post( new Runnable(){
            public void run()
            {
                TextView showTextView;

                if( news != null && news.size() >= 2 )
                {
                    if( ( showTextView = (TextView) findViewById(R.id.News1) ) != null )
                        showTextView.setText("[News] "+(String)news.get(0));
                    if( news.size() == 4 && ( showTextView = (TextView) findViewById(R.id.News2) ) != null )
                        showTextView.setText("[News] "+(String)news.get(2));
                }
                if( ( showTextView = (TextView) findViewById(R.id.UpdateDate) ) != null )
                {
                    DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                    showTextView.setText( "[Update @ " + dateFormat.format(new Date() ) + "]" );
                }
            }
        });
    }
}


GetYahooNews.java


package com.test.web;

import java.io.*;
import java.util.ArrayList;
import java.util.regex.*;
import java.net.HttpURLConnection;
import java.net.URL;

import android.util.Log;

import com.test.web.MyWeb;

public class GetYahooNews implements Runnable {
    MyWeb activity;
    
    GetYahooNews( MyWeb n)
    {
        this.activity = n;
    }
    public static void report( String message )
    {
        //System.out.println( "[Report] " + message );
        Log.d( "Report" , message );
    }
    public static ArrayList<String> getHotNews()
    {
        ArrayList<String> news = new ArrayList<String>();
        HttpURLConnection con = null;
        try
        {
//*
            URL url = new URL("http://tw.yahoo.com");
            con = (HttpURLConnection) url.openConnection();            
            con.setReadTimeout(10000);
            con.setConnectTimeout(15000);
            con.setRequestMethod("GET" );
            con.addRequestProperty("User-Agent","Mozilla/5.0 (Windows; U; Windows NT 5.2; en-GB; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9");
            con.setDoInput(true);
            con.connect();

            BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream(), "UTF-8" ));
// */
//            BufferedReader reader = new BufferedReader(new InputStreamReader( new FileInputStream("fetch.html"), "UTF-8" ));

            String n, result="";
//            while( ( n = reader.readLine() ) != null )
//                result += n;

            StringBuilder htmlContent = new StringBuilder();
            while ((n = reader.readLine()) != null)
               htmlContent.append(n);

            result = htmlContent.toString();

//            BufferedWriter out = new BufferedWriter( new FileWriter("fetch.html") );out.write(result);out.close();
            
            String pattern;
            int at;

            pattern = "<label class=\"img-border clearfix\">";
            if( ( at = result.indexOf(pattern) ) < 0 )
            {
                news.add( "format error 1" );
                report( "format error 1" );
                return news;
            }
            result = result.substring( at );

            pattern = "<ol class=\"newsad clearfix\">";
            if( ( at = result.indexOf(pattern) ) < 0 )
            {
                news.add( "format error 2" );
                report( "format error 2" );
                return news;
            }
            result = result.substring( 0 , at  );

            pattern = "<h3[^>]*>[^<]*<a href=\"(.*?)\"[^>]*>(.*?)</a></h3>";
            Pattern p = Pattern.compile( pattern , Pattern.CASE_INSENSITIVE | Pattern.DOTALL );
            Matcher m = p.matcher( result );

            while( m.find() )
            {
//                report( "\n==== Get === \n" + m.group() + "\n" );
//                report( "URL: " + m.group(1) );
//                report( "Title: " + m.group(2) );
                String newsTitle = ""+ m.group(2);
                String newsUrl = "" + m.group(1);
                if( ( at = newsUrl.indexOf( "http:" ) ) > 0 )
                    newsUrl = newsUrl.substring( at );
                news.add( newsTitle );
                news.add( newsUrl );
            }
        }catch(Exception e)
        {
            news.add( "Exception:"+e );
        }
        finally
        {
            if ( con != null )
                con.disconnect();
        }
        return news;
    }
    public void run()
    {
        try
        {
            while( true )
            {
                try
                {
//                    ArrayList<String> demo = new ArrayList<String>();demo.add("Yo1");demo.add("Yo2");demo.add("Yo3");demo.add("Yo4");activity.updateNews( demo );
                    activity.updateNews( getHotNews() );
                    Thread.sleep( 1000 * 60 * 60 * 2  );    // 2 hrs
                }
                catch(Exception e)
                {
                    report( "run while Exception:"+e );
                    break;
                }
            }
        }
        catch( Exception e )
        {
            report( "run Exception:"+e );
        }
    }
}


在 MyWeb 裡有一樣東西比較特別,叫做 Handler,可以把他當作管理 Thread 的用法,在官網的描述裡,比較重要的敘述:


There are two main uses for a Handler: (1) to schedule messages and runnables to be executed as some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.


在這邊使用 Handler,是讓更新 UI 的部份能夠透過 Runnable 來處理事件,如果不使用的話,會有無法更新 UI 的現象,暫時還沒了解底層的問題。


接著是 MyWeb 裡的 updateNews 函數,其參數是使用 final 的描述,加上這個描述後,可以讓接下來的 new Runnalbe 裡,可以直接用傳進來的參數,如果不用 final 也有解法啦,就是自己在定義一個實做 Runnable 的物件,然後把參數都傳遞好,稍微麻煩一點:


public void updateNewsPrev( ArrayList<String> news )
{
    class UIUpdate implements Runnable
    {
        ArrayList<String> news;
        UIUpdate( ArrayList<String> in )
        {
            news = in;
        }
        public void run()
        {
            TextView showTextView;
            if( news != null && news.size() >= 2 )
            {
                if( ( showTextView = (TextView) findViewById(R.id.News1) ) != null )
                    showTextView.setText("[News] "+(String)news.get(0));
                if( news.size() == 4 && ( showTextView = (TextView) findViewById(R.id.News2) ) != null )
                    showTextView.setText("[News] "+(String)news.get(2));
            }
            if( ( showTextView = (TextView) findViewById(R.id.UpdateDate) ) != null )
            {
                DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                showTextView.setText( "[Update @ " + dateFormat.format(new Date() ) + "]" );
            }
        }
    }
    jobs.post( new UIUpdate( news ) );
}


接下來是 GetYahooNews 的部份,其中 getHotNews 函數是擷取台灣 Yahoo 首頁的兩則新聞出來,以此當作資料,接著就只是單純的當個 Runnalbe 在跑,並且設定 2 小時跑一次,跑完一次會呼叫 MyWeb 的 updateNews 去更新 UI 部份。至於怎樣從 GetYahooNews 呼叫 MyWeb 呢?那就是一開始在使用 GetYahooNews 時,就把 MyWeb 傳進去給他記住,在此使用 activity 變數紀錄。


Android 開發教學筆記 - 使用 String 和 StringBuilder 的差別

在練習使用 HttpURLConnection 來取得指定 URL 網址內容時,發現透過 Android 模擬器跑得非常非常緩慢,但我把相同的 Java Code 用桌機的環境執行,卻十分快速,讓我不禁感到奇怪,難道這是模擬器的問題嗎?還是初始化網路連線所耗的資源問題呢?


後來看到一則文章:Re: [android-developers] Re: First HTTP/S requests are slow. [Android 1.5] - msg#03831,看到了解法!


原來我寫得程式碼:


String n, result="";
while( ( n = reader.readLine() ) != null )
    result += n;


文章提到的寫法:


String n, result="";
StringBuilder htmlContent = new StringBuilder();
while ((n = reader.readLine()) != null)
    htmlContent.append(n);
result = htmlContent.toString();


這兩者的速度至少差 20 倍啊!讓我寫得程式從原先要等約 130 秒,瞬間變成只要等五秒,真是太神奇了。我的 CPU 是 AMD X4 945 + 4GB 記憶體,也不見得會跑很慢才是,因此找了不少文章,才發現是寫法與模擬器的影響吧。程式還包括網路連線,變數很多,很難找出此點。


完整的程式碼:


HttpURLConnection con = null;
try
{
    URL url = new URL("http://tw.yahoo.com");
    con = (HttpURLConnection) url.openConnection();            
    con.setReadTimeout(10000);
    con.setConnectTimeout(15000);
    con.setRequestMethod("GET" );
    con.addRequestProperty("User-Agent","Mozilla/5.0 (Windows; U; Windows NT 5.2; en-GB; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9");
    con.setDoInput(true);
    con.connect();

    BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream(), "UTF-8" ));

    String n, result="";

//    while( ( n = reader.readLine() ) != null )
//        result += n;

    StringBuilder htmlContent = new StringBuilder();
    while ((n = reader.readLine()) != null)
       htmlContent.append(n);
    result = htmlContent.toString();
}
}catch(Exception e)
{
}
finally
{
    if ( con != null )
        con.disconnect();
}


Android 開發教學筆記 - 排版更新或顯示問題

花了幾個小時,才發現這個蠢問題,特別紀錄一下。


在實作 Android 程式時,新增了 3 個 TextView,想要在特定情況下一起更新三個欄位,但很奇妙的只有第一個 TextView 會顯示出來,後面來兩個 TextView 卻看不到。花時間不斷地確認,最後才發現錯誤的地方是在 layout 的部份:


<TextView android:id="@+id/UpdateDate" android:layout_width="fill_parent" android:layout_height="fill_parent"></TextView>
<TextView android:id="@+id/News1" android:layout_width="fill_parent" android:layout_height="fill_parent"></TextView>
<TextView android:id="@+id/News2" android:layout_width="fill_parent" android:layout_height="fill_parent"></TextView>


有發現到了嗎?因為 layout_height 都是 fill_parent 的數值,將導致只又 UpdateDate 這個 TextView 會佔滿剩下的螢幕空間,導致 News1 和 News2 不會顯示出來,再加上一開始沒有給初始值,剛好沒看到這個現象。


修正方式,改使用 wrap_content 即可:


<TextView android:id="@+id/UpdateDate" android:layout_width="fill_parent" android:layout_height="wrap_content"></TextView>
<TextView android:id="@+id/News1" android:layout_width="fill_parent" android:layout_height="wrap_content"></TextView>
<TextView android:id="@+id/News2" android:layout_width="fill_parent" android:layout_height="wrap_content"></TextView>


紀錄一下,提醒自己。


2010年12月14日 星期二

[Java] Regular Expression 與 HttpURLConnection 練習

年初,用 PHP 寫了這個,[PHP] 使用官方 Plurk API 實作簡單的機器人 - 靠機器人救 Karma!以 Yahoo News 為例,年終時,給他拿來當作 Android 程式的練習題目,結果弄了半天,發現 Java 語法熟練度很差,因此乾脆跑回去練習 Java 了!需搞懂的就是如何使用 Regular Expression 和網路的連線處理。


簡易範例:


import java.io.*;
import java.util.regex.*;
import java.net.HttpURLConnection;
import java.net.URL;

class Test
{
    public static void report( String message )
    {
        System.out.println( "[Report] " + message );
    }
    public static void main(String argv[])
    {
        HttpURLConnection con = null;
        try
        {
//*
            URL url = new URL("http://tw.yahoo.com");
            con = (HttpURLConnection) url.openConnection();
            con.setReadTimeout(10000);
            con.setConnectTimeout(15000);
            con.setRequestMethod("GET" );
            con.addRequestProperty("User-Agent","Mozilla/5.0 (Windows; U; Windows NT 5.2; en-GB; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9");
            con.setDoInput(true);
            con.connect();

            BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream(), "UTF-8" ));
// */
//            BufferedReader reader = new BufferedReader(new InputStreamReader( new FileInputStream("fetch.html"), "UTF-8" ));
            String n , result = "";
            while( ( n = reader.readLine() ) != null )
                result += n;

//            BufferedWriter out = new BufferedWriter( new FileWriter("fetch.html") );out.write(result);out.close();
//            System.out.println( "Result:" + result );

            String pattern;
            int at;

            pattern = "<label class=\"img-border clearfix\">";
            if( ( at = result.indexOf(pattern) ) < 0 )
            {
                report( "format error 1" );
                return;
            }
            result = result.substring( at );

            pattern = "<ol class=\"newsad clearfix\">";
            if( ( at = result.indexOf(pattern) ) < 0 )
            {
                report( "format error 2" );
                return;
            }
            result = result.substring( 0 , at  );

            pattern = "<h3[^>]*>[^<]*<a href=\"(.*?)\"[^>]*>(.*?)</a></h3>";
            Pattern p = Pattern.compile( pattern , Pattern.CASE_INSENSITIVE | Pattern.DOTALL );
            Matcher m = p.matcher( result );

            while( m.find() )
            {
                report( "\n==== Get === \n" + m.group() + "\n" );
                report( "URL: " + m.group(1) );
                report( "Title: " + m.group(2) );
            }
        }
        catch( Exception e )
        {
            report( "Error:" + e );
        }
        finally
        {
            if ( con != null )
                con.disconnect();
        }
    }
}


執行結果:


$ javac Test.java && java Test
[Report]
==== Get ===
<h3><a href="news/a/h1/t/*http://tw.news.yahoo.com/article/url/d/a/101214/5/2iy8o.html" title="最新!美元弱 新台幣升破30">最新!美元弱 新 台幣升破30</a></h3>

[Report] URL: news/a/h1/t/*http://tw.news.yahoo.com/article/url/d/a/101214/5/2iy8o.html
[Report] Title: 最新!美元弱 新台幣升破30
[Report]
==== Get ===
<h3><a href="news/a/h2/t/*http://tw.news.yahoo.com/article/url/d/a/101214/2/2iy6j.html" title="情人多愛你 手碰觸方式露餡">情人多愛你 手碰 觸方式露餡</a></h3>

[Report] URL: news/a/h2/t/*http://tw.news.yahoo.com/article/url/d/a/101214/2/2iy6j.html
[Report] Title: 情人多愛你 手碰觸方式露餡


說真的,細算的話,五年前 Java 是我最常用的語言,至少用了一年多,但如今什麼都忘光光了!


2010年12月13日 星期一

Android 開發教學筆記 - 簡單設定 Google Maps

參考資料:



想要在 Android 上頭使用 Google Maps 的功能,有幾道流程:



  1. 申請 Android Maps API Key

  2. 建立使用 Google APIs 的模擬器

  3. 建立使用 Google Maps API 的 projects (在此 Build Target 使用 Google APIs/Google Inc./2.2/8 )

  4. 設定 Android Application 環境,使用 INTERNET permissions 及相關使用的 lib

  5. 設定 MapView 的 layout

  6. 設定程式碼


首先關於取得 Google Maps API Key,此部份跟一般申請上的不一樣,需使用 Android 的申請 Sign Up for the Android Maps API - Android Maps API - Google Code,此時會需要一組認證指紋(MD5),而這組序號用在開發程式的階段(debug mode),而當你要將程式上架時,必須在用另一組(release mode),細節請參考 Getting the MD5 Fingerprint of the SDK Debug Certificate,在此就依 debug mode 申請一組:


$ keytool -list -keystore ~/.android/debug.keystore
...
Certificate fingerprint (MD5): 94:1E:43:49:87:73:BB:E6:A6:88:D7:20:F1:8E:B5:98


其中認證指紋(MD5)為 94:1E:43:49:87:73:BB:E6:A6:88:D7:20:F1:8E:B5:98 (此為範例,請輸入自己產生的),帶著這組就可以去申請 Android Maps API Key 囉,請輸入到 My certificate's MD5 fingerprint 欄位。送出後,頁面將顯示 "您的金鑰" 、 "金鑰適合所有使用以下指紋憑證所簽署的應用程式" 和 "此處提供您 xml 配置的範例",共三筆資。請保存好,這邊只會使用"您的金鑰"。


建立 Android API 模擬器,如果你已經有設定一台可運行 Google APIs 2.2 的話,那可以略過此步。


[Eclipse]->[Window]-> [Android SDK and AVD Manager]->[New]

Name: Map
Target: Google APIs(Google Inc.) - API Level 8
    
按下 Create AVD 及建立完成


建立一個專案


[Eclipse]->[File]->[New]->[Android Project]

Project name: MyMap
Build Target: Google APIs, Google Inc., 2.2, 8
Application name: MyMap
Package name: com.test.map
Create Activity: MyMap
Min SDK Version: 8

設定完就可以按 Finish 囉


Android Project Structure


緊接著設定 Application 的權限和相關 lib 部份,請在左邊點選 AndroidManifest.xml 檔案,此時右邊視窗則顯示該檔相關的設定,可以在底部找到 Manifest/Application/Permissions/Instrumentation/AndroidManifest.xml 等子頁面切換,在此切換 AndroidManifest.xml 分頁,直接編輯此 xml 檔案,在 <application> 裡頭增加 <uses-library android:name="com.google.android.maps" /> 資訊,在 <manifest> 裡增加 <uses-permission android:name="android.permission.INTERNET" />,分別代表要使用函式庫:(com.google.android.maps)和需要使用網路資源(android.permission.INTERNET)


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.test.map"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".MyMap"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <uses-library android:name="com.google.android.maps" />
    </application>
    <uses-sdk android:minSdkVersion="8" />
    <uses-permission android:name="android.permission.INTERNET" />
</manifest>


設定完應用程式執行環境後,接著設定排版部份(layout),透過左邊視窗請點選 main.xml 檔案,直接用下面的資訊覆蓋掉:


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mainlayout"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <com.google.android.maps.MapView
        android:id="@+id/mapview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:clickable="true"
        android:apiKey="Your Maps API Key"
    />

</RelativeLayout>


其中 Your Maps API Key 請改填"您的金鑰",如此一來即完成排版的部份


最後,則是修改程式碼的部份,請點選 MyMap.java 檔案



  1. 增加 import com.google.android.maps.*;

  2. 將 public class MyMap extends Activity 修改為 public class MyMap extends MapActivity

  3. 實做必要函數 boolean isRouteDisplayed()


完整程式碼:


package com.test.map;

import android.app.Activity;
import android.os.Bundle;
import com.google.android.maps.*;

public class MyMap extends MapActivity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
    @Override
    protected boolean isRouteDisplayed() {
        return false;
    }

}


如此一來,則可以執行,並看到模擬器顯示 Google Maps 囉!


AndroidMaps


上述算是一個簡單的設定流程,但僅供了解流程而已,因為產生的程式除了呈現一個 Google Maps 外,沒有太多的互動,因此,如果想要增加一些使用者互動部份,如 Zoom In/Zoom Out,請在 onCreate 加上兩行程式碼:


MapView mapView = (MapView) findViewById(R.id.mapview);
mapView.setBuiltInZoomControls(true);


請記得加到 setContentView(R.layout.main); 後面,如果加在前面會導致程式 crash 喔,可以看看 setBuiltInZoomControls method for MapView causes application to crash 的敘述,避免 crash 也可以改成:


MapView mapView;
if( ( mapView = (MapView) findViewById(R.id.mapview) ) != null )
    mapView.setBuiltInZoomControls(true);


但沒擺在 setContentView(R.layout.main); 後面,還是沒用的喔。


AndroidMapsZoom


其他資料:



2010年12月10日 星期五

冰箱 Get!

冰箱、食材


冰箱啊~這是多麼奢侈的設備啊。記得大學時期,好像是升大三還升大四時,室友在校園討論板上,終於!搶到了冰箱,一行人推著推車很高興地去跟學長買二手的,我記得好像賣 1200 的樣子,還附一個烤箱跟平底鍋,當時室友們都很黑皮!對我而言倒覺得還好,因為我只三餐偶爾宵夜而已,並且不怎喝飲料,冰箱對我而言真的有跟沒有是一樣的。後來,過了幾個月後,那台冰箱的使用度真的降到了冰點,除了一開始很黑皮冰的飲料外,好像就沒什麼特別了。


直到幾天前,多虧同事的幫忙,在公司的討論網上終於買了一台二手冰箱,且賣冰箱的正巧一年前坐在我座位附近,真巧啊!就這樣重複著大學時的步驟,借推車、運冰箱和定位。對了,搬冰箱最好搬完靜待一會兒喔,所以我是隔天下班後才正式啟用它。


據同事的說法,擔心冷藏不冷,所以不怎使用,因此,第一件事就是冰塊的測試 XD 空蕩蕩的冰箱只冰那一小盤的生水。慶幸地,一切 OK ,就算我把冰箱的強度降到最低,早上的生水,下班時還是會結冰,看來運氣不錯。


沒多久後,就跟同事去賣場,買了一包僅 49 元的韭菜水餃,成了第二住進冰箱的食材。今晚上班就去附近的果菜賣場買了相關的工具,如鍋、瓢子等設備,順便試了買了一包料理包以及一把青菜和一包豆皮,看來未來又有東西可學了,來煮點東西訓練一下。


2010年12月1日 星期三

[Javascript] 使用 jQuery 把表單資料紀錄在 cookie 裡

做了一個有很多選項要填寫的表單,但由於一些環境上的考量,有點懶的把已填寫過過得表單資料記錄在 server 端,因此就想到稍微惡搞一下,把表單資料都紀錄在 client 上!由於該表單也沒有啥機密資訊,所以也不用太擔心什麼。


紀錄方式:


當使用者按下送出表單的那一刻時,除了會加上一些判斷,確認資料是否有輸入完整,而後可以做的小動作就是把表單的資料都儲存在 cookie 裡,以後有需要可以從 cookie 撈出來幫忙填寫。


相關環境:



  • jQuery

    • <script type="text/javascript" src="http://code.jquery.com/jquery-1.4.3.min.js"></script>



  • jQuery Plugin - Cookie (非必要)

    • <script type="text/javascript" src="http://plugins.jquery.com/files/jquery.cookie.js.txt"></script>

    • 自己管也可以,但我有點懶的回顧 cookie 語法,就改用這個 plugin 啦,用 $.cookie( 'Name' , 'Value' , { path: '/', expires: 365 } ); 儲存,用 $.cookie( 'Name' ) 取回數值




用法:


<form method="POST" onsubmit="return checkData();">
    <dl>
        <dt><button onclick="reload(); return false">讀取前次資料</button></dt>

        <dd>&nbsp;</dd>


        <dt>姓名</dt>
        <dd><input class="cookie" name="builder_name" style="width:50%;"/></dd>
        <dd>&nbsp;</dd>

        <dt>喜愛運動</dt>
        <dd>
            <select class="cookie" name="status">
                <option value="0" selected>否</option>
                <option value="1">是</option>
            </select>
        </dd>
        <dd>&nbsp;</dd>


        <dt>狀態</dt>

        <dd><input type="checkbox" name="status"/> 已婚</dd>
        <dd>&nbsp;</dd>

        <dd>&nbsp;</dd>

       
<dt><button type="submit">執行</button></dt>

    </dl>
</form>


<script type="text/javascript">

function checkData()
{
    // ... do check ...

    // ... do save ...
    save();

    return true;
}

function save()
{
    $( '.cookie' ).each( function(){
        var target_cookie = '__' + $(this).attr('name');
        var data = $(this).val();
        if( $(this).attr('type') == 'checkbox' )
            data = $(this).is(':checked') ? 'on' : 'off';
    
        $.cookie( target_cookie, data , { path: '/', expires: 365 } );
        //console.log( target_cookie +' : ' + $(this).attr('type') + ' : ' + $.cookie( target_cookie ) + ' : ' + data );
    });
}

function reload()
{
    $( '.cookie' ).each( function(){
        var target_cookie = '__' + $(this).attr('name');
        if( $.cookie( target_cookie ) )
        {
            if( $(this).attr('type') == 'checkbox' )
            {
                if( $.cookie( target_cookie ) == 'on' )
                    $(this).attr('checked', true);
                else
                    $(this).attr('checked', false);
            }
            else if( $(this).attr('type') == 'select-one' && $(this).get(0) && $(this).get(0).options && $(this).get(0).options.length )
            {
                var data = $.cookie( target_cookie );
                var options_list = $(this).get(0).options;
                for( var i=0 , cnt = options_list.length ; i<cnt ; ++i )
                {
                    if( options_list[i].value == data )
                    {
                        $( options_list[i] ).attr( 'selected' , 'selected' );
                        break;
                    }
                }
                $(this).trigger('change');  // 有些 select 可以搭配 change 事件
            }
            else
                $(this).val( $.cookie( target_cookie ) );
            //console.log( target_cookie +' : ' + $(this).attr('type') + ' : ' + $.cookie( target_cookie ) );
        }
    });
}
</script>


如此一來,就可以儲存表單文字、checkbox以及 select 等,都可以處理。這邊比較投機的是對要儲存的表單,多設一個 class 數值,用來讓 jQuery 準確地撈出來。


2010年11月27日 星期六

iPhone 開發教學 - 框框圓角、置中和自動調整大小

autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight setCornerRadius


很久沒練習 iPhone 程式,大概有半年吧?剛好無意間看到片斷的程式,就順便記錄一下。


這兩張圖共三個重點:



  • 中間顯示的框框是有圓角的

  • 框框無論 iPhone 擺在哪,都可以置中

  • 無論直看或橫向,框框都會自動地調整填滿


程式碼:


@untitled.h (新增一個 UIViewController)


#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>

@interface untitled : UIViewController {
        UIView *_blockerView;
}       
        
@end


@untitled.m


- (void)viewDidLoad {
    [super viewDidLoad];
    CGSize _blockerViewSize = CGSizeMake(250, 450);

    if( UIInterfaceOrientationIsLandscape( [[UIDevice currentDevice] orientation] ) )
        _blockerViewSize = CGSizeMake(450, 250);
        
    _blockerView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, _blockerViewSize.width, _blockerViewSize.height)] autorelease];
    _blockerView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.8];
    _blockerView.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2);
    _blockerView.clipsToBounds = YES;
    if( [_blockerView.layer respondsToSelector:@selector(setCornerRadius:)] )
        [_blockerView.layer setCornerRadius:10];

    _blockerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

    [self.view addSubview:_blockerView];

}

- (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation {
    _blockerView.center = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2);
}


// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return YES;
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}


各項部分:



  • 圓角

    • #import <QuartzCore/QuartzCore.h>

      if( [_blockerView.layer respondsToSelector:@selector(setCornerRadius:)] )
          [_blockerView.layer setCornerRadius:10];



  • 置中

    • _blockerView.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2);

    • 並且實作兩個對應函數

      • - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation

      • - (void)didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation





  • 自動調整大小

    • _blockerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;




此部分常用在呈現 loading 或 waiting 的訊息,此時顯示黑色框框,大部分大小會調整成小型的正方型,不需要自動調整大小的功能,僅需置中即可,但如果需要疊一層圖層避免使用者點選到後面的部分,那就會需要自動調整大小的功能,以符合當下顯示或阻擋的大小。用法就跟在 Web Programming 上,使用 div 圖層顯示資訊或擋住不讓使用者點選到其他功能。


2010年11月22日 星期一

尋根?

假期度過了一半,我還在尋根嗎?


這些日子,聽週邊的朋友道述務農跟畜牧所掙錢的故事,發現有地真好!可以從事自己想做的事,也可以自給自足。記得一年前碰到務農的朋友,他跟我說,有些事現在不做,以後就沒辦法做了。我很認同他的感受,但我手邊卻沒有那對應的資源啊。


至於會不會羨慕呢?我不是很確定自己的感受,好像是又好像不是。記得幾個禮拜前的婚禮,當大家閒聊務農的收入時,某位 A 直接問某 B 會不會因此想務農,某 B 非常直接地回應:不會!某 B 還是喜歡台北的便利性。我真佩服這樣的人,很爽朗地回應,並且非常清楚自己想要的是什麼。我大概幾年之內還是會為錢而作事吧。


別人常說,要嘛為興趣工作,要嘛為錢工作。但是,愛錢不也是一種興趣嗎?別人常常會稱讚那些廢寢忘食鑽研自己興趣的人,卻總是用低俗的眼光看待那些為錢工作的人,例如賺錢買藥 :P 不曉得這是不是酸葡萄的反應呢?


我到現在還是沒有很執著於所學的行業,記得大學畢業那年,我甚至只想找一間制度不錯,可以努力升等的場所就好,偏偏我現在的環境不是努力就能升等的場所,因為最基本的升等條件是博士學歷 :P 但相對之下也還有其他優劣啦,至少我還能樂在其中吧!


晚上跟學弟聊天,才有想起來自己有點想從事傳統業的小想法,並且認為資訊能力啊,不該只是用來呈現多絢麗的技巧或成品,我反而還滿喜歡用來解決生活的小問題。希望兩年內自己能夠越來越清楚自己真正想要的是什麼,並且好好地執行下去。


2010年11月21日 星期日

PCMan Combo - 開不起來、Runtime Error

印象中,我以前在 Windows XP 時,用 PCMan Combo 偶爾會發生 PCMan 開不起來的問題,當時的解法就只是重新安裝 PCMan 就可以解決了,但這次碰到一位在 Vista 環境使用 PCMan Combo,發生 Runtime Error 的訊息,但透過重新安裝 PCMan 也無法解決,之後在 Ptt Ask 板看到解法:



  1. 刪掉 C:\Users\使用者帳號名稱\AppData\Roaming\PCMAN Combo 目錄

  2. 重新安裝 PCMan Combo


經測試的確可以解決。


話說我也好一陣子沒用 PCMan Combo 版,比較常用 PCMan Lite + Firefox/Google Chrome/IE,只是工作後也比較少逛 BBS 了,獲取再過一陣子就只會用 Firefox plugin 了


2010年11月14日 星期日

好用的、免費的或曾經限時免費過的 iPhone App

記錄一下自己常用的 iPhone Apps!以前每次下載軟體都會有 log 寄到信箱,但現在好像變成要購買付費軟體才會有記錄。寫篇 blog 記錄的好處是以後直接用 iPhone/iPod Touch/iPad 上的 Safari 開啟這篇時,點選其 link 則會被導去 iTunes App Store 並且很方便地就能下載安裝。這邊大多是免費的或是過去曾限時免費的,附帶一提,我還沒花錢買過軟體或遊戲 XD 先記錄幾筆,剩下的有空再慢慢增加。


以下是免費或曾經免費過的軟體,下載時請注意是否已變成付費軟體



TowerMadness™

這款算是我第一款上手的遊戲,常常無聊會玩的,有些關卡我還是沒破關!這是付費版本,之前有一陣子免費下載,所以就趕上順風車啦!他有免費版 Towermadness™ zero ,可以抓這個版本就好,唯一的差別就只是有廣告跟沒廣告





BowQuest: PandaMania!


這款也是在免費的時候下載的!最難的我還沒破,卡再倒數第二關,其他的都破關啦





Sigma


很簡單的方塊對消遊戲,當初也是免費時下載的





Stone Wars

偶爾會免費的,也算是益智類的偏靜態射擊遊戲





Super Laser: The Alien Fighter

免費的時候下載的,動態射擊類遊戲,滿好玩的





QS Chinese Chess

這款介面跟配樂我覺得很好,並且有支援 iPad 的,主要有兩個功能,一個是與電腦對奕,另一個則是玩家對奕。前者的部分,電腦 AI 算不太好,因此對一些高手來說一定會覺得不開心,但對不太懂象棋的人來說,或是初學者,我覺得沒什麼不好的;後者就是可以兩個玩家輪流下象棋,我覺得也算是一個很不錯的平台了





Dinosaur Slayer

這款也是防衛型的遊戲,就是可以透過賺錢以及花錢提供工具的方式,不斷地闖關,主要是使用弓箭把龍打下來。目前玩到 199 關,說真的不知道要玩到何時才算破關





Rebirth of Fortune

這遊戲像以前的炎龍騎士團老遊戲,也是一關關地闖以及棋盤方式的進行,這個有 100 關,到後面都用法師破關啦





Angry Birds Lite

這款已經變成 iPhone 上頭最經典的遊戲了!很簡單又富有變化的射擊類遊戲,但不是那種即時性要發射的,而是讓你慢慢橋好方向跟力道再發射的遊戲





Pee Monkey Plant Bloom

跟 Angry Birds 的玩法很像,但我覺得畫面的呈現也挺不錯的





OvenBreak-Infinity

一款簡單操作的遊戲,類似馬力兄弟那種的遊戲,只是更簡單許多,但步調會隨著時間越來越快囉





Geared

很讚的益智遊戲,透過關卡中指定使用的齒輪,把所有的齒輪都連接在一起,即可過關,非常有意思,沒有時間上的限制,還滿適合我這種慵懶玩法





Geared 2!

同上,多一些變化





Bonsai Blast

畫面、配樂都很讚的遊戲,音樂很有中國風!算有一點點益智的味道,將同樣顏色的珠子串再一起消除的遊戲





Revolt

滿流暢的第二人稱的射擊遊戲,在 iphone 上畫面小,還滿刺激的





Solomon's Keep

魔法師遊戲,可以使用雷或火去打骷髏頭,也算是一種射擊遊戲吧





Fishing Kings

很不錯的釣魚遊戲,原本也覺得沒什麼,但細玩一下才發現還不錯,唯一的缺點大概是不適合躺著玩,以及任務一直解不完會開始有點膩,除此之外,覺得設計上還不錯






Baseball Superstars® 2011.

還不錯的棒球遊戲,內容很豐富,可以當投手也可以當打擊手,但很傷眼睛,所以也開始不玩

















Facebook

這應該不用多說吧!有時在家很懶得開電腦時,有 wifi 時就會拿來用





iBBS

若是在台灣念過大學的,應該對 BBS 不會太過生疏,甚至一堆班版或討論的都是在 BBS 上進行的!這軟體前陣子免費,所以也下載到囉!他還有 iPad 版本,理論上應該用 iPad 版比較好。之前有次有 3G 網路,就剛好試用了一下,偶爾當作查資料還 ok 啦。但感覺上還不太適合像手機這類 Mobile 端的使用





Miu Term

另一款逛 BBS 的程式,在老婆生日的那天限時免費,支援 3.x





Miu Ptt

作者很有心,直接弄出一個專門瀏覽 PTT 的軟體,將資料轉成 HTML 重新排版一下,還算可以用





TaiwanYo 台灣遊

這款也算是在 3G 網路下才會感到方便的東西!上次去台北不知要去哪邊吃吃喝喝,就隨手用他一下,可以得知附近的餐飲店囉!





ReaddleDocs

最近免費!所以下載啦~可以在 iPhone 上看文件,不錯!對於我這個沒 3G 網路的人來說,可以把資料先丟到 Google Docs 中,再有 wifi 環境中,接著下載到手持裝置,一整個方便!





Awesome Note Lite (+Todo)

這是免費的版本,有限定只能用七個文件。在還沒下載 ReaddleDocs 時,出遊或開會時,我也一樣把地點資訊擺在 Google Docs 再 sync 用。這套我覺得 UI 還滿不錯的





Stanza

這款是電子書閱讀器。雖然有 iBooks 這套,但我十分強烈要安裝這套,理由是...透過它可以去抓大陸的書 XD
大陸的掌上書苑超多書可以下載,但需要註冊一下帳號才能下載。說真的我搞不懂版權到底怎麼了 :P
下載方式:[獲取書籍]->[分享]->[編輯]->[添加書籍來源]-> 輸入 www.cnepub.com
。這是一位超愛看書的同事告訴我的。上頭還有漫畫可以下載,但我沒有一次成功下載過





超級收音機-免費版(臺灣)

難得免費的廣播軟體,偶爾透過 wifi 再加上接著電源線會用一下。有些軟體標榜省電,但只要是長時間使用 wifi 的軟體,一律只有兩個--耗電!不,是三個字,超耗電!





Radio Taiwan

免費又有很多電台,還可以把節目錄起來喔





OXradio-Full

免費又可以聽到各國電台的廣播軟體





IdeoCal 農曆萬年曆

農民曆對台灣人來說,也算常用到的軟體





万年历黄历(免费)Chinese calendar

這也是一款農民曆軟體,特色是描述更多的資訊。這款裡頭可以切換語言為繁體。說真的,對岸開發超多款農民曆軟體,關鍵字是"萬年曆"





TW Weather - 台灣天氣

這款非常夠用!並且也算是個 open source 哩!記得大學時我也研究過類似的服務,當時是去抓 Weather.com 的資訊,純研究性質的啦。使用 Weather.com 理由是有些地方可是 30 分鐘就更新資訊的唷!當時覺得為啥台灣本身的天氣資訊不能即時呈現,有點點感到失望





hiPage 搜go!

還記得以前中華電信的那本黃色本子嗎?就是傳統的黃頁電話簿的應用啦~這個也算挺威的,只是他的狀態就像黃頁一樣,須要靠人眼去刪選囉。印象中碩班老闆曾說過,當他想要黑板時,一時之間想不到去哪買,就是連去中華電信的黃頁網站去找囉!





Converter Touch ~ Drag-and-Drop Unit Converter

>支援的單位換算還算不錯用





趣遊台北地圖

在 App Store 上有眾多付費軟體,據凡搭公車、火車、高鐵、找停車位甚至搭飛機等等的,這款都算是有 50% 的服務吧!雖然還不如那些付費軟體,但我覺得已經不錯啦!除此之外還有天氣、即時路況等等。據說這款是某非營利的團體幫政府做的,唯一感到有點小糟的就 LBS 服務,預設位置是捷運西門站,查詢一個地方竟然蹦出超過 50 個密密麻麻的點,有點搞笑 XD 狀態跟不能用差不多了





MetroNav Taipei

還不錯的捷運軟體





Taipei Transit

這款也是台北捷運,印象中是有支援離線版的,也就是使用上不須網路,但別忘了留意資料的新鮮度囉!我查了一下有類似並標上 Lite 的字樣,不曉得這款是不是以前要付費,現在改成免費的樣子;





Tacts: Smart Contact Manager, Group Text & Email and Favorites

好像是台灣人做的通訊錄管理軟體,不錯喔





Create Ringtones!

用來製作鈴聲的軟體





ShuBook 書僕

好用的電子書閱讀器!可以簡繁互轉喔,也可以透過 OPDS 、好讀網等下載書籍,該用的閱讀功能都齊了





aNobii

香港的書籍討論網,算不小的。可以透過相機去掃描書碼,再加入到個人的書櫃中





Viber - Free Phone Calls

類似 Skype 的軟體,只要雙方都安裝 Viber 的話,就可以講網路電話,使用手機號碼當作帳號,也算是有簡易的通訊錄軟體





Skype

把 iPod Touch 裝一下,接著就可拿來當網路手機用,有挺方便的,算是常在 PC 上用 Skype 的人,可以轉到手機用啦





Dropbox

&可以用來查看擺放的文件,如 PDF 等,還不錯





愛評餓點零

&可以用來找美食的,但還不少人說上頭的資料不新





轉乘通

很方便的程式,可以查詢客運、捷運、火車、高鐵等大眾交通資訊





WaWaBank 卡方便

可以查詢附近有哪些信用卡福利























Flipboard

這款也是在國外很夯的雜誌類型的軟體,必須 iPad 才能安裝使用。這也稱得上社群雜誌,透過登入自己的 Facebook 或 Twitter 帳號,他會幫你把所有好友分享的文章統整成為雜誌的面貌,基本上我覺得上頭提供的 Tech 相關的雜誌已經很夠用了,只要兩三天翻個一次,就可以掌握住目前最夯的時勢囉






Instagram

這款在國外也非常夯,用照片記錄生活以及上傳分享,無聊時可以打開這款軟體,看看有哪些驚奇的漂亮照片





Photo Mess

可以將多張照片隨意疊起來製成另一張照片,可以用在一些活動尾聲的回顧介紹或是製作一些回憶類照片,還滿簡單好用的





Iris Photo Suite

當初免費時下載的,有超多功能的圖片修改軟體,但一時之間我還覺得功能太多,多到讓我不知該怎樣上手,哪天在來好好研究一下







SeeSee

第一次看到這個介面覺得還不錯,但看 review 後,感覺這種設計好像是原先 Windows Mobile 上面的





Retro Camera Plus

很多相機效果





Camera 4 Line Art

把照片弄成素描那種樣子









typogr.am

拍照後加文字的處理





Instant Sketch

簡易照片素描特效







Camera FX!

以九宮格即時呈現照片特效結果





Private Photo - Also Support Video

照片、影片目錄管理,有 30 萬人次下載





BasicCamera

陽春的照片處理





Photosynth

微軟做的,提供把多張照片組合成一張





qbro

處理照片特效,有 60 萬人次下載






Snapbucket

特效處理





Photo&Memo

照片筆記





myCamera Overlays+

照片特效





Super 8™

影片拍攝軟體





Relievos

將 2D 照片弄成 3D 效果





Camera Fun Pro

照片特效





Piccies

可顯示照片資訊的瀏覽軟體





美图秀秀

有不錯的照片處理,如多張合一等