compound interest

Reference

Description

不想工作, 最好投資可以每年幫你賺 200萬, 就是在不工作的情況下還是可以賺200萬.
怎麼做呢? 我先做個網頁, 用複利的方式去替你算每年的目標是多少.
二三十年的規畫下來, 定好目標之後, 要怎麼執行則是另一個 story..

Android APP: GameVolumn

Reference

Description

昨晚才做完 BrightImage, 想說再做一個甚麼東西, 想到上次有朋友提個需求:
由於每次進到不同的 APP 都會需要調整不同的音量, 重點是每次調整的音量都變來變去.
有時是鈴聲, 有時是電話, 有時鬧鐘, 有時音樂...etc.
他希望有個 APP 可以只調整遊戲音量. 所以就做了 GameVolumn.

Development Note

  1. 首先查一下調整音量那個左右滑動的叫甚麼 => 結果叫做 SeekBar, OK.
  2. 希望 Activity 開起之後只出線 SeekBar 就好 => 把 Activity 設定為透明. OK.
  3. 看一下怎麼讀取跟設定音量 => 使用 AudioManager
  4. 設定好想要 beep 一下 => 使用 ToneGenerator
  5. 設定好, 當 user 想要離開, 點 SeekBar 以外的地方就關掉 Activity => 用 onTouchEvent
  6. 結束上傳
有點粗糙, 不過功能算是完成了. 

Fight with Bitmap OOM problem.

Reference

Description

為了買大樂透不想每次都開 APP 顯示 QR Code 很慢, 所以做了 MAX Brightness 這個 Widget.
做好之後, 的確可以方便的把亮度調最大再開 QR Code 的 screenshot, 掃完圖後也可以方便的恢復原本亮度.
朋友聽完這個 Widget 之後就問: 幹嘛不乾脆做一個功能直接把圖秀出來亮度最亮就好?
真是一語驚醒夢中人... 於是就開始做 BrightImage 這個 APP.
需求就很簡單: 選一張圖顯示, 顯示的時候調最亮, 選過一次就不用重選.

Development Note

整個 APP 開發起來很簡單, 比較麻煩的是 Bitmap 這個東西.
由於功能包含可以選圖呈現, 所以測試的時候就也亂選圖, 結果選到一張手機拍的照片後程式就 crash 了... Orz
看 log 發現是 OutOfMemory, 引發 OutOfMemory 的圖有 3264*1826 這麼大.
程式的寫法是

Bitmap bitmap = BitmapFactory.decodeFile(picturePath);
imageView.setImageBitmap(bitmap);

這樣的寫法, 小圖沒問題, 大圖就爆了. 就算沒爆, 只要橫放&直放手機或者重複幾次就會爆.
後來上網查, 發現要 recycle, 所以我就在 onPause 去把 Bitmap recycle 掉, 結果沒那麼簡單. recycle 的時機很重要, 有時候 recycle 了, 底層還沒 recycle 完, 有時是 imageView 預設的圖在佔空間.
試了多種組合都沒用. 由於每天大概只做半小時吧, 加上最近忙家裡有事, 這個問題擺了幾週..

這幾週中有試到一種方式不會 OutOfMemory.

Display display = getWindowManager().getDefaultDisplay();
imageView.setImageBitmap(Bitmap.createScaledBitmap(BitmapFactory.decodeFile(picturePath),display.getWidth(), display.getHeight(), true));

這樣的寫法, 雖然圖片在螢幕翻轉的時候圖片會被 scale 成怪怪的樣子, 但不會 OOM.
當時沒發現原因, 直到今天才突然想到: 啊! 其實存在 imageView 裡面的 Bitmap size 比較小所以不會 OOM.
加 log 去檢查發現果然沒錯 => 雖然很簡單的規則卻過很久才想到 Orz

一開始想說是不是要自己算寬高, 可是這樣程式會比較雜. 想說難道沒有相關的 API 嗎?
看 Bitmap 有個 API: Bitmap#getScaledWidth(targetDensity:int), 就去查 Density 甚麼意思.
然後發現 Bitmap#getScaledWidth(targetDensity:int) 似乎可以達到把圖縮小的目的.
重要的是不用自己去算寬高, 感覺太雜了.

一開始縮小圖片的方式只有

bitmap.getScaledHeight(DisplayMetrics.DENSITY_LOW);

一開始測試沒問題, 結果上傳到 Google Play 後自己測試又遇到 OOM!!
看 log 發現原來原本的 density 是 240, DENSITY_LOW 是 120, 用 DENSITY_LOW 去縮小圖卻沒用, 因為圖片還是太大, 還是會遇到 OOM.
想是不是還是得自己算寬高的時候, 突然想到一個很瞎的方式: catch OOM 然後縮小 targetDensity 來縮小記憶體用量.

