ทำความรู้จัก Glide ไลบรารี่โหลดภาพคู่แข่ง Picasso ที่กูเกิ้ลแนะนำให้ใช้ มีดีอย่างไรมาดูกัน!

Posted on 24 Mar 2015 00:46 | 24004 reads | 0 shares
 

ในงาน Google Developer Summit ที่ผ่านมา กูเกิ้ลนำ Image Loader Library ที่ bumptech ทำขึ้นมาแจกอย่าง Glide มาแนะนำให้นักพัฒนาอย่างเราๆได้รู้จัก ในฐานะ "Library ที่กูเกิ้ลเลือกใช้" การันตีคุณภาพจากการที่มันทำหน้าที่โหลดรูปในแอพฯ Google I/O 2014 ที่ผ่านมา

ดูแล้วน่าสนใจดี เราก็เลยแอบลองไปเล่นมานิดหน่อย ... แว้บแรกที่เห็นหรอ ... นี่มัน Picasso Clone นี่นาาาาาา

แต่ก็พบอะไรน่าสนใจๆที่เหนือกว่า Picasso อยู่หลายอย่าง ก็เลยเอามาเล่าให้ฟังกันคร้าบผม

การนำเข้าสู่โปรเจค

ทั้งสองไลบรารี่มีอยู่บน jcenter สามารถนำเข้าผ่าน dependency ปกติได้เลย ตามนี้

Picasso

dependencies {
    compile 'com.squareup.picasso:picasso:2.5.1'
}

Glide

dependencies {
    compile 'com.github.bumptech.glide:glide:3.5.2'
    compile 'com.android.support:support-v4:22.0.0'
}

ทั้งนี้ Glide มีความจำเป็นต้องใช้ Android Support Library v4 ด้วย ดังนั้นก็เลยต้องใส่ตัว support-v4 เข้ามาด้วยถ้าจะใช้ไลบรารี่ตัวนี้ แต่ก็ไม่ใช่ปัญหาอะไร เพราะแอพฯทุกตัวของเราจากนี้ยังไงก็ต้องใช้ Android Support Library v4 อยู่แล้ว

รูปแบบคำสั่งโหลดรูป

อย่างที่บอกว่ามันลอก Picasso มาเลย ดังนั้นหน้าตาโดยรวมของมันเลยค่อนข้างเหมือนกันมาก ตามนี้

Picasso

Picasso.with(context)
    .load("http://nuuneoi.com/image.jpg")
    .into(ivImg);

Glide

Glide.with(context)
    .load("http://nuuneoi.com/image.jpg")
    .into(ivImg);

บอกแล้วว่าลอกกันมา -_-

แต่ Glide ค่อนข้างทำมาได้รอบคอบกว่าเพราะคำสั่ง with ไม่ได้รับแค่ Context เหมือน Picasso แต่สามารถรับ Activity และ Fragment ได้ด้วย เดี๋ยวมันไปแงะเอา Context มาเอง ซึ่งสะดวกกว่านิดนึง

with

ถามว่ามันมีข้อดียังไง? หลักๆก็คือ Glide มันสามารถทำงานร่วมกับ Activity/Fragment Lifecycle ได้ (หยุดโหลดตอน Pause กลับมาโหลดใหม่ตอน Resume) ถ้าโยน Activity หรือ Fragment เข้าไปอย่างถูกต้อง มันก็จะทำงานร่วมกันได้อย่างสมบูรณ์นั่นเอง

Default Bitmap Format คือ RGB_565

และนี่คือผลเบื้องต้นของการโหลดรูป (รูปขนาด 1920x1080 พิกเซล โหลดใส่ ImageView ขนาด 768x432 พิกเซล)

firstload

สิ่งที่เห็นด้วยตาเปล่าได้เลยคือ Glide โหลดรูปมาได้ Quality แย่กว่า Picasso มาก ทั้งนี้เพราะว่าโดย Default แล้ว Glide ตั้ง Bitmap Format ไว้ที่ RGB_565 เพราะมันใช้แรมน้อยกว่า ARGB_8888 สองเท่านั่นเอง (และ Glide ก็ Cache ตัว 565 ลง Disk ด้วย คือถ้าโหลดแล้วจะเปลี่ยนมาใช้ 8888 ก็ไม่ได้เลย นอกจากจะบังคับให้มันโหลดใหม่ ดังนั้นโปรดใช้อย่างระวัง)

การใช้แรมก็เลยต่างกันไปตามนี้ (Picasso: ARGB8888, Glide: RGB565) (แอพฯใช้ Base Memory อยู่ที่ 8MB)

