ผมลองเขียนโปรแกรม Android ให้โหลดรูปจากอินเทอร์เน็ต แล้วก็นำมาแสดงใน ImageView แต่ทุกครั้งที่โหลดรูปใหม่มาแสดง จะมี garbage collector (GC_CONCURRENT กับ GC_FOR_ALLOC) มา free memory ทุกครั้ง (ดูจาก Log Cat)
ใน DDMS มี heap size 10.937/16.133 MB และ allocation tracker แจ้ง size ที่ใหญ่ที่สุดมาจาก BitmapFactory ผมจะลดตรงนี้ได้วิธีไหนบ้างครับ
(รูปภาพที่ดาวโหลดส่วนใหญ่เป็น .jpg ภาพไม่ใหญ่มากนัก กว้างยาวไม่เกิน 640px ครับ)
ฟังชั่นดาวโหลดภาพ
{syntaxhighlighter brush:java}
public static Bitmap getImage(String url) {
AndroidHttpClient client = AndroidHttpClient.newInstance("Android");
HttpGet http = new HttpGet(url);
InputStream stream = null;
Bitmap bmp = null;
// try to connect to server
try {
HttpResponse response = client.execute(http);
int statusCode = response.getStatusLine().getStatusCode();
// connected, everything OK
HttpEntity entity = response.getEntity();
if (statusCode != HttpStatus.SC_OK) {
Log.e(TAG, "Server error: " + statusCode);
return null;
}
stream = entity.getContent();
if (stream == null) {
return null;
}
bmp = BitmapFactory.decodeStream(stream);
stream.close();
entity.consumeContent();
}
catch (Exception e) {
Log.e(TAG, "Unknown Error: " + e.toString());
}
finally {
client.close();
}
return bmp;
}
{/syntaxhighlighter}
ฟังชั่นแสดงภาพ ผมเก็บรูปภาพลงตัวแปรก่อน เพราะจะได้นำมาแสดงอีกครั้งตอนหมุนจอครับ
{syntaxhighlighter brush:java}
public class StartFragment extends Fragment {
private Bitmap bmp = null;
// --skip--
public void displayImage() {
ImageView view = (ImageView) getActivity().findViewById(R.id.image);
view.setImageBitmap(bmp);
}
public void getImage() {
// normally run in AsyncTask
bmp = getImage("http://example.com/photo.jpg");
}
}
{/syntaxhighlighter}
แบบนี้จะมีปัญหาอะไรไหม เพราะลองหาอ่านจากหลาย ๆ เว็บ เขาบอกว่าการที่ gc ขึ้นบ่อย ๆ อาจเกิดจาก memory leak แต่เท่าที่ผมดูผ่าน Setting แอพที่เขียนกินแรมประมาณ 20 MB (แล้วแต่ขนาดภาพที่แสดงอยู่)
มือถือผมแรมเหลือค่อนข้างน้อย (แรมแค่ 512 แต่ลง JB) เกี่ยวไหม หรือว่าผมคิดมากไปเองครับ?
เอ.. ลองดูว่า getImage(String url) ถูกเรียกถี่ด้วยไหมครับ หรือมีส่วนไหนอีกไหมที่ใช้ BitmapFactory?
ภาพมันเป็น .png, .gif กับ .jpg คิดว่าคงจะดาวโหลดผ่านฟังชั่นนี้ไม่ได้ครับ
01-02 14:57:29.805: I/System.out(3176): resolveUri failed on bad bitmap uri: http://example.com/image.gif
Jusci - Google Plus - Twitter
อ่อ ครับ แต่ที่ตั้งใจจะถามจริงๆ คือ
bmp = BitmapFactory.decodeStream(stream);
นี่รันเท่ากับจำนวนภาพที่ต้องการจะ download มาจริงๆรึปล่าวเท่านั้นเองครับ เช่น ต้องการโหลดภาพเพียง 2 ภาพ แต่ใน log มี report เกี่ยวกับ BitmapFactory หลายครั้ง แสดงว่า code บรรทัดนี้ถูกเรียกหลายครั้งโดยไม่ได้ตั้งใจ หรืออาจเป็น code ส่วนอื่น (ที่มีการใช้ BitmapFactory ทั้งที่เราเขียนเอง หรือมากับ android) ที่เป็นต้นเหตุครับ
อ๋อครับ BitmapFactory ที่ระบบเรียกผมไม่รู้จะดูยังไง แต่ที่ผมเรียกใช้เอง ลองให้มันแสดง log หลังจากเรียกจบ ก็เท่ากับรูปที่โหลดมาครับ
Jusci - Google Plus - Twitter
ไม่ทราบว่าภาพที่โหลดมามีไซส์เท่าไหร่ครับ ?
200k - 300k ครับ (รูปขนาดกว้างยาวไม่เกิน 640px ครับ)
Jusci - Google Plus - Twitter
ตรงนี้ผมว่าต้องเอาขนาดภาพที่เป็น uncompressed มาคิดนะครับ อย่าง ภาพขนาด 640 * 480 ที่ 32 บิทสี (สี่ไบท์) จะกินที่ในหน่วยความจำประมาณ 1.2MB น่ะ
จะว่าไปผมก็ทำ App ดูรูปอยู่เหมือนกัน (แต่เป็นอ่านจาก Samba Share) ทั้ง App ผมใช้ Memory ราว ๆ 45-50MB ครับ ก็ขึ้น OutOfMemory อยู่บ่อย ๆ แหละ แต่ว่ายังทำไม่ถึงขั้นที่จะลงไป profile ขนาดนั้น ฮะๆๆ ของผมส่วนที่กินเยอะที่สุดคือส่วน LRUCache ๕รับ
อันนี้ทดสอบบน Android version ไหนครับ แล้วตัวอุปกรณ์มีแรมเหลือเท่าไหร่
HD2 กับ MIUI 2.10.12 บน Android 4.1.2 แรมก่อนเปิดโปรแกรม 135 MB (หน้า Setting/App)
หลังจากผมกด clear แรมไปใน task manager ของ MIUI จะแจ้ง 336/512MB ก่อนเปิดโปรแกรม พอเปิดแล้วจะเป็น 395/512MB (ซอฟแวร์ใช้ได้แค่ 448 ครับ) (อันนี้น่าจะเพราะ service ต่างเริ่มกลับทำงานด้วยมั้งครับ เพราะ cache process ของแอพมันแค่ 15 - 20 MB เอง)
Xperia Sola กับ Stock Android 4.0.4 แรมก่อนเปิดโปรแกรมก็ 133 MB (Setting/App) ก็ขึ้น GC เหมือนกัน
Jusci - Google Plus - Twitter
เอ่อ ... ไปไม่ถูกเลยทีเดียว
เพราะ Android ก่อน HC จะมีปัญหาเรื่อง GC ขึ้นถี่ครับ อีกปัญหาที่ GC ขึ้นถี่เกินก็คือแรมเหลือน้อย แต่เท่าที่ดูก็ค่อนข้างอยู่ในเกณฑ์ที่ GC ไม่น่าจะถี่ขนาดนั้น
อ้อ ... 300K เกือบเข้าข่ายภาพที่จัดอยู่ในข่าย "ใหญ่" นะครับ
ลองดูอันนี้เผื่อช่วยได้
ถ้าเป็น 640*640px ถอดแบบ ARGB_8888 (4b/px) ขนาดมันในแรมจะเป็น 1.56MB ครับ แต่ภาพมันเป็น jpg ผมเลยถอดด้วย RGB_565 ครับ (แต่ไม่รู้สึกว่าขนาดมันจะต่างกันสักเท่าไร่เลย)
พอดีผม decodestream มันเลยสั่ง decode 2 ครั้งไม่ได้ กำลังมองหาวิธีอื่นในการสร้างค่า inSampleSize อยู่เหมือนกัน แต่ผมอยากได้ภาพชัด ๆ มากกว่า เผื่อหมุนมือถือ ภาพที่ได้จะได้ชัดตลอด
ผมกังวลเรื่อง memory leak มากกว่า ตามความเข้าใจผม สิ่งที่ GC ลบทิ้งไปคือตัวแปร bmp ในฟังชั่น getImage() ถ้ามันไม่ leak ก็คงไม่เป็นไร (แต่รู้สึกว่า ผมจะเก็บ reference ของ view ต่าง ๆ ไว้เป็น global var ไม่ได้ คงโดน GC เก็บทิ้ง)
ป.ล. ลองแอบดู QuickPic มันก็ขึ้น GC บ่อย ๆ สบายใจขึ้นนิดนึง :D
Jusci - Google Plus - Twitter
คงเป็นเพราะ heap ไม่พอละมั้งครับ (max heap size มันเป็น 16 MB รึเปล่าครับ) gc เลยรันบ่อยๆ ลองเซ็ต android:largeHeap="true" ดูรึยังครับ
รึไม่ก็ลอง decode ภาพออกมาให้เป็นภาพเล็กๆตาม link นี้ดูครับ (คงเคยอ่านแล้วมั้ง)
https://developer.android.com/training/displaying-bitmaps/load-bitmap.html
แต่จริงๆภาพของคุณก็เล็กอยู่แล้วล่ะนะ :) แต่ลองทำให้เล็กกว่านี้เพื่อเทสดูก็ได้นะครับ ว่าถ้าเหลือแต่ภาพจิ๋วๆแล้วยังมีปัญหาอยู่ไหม
ลอง decode เป็นภาพเล็ก (ผ่าน inSimpleSize) allocation size ของ BitmapFactory เล็กลง แต่ heap ยังคงอยู่ที่ 13M กว่า ๆ เหมือนเดิม แล้วก็ยังมี GC_CONCURRENT ขึ้นอยู่ ไม่มี GC_ALLOC ไม่ขึ้น grow heap (largeHeap เก็บพอ?)
เผื่อเกี่ยว: ตอนนี้ผมสั่งให้มันโหลดภาพมาเก็บเพิ่มเป็น 3 ภาพครับ
Jusci - Google Plus - Twitter