Reference
Description
為了買大樂透不想每次都開 APP 顯示 QR Code 很慢, 所以做了 MAX Brightness 這個 Widget.
做好之後, 的確可以方便的把亮度調最大再開 QR Code 的 screenshot, 掃完圖後也可以方便的恢復原本亮度.
朋友聽完這個 Widget 之後就問: 幹嘛不乾脆做一個功能直接把圖秀出來亮度最亮就好?
真是一語驚醒夢中人... 於是就開始做 BrightImage 這個 APP.
需求就很簡單: 選一張圖顯示, 顯示的時候調最亮, 選過一次就不用重選.
由於功能包含可以選圖呈現, 所以測試的時候就也亂選圖, 結果選到一張手機拍的照片後程式就 crash 了... Orz
看 log 發現是 OutOfMemory, 引發 OutOfMemory 的圖有 3264*1826 這麼大.
程式的寫法是
這樣的寫法, 小圖沒問題, 大圖就爆了. 就算沒爆, 只要橫放&直放手機或者重複幾次就會爆.
後來上網查, 發現要 recycle, 所以我就在 onPause 去把 Bitmap recycle 掉, 結果沒那麼簡單. recycle 的時機很重要, 有時候 recycle 了, 底層還沒 recycle 完, 有時是 imageView 預設的圖在佔空間.
試了多種組合都沒用. 由於每天大概只做半小時吧, 加上最近忙家裡有事, 這個問題擺了幾週..
這幾週中有試到一種方式不會 OutOfMemory.
這樣的寫法, 雖然圖片在螢幕翻轉的時候圖片會被 scale 成怪怪的樣子, 但不會 OOM.
當時沒發現原因, 直到今天才突然想到: 啊! 其實存在 imageView 裡面的 Bitmap size 比較小所以不會 OOM.
加 log 去檢查發現果然沒錯 => 雖然很簡單的規則卻過很久才想到 Orz
一開始想說是不是要自己算寬高, 可是這樣程式會比較雜. 想說難道沒有相關的 API 嗎?
看 Bitmap 有個 API: Bitmap#getScaledWidth(targetDensity:int), 就去查 Density 甚麼意思.
然後發現 Bitmap#getScaledWidth(targetDensity:int) 似乎可以達到把圖縮小的目的.
重要的是不用自己去算寬高, 感覺太雜了.
一開始縮小圖片的方式只有
一開始測試沒問題, 結果上傳到 Google Play 後自己測試又遇到 OOM!!
看 log 發現原來原本的 density 是 240, DENSITY_LOW 是 120, 用 DENSITY_LOW 去縮小圖卻沒用, 因為圖片還是太大, 還是會遇到 OOM.
想是不是還是得自己算寬高的時候, 突然想到一個很瞎的方式: catch OOM 然後縮小 targetDensity 來縮小記憶體用量.
結果這個方法有用. 想想應該不會有情況是把圖片的精細度縮 1024 倍還看不到, 就設定只要測10次就好了.
PS. 這次有加可以選圖的功能, 上網查詢後發現意外的簡單.
朋友聽完這個 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(); } }
沒有留言:
張貼留言