สำหรับการทำงานของแอปบนแอนดรอยด์ ในบางครั้งนักพัฒนาก็ต้องการให้ข้อมูลบางอย่างถูกเก็บไว้ในเครื่อง (Persistent Storage) แทนการเก็บเป็นตัวแปรในโค้ด (In-memory) และข้อมูลดังกล่าวเป็น Primitive Value (เช่น Boolean, String, Integer หรือ Float เป็นต้น)

ซึ่งบนแอนดรอยด์ก็สามารถเก็บข้อมูลแบบนี้ได้เช่นกัน โดยผ่านชุดคำสั่งที่เรียกกันว่า Shared Preferences

SharedPrefences เป็นวิธีการเก็บข้อมูลไว้ในเครื่องแบบถาวรวิธีหนึ่งที่เหมาะแก่การเก็บข้อมูลในลักษณะของ Key-value เพื่อให้นักพัฒนาสามารถเก็บข้อมูลที่เป็น Primitive Value ไว้ได้ถึงแม้ว่าผู้ใช้จะออกจากแอปไปแล้วก็ตาม จึงทำให้ SharedPreferences เหมาะกับหลาย ๆ สถานการณ์ เช่น เก็บ Username ในหน้า Sign-in ไว้ เพื่อช่วยให้ผู้ใช้ไม่ต้องเสียเวลาพิมพ์ใหม่เมื่อกลับเข้ามาที่หน้า Sign-in อีกครั้ง

Shared Preferences มีอยู่ทั้งหมด 2 แบบด้วยกัน

  • SharedPreferences - เก็บข้อมูลและเรียกใช้งานจากที่ใดในแอปก็ได้ โดยจะต้องเรียกผ่าน Context ด้วยคำสั่ง getSharedPreferences(...)
  • Preferences - เก็บข้อมูลและเรียกใช้งานได้เฉพาะใน Activity ที่เรียกใช้งานเท่านั้น โดยจะต้องเรียกผ่าน Activity ด้วยคำสั่ง getPreferences(...)

ในกรณีที่คำสั่งเก็บข้อมูลและคำสั่งเรียกใช้งานข้อมูลอยู่คนที่ Activity กัน หรือไม่ได้อยู่ใน Activity แนะนำให้ใช้เป็น SharedPreferences แต่ถ้าข้อมูลดังกล่าวถูกเรียกใช้งานอยู่แค่ใน Activity เท่านั้น การใช้ Preferences ก็เพียงพอต่อการใช้งานแล้ว

// SharedPreferences
val context: Context = /* ... */
val name = "sign_in_preferences"
val mode = Context.MODE_PRIVATE
val preferences = context.getSharedPreferences(name, mode)

// Preferences
val activity: Activity = /* ... */
val mode = Context.MODE_PRIVATE
val preferences = activity.getPreferences(mode)

เนื่องจาก SharedPreferences สามารถเรียกใช้งานจากที่ใดในแอปก็ได้ โดยนักพัฒนาจะต้องกำหนดชื่อให้กับ SharedPreferences เสมอ จึงสามารถแบ่งข้อมูลเก็บไว้ใน SharedPreferences หลาย ๆ ชุดได้

// SharedPreferences
val context: Context = /* ... */
val mode = Context.MODE_PRIVATE

val name = "sign_in_preferences"
val signInPreferencesPref: SharedPreferences = context.getSharedPreferences(name, mode)

val name = "user_tips_preferences"
val userTipsPref: SharedPreferences = context.getSharedPreferences(name, mode)

ในขณะที่ Preferences ไม่จำเป็นต้องกำหนดชื่อ เพราะถูกเรียกใช้งานแค่ภายใน Activity เท่านั้น

กรณีที่ต้องการใช้ Shared Preferences เพื่อเก็บข้อมูลการตั้งค่าใช้งานภายในแอป  (App Settings) แนะนำให้เรียกผ่านคำสั่ง PreferenceManager.getDefaultSharedPreferences(...) แทน

สำหรับ Mode ของ Shared Preferences เมื่อก่อนนักพัฒนาสามารถกำหนดเพื่อเปิดให้แอปอื่นสามารถเข้าถึงข้อมูลใน Shared Preferences ได้ แต่ด้วยเหตุผลด้านความปลอดภัย จึงทำให้ในปัจจุบันกำหนดได้แค่เพียง Context.MODE_PRIVATE (เรียกใช้งานได้แค่ภายในแอป) เท่านั้น

