碎碎念
Friday, April 27, 2012
Tuesday, April 17, 2012
成本降就該降售價嗎?
台灣媒體從業人員笨死了,成天在媒體上獵女巫,檢討別人的成本結構;殊不知,(除了原物料必需品外),售價是由供需曲線決定的,而不是由成本所決定的,成本,只決定是否有業者會提供某項產品。
媒體成天講什麼,稅金降所以該降價,成本漲所以物價漲,全民的腦力都浪費在幫別人算成本,而不是幫自己開創新產品。
講多了,全民都變笨了。而且還偏好用名牌,漲價時不會去選用替代品,只會忙著叫龍頭降價,問題是,你越愛用,龍頭業者越有調漲的本錢。
最近年紀到了,開始準備生小孩,就開始頭痛未來教育問題,不知道怎麼幫他規劃如何活在這塊土地上,而不被一些錯誤的觀念所誤導呢?
還是說,我又該離鄉背景工作個幾年,幫小孩弄個外國籍,未來送到台北歐洲學校或日僑學校去(美僑學校已經招生爆炸,只收父母都是美國人的小孩了)
媒體成天講什麼,稅金降所以該降價,成本漲所以物價漲,全民的腦力都浪費在幫別人算成本,而不是幫自己開創新產品。
講多了,全民都變笨了。而且還偏好用名牌,漲價時不會去選用替代品,只會忙著叫龍頭降價,問題是,你越愛用,龍頭業者越有調漲的本錢。
最近年紀到了,開始準備生小孩,就開始頭痛未來教育問題,不知道怎麼幫他規劃如何活在這塊土地上,而不被一些錯誤的觀念所誤導呢?
還是說,我又該離鄉背景工作個幾年,幫小孩弄個外國籍,未來送到台北歐洲學校或日僑學校去(美僑學校已經招生爆炸,只收父母都是美國人的小孩了)
Wednesday, April 11, 2012
Lesson Learned form Developing Locadz SDK
這是我 4/25 號要在 GTUG 發表的東西,先把講稿寫在 blog 上。
市面上大部份講 Android 的書,多數都是在講 API 要怎麼樣使用,但是,許多的書都沒有提到一個重要的問題,就是什麼是 UI Thread (有時被稱做Main Thread) ,為什麼要有 UI Thread 以及為什麼不可以在 UI Thread 中執行耗時間的運算。
在本文中,我講談一談我們在開發Locadz SDK所使用到的一些技巧。
UI Thread是Android(or other GUI library)用來處理 Event 的 thread ,這些 Event 可能是使用者處發的,如Touch Event,或者是其它程式或者是系統底層的事件,如 Intent 或 Location Updates
在 UI Thread 處理完一個事件後, UI Thread 會重畫整個 Application ,而前面這句話解釋了處理 ANR 的兩個原則
有許多的文章談到如何用 AsyncTask 或 ResultReceiver 來把需要大量運算的程式,放在另一個 Thread 來做處理。
然而,這些範例都有著一個常備忽略的問題,那就是,這些 AsyncTask or ResultReceiver 常會把前景的Activity包進來,如果說你的程式只有一個 Activity 的話,這或許不是什麼問題,但是若是你的 App 有多個畫面的話,這些在背景執行的程式可能就會讓你的程式有 memory leak 的問題;如某家廣告商的 SDK 就有此問題。
一個可能的發生狀況是,Activity A透過 AsyncTask 去遠端下載一個圖片來顯示在Activity A之上,但因為某些因素(如忘了設 connection timeout),這個下載的程緒卡住了,既使 User 以從Activity A切換到Activity B之上,這個Activity A還是不會被 GC 回收掉。
要避開因為有個 Strong Reference 造成 Activity 無法被回收的問題,我們在把有可能被回收的物件傳到另一個 Thread 中被延後執行時,必需用 WeakReference包住這個物件;如此一來,當Garbage Collector碰到一個已經不在前景的 Activity 時,Garbage Collector會把這物件處理掉,如此一來,就不會有 memory leak 的問題。
AsyncTask是 Android 最常被用來處理複雜運算時用的工具,透過AsyncTask,我們可以在背景處裡一些複雜的運算,再把結果放回前景之上。
但據我的經驗,使用 AsyncTask 同時間來擔任 MVC 中的 View & Controller 的工作,最後往往是把程式碼弄成一團麵線。因此,在開發 Locadz SDK 時,我們把一些跟 UI 無關的運算,都切出來變成 IntentService或者是非Inner class的AsyncTask,把所有的運算邏輯從 Activity 中切出來,增加重用的可能。
然後運算的結果,再透過 getHandler().post(...) 更新到 UI 之上.
底下的圖表,是Jeff Dean發表的,在談的是讀取資料的的效率,我們把這幾個數字先記起來,再加一個代表UI設計時人體覺得是即時反應的反應時間上限 100 ms。然後我們再來談 Android UI 的設計。
大家可以看到 Main Memory Reference(0.001ms) 與 Disk Seek(10ms) Disk Read(30ms) 的重大差距,然而,後者的數字在 Mobile Phone 上就不是這樣了。在 Mobile Phone 上,傳統的硬碟扮演的角色,被NAND Flash Memory, External SD Card所取代了。在存取效率上 NAND Flash Memory 雖然不比 RAM 快,但是,也仍是 seek time ~1ms 的狠角色。
這 1ms 的負擔,雖比 0.001ms 的負擔高上百倍,以上,但是在 100ms 這UI 反應需求上,卻又變得很渺小了。
因此,在這邊,我會建議大家,若是有 cache 的需求時,直接往 Internal Storage 塞吧,不要放在Main memory上,或用個SoftReferenceMap包著。
在上面第一個範例中有個錯誤,那就是DownloadImagesTask.onPostExecute()會在呼叫DownloadImagesTask.execute(...)的那個 Thread 上執行,如果說,很不幸的,這個 DownloadImagesTask 並不是從 UI Thread 上來呼叫的話,那麼,imageView.setImageBitmap(result)便有可能不會即時更新到UI之上。
如果你的開發環境會有這種問題,在包在層層呼叫後,無法確保 Method 是否是在 UI Thread 上執行;那麼我會建議你把會更新 UI 的 Method ,標成 protected ,然後開放一個 public async method 出來,範例如下:
Introduction
市面上大部份講 Android 的書,多數都是在講 API 要怎麼樣使用,但是,許多的書都沒有提到一個重要的問題,就是什麼是 UI Thread (有時被稱做Main Thread) ,為什麼要有 UI Thread 以及為什麼不可以在 UI Thread 中執行耗時間的運算。
在本文中,我講談一談我們在開發Locadz SDK所使用到的一些技巧。
UI Thread
UI Thread是Android(or other GUI library)用來處理 Event 的 thread ,這些 Event 可能是使用者處發的,如Touch Event,或者是其它程式或者是系統底層的事件,如 Intent 或 Location Updates
在 UI Thread 處理完一個事件後, UI Thread 會重畫整個 Application ,而前面這句話解釋了處理 ANR 的兩個原則
- UI事件的處理要越快越好,在處理完前,UI不會有反應
- UI的更新,一定要發生在UI Thread,要不然,要等到下次UI Event被處發時才會一併被處理
Lesson I: Use WeakReference to Avoid Strong Reference and Memory Leak
有許多的文章談到如何用 AsyncTask 或 ResultReceiver 來把需要大量運算的程式,放在另一個 Thread 來做處理。
然而,這些範例都有著一個常備忽略的問題,那就是,這些 AsyncTask or ResultReceiver 常會把前景的Activity包進來,如果說你的程式只有一個 Activity 的話,這或許不是什麼問題,但是若是你的 App 有多個畫面的話,這些在背景執行的程式可能就會讓你的程式有 memory leak 的問題;如某家廣告商的 SDK 就有此問題。
一個可能的發生狀況是,Activity A透過 AsyncTask 去遠端下載一個圖片來顯示在Activity A之上,但因為某些因素(如忘了設 connection timeout),這個下載的程緒卡住了,既使 User 以從Activity A切換到Activity B之上,這個Activity A還是不會被 GC 回收掉。
要避開因為有個 Strong Reference 造成 Activity 無法被回收的問題,我們在把有可能被回收的物件傳到另一個 Thread 中被延後執行時,必需用 WeakReference包住這個物件;如此一來,當Garbage Collector碰到一個已經不在前景的 Activity 時,Garbage Collector會把這物件處理掉,如此一來,就不會有 memory leak 的問題。
/** * AsyncTask to Load Image */ public class DownloadImagesTask extends AsyncTask<Uri, Void, Bitmap> { WeakReference<imageview> imageViewWeakReference = null; public DownloadImagesTask(ImageView imageView) { imageViewWeakReference = new WeakReference<Imageview>(imageView); } @Override protected Bitmap doInBackground(Uri... uri) { return downloadImage(uri); } @Override protected void onPostExecute(Bitmap result) { ImageView imageView = imageViewWeakReference.get(); if (imageView != null) { imageView.setImageBitmap(result); } } private Bitmap downloadImage(Uri url) { ... }
Lesson II: Use IntentService to Run Business Logic
AsyncTask是 Android 最常被用來處理複雜運算時用的工具,透過AsyncTask,我們可以在背景處裡一些複雜的運算,再把結果放回前景之上。
但據我的經驗,使用 AsyncTask 同時間來擔任 MVC 中的 View & Controller 的工作,最後往往是把程式碼弄成一團麵線。因此,在開發 Locadz SDK 時,我們把一些跟 UI 無關的運算,都切出來變成 IntentService或者是非Inner class的AsyncTask,把所有的運算邏輯從 Activity 中切出來,增加重用的可能。
然後運算的結果,再透過 getHandler().post(...) 更新到 UI 之上.
/** Service that retrieve the ad unit allocations from external source and cache locally in SharedPreference. */ public class AdUnitAllocationService extends IntentService { private static final int CACHE_EXPIRATION_PERIOD = 30 * 60 * 1000; // 30 minutes. private final static String PREFS_STRING_TIMESTAMP = "timestamp"; private final static String PREFS_STRING_CONFIG = "config"; // response code for possible result. public static final int RESULT_OK = 1; public AdUnitAllocationService() { super(AdUnitAllocationService.class.getCanonicalName()); } @Override protected void onHandleIntent(Intent intent) { AdUnitContext adUnitContext = (AdUnitContext) intent.getParcelableExtra(IntentConstants.EXTRA_ADUNIT_CONTEXT); AdUnitAllocation adUnitAllocation = getAdUnitAllocation(adUnitContext); if (adUnitAllocation != null) { Ration ration = getRandomRation(adUnitAllocation.getRations()); // send response through ResultReceiver. ResultReceiver receiver = intent.getParcelableExtra(IntentConstants.EXTRA_RECEIVER); Bundle resultData = new Bundle(); resultData.putString(IntentConstants.EXTRA_ADUNIT_ID, adUnitContext.getAdUnitId()); resultData.putSerializable(IntentConstants.EXTRA_RATION, ration); resultData.putSerializable(IntentConstants.EXTRA_EXTRA, adUnitAllocation.getExtra()); receiver.send(RESULT_OK, resultData); } } /** * Select a random ration form the provided rations. * @param rations the candidates. * @return a random ration from the candidates. */ private Ration getRandomRation(List<Ration> rations) { //... } /** * Get the allocation configuration for the adunit. * @param adUnitContext the context of the adunit. * @return the allocation configuration for the adunit. */ AdUnitAllocation getAdUnitAllocation(AdUnitContext adUnitContext) { //... } }
Lesson III: Use Disk Cache instead of (Main) Memory Cache
底下的圖表,是Jeff Dean發表的,在談的是讀取資料的的效率,我們把這幾個數字先記起來,再加一個代表UI設計時人體覺得是即時反應的反應時間上限 100 ms。然後我們再來談 Android UI 的設計。
大家可以看到 Main Memory Reference(0.001ms) 與 Disk Seek(10ms) Disk Read(30ms) 的重大差距,然而,後者的數字在 Mobile Phone 上就不是這樣了。在 Mobile Phone 上,傳統的硬碟扮演的角色,被NAND Flash Memory, External SD Card所取代了。在存取效率上 NAND Flash Memory 雖然不比 RAM 快,但是,也仍是 seek time ~1ms 的狠角色。
這 1ms 的負擔,雖比 0.001ms 的負擔高上百倍,以上,但是在 100ms 這UI 反應需求上,卻又變得很渺小了。
因此,在這邊,我會建議大家,若是有 cache 的需求時,直接往 Internal Storage 塞吧,不要放在Main memory上,或用個SoftReferenceMap包著。
Lesson IV: Make All Public Method Async to Avoid UI Update Issue
在上面第一個範例中有個錯誤,那就是DownloadImagesTask.onPostExecute()會在呼叫DownloadImagesTask.execute(...)的那個 Thread 上執行,如果說,很不幸的,這個 DownloadImagesTask 並不是從 UI Thread 上來呼叫的話,那麼,imageView.setImageBitmap(result)便有可能不會即時更新到UI之上。
如果你的開發環境會有這種問題,在包在層層呼叫後,無法確保 Method 是否是在 UI Thread 上執行;那麼我會建議你把會更新 UI 的 Method ,標成 protected ,然後開放一個 public async method 出來,範例如下:
/** * Remove old ad views and push the new one. * * @param subView the adview to push. */ protected void pushSubView(ViewGroup subView) { //.... } /** * submit a push view request to Android's handler. This will remove * old ad view and push a new one to this layout asynchronously. * * @param subView the adview to push. */ public void submitPushSubViewRequest(ViewGroup subView) { Log.d(LOG_TAG, String.format("Scheduled pushSubView(%s)", subView)); getHandler().post(new ViewAdRunnable(this, subView)); } /** * Runnable runs on the Main Thread that pushes an AdView to the layout. */ private static final class ViewAdRunnable implements Runnable { private final WeakReference<Adunitlayout> locadzLayoutWeakReference; private ViewGroup subView; public ViewAdRunnable(AdUnitLayout layout, ViewGroup subView) { locadzLayoutWeakReference = new WeakReference<Adunitlayout>(layout); this.subView = subView; } @Override public void run() { AdUnitLayout locadzLayout = locadzLayoutWeakReference.get(); if (locadzLayout != null) { locadzLayout.pushSubView(subView); } } }
Tuesday, March 27, 2012
[Service上線前你該知道的事]How to Debian-ize Your Web Application.
在布建一個Web Application時,用.war來佈屬服務,算是最沒有效率的一種方式,因為,這樣無法把web container所需要的設定檔一起佈建出去,同時,也無法板本控製你的應用程式;本文接下來會敘述,如何用maven & jetty & debian來佈屬你的Web Application。
首先,先講講我的需求,我的需求如下
這些需求,我是靠 unix-maven-plugin ,幫我把我的 web application 包成一個 .deb 來做到的,包成 .deb 的好處是,在 debian 上可以簡單的安裝,若是公司有架 debian package repository, 那麼,打個 apt-get install 就可以安裝或昇級到最新版,同時間,可以透過 debian 安裝的機制,來把些 script 掛入 rc.*
由於 unix-maven-plugin 已被原作者遺棄,所以需要從 github下載並安裝後人修改過的版本。
然後依附錄的方式,增修你的設定檔,需要增修的檔案有
將這些檔案設定好後,在 debian 的機器上打 mvn package deploy ,就可以自動生成 .deb 的檔案,並且上傳到你的 maven repository 上去,如果貴公司有架 debian package repository 的話,還可以加一段程式碼把 .deb 傳到 debian package repository 去。
接著,打 dpkg -i xxxx.deb 就可以把你的 web application 當成一個 debian service 安裝到你的 debain machine 上去。
查詢目前安裝的板本則是打 dpkg -l xxxx 即可
把底下的這段,加入你的 pom.xml
然後把下面這塊存成 src/main/unix/resources/default/${service.user}
然後把下面這塊存成 src/main/unix/resources/ect/jetty.conf
然後把下面這塊存成 src/main/unix/resources/etc/start.conf
然後把下面這塊存成 src/main/unix/scripts/${service.user}
首先,先講講我的需求,我的需求如下
- Web Application的版本必需能夠簡單被管理,要能簡單的就知道目前安裝的版本,並且輕鬆的昇級到新版
- Web Application被佈建時,要連Web Container及設定擋包在一起
- Debian Box開關機時,要能自動開關Web Container
這些需求,我是靠 unix-maven-plugin ,幫我把我的 web application 包成一個 .deb 來做到的,包成 .deb 的好處是,在 debian 上可以簡單的安裝,若是公司有架 debian package repository, 那麼,打個 apt-get install
由於 unix-maven-plugin 已被原作者遺棄,所以需要從 github下載並安裝後人修改過的版本。
然後依附錄的方式,增修你的設定檔,需要增修的檔案有
- pom.xml: 加入 unix-maven-plugin 及服務的名稱及PORT
- src/main/unix/resources/default/${service.user} : 給 jetty 用的 default 設定值,在這邊你可以修改 JAVA_OPTIONS 及 NO_START
- src/main/unix/resources/etc/jetty.conf
- src/main/unix/resources/etc/start.conf
- src/main/unix/resources/scripts/${service.user} : 給 debian 用的 init script
- src/main/unix/resources/scripts/post-install post-remove pre-install pre-remove : debian 在安裝及昇級 .deb 時會跑的 script
將這些檔案設定好後,在 debian 的機器上打 mvn package deploy ,就可以自動生成 .deb 的檔案,並且上傳到你的 maven repository 上去,如果貴公司有架 debian package repository 的話,還可以加一段程式碼把 .deb 傳到 debian package repository 去。
接著,打 dpkg -i xxxx.deb 就可以把你的 web application 當成一個 debian service 安裝到你的 debain machine 上去。
查詢目前安裝的板本則是打 dpkg -l xxxx 即可
||/ Name Version Description +++-==============-==============-============================================ ii backend 1.2.0-20120327.143115 backend
附錄
把底下的這段,加入你的 pom.xml
4.0.0 com.locadz backend war backend 1.2.0-SNAPSHOT backend 1.0-alpha-6.1 7.4.5.v20110725 Locadz Backend /var/lib/bluetang/locadz-backend locadz-backend 9080 debian-package unix linux maven-resources-plugin 2.5 copy-resources process-resources copy-resources ${basedir}/target src/main/unix unix **/* true org.mortbay.jetty.toolchain unix-maven-plugin ${version.maven.unix.plugin} true package package-deb-attached true yho@bluetangstudio.com yho@bluetangstudio.com ${service.user} adm ${service.user} adm bluetang extra org.eclipse.jetty:jetty-distribution:zip ${service.home} /jetty-distribution-${version.jetty}(.*) $1 */contexts/** */contexts-available/** */webapps/** */javadoc/** */LICENSES/** */logs/** **/win32/** ${project.build.directory}/${project.build.finalName}.${project.packaging} ${service.home}/webapps/ROOT.war ${basedir}/target/unix/resources/default/${service.user} /etc/default ${basedir}/target/unix/resources/etc /etc/bluetang/${service.user} ${basedir}/target/unix/scripts/${service.user} /etc/init.d 0754 ${service.user} admin org.eclipse.jetty jetty-distribution ${version.jetty} zip * *
然後把下面這塊存成 src/main/unix/resources/default/${service.user}
# Defaults for jetty see /etc/${service.user}/jetty for more # change to 0 to allow Jetty to start NO_START=0 # change to 'no' or uncomment to use the default setting in /etc/default/rcS VERBOSE=yes # Run Jetty as this user ID (default: jetty) # Set this to an empty string to prevent Jetty from starting automatically JETTY_USER=${service.user} # Listen to connections from this network host # Use 0.0.0.0 as host to accept all connections. # Uncomment to restrict access to localhost #JETTY_HOST=$(uname -n) JETTY_HOST=0.0.0.0 # The network port used by Jetty JETTY_PORT=${service.port} # Timeout in seconds for the shutdown of all webapps #JETTY_SHUTDOWN=30 # Additional arguments to pass to Jetty #JETTY_ARGS= # Extra options to pass to the JVM JAVA_OPTIONS="-Xmx256m -Djava.awt.headless=true -Duser.timezone=UTC" # Home of Java installation. #JAVA_HOME= # The first existing directory is used for JAVA_HOME (if JAVA_HOME is not # defined in /etc/default/jetty). Should contain a list of space separated directories. #JDK_DIRS="/usr/lib/jvm/default-java /usr/lib/jvm/java-6-sun" # Java compiler to use for translating JavaServer Pages (JSPs). You can use all # compilers that are accepted by Ant's build.compiler property. #JSP_COMPILER=jikes # Jetty uses a directory to store temporary files like unpacked webapps JETTY_TMP=/var/cache/bluetang/jetty # Jetty uses a config file to setup its boot classpath JETTY_START_CONFIG=/etc/bluetang/${service.user}/start.config # Default for number of days to keep old log files in /var/log/jetty/ #LOGFILE_DAYS=14
然後把下面這塊存成 src/main/unix/resources/ect/jetty.conf
# list of jetty configuration and property files /var/lib/bluetang/${service.user}/etc/jetty-logging.xml
然後把下面這塊存成 src/main/unix/resources/etc/start.conf
# This file controls what file are to be put on classpath or command line. # # Format is as follows: # Each line contains entry for one JAR file. # Format of line: # # SUBJECT [ [!] CONDITION [AND|OR] ]* # # where SUBJECT: # ends with ".class" is the Main class to run. # ends with ".xml" is a configuration file for the command line # ends with "/" is a directory from which to add all jar and zip files. # ends with "/*" is a directory from which to add all unconsidered jar and zip files. # ends with "/**" is a directory from which to recursively add all unconsidered jar and zip files. # Containing = are used to assign system properties. # all other subjects are treated as files to be added to the classpath. # # Subjects may include system properties with $(propertyname) syntax. # # Files starting with "/" are considered absolute, all others are relative to # the home directory. # # CONDITION is one of: # always # never # available classname # true if class on classpath # property name # true of set # java OPERATOR version # java version compared to literal # nargs OPERATOR number # number of command line args compared to literal # OPERATOR := one of "<",">","<=",">=","==","!=" # # CONDITIONS can be combined with AND OR or !, with AND being the assume # operator for a list of CONDITIONS. # Classpath operations are evaluated on the fly, so once a class or jar is # added to the classpath, subsequent available conditions will see that class. # $(jetty.class.path) always $(jetty.lib)/** exists $(jetty.lib) jetty.home=/var/lib/bluetang/${service.user} always # The main class to run org.mortbay.xml.XmlConfiguration.class # The default configuration files $(jetty.home)/etc/jetty.xml nargs == 0 /usr/share/java/servlet-api-2.5.jar /usr/share/java/slf4j-api.jar # Optional stuff for libjetty-extra-java /usr/share/java/gnumail.jar /usr/share/java/activation.jar /usr/share/java/ant.jar # Set the jetty classpath /usr/share/jetty/lib/** # Add a resources directory if it is there $(jetty.home)/resources/
然後把下面這塊存成 src/main/unix/scripts/${service.user}
#!/bin/bash ### BEGIN INIT INFO # Provides: ${service.user} # Required-Start: # Required-Stop: # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Start daemon at boot time # Description: Enable service provided by daemon. ### END INIT INFO # # Startup script for jetty under *nix systems (it works under NT/cygwin too). # # Configuration files # # /etc/default/${service.user} # If it exists, this is read at the start of script. It may perform any # sequence of shell commands, like setting relevant environment variables. # # /etc/bluetang/${service.user}/jetty.conf # If found, and no configurations were given on the command line, # the file will be used as this script's configuration. # Each line in the file may contain: # - A comment denoted by the pound (#) sign as first non-blank character. # - The path to a regular file, which will be passed to jetty as a # config.xml file. # - The path to a directory. Each *.xml file in the directory will be # passed to jetty as a config.xml file. # # The files will be checked for existence before being passed to jetty. # # $JETTY_HOME/etc/jetty.xml # If found, used as this script's configuration file, but only if # /etc/bluetang/${service.user}/jetty.conf was not present. See above. # # Configuration variables # # JAVA # Command to invoke Java. If not set, java (from the PATH) will be used. # # JAVA_OPTIONS # Extra options to pass to the JVM # # JETTY_HOME # Where Jetty is installed. If not set, the script will try go # guess it by first looking at the invocation path for the script, # and then by looking in standard locations as $HOME/opt/jetty # and /opt/jetty. The java system property "jetty.home" will be # set to this value for use by configure.xml files, f.e.: # #然後把下面這塊存成 src/main/unix/scripts/post-install,並修改 USER=locadz-backend 這一行成跟你 ${service.user} 一樣的值# # JETTY_PORT # Override the default port for Jetty servers. If not set then the # default value in the xml configuration file will be used. The java # system property "jetty.port" will be set to this value for use in # configure.xml files. For example, the following idiom is widely # used in the demo config files to respect this property in Listener # configuration elements: # # /webapps/jetty.war # # Note: that the config file could ignore this property simply by saying: # # 8080 # # JETTY_RUN # Where the jetty.pid file should be stored. It defaults to the # first available of /var/run, /usr/var/run, and /tmp if not set. # # JETTY_PID # The Jetty PID file, defaults to $JETTY_RUN/jetty.pid # # JETTY_ARGS # The default arguments to pass to jetty. # # JETTY_USER # if set, then used as a username to run the server as # JETTY_HOME=${service.home} JETTY_USER=${service.user} JETTY_PID=$JETTY_RUN/${service.user}.pid usage() { echo "Usage: ${0##*/} [-d] {start|stop|run|restart|check|supervise} [ CONFIGS ... ] " exit 1 } [ $# -gt 0 ] || usage ################################################## # Some utility functions ################################################## findDirectory() { local L OP=$1 shift for L in "$@"; do [ "$OP" "$L" ] || continue printf %s "$L" break done } running() { local PID=$(cat "$1" 2>/dev/null) || return 1 kill -0 "$PID" 2>/dev/null } readConfig() { (( DEBUG )) && echo "Reading $1.." source "$1" } ################################################## # Get the action & configs ################################################## CONFIGS=() NO_START=0 DEBUG=0 while [[ $1 = -* ]]; do case $1 in -d) DEBUG=1 ;; esac shift done ACTION=$1 shift ################################################## # See if there's a default configuration file ################################################## if [ -f /etc/default/${service.user} ] ; then . /etc/default/${service.user} fi ################################################## # Set tmp if not already set. ################################################## TMPDIR=${TMPDIR:-/tmp} ################################################## # Jetty's hallmark ################################################## JETTY_INSTALL_TRACE_FILE="etc/jetty.xml" ################################################## # Try to determine JETTY_HOME if not set ################################################## if [ -z "$JETTY_HOME" ] then JETTY_SH=$0 case "$JETTY_SH" in /*) ;; ./*) ;; *) JETTY_SH=./$JETTY_SH ;; esac JETTY_HOME=${JETTY_SH%/*/*} if [ ! -f "${JETTY_SH%/*/*}/$JETTY_INSTALL_TRACE_FILE" ] then JETTY_HOME= fi fi ################################################## # if no JETTY_HOME, search likely locations. ################################################## if [ -z "$JETTY_HOME" ] ; then STANDARD_LOCATIONS=( "/usr/share" "/usr/share/java" "${HOME}" "${HOME}/src" "${HOME}/opt" "/opt" "/java" "/usr/local" "/usr/local/share" "/usr/local/share/java" "/home" ) JETTY_DIR_NAMES=( "jetty-7" "jetty7" "jetty-7.*" "jetty" "Jetty-7" "Jetty7" "Jetty-7.*" "Jetty" ) for L in "${STANDARD_LOCATIONS[@]}" do for N in "${JETTY_DIR_NAMES[@]}" do POSSIBLE_JETTY_HOME=("$L/"$N) if [ ! -d "$POSSIBLE_JETTY_HOME" ] then # Not a directory. skip. unset POSSIBLE_JETTY_HOME elif [ ! -f "$POSSIBLE_JETTY_HOME/$JETTY_INSTALL_TRACE_FILE" ] then # Trace file not found. skip. unset POSSIBLE_JETTY_HOME else # Good hit, Use it JETTY_HOME=$POSSIBLE_JETTY_HOME # Break out of JETTY_DIR_NAMES loop break fi done if [ -n "$POSSIBLE_JETTY_HOME" ] then # We have found our JETTY_HOME # Break out of STANDARD_LOCATIONS loop break fi done fi ################################################## # No JETTY_HOME yet? We're out of luck! ################################################## if [ -z "$JETTY_HOME" ]; then echo "** ERROR: JETTY_HOME not set, you need to set it or install in a standard location" exit 1 fi cd "$JETTY_HOME" JETTY_HOME=$PWD ##################################################### # Check that jetty is where we think it is ##################################################### if [ ! -r "$JETTY_HOME/$JETTY_INSTALL_TRACE_FILE" ] then echo "** ERROR: Oops! Jetty doesn't appear to be installed in $JETTY_HOME" echo "** ERROR: $JETTY_HOME/$JETTY_INSTALL_TRACE_FILE is not readable!" exit 1 fi ################################################## # Try to find this script's configuration file, # but only if no configurations were given on the # command line. ################################################## if [ -z "$JETTY_CONF" ] then if [ -f /etc/bluetang/${service.user}/jetty.conf ] then JETTY_CONF=/etc/bluetang/${service.user}/jetty.conf elif [ -f "${JETTY_HOME}/etc/jetty.conf" ] then JETTY_CONF="${JETTY_HOME}/etc/jetty.conf" fi fi ################################################## # Get the list of config.xml files from jetty.conf ################################################## if [ -z "$CONFIGS" ] && [ -f "$JETTY_CONF" ] && [ -r "$JETTY_CONF" ] then while read -r CONF do if expr "$CONF" : '#' >/dev/null ; then continue fi if [ -d "$CONF" ] then # assume it's a directory with configure.xml files # for example: /etc/jetty.d/ # sort the files before adding them to the list of CONFIGS for file in "$CONF/"*.xml do if [ -r "$FILE" ] && [ -f "$FILE" ] then CONFIGS+=("$FILE") else echo "** WARNING: Cannot read '$FILE' specified in '$JETTY_CONF'" fi done else # assume it's a command line parameter (let start.jar deal with its validity) CONFIGS+=("$CONF") fi done < "$JETTY_CONF" fi ##################################################### # Find a location for the pid file ##################################################### if [ -z "$JETTY_RUN" ] then JETTY_RUN=$(findDirectory -w /var/run /usr/var/run /tmp) fi ##################################################### # Find a PID for the pid file ##################################################### if [ -z "$JETTY_PID" ] then JETTY_PID="$JETTY_RUN/jetty.pid" fi ################################################## # Setup JAVA if unset ################################################## if [ -z "$JAVA" ] then JAVA=$(which java) fi if [ -z "$JAVA" ] then echo "Cannot find a Java JDK. Please set either set JAVA or put java (>=1.5) in your PATH." 2>&2 exit 1 fi ##################################################### # See if JETTY_PORT is defined ##################################################### if [ "$JETTY_PORT" ] then JAVA_OPTIONS+=("-Djetty.port=$JETTY_PORT") fi ##################################################### # See if JETTY_LOGS is defined ##################################################### if [ "$JETTY_LOGS" ] then JAVA_OPTIONS+=("-Djetty.logs=$JETTY_LOGS") fi ##################################################### # Are we running on Windows? Could be, with Cygwin/NT. ##################################################### case "`uname`" in CYGWIN*) PATH_SEPARATOR=";";; *) PATH_SEPARATOR=":";; esac ##################################################### # Add jetty properties to Java VM options. ##################################################### JAVA_OPTIONS+=("-Djetty.home=$JETTY_HOME" "-Djava.io.tmpdir=$TMPDIR") [ -f "$JETTY_HOME/etc/start.config" ] && JAVA_OPTIONS=("-DSTART=$JETTY_HOME/etc/start.config" "${JAVA_OPTIONS[@]}") ##################################################### # This is how the Jetty server will be started ##################################################### JETTY_START=$JETTY_HOME/start.jar [ ! -f "$JETTY_START" ] && JETTY_START=$JETTY_HOME/lib/start.jar START_INI=$(dirname $JETTY_START)/start.ini [ -r "$START_INI" ] || START_INI="" RUN_ARGS=(${JAVA_OPTIONS[@]} -jar "$JETTY_START" $JETTY_ARGS "${CONFIGS[@]}") RUN_CMD=("$JAVA" ${RUN_ARGS[@]}) ##################################################### # Comment these out after you're happy with what # the script is doing. ##################################################### if (( DEBUG )) then echo "JETTY_HOME = $JETTY_HOME" echo "JETTY_CONF = $JETTY_CONF" echo "JETTY_RUN = $JETTY_RUN" echo "JETTY_PID = $JETTY_PID" echo "JETTY_ARGS = $JETTY_ARGS" echo "CONFIGS = ${CONFIGS[*]}" echo "JAVA_OPTIONS = ${JAVA_OPTIONS[*]}" echo "JAVA = $JAVA" echo "RUN_CMD = ${RUN_CMD}" fi ################################################## # Do the action ################################################## case "$ACTION" in start) echo -n "Starting ${service.name}: " if (( NO_START )); then echo "Not starting ${service.user} - NO_START=1"; exit fi if type start-stop-daemon > /dev/null 2>&1 then unset CH_USER if [ -n "$JETTY_USER" ] then CH_USER="-c$JETTY_USER" fi if start-stop-daemon -S -p"$JETTY_PID" $CH_USER -d"$JETTY_HOME" -b -m -a "$JAVA" -- "${RUN_ARGS[@]}" --daemon then sleep 1 if running "$JETTY_PID" then echo "OK" else echo "FAILED" fi fi else if [ -f "$JETTY_PID" ] then if running $JETTY_PID then echo "Already Running!" exit 1 else # dead pid file - remove rm -f "$JETTY_PID" fi fi if [ "$JETTY_USER" ] then touch "$JETTY_PID" chown "$JETTY_USER" "$JETTY_PID" # FIXME: Broken solution: wordsplitting, pathname expansion, arbitrary command execution, etc. su - "$JETTY_USER" -c " ${RUN_CMD[*]} --daemon & disown \$! echo \$! > '$JETTY_PID'" else "${RUN_CMD[@]}" & disown $! echo $! > "$JETTY_PID" fi echo "STARTED ${service.name} `date`" fi ;; stop) echo -n "Stopping ${service.name}: " if type start-stop-daemon > /dev/null 2>&1; then start-stop-daemon -K -p"$JETTY_PID" -d"$JETTY_HOME" -a "$JAVA" -s HUP TIMEOUT=30 while running "$JETTY_PID"; do if (( TIMEOUT-- == 0 )); then start-stop-daemon -K -p"$JETTY_PID" -d"$JETTY_HOME" -a "$JAVA" -s KILL fi sleep 1 done rm -f "$JETTY_PID" echo OK else PID=$(cat "$JETTY_PID" 2>/dev/null) kill "$PID" 2>/dev/null TIMEOUT=30 while running $JETTY_PID; do if (( TIMEOUT-- == 0 )); then kill -KILL "$PID" 2>/dev/null fi sleep 1 done rm -f "$JETTY_PID" echo OK fi ;; restart) JETTY_SH=$0 if [ ! -f $JETTY_SH ]; then if [ ! -f $JETTY_HOME/bin/jetty.sh ]; then echo "$JETTY_HOME/bin/jetty.sh does not exist." exit 1 fi JETTY_SH=$JETTY_HOME/bin/jetty.sh fi "$JETTY_SH" stop "$@" "$JETTY_SH" start "$@" ;; supervise) # # Under control of daemontools supervise monitor which # handles restarts and shutdowns via the svc program. # exec "${RUN_CMD[@]}" ;; run|demo) echo "Running Jetty: " if [ -f "$JETTY_PID" ] then if running "$JETTY_PID" then echo "Already Running!" exit 1 else # dead pid file - remove rm -f "$JETTY_PID" fi fi exec "${RUN_CMD[@]}" ;; check) echo "Checking arguments to Jetty: " echo "JETTY_HOME = $JETTY_HOME" echo "JETTY_CONF = $JETTY_CONF" echo "JETTY_RUN = $JETTY_RUN" echo "JETTY_PID = $JETTY_PID" echo "JETTY_PORT = $JETTY_PORT" echo "JETTY_LOGS = $JETTY_LOGS" echo "START_INI = $START_INI" echo "CONFIGS = ${CONFIGS[*]}" echo "JAVA_OPTIONS = ${JAVA_OPTIONS[*]}" echo "JAVA = $JAVA" echo "CLASSPATH = $CLASSPATH" echo "RUN_CMD = ${RUN_CMD[*]}" echo if [ -f "$JETTY_RUN/jetty.pid" ] then echo "Jetty running pid=$(< "$JETTY_RUN/jetty.pid")" exit 0 fi exit 1 ;; *) usage ;; esac exit 0
#!/bin/sh set -e USER=locadz-backend case "$1" in configure) if ! id $USER > /dev/null 2>&1 ; then adduser --system --home /var/lib/bluetang/$USER --no-create-home \ --group --disabled-password --shell /bin/false \ $USER fi if [ ! -e /var/log/bluetang ] then mkdir -p /var/log/bluetang fi if [ ! -e /var/log/bluetang/$USER/ ] then ln -s /var/lib/bluetang/$USER/logs /var/log/bluetang/$USER fi if [ ! -e /var/cache/bluetang/$USER ] then mkdir -p /var/cache/bluetang/$USER fi chown -R $USER:adm /var/cache/bluetang/$USER /var/log/bluetang/$USER /var/lib/bluetang/$USER chmod 0744 /etc/init.d/$USER update-rc.d $USER defaults ;; abort-upgrade|abort-remove|abort-deconfigure) ;; *) echo "$0 called with unknown argument \`$1'" >&2 exit 1 ;; esac然後把下面這塊存成 src/main/unix/scripts/post-remove,並修改 USER=locadz-backend 這一行成跟你 ${service.user} 一樣的值
#!/bin/sh set -e USER=locadz-backend #DEBHELPER# # Remove cached files rm -rf /var/cache/blutang/$USER/* case "$1" in remove) # Remove ROOT webapp if not modified RWLOC="/var/lib/bluetang/$USER/webapps/root" RWFILES="$RWLOC/index.html $RWLOC/jetty_banner.gif" if [ "`(cat $RWFILES | md5sum -) 2>/dev/null | cut -d ' ' -f 1`" \ = "12471c4b3020defb7ebd30ef84c0f9dd" ] ; then rm $RWFILES rmdir --ignore-fail-on-non-empty \ /var/lib/bluetang/$USER/webapps/root \ /var/lib/bluetang/$USER/webapps \ /var/lib/bluetang/$USER || true fi if [ -d "/var/cache/bluetang/$USER" ] ; then rm -rf /var/cache/bluetang/$USER fi ;; purge) # Remove user/group and log files (don't remove everything under # /var/lib/jetty because there might be user-installed webapps) deluser jetty || true rm -rf /var/log/bluetang/$USER if [ -d "/var/lib/bluetang/$USER" ] ; then rmdir --ignore-fail-on-non-empty /var/lib/bluetang/$USER || true fi rmdir --ignore-fail-on-non-empty /etc/bluetang/$USER/contexts /etc/bluetang/$USER || true ;; remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) # Nothing to do here ;; *) echo "$0 called with unknown argument \`$1'" >&2 exit 1 ;; esac然後把下面這塊存成 src/main/unix/scripts/pre-install,並修改 USER=locadz-backend 這一行成跟你 ${service.user} 一樣的值
#!/bin/sh # # This is the preinst script for the Blue Tang Servers # # Written by Yung-Lin Ho yho@bluetangstudio.com set -e USER=locadz-backend SCRIPT=/etc/init.d/$USER case "$1" in upgrade|remove|purge) $SCRIPT stop ;; install|failed-upgrade|abort-install|abort-upgrade|disappear) ;; *) echo "preinst called with unknown argument \`$1'" >&2 ;; esac exit 0下面這塊存成 src/main/unix/scripts/pre-remove,並修改 USER=locadz-backend 這一行成跟你 ${service.user} 一樣的值
#!/bin/sh # # This is the prerm script for the Blue Tang Servers # # Written by Yung-Lin Ho yho@bluetangstudio.com set -e USER=locadz-backend SCRIPT=/etc/init.d/$USER case "$1" in upgrade) ;; remove|purge) $SCRIPT stop ;; failed-upgrade|abort-install|abort-upgrade|disappear) ;; *) echo "prerm called with unknown argument \`$1'" >&2 ;; esac exit 0
Bravo to Spotify Apps.
spotify's app is pretty good business model. 之前我抱怨 #spotify 的 editorial material 比不上 #rhapsody. 用 Rhapsody 時,因為 Rhapsody 內有十多位人員負責編排本週推薦的音樂,還有客製化的播放清單及電台,所以訂閱 rhapsody 就像有 DJ 幫你找歌來聽。
但是用 Spotify ,就只能夠聽,你已知的歌手, spotify 沒有一個好用的推薦功能可用。
最近 spotify 的 gui client 開始可以放上第三方App的功能,馬上就把缺的地方補上,而且還更好。
像是串入 last.fm 就有推薦清單可用,soundrop可以當線上DJ,TuneWiki可以看歌詞,歌詞還會照時間來跳到現在唱的句子;這證明了 Open API的威力,你有缺的地方,用 3rd party app 來補上,而且可以做的更好。
其實 Rhapsody 內部早在 2005 已有這種 API ,我在 real.com 時就是做這一塊的,許多第三方的程式及硬體就是呼叫我們的API,如 sonos, sandisk sansa, 我自己也寫了隻程式給我的Nokia N82用,只是可惜不能放出來。
Rhapsody 再不把 API 放出來,恐怕就連美國這塊市場都要開始敗退了...
但是用 Spotify ,就只能夠聽,你已知的歌手, spotify 沒有一個好用的推薦功能可用。
最近 spotify 的 gui client 開始可以放上第三方App的功能,馬上就把缺的地方補上,而且還更好。
像是串入 last.fm 就有推薦清單可用,soundrop可以當線上DJ,TuneWiki可以看歌詞,歌詞還會照時間來跳到現在唱的句子;這證明了 Open API的威力,你有缺的地方,用 3rd party app 來補上,而且可以做的更好。
其實 Rhapsody 內部早在 2005 已有這種 API ,我在 real.com 時就是做這一塊的,許多第三方的程式及硬體就是呼叫我們的API,如 sonos, sandisk sansa, 我自己也寫了隻程式給我的Nokia N82用,只是可惜不能放出來。
Rhapsody 再不把 API 放出來,恐怕就連美國這塊市場都要開始敗退了...
Wednesday, March 7, 2012
[Job]Job Market in US
這篇的草稿躺在我的部絡格兩個月了,看來完成遙遙無期,所以先把屍體放出來好了
要在美國求職,第一要務,就是先了解到,美國的求職市場是怎樣的環境。
大多人在台灣的求職的方式,是上 104 找適合的工作,然後再寄信過去應徵工作;然而,很不幸的是,你把求職市場放大十倍,這種方式就變得不是很有效率了。
首先,你要知道的是,在矽谷,一個工作被登上美國的求職網站,兩週內,可能吸引到2000個應徵者投遞履例表,這數字是什麼意思呢,這是說,當 HR 過濾履例表時,既使只花30秒在一份履例表上,也要花上整整16小時兩個工作天,才能篩檢完畢。我曾經看過同事在過濾履例時,就像在拿電風扇在吹一樣,看到過去工作過的公司有趣留下來,看到經驗有趣的留下來,看到名字念起來有趣的留下來;有時後,會不會被找來面識,真的是要靠一點運氣的。
所以說,要寫出一份好的履例表,讓 HR 一目了然,抓到重點,把你的履例表留下來細看,就是一件很重要的事了。
好的履例表,第一要鍵就是,格式要清楚,格式不清楚,當下就是被放一邊,不會有 HR 去細看的;第二則是,不管你經歷有多少,履例表的內容,一定要是一業滿版,比一頁多,你要刪成只有一頁,比一頁少,你不管怎樣就是要加到一頁滿版。
底下是我2005時,剛畢業時用的履例表;內容的格式有點小問題,不過這個板型也是我後來一直延用下去的格式。
這份履例表的排列是社會新鮮人用的,(未完待續)
Friday, February 17, 2012
Subscribe to:
Posts (Atom)