ram1_1

หากรู้สึกว่า Quality มันโอเคแล้ว ก็ไม่ต้องไปทำอะไรมัน แต่ส่วนใหญ่เรามักจะดูออกด้วยตาเปล่าว่ามันไม่ดี (อย่างเช่นด้านบน) เราก็สามารถเปลี่ยน Default Bitmap Format ให้เป็น ARGB_8888 ได้ แต่วิธีค่อนข้างซับซ้อนเล็กน้อย ต้องประกาศคลาสใหม่ขึ้นมาตัวนึงดังนี้

public class GlideConfiguration implements GlideModule {

    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        // Apply options to the builder here.
        builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
    }

    @Override
    public void registerComponents(Context context, Glide glide) {
        // register ModelLoaders here.
    }
}

แล้วก็ผูก meta-data ไว้ใน AndroidManifest.xml ตามนี้

<meta-data android:name="com.inthecheesefactory.lab.glidepicasso.GlideConfiguration"
            android:value="GlideModule"/>

จากนั้นรูปที่โหลดด้วย Glide ก็จะสีสวยงามแล้ว

quality2

ซึ่งน่าสนใจที่ Glide ใช้ Memory เพิ่มขึ้นเกือบๆ 2 เท่าจากตอนเป็น RGB565 (หักลบ Base Memory ของแอพฯแล้ว) แต่ยังคงน้อยกว่า Picasso พอสมควรเลยทีเดียว

ram2_1

ทั้งนี้เพราะ Picasso โหลดรูปทั้งรูปเข้าสู่ Memory แล้วค่อยเอามาวาดในขนาดที่ต้องการ ทำให้มันจึงกิน Memory ไปเต็มๆของภาพขนาด 1920x1080 พิกเซล แต่ตัว Glide มันจะโหลดภาพขนาดเท่าที่ต้องการลง Memory เท่านั้น ... ซึ่งเป็น Best Practice นะ ปกติถ้าจะโหลดภาพจาก Picasso ก็ต้องมา resize ตอนหลังให้ขนาดเท่ากับที่ต้องการด้วย ไม่งั้นจะกินแรมเกินความจำเป็น ถ้าจะให้ผลการทำงานเหมือนกันในแง่ Memory ก็ต้องสั่งแบบนี้

Picasso.with(this)
    .load("http://nuuneoi.com/uploads/source/playstore/cover.jpg")
    .resize(768, 432)
    .into(ivImgPicasso);

หรือถ้า ImageView มีขนาดที่แน่นอน ไม่ได้กำหนดด้วย wrap_content ก็สามารถสั่งว่า

Picasso.with(this)
    .load("http://nuuneoi.com/uploads/source/playstore/cover.jpg")
    .fit()
    .centerCrop()
    .into(ivImgPicasso);

ได้ด้วยเช่นกัน

การกินแรมก็จะลดลงมาเป็นแบบนี้

memory3

ซึ่งก็จะกินแรมพอๆกันละ แต่ถ้าถามว่าอันไหนเตรียมมาเหมาะสมกว่ากันก็ต้องบอกว่า Glide นะ เพราะไม่ต้องมานั่งวัดขนาดของ ImageView เอง แม้จะกำหนดความกว้างหรือสูงเป็น wrap_content ก็ทำงานมาได้ถูกต้อง เดี๋ยวมันจะไปทำภาพให้ขนาดเท่า ImageView ให้เอง

เรื่องนี้ Glide ดูเหมือนจะชนะ แต่มันยังไม่จบแค่นั้น ...

คุณภาพของภาพที่ถูกโหลดขึ้นมา

ลองขยายภาพที่โหลดมาบน ImageView แบบ 100% ดูแล้ว ได้ผลเป็นแบบนี้

quality3

ดูด้วยตาเปล่าบนจอ Galaxy Note 3 LTE ก็มองไม่ออกนะ แต่พอมาลองบนจอ 768x1280 พิกเซลก็เริ่มมองออกว่ามีอะไรแปลกไป รู้สึกได้ว่าภาพของ Glide มันมีความคมอะไรบางอย่างอยู่ ไม่เหมือน Picasso ที่ดูนุ่มไปหมดทั้งภาพ สรุปก็เป็นเพราะแบบนี้นี่เอง

แต่ถ้าไม่มีรูปมาเทียบกันสองรูป รูปจาก Glide ก็ไม่ได้เลวร้ายอะไร สามารถใช้งานจริงได้ครับ (แต่ต้องเป็นโหมด ARGB_8888 นะ!)

ลองไปค้นๆดูวิธีการ Resize รูปของ Glide ยังหาไม่เจอว่าจะไปปรับอัลกอริทึมตรงไหนได้บ้าง อย่างไรก็ตาม Default Settings ภาพออกมาคุณภาพไม่เท่า Picasso

การ Disk Cache

แนวคิด Disk Cache ค่า Default ของ Picasso และ Glide ต่างกันมาก โดยการทดลองโหลดรูปเดียวกันมาแสดงบน ImageView ตามตัวอย่างด้านบน พอไปเปิดดู Cache พบว่า Glide เลือก Cache ขนาดที่แสดงบน ImageView ไม่ใช่ขนาดที่โหลดมา ส่วน Picasso จะ Cache ตัวที่โหลดมาเลย (ภาพเต็ม) ดังนี้

cache

ส่งผลให้ภาพที่ Glide Cache จึงเป็นภาพที่ Quality ไม่ค่อยดีเท่าไหร่ (หยักๆเหมือนที่ Zoom ให้ดูด้านบน) รวมถึงถ้าโหลดเป็นโหมด RGB565 มันก็จะ Cache เป็น 565 พอเปิดครั้งต่อไปก็จะได้ภาพที่คุณภาพไม่ดีถึงจะปรับเป็น 8888 แล้วก็ตาม

และพอลองปรับขนาดรูปเป็นขนาดต่างๆกันไปแล้วโหลดด้วย Picasso/Glide ดู ก็พบว่าไม่ว่าจะเป็นรูปขนาดเท่าไหร่ Picasso ก็สามารถโหลดจาก Cache เดิมมาแสดงได้หมด (ด้วยการ Resize แบบ Realtime ก่อนจะไปโชว์บน ImageView)

แต่กับ Glide มันกลับเลือก Cache 1 ขนาดภาพต่อ 1 ไฟล์ ผลก็คือถ้ามี ImageView ขนาดไม่เท่ากันในหน่วยพิกเซล มันจะต้องโหลดใหม่แล้วมา Resize ใหม่ก่อน Cache ทั้งๆที่เป็นภาพเดียวกัน ...

ผลคือถ้าหน้าแรกมี ImageView ขนาด 200x200 พิกเซล แต่หน้าด้านในมี ImageView ขนาด 100x100 พิกเซล ถึงหน้าแรกจะโหลดเสร็จจนแสดงผลแล้ว พอกดเข้าไปหน้าด้านในก็ต้องโหลดใหม่อยู่ดี (ซึ่งไม่ Make Sense เลย)

ในขณะเดียวกัน หากใช้ Picasso จะสามารถแสดงผลได้ทันทีไม่ต้องโหลดใหม่ให้เสียเวลาแต่อย่างใด

อย่างไรก็ตาม เราสามารถปรับเปลี่ยน Behavior ตรงนี้ได้ ด้วยการบังคับให้ Glide Save ทั้งรูปขนาดเต็มและรูปขนาดตาม ImageView ลง Cache ไว้ ด้วยคำสั่งนี้

        Glide.with(this)
             .load("http://nuuneoi.com/uploads/source/playstore/cover.jpg")
             .diskCacheStrategy(DiskCacheStrategy.ALL)
             .into(ivImgGlide);

พอรันแล้วไปเช็ค Cache ก็จะเห็นทั้งรูปขนาดเต็มและรูปขนาดเท่า ImageView กองอยู่ใน Cache Folder เรียบร้อย รอบหน้าตอนมีการเรียก URL เต็ม มันก็จะไม่ไปโหลดใหม่ละ เอาภาพขนาดเต็มที่ Cache ไว้ แล้วก็ Resize ให้ตรงตามขนาด ImageView ก่อนจะ Cache เพิ่มเข้าไปอีกขนาด (กลายเป็นรวม 3 ขนาด)

ด้วยแนวทางที่ Glide ทำมา ก็มีข้อดีตรงที่ถ้าเปิดแอพฯใหม่มามันจะโหลดภาพขึ้นมาได้เร็วมาก เร็วกว่า Picasso อย่างเห็นได้ชัด เพราะว่า Glide มันไม่ต้องทำอะไรแล้ว โหลดขึ้นมาแสดงได้เลย มัน Cache ขนาดที่ถูกต้องไว้ให้หมดแล้ว แต่ตัว Picasso ต้องโหลดมาแล้ว Resize ก่อนแล้วค่อยเอามาใส่ใน ImageView จะใช้เวลาในการทำทั้งหมดนิดนึง บางทีก็โผล่มาเลยพร้อม UI บางทีก็ต้องรอแป๊บนึง (200ms ไม่เกินนั้น) แค่อาจจะต้องใส่เพิ่มหนึ่งบรรทัดเพื่อให้มันปรากฎอย่างรวดเร็ว

//Picasso
.noFade();

loading3

เรื่อง Disk Cache ก็เป็น Trade Off กันไป ทั้งสองตัวมีข้อดีข้อเสียครับ หากมั่นใจว่ารูปนั้นๆไม่มีการเปลี่ยนขนาดแน่นอน Glide ก็อาจจะตอบโจทย์กว่า แต่ถ้ารูปเดียวกันมีใช้หลายที่ก็พิจารณาดูว่าอันไหนคุ้มกว่ากันครับ

สำหรับเนย ในกรณีทั่วไปเนยชอบ Glide นะเพราะมันทำงานได้เร็วกว่า Picasso มาก ถึงจะใช้พื้นที่ Cache เพิ่มขึ้นก็ตาม

คำสั่งต่างๆ

สามารถสั่งอะไรแบบ Picasso ได้เกือบหมด และลอกแนวทางการเขียนมาด้วย แต่คำสั่งไม่เหมือนกันแบบเป๊ะๆ มีต่างกันไปบ้าง เช่น การ Resize รูป

// Picasso
.resize(300, 200);

// Glide
.override(300, 200);

การทำ Center Crop

// Picasso
.centerCrop();

// Glide
.centerCrop();

การ Transform ที่เป็นเสน่ห์ของ Picasso ก็ทำได้เช่นกัน (รูปแบบการเขียนคลาส Transformation ก็เหมือนกันแทบจะเป๊ะๆอีกตะหาก)

// Picasso
.transform(new CircleTransform())

// Glide
.transform(new CircleTransform(context))

หรือจะกำหนด Placeholder (ภาพระหว่างรอโหลด) และ Error (ภาพที่เอาแสดงกรณีที่โหลดไม่สำเร็จ) ก็ทำได้

// Picasso
.placeholder(R.drawable.placeholder)
.error(R.drawable.imagenotfound)

// Glide
.placeholder(R.drawable.placeholder)
.error(R.drawable.imagenotfound)

สรุปก็คือ Picasso มีอะไร Glide มีเกือบหมดนั่นแล

สิ่งที่ Glide มีแต่ Picasso ไม่มี

อย่างแรกที่เราคิดว่าน่าจะเด็ดที่สุดเลยคือ "การโหลด GIF Animation" เพราะมันไม่ได้แค่โหลด มันยังจัดการเรื่องการเล่น Animation ให้อีกด้วยบน ImageView ธรรมดาๆนี่แหละ

gifanimation2

และเนื่องจากมันสามารถทำงานร่วมกับ Activity/Fragment Lifecycle ได้เป็นอย่างดี มันก็เลยมีความสามารถในการ "หยุดเล่น" ตอนที่ Activity Paused และกลับมาเล่นใหม่ในโหมด Resumed โดยอัตโนมัติอีกด้วย

ถามว่ามัน Cache แบบไหน? ตอบว่ามัน Cache แบบ Resize ก่อน Cache เช่นเดียวกับกรณีภาพนิ่งครับ (ไปเอาพลังไหนมา Reencode นะ)

อย่างไรก็ตาม GIF Animation นี่มันกินแรมมากจริงๆ พยายามอย่าใช้ใหญ่มาก ไม่งั้นแอพฯอาจจะมอดม้วยมรณาได้ง่ายๆ

นอกจากจะโหลด GIF Animation ได้แล้ว ก็ยังสามารถ Decode Video File ออกมาเป็นภาพนิ่งได้อีกด้วย แต่ข้อจำกัดคือสามารถ Decode ไฟล์ที่อยู่ในเครื่องได้เท่านั้น ไม่สามารถโหลดจาก Internet ได้

อีกอันที่น่าจะมีประโยชน์คือ เราสามารถทำ Animation การปรากฎของภาพได้ด้วย จากเดิมมันจะโผล่มาเฉยๆ ส่วน Picasso โผล่มาแบบ Fade In แต่กับ Glide เราสามารถสร้าง Animator (R.animator) เพื่อมาผูกกับการโหลดรูปได้อีกด้วย แบบว่าอยากให้มันพลิกตัวพร้อมหมุน 3 รอบตอนโหลดเสร็จไรงี้ เต็มที่ !