การใช้งาน Shared Preferences

การบันทึกข้อมูล (Write) จะต้องเรียกคำสั่งผ่านคลาส SharedPreferences.Editor แต่การอ่านข้อมูลสามารถเรียกคำสั่งจากคลาส SharedPreferences ได้เลย

val context: Context = /* ... */
val mode = Context.MODE_PRIVATE
val name = "sign_in_preferences"
val preferences = context.getSharedPreferences(name, mode)

// Write
preferences.edit {
    putString("username", "Sleeping For Less")
}

// Read
val username: String? = preferences.getString("username", null)
คำสั่ง edit ในตัวอย่างข้างบนนี้ เป็น Extension Function ที่อยู่ใน AndroidX Core KTX ที่ช่วยให้นักพัฒนาสามารถเรียกใช้งาน SharedPreferences.Editor ด้วยคำสั่งที่สั้นกระชับมากขึ้น

ในการบันทึกข้อมูลจะต้องกำหนดทั้ง Key เพื่อใช้ในการอ้างอิงและ Value ที่ต้องการเก็บไว้ใน Shared Preferences ส่วนการอ่านข้อมูลก็จะต้องกำหนด Key ให้ตรงกันด้วย และจะต้องกำหนด Default Value ไว้สำหรับกรณีที่ไม่มีข้อมูลสำหรับ Key ดังกล่าว

และถ้าต้องการลบข้อมูลที่อยู่ใน Shared Preferences จะเลือกได้ว่าต้องการลบข้อมูลแค่บาง Key หรือจะลบข้อมูลทั้งหมด

val context: Context = /* ... */
val preferences = context.getSharedPreferences(/* ... */)

// Remove any key
pref.edit {
    remove("username")
    remove("last_signed_in_timestamp")
}

// Remove all keys
pref.edit {
    clear()
}

ซึ่งแน่นอนว่าคำสั่ง remove(...) และ clear() จะเรียกใช้งานได้จากคลาส SharedPreferences.Editor เท่านั้น

สามารถเก็บข้อมูลแบบไหนได้บ้าง​

อย่างที่บอกในตอนแรกว่า Shared Preferences มีไว้เก็บข้อมูลที่เป็น Primitive Value เท่านั้น แต่เอาเข้าจริงคือสามารถเก็บได้แค่บางประเภทเท่านั้น ซึ่งจะมีดังนี้

  • Boolean
  • Integer
  • Float
  • Long
  • String
  • String Set

ดังนั้นการเก็บข้อมูลบางประเภทก็อาจจะต้องแปลงให้ข้อมูลอยู่ในรูปแบบ Shared Preferences รองรับเสียก่อน

สิ่งที่นักพัฒนาควรรู้สำหรับการใช้งาน Shared Preferences

ถึงแม้ว่าการใช้งาน Shared Preferences นั้นง่าย และไม่ซับซ้อน แต่ก็มีสิ่งสำคัญที่นักพัฒนาควรรู้เพื่อใช้งานได้อย่างเหมาะสมและปลอดภัยด้วยเช่นกัน

Shared Preferences เก็บข้อมูลไว้ที่ไหนในเครื่อง?

ข้อมูลที่เก็บไว้ใน Shared Preferences จะถูกเก็บไว้ใน App-specific Directory ซึ่งเป็น Directory สำหรับเก็บข้อมูลของแต่ละแอปแยกกัน ที่จะอยู่ใน /data/data/<package_name>/ โดย Shared Preferences จะอยู่ใน Sub Directory ที่ชื่อว่า shared_prefs

และเมื่อลองเปิดไฟล์ที่อยู่ข้างในก็จะพบว่าข้อมูลถูกเก็บไว้ในรูปแบบของ XML

// sign_in_preferences.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="username" value="Sleeping For Less" />
    <!-- ... -->
</map>

โดยข้อมูลที่เก็บไว้ก็จะแยกไฟล์ตามชื่อ Shared Preferences ที่กำหนดไว้ และข้อมูลที่กำหนดไว้ในชื่อเดียวกันก็จะอยู่ในไฟล์เดียวกันนั่นเอง