for ( int i = 1; i < 10; i++ ) {
  int targetDensity = bitmap.getDensity() / i;
  try {
    int h = bitmap.getScaledHeight(DisplayMetrics.DENSITY_LOW);
    int w = bitmap.getScaledWidth(targetDensity);
    Log.i(getClass().getName(), "reduce density to " + targetDensity);
    imageView.setImageBitmap(Bitmap.createScaledBitmap(bitmap, w, h, true));
    break;
  } catch (OutOfMemoryError e) {
    Log.w(getClass().getName(), "OOM when targetDensity:" + targetDensity);
  }    
}

結果這個方法有用. 想想應該不會有情況是把圖片的精細度縮 1024 倍還看不到, 就設定只要測10次就好了.

PS. 這次有加可以選圖的功能, 上網查詢後發現意外的簡單.

private void selectPicture() {
  Intent i = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
  startActivityForResult(i, RESULT_LOAD_IMAGE);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  if (requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && null != data) {
    Uri selectedImage = data.getData();
    String[] filePathColumn = { MediaStore.Images.Media.DATA };
  
    Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);
    cursor.moveToFirst();
  
    int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
    String picturePath = cursor.getString(columnIndex);
    cursor.close();
                       
    loadImage();
  }
}

First Android APP - MAX Brightness Widget

Reference

Description

現在買大樂透很方便, 可以用 APP 產生 QR Code 去給樂透彩店掃描, 省紙.
可是我手機很爛, 光是要把 APP 打開到 QR Code 那頁就很慢了.
所以就把 QR Code 那頁切下來存在手機, 想說要買大樂透的時候就打開圖片給店員掃.
店員拿去, 掃不到, 原來是螢幕不夠亮. 要把螢幕亮度調到最大才能掃到.

好吧, 後來每次買大樂透都要先把亮度調到最大再開圖, 掃完後再恢復.
太麻煩了, 就想 DIY 一個 APP 來做到 "點一下亮度最大, 再點一下亮度恢復" 的功能.
可能 Google Play 已經有人做好了吧...? Don't care. XD

做好後, 就放到 Github 跟 Google Play, 還花了 US$25, 有趣.

Development Notes

開發部分, 其實就是看 Android Developer Training 就行.
比較麻煩的是原本 Widget 點下會有個 Activity 跑出來, 不符需求.
而且在 Widget 無法拿到 Window instance, 沒有 Window instance 就不能調亮度.
在 Activity 就可以拿 Window instance, 但我又不想跑另一個 Activity 起來. @@

最後在 stackoverflow 網站上找到高手, 原來這時候只要把 Activity 用 style 設定為隱形就好了.
在 Activity 的 onCreate 最後發個 message 把自己結束掉就完成.

最後花錢放到 Google Play, 就發現整個東西很粗糙, 檔案 size 也很大.
Anyway...

PS

現在使用這個 Widget 是可以把亮度調高沒錯, 但還是要把圖打開.
決定要再做一個是可以設定要 show 哪張圖, 打開 APP 後就直接亮度最亮 & show 圖.
另外手機太爛, 沒空間了, 要可以放到 SD 卡才對.. (Widget 不能放 SD card)

split big log file

Description

一個 log 檔 3G 怎麼看? 不想裝工具, 就只能把檔案切小

Dependencies

JDK7
apache commons io

Codes

    public static void main(String[] params) throws IOException {
        String bigPath = "D:\\logfiles\\biglogfile.log";
        File f = new File(bigPath);
        try (BufferedReader r = new BufferedReader(new FileReader(f))) {
            System.out.println(f.exists());
            int fileCnt = 0;
            List lines = new ArrayList();
            String line;
            while ((line = r.readLine()) != null) {
                if ( lines.size() == 10000 ) {
                    File fileToWrite = new File(f.getParentFile(), f.getName() + "." + fileCnt++);
                    FileUtils.writeLines(fileToWrite, lines);
                    System.out.println("write file:" + fileToWrite);
                    lines.clear();
                }
                lines.add(line);
            }
            File fileToWrite = new File(f.getParentFile(), f.getName() + "." + fileCnt++);
            FileUtils.writeLines(fileToWrite, lines);
            System.out.println("write file:" + fileToWrite);
        } catch (Throwable ex) {
            ex.printStackTrace();
        } 
    }

別名演算法 Alias Method

 題目 每個伺服器支援不同的 TPM (transaction per minute) 當 request 來的時候, 系統需要馬上根據 TPM 的能力隨機找到一個適合的 server. 雖然稱為 "隨機", 但還是需要有 TPM 作為權重. 解法 別名演算法...