สุดท้ายคือสามารถสร้าง Thumbnail ของภาพที่โหลดได้ด้วย (ซึ่งจริงๆก็ไม่มีอะไร มันคือการ Resize)

จริงๆยังมีอย่างอื่นอีก เช่นการ Transcode ภาพที่โหลดมาเป็น Byte Array เพื่อทำอย่างอื่นต่อไป แต่ส่วนใหญ่ไม่ค่อยสำคัญละ

การปรับแต่ง Configuration

Glide สามารถปรับ Configuration ได้หลายแบบอยู่เช่น ตำแหน่งและขนาดของ Disk Cache/Memory Cache รวมถึงการกำหนด Bitmap Format ลองไปอ่านดูเพิ่มเติมได้จากหน้า Configuration ครับ ไม่เน้นละกันนะ ไม่ค่อยมีอะไรมาก

ขนาดของไลบรารี่

ขนาดของ Library ทั้งสองตัวต่างกันเยอะอยู่ โดย Picasso (v2.5.1) มีขนาด 118KB ส่วน Glide (v3.5.2) มีขนาดถึง 430KB

librarysize

แต่ถ้ามาหักลบกันก็เหลือแค่ 312KB แหละนะ ไม่ได้มีนัยสำคัญมากมาย

ทางด้าน Method Count อยู่ที่ 840 และ 2678 สำหรับ Picasso และ Glide ตามลำดับ

methodcount

2678 ก็ถือว่าเยอะทีเดียวสำหรับ Limit 65535 methods ของ Android DEX File ใครเลือกใช้ Glide ก็อย่าลืมเปิด ProGuard ด้วยละกันครับ (จริงๆถึงจะใช้อะไรก็ควรเปิดไว้สำหรับตัว Production อ่านะ)

สรุป

จากการทดสอบ Glide กับ Picasso ยังไม่มีตัวไหน Perfect แต่เหมือน Glide จะภาษีดีกว่าพอสมควร ทำออกมาได้เหมาะสมกว่า Picasso ในหลายๆเรื่องโดยเฉพาะอย่างยิ่งการโหลดภาพขนาดเท่า ImageView เข้า Memory โดยอัตโนมัติ ไม่ต้องมานั่ง Resize เอง เพราะปัญหาตรงนี้สร้าง OutOfMemoryError ให้นักพัฒนามานักต่อนักแล้ว

จะติดก็คงเป็นเรื่องของคุณภาพภาพที่ไม่ดีเท่า Picasso กับการตั้งค่าเริ่มต้นมาไม่ค่อยดี ต้องปรับโน่นนี่ให้เรียบร้อยก่อนเริ่มใช้งาน (RGB565 นี่ตัวดีเลย) แต่ที่เหลือก็เท่ากับหรือเหนือกว่า Picasso ทั้งหมด (โดยเฉพาะอย่างยิ่ง GIF Animation) ส่วน Picasso จะมีจุดอ่อนตรงเรื่องการโหลดภาพจาก Cache ที่จะช้ากว่า Glide อย่างเห็นได้ชัดนั่นเอง

ถามว่าจะสลับมาใช้ Glide ดีมั้ย? จากที่ลองเล่นแล้ว ผมว่าโอเคเลยนะ แค่อย่าลืมปรับจูนค่า ARGB8888 และการบังคับ Cache ทั้งรูปเต็มและรูปย่อแล้ว ที่เหลือก็น่าจะเรียบร้อยคร้าบผม

Resources

มี Resources ที่เขียนเกี่ยวกับ Glide ยังไม่เยอะ รวบรวมมาให้ลองอ่านเล่นกันตามนี้ครับ

Glide 3.0: a media management library for Android

- Glide Wiki

Android Picasso vs Glide

Android: Image loading libraries Picasso vs Glide

ผู้เขียน: nuuneoi (Android GDE, CTO & CEO at The Cheese Factory)
นักพัฒนาแบบ Full-Stack ที่มีประสบการณ์ในการพัฒนาแอพฯแอนดรอยด์มากว่า 6 ปีและอยู่ในวงการพัฒนาแอพฯมือถือมากว่า 12 ปี มีความสนใจทางด้าน Infrastucture, Service Side, Design, UI&UX, Hardware, Optimization, Cooking, Photographing, Blogging, Training, Public Speaking และรักที่จะแชร์เรื่องราวให้ผู้คนได้อ่านได้ฟังกันผ่าน Blog