การเข้าไปเปิดดูไฟล์ของ Shared Preferences ที่อยู่ในเครื่อง

โดยปกติแล้วไฟล์ของ Shared Preferences จะถูกกำหนด Permission เพื่อให้แอปที่เป็นเจ้าของไฟล์เข้าถึงเท่านั้น แต่เพื่ออำนวยความสะดวกในระหว่างพัฒนาแอป นักพัฒนาจึงสามารถเข้าไปอ่านไฟล์ของ Shared Preferences ได้ก็ต่อเมื่อแอปถูกติดตั้งเป็นแบบ Debug Build เท่านั้น

สำหรับเงื่อนไขของแอปที่เป็น Debug Build ก็คือการกำหนด Flag ที่ชื่อว่า debuggable ใน build.gradle นั่นเอง

และวิธีเปิด App-specific Directory ในอุปกรณ์แอนดรอยด์ที่ง่ายที่สุดก็คือการใช้ Device File Explorer ที่อยู่ใน Android Studio นั่นเอง

สามารถเปิดดูไฟล์ของ Shared Preferences ด้วย Rooted Device ได้

เนื่องจาก Rooted Device หรือเครื่องที่ถูก Root แล้ว จะเข้าถึงสิทธิ์เป็น Super User ได้ จึงทำให้สามารถเปิดเข้าไปดูไฟล์ใน App-specific Directory ของทุกแอปได้ ซึ่งรวมไปถึงไฟล์ของ Shared Preferences ด้วยเช่นกัน

ดังนั้นถ้าไม่จำเป็น ก็ไม่ควรเก็บข้อมูลที่สำคัญต่อการทำงานของแอปไว้ใน Shared Preferences

ถ้าจำเป็นต้องเก็บข้อมูลไว้ใน Shared Preferences และเป็นข้อมูลที่สำคัญมาก ๆ ให้เข้ารหัสก่อนทุกครั้ง

ยกตัวอย่างเช่น Access Token เพื่อให้แอปสามารถเรียกข้อมูลจาก Web Server ได้ทันทีโดยไม่ต้อง Sign In ใหม่ทุกครั้ง

สำหรับข้อมูลประเภทนี้ควรทำการเข้ารหัสข้อมูลก่อนเก็บลงใน Shared Preferences ทุกครั้ง โดยวิธีที่แนะนำที่สุดก็คือใช้ EncryptedSharedPreferences ของ AndroidX Secutity

ข้อมูลใน Shared Preferences จะหายไปก็ต่อเมื่อ

  • นักพัฒนาใช้คำสั่งลบข้อมูลจากในโค้ด
  • ผู้ใช้กดลบข้อมูล (Clear Data) ผ่าน App Info
  • ผู้ใช้กดลบแอป (Uninstall)
  • ผู้ใช้สั่ง Factory Reset เครื่อง

และแน่นอนว่าข้อมูลใน Shared Preferences จะไม่หายไป ในระหว่างที่ผู้ใช้อัปเดตแอปเป็นเวอร์ชันที่ใหม่กว่า

สรุป

Shared Preferences เป็นวิธีการเก็บข้อมูล Key-value ใด ๆ ไว้ในอุปกรณ์แอนดรอยด์แบบถาวร จึงเหมาะสำหรับการทำงานบางอย่างที่อยากให้ค่าหรือข้อมูลใด ๆ ยังคงอยู่ ถึงแม้ว่าผู้ใช้จะปิดแอปแล้วเปิดขึ้นมาใหม่ในภายหลัง

และในขณะเดียวกัน การใช้งาน Shared Preferences ก็ควรคำนึงถึงความปลอดภัยของผู้ใช้ด้วย เนื่องจากมีวิธีเก็บข้อมูลที่เรียบง่าย ไม่ซับซ้อน็ จึงมีความปลอดภัยที่ต่ำด้วยเช่นกัน จึงเหมาะกับข้อมูลที่ไม่สำคัญมากนัก แต่ถ้าจำเป็นต้องเก็บข้อมูลสำคัญไว้ในนี้ก็แนะนำให้ใช้ 3rd Party Library อย่าง AndroidX Security เข้ามาช่วยจัดการแทน