หลังจากที่ Android 8.0 Oreo เปิดตัวมาได้ซักพัก ก็ได้มีบทความเกี่ยวกับการเปลี่ยนแปลงในเวอร์ชันใหม่นี้ไปแล้ว ซึ่งเจ้าของบล็อกก็ไม่มีเหตุผลว่าทำไมต้องไปเขียนซ้ำกับคนอื่นอีก ก็เลยหนีมาเขียนเรื่องนี้แทน เพื่อให้ผู้ที่หลงเข้ามาอ่านได้รู้ว่านอกจากฟีเจอร์หลักๆแล้ว มีคำสั่งอะไรบ้างที่เปลี่ยนแปลงไป

ก่อนที่จะอ่านต่อ

ถ้ายังไม่รู้ว่าฟีเจอร์หลักของ Android 8.0 Oreo มีอะไรบ้างที่มีผลต่อนักพัฒนา ให้ไปอ่านจากบทความเพื่อนบ้านก่อนเลย

ป่ะ ลุยกันต่อ

นอกจากจะมีฟีเจอร์ใหม่ๆ และคำสั่งใหม่ๆสำหรับฟีเจอร์เหล่านั้นแล้ว ก็ยังมีการเปลี่ยนแปลงของโค้ดใน API เก่าที่มีอยู่ด้วย ซึ่งเจ้าของบล็อกเข้าไปนั่งไล่ส่องมาแล้ว ก็เลยขอหยิบเฉพาะคำสั่งของคลาสที่สำคัญๆมาให้ดูนะ

Configuration

ชื่อเต็ม : android.content.res.Configuration

มีการเพิ่มคำสั่งเข้ามาเพื่อให้รองรับการแสดงผลบนหน้าจอที่รองรับ HDR และ Wide Color Gamut

val context: Context = ...
val isScreenHdr: Boolean = context.resources.configuration.isScreenHdr
val isScreenWideColorGamut: Boolean = context.resources.configuration.isScreenWideColorGamut

ซึ่งถ้าลงละเอียดไปลึกกว่านั้น ผู้ที่หลงเข้ามาอ่านสามารถดึงค่า Color Mode ด้วยคำสั่งแบบนี้ได้เลย

val colorMode = resources.configuration.colorMode

แล้วอยากรู้ว่ามีค่าเป็นอะไรบ้างก็ให้ทำ Bit Mask กับค่า Color Mode ที่ได้ฮะ

Configuration.COLOR_MODE_HDR_MASK    
Configuration.COLOR_MODE_HDR_NO     
Configuration.COLOR_MODE_HDR_SHIFT     
Configuration.COLOR_MODE_HDR_UNDEFINED     
Configuration.COLOR_MODE_HDR_YES     
Configuration.COLOR_MODE_UNDEFINED     
Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_MASK     
Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO     
Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED     
Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES

แต่แนะนำว่าอย่าไปทำครับ เหนื่อยตายกันพอดี ไปเช็คจากคำสั่งที่มีให้โดยตรงเถอะนะ..

และมีการเพิ่ม UI Mode สำหรับ VR Headset ให้ด้วย สำหรับนักพัฒนาที่ทำเกี่ยวกับ VR ก็สามารถ Detected ได้จาก Configuration แล้วว่าแสดงผลหน้าจอสำหรับ VR อยู่หรือไม่

Configuration.UI_MODE_TYPE_VR_HEADSET

Display

ชื่อเต็ม : android.view.Display

เพิ่มคำสั่งสำหรับเช็คว่าหน้าจอที่แสดงผลอยู่นั้นเป็น HDR หรือ Wide Color Gamut หรือป่าว

val windowManager: WindowManager= this
val isHdr = windowManager.defaultDisplay.isHdr
val isWideColorGamut = windowManager.defaultDisplay.isWideColorGamut

Window

ชื่อเต็ม : android.view.Window

เพิ่มคำสั่งสำหรับดึงค่า Color Mode ของหน้าจอ

int colorMode = getWindow().getColorMode();

Permission

ชื่อเต็ม : android.Manifest.permission

เพิ่ม Permission เข้ามาใหม่อีก 10 ตัว

Manifest.permission.ALLOCATE_AGGRESSIVE     
Manifest.permission.BIND_AUTO_FILL     
Manifest.permission.BIND_VISUAL_VOICEMAIL_SERVICE     
Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE     
Manifest.permission.MANAGE_OWN_CALLS     
Manifest.permission.READ_PHONE_NUMBER     
Manifest.permission.REQUEST_DELETE_PACKAGES     
Manifest.permission.RESTRICTED_VR_ACCESS     
Manifest.permission.RUN_IN_BACKGROUND     
Manifest.permission.USE_DATA_IN_BACKGROUND

ALLOCATE_AGGRESSIVE เป็นการบังคับจองพื้นที่ข้อมูล ใช้สำหรับ System เท่านั้น ไม่สามารถเข้าไปใช้งานได้

BIND_AUTO_FILL สำหรับใช้งาน Auto Fill ที่เพิ่มเข้ามาใหม่ในเวอร์ชันนี้

BIND_VISUAL_VOICEMAIL_SERVICE สำหรับใช้งาน Visual Voicemail ที่เพิ่มเข้ามาใหม่ในเวอร์ชันนี้ (สามารถ)

INSTANT_APP_FOREGROUND_SERVICE เพื่อให้ Instant App สามารถทำงานเป็น Foreground Service ได้

MANAGE_OWN_CALLS สำหรับควบคุมการรับสายผ่านโค้ด (เวอร์ชันนี้ยอมให้ทำแบบนี้ได้แล้ว)

READ_PHONE_NUMBER ใช้ในการขอดึงเบอร์มือถือจากเครื่อง

REQUEST_DELETE_PACKAGES เพื่อให้สามารถเรียกหน้าต่างลบแอปฯขึ้นมาด้วยคำสั่ง ACTION_UNINSTALL_PACKAGE (ใช้ใน Intent) ซึ่งมีตั้งแต่สมัย API 14 แล้วล่ะ แต่ใน API 26 เป็นต้นไปจะต้องประกาศ Permission นี้ด้วยทุกครั้ง

RESTRICTED_VR_ACCESS เข้าถึง VR API บางอย่างที่ถูกจำกัดสิทธิ์ไว้ Permission นี้สำหรับ System App เท่านั้น

RUN_IN_BACKGROUND เพื่อให้แอปฯสามารถทำงานใน Background ได้ (ใช้ใน CompanionDeviceManager สำหรับ System App เท่านั้น)

USE_DATA_IN_BACKGROUND เพื่อให้แอปฯสามารถใช้งานอินเตอร์เน็ตใน Background ได้ (ใช้ใน CompanionDeviceManager สำหรับ System App เท่านั้น)

Fingerprint Gesture

เป็นหนึ่งฟีเจอร์ที่อยู่ใน Accessibility หรือโหมดคนพิการ ซึ่งเปิดให้นักพัฒนาสามารถดัก Event การ Gesture บน Fingerprint ได้

ถ้านึกไม่ออก ให้นึกถึง Google Pixel ที่ Fingerprint สามารถทำ Gesture ง่ายๆได้ เช่นปัดลงเพื่อเปิด Notification นั่นล่ะครับ ที่สามารถทำบน Android 8.0 Oreo ได้เลย แต่ก็ขึ้นอยู่กับแต่ละเครื่องด้วยว่า Fingerprint มีมั้ย และรองรับการทำ Gesture มั้ย

โดยจะมีคลาสสำคัญที่ชื่อว่า FingerprintGestureController ที่ใช้ในการ Register/Unregister Event Callback และลักษณะของ Gesture ก็จะมีอยู่ 4 แบบเท่านั้น คือ ปัดขึ้น, ปัดลง, ปัดซ้าย และปัดขวา

FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_DOWN
FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_LEFT
FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_RIGHT
FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_UP

Animator

เป็นหนึ่งใน Animation API ที่ใช้งานกันอยู่บ่อยๆ ในตอนนี้มีการเพิ่มคำสั่งให้กำหนดระยะเวลาที่จะเริ่มเล่นได้ เช่น ใช้ Animator เลื่อนจากตำแหน่ง X : 0 ไปยังตำแหน่ง X : 100 โดยใช้เวลาทั้งหมด 1,000 มิลลิวินาทีหรือ 1 วินาที

val windowManager: WindowManager= this
val isHdr = windowManager.defaultDisplay.isHdr
val isWideColorGamut = windowManager.defaultDisplay.isWideColorGamut

ระหว่างที่ Animator กำลังเล่นอยู่นั้น มีคำสั่งสำหรับเช็คได้ว่าตอนนี้กำลังเล่นอยู่ที่วินาทีเท่าไร และกำหนดได้ว่าจะให้ข้ามไปแสดงผลเป็นวินาทีที่เท่าไร (เหมือนเวลาดูหนังแล้วสั่งให้ข้ามไปเล่นวินาทีที่ต้องการ)

val currentPlayTime = animator.currentPlayTime
animator.currentPlayTime = 500

จากคำสั่ง setCurrentPlayTime จะทำให้ Animator ข้ามไปแสดงผลที่ 500 มิลลิวินาทีหรือ 0.5 วินาที ทันที

และสามารถสั่งให้เล่นย้อนกลับได้ด้วยนะเออ

animator.reverse()

ซึ่งคำสั่งสำหรับ Current Play Time และ Reverese นั้นจะใช้ได้ก็ต่อเมื่อ Animator ทำงานแล้วเท่านั้นนะจ๊ะ

เมื่อ Animator สามารถทำงานแบบ Reverse ได้ ดังนั้น Animator Listener จึงเพิ่ม Event เข้ามาอีก 2 ตัวเพื่อรองรับการทำงานแบบ Reverse

animator.addListener(object: Animator.AnimatorListener {
    override fun onAnimationStart(animation: Animator?) {

    }

    override fun onAnimationEnd(animation: Animator?) {
        
    }
    ...
})
แนะนำให้ใช้ AnimatorListenerAdapter แทน Animator.AnimatorListener

Fragment

สำหรับ Fragment Manager ในตอนนี้มี Fragment Lifecycle Callback ให้ใช้แล้วจ้าาาาาาาาาาาาาาา (Support v4 ก็มีให้ใช้แล้วเหมือนกัน)

private val callback: FragmentManager.FragmentLifecycleCallbacks =
    object : FragmentManager.FragmentLifecycleCallbacks() {
        override fun onFragmentPreAttached(manager: FragmentManager, fragment: Fragment, context: Context) {}
        override fun onFragmentAttached(manager: FragmentManager, fragment: Fragment, context: Context) {}
        override fun onFragmentCreated(manager: FragmentManager, fragment: Fragment, savedInstanceState: Bundle?) {}
        override fun onFragmentActivityCreated(manager: FragmentManager, fragment: Fragment, savedInstanceState: Bundle?) {}
        override fun onFragmentViewCreated(manager: FragmentManager, fragment: Fragment, view: View, savedInstanceState: Bundle?) {}
        override fun onFragmentStarted(manager: FragmentManager, fragment: Fragment) {}
        override fun onFragmentResumed(manager: FragmentManager, fragment: Fragment) {}
        override fun onFragmentPaused(manager: FragmentManager, fragment: Fragment) {}
        override fun onFragmentStopped(manager: FragmentManager, fragment: Fragment) {}
        override fun onFragmentSaveInstanceState(manager: FragmentManager, fragment: Fragment, outState: Bundle?) {}
        override fun onFragmentViewDestroyed(manager: FragmentManager, fragment: Fragment) {}
        override fun onFragmentDestroyed(manager: FragmentManager, fragment: Fragment) {}
        override fun onFragmentDetached(manager: FragmentManager, fragment: Fragment) {}
    }

ซึ่ง Callback ตัวนี้จะบอกให้หมดว่า Fragment ตัวไหนเกิด Lifecycle อะไร ส่วนการใช้งานก็จะเป็นลักษณะของ Register/Unregister

val manager = supportFragmentManager
manager.registerFragmentLifecycleCallbacks(callback, true)

// เมื่อไม่ใช้งานแล้ว
manager.unregisterFragmentLifecycleCallbacks(callback)

คำสั่ง Register นั้นสามารถกำหนดได้ว่าจะให้คำสั่งนี้ Recursive ใน Child Fragment Manager ด้วยหรือไม่

ส่วน Fragment นั้นก็มีการเพิ่มคำสั่งเข้ามาเล็กน้อย

fun isStateSaved(): Boolean
fun postponeEnterTransition()
fun startPostponedEnterTransition()

isStateSaved เช็คว่า Fragment State มีการ Save เก็บไว้เรียบร้อยแล้วหรือยัง

postponeEnterTransition กำหนดให้ Transition ของ Fragment ตัวนั้นอย่าเพิ่งทำงาน จนกว่าจะสั่งด้วยคำสั่ง startPostponedEnterTransition (มี Transition แต่ยังไม่อยากให้เล่นทันที)

startPostponedEnterTransition สั่งให้ Transition ของ Fragment ที่กำหนดด้วยคำสั่ง postponeEnterTransition เริ่มทำงาน

Notification

เพิ่มคำสั่งเพื่อรองรับ Channel และ Channel Group ซึ่งเจ้าของบล็อกแยกเป็นบทความเรื่องนี้ให้แล้ว สามารถตามไปอ่านกันได้ที่ Notification in Android ตอนที่ 5 — Notification Channel

Activity

สำหรับคลาส Activity จะเพิ่มคำสั่งต่างๆเพื่อให้รองรับการทำงานแบบ Picture in Picture และ Freeform Window

fun enterPictureInPictureMode(arge: PictureInPictureArgs): Boolean
fun isOverlayWithDecorCaptionEnabled(): Boolean
fun setOverlayWithDecorCaptionEnabled(enabled: Boolean)
fun setPictureInPictureArgs(args: PictureInPictureArgs)

และมีการเพิ่มคำสั่ง isActivityTransitionRunning เพื่อให้เช็คได้ว่า Activity ณ ตอนนั้นกำลังแสดง Transition ใดๆอยู่หรือป่าว

isActivityTransitionRunning(): Boolean

AppWidgetManager

ชื่อเต็ม : android.appwidget.AppWidgetManager

เพิ่มคำสั่งสำหรับแปะ Widget ลงใน Home Screen ผ่านโค้ดได้แล้ว เพราะเดิมทีนั้นให้ผู้ใช้เป็นคนลาก Widget ไปใส่เองเท่านั้น

fun isRequestPinAppWidgetSupported(): Boolean
fun requestPinAppWidget(ComponentName provider, PendingIntent successCallback): Boolean

Content Provider

มีการ Overload Method เพิ่มเข้ามาให้กับ Query และ Refresh เพื่อให้รองรับการยกเลิกกลางคันโดยใช้คลาส CancellationSignal

val signal = CancellationSignal()

// ใช้กับ Query
provider.query(uri, projection, queryArgs, signal)

// ใช้กับ Refresh
provider.refresh(uri, args, signal)

// เมื่อต้องการยกเลิกการ Query หรือ Refresh กลางคัน
signal.cancel()

Context

มีการเพิ่ม Service Manager เข้ามาใหม่ทั้งหมด 4 ตัวด้วยกัน (บางอันใช้สำหรับ System App เท่านั้น)

Context.COMPANION_DEVICE_SERVICE
Context.STORAGE_STATS_SERVICE
Context.TEXT_CLASSIFICATION_SERVICE
Context.WIFI_AWARE_SERVICE

และเพิ่มคำสั่งเข้ามาอีก 1 ตัว

fun createContextForSplit(splitName: String): Context

เป็นคำสั่งที่ใช้สำหรับสร้าง Context ขึ้นมาใหม่โดยใช้ Context ที่มีอยู่ ซึ่ง Context ที่ถูกสร้างขึ้นมาใหม่จะเป็นคนละตัวกับ Context ตัวเก่า แต่ก็ได้ Resource จากตัวเก่ามาใช้งานได้อยู่นะ

Intent

เพิ่มคำสั่ง removeFlags มาให้ จะได้ลบ Flag ที่ไม่ต้องการออกไปได้ (เดิมทีมีแค่ setFlags กับ addFlags)

val intent = Intent(...)
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)

// ลบบาง Flag ออก
intent.removeFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)

เพิ่ม Constant สำหรับใช้ใน Intent (Action, Category และ Extra) เข้ามาใหม่

Intent.ACTION_CLEAR_PACKAGE     
Intent.CATEGORY_TYPED_OPENABLE     
Intent.CATEGORY_VR_HOME     
Intent.EXTRA_CONTENT_ANNOTATIONS     
Intent.EXTRA_QUICK_VIEW_ADVANCED

และประกาศ Deprecated Constant ของ App Shortcut ทั้งหมด แล้วให้เปลี่ยนไปใช้คำสั่งที่เพิ่มเข้ามาใหม่ใน ShortcutManager แทน

// Deprecated
Intent.EXTRA_SHORTCUT_ICON
Intent.EXTRA_SHORTCUT_ICON_RESOURCE
Intent.EXTRA_SHORTCUT_INTENT
Intent.EXTRA_SHORTCUT_NAME

// ใช้อันนี้แทน
val manager: ShortcutManager = getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager
val info = ShortcutInfo.Builder(context, name)
    .setIcon(icon)
    .setIntent(intent)
    .build()
val intent = manager.createShortcutResultIntent(info)

Package Manager

ชื่อเต็ม : android.content.pm.PackageManager

เพิ่ม Feature เข้ามาใหม่ 3 ตัว

PackageManager.FEATURE_EMBEDDED 
PackageManager.FEATURE_VULKAN_HARDWARE_COMPUTE
PackageManager.FEATURE_WIFI_AWARE

Bitmap

คลาส Bitmap มีการเพิ่ม​ Static Method ที่ใช้ในการสร้าง Bitmap Instance มา 2 ตัว สำหรับการสร้าง Bitmap แบบ ARGB_8888 หรือ RGBA_16F

fun createBitmap(display: DisplayMetrics, width: Int, height: Int, config: Bitmap.Config, hasAlpha: Boolean): Bitmap
fun createBitmap(width: Int, height: Int, config: Bitmap.Config, hasAlpha: Boolean): Bitmap

ส่วน BitmapFactory.Options เพิ่มตัวแปรที่ชื่อว่า outConfig เข้ามาด้วย เป็นค่าที่บอกว่า Bitmap นั้นๆใช้ Decode แบบไหนอยู่

BitmapFactory.Options options = new BitmapFactory.Options(); options.outConfig = Bitmap.Config.RGBA_F16;

Canvas

ชื่อเต็ม : android.graphics.Canvas

มีการประกาศ Deprecated คำสั่งตระกูล Clip ที่ต้องกำหนด Region.Op ทุกตัว เพื่อให้ไปใช้เป็นคำสั่ง Clip Out แทน

// Deprecated
fun clipPath(path: Path, op: Region.Op): Boolean
fun clipRect(rect: Rec, op: Region.Op): Boolean
fun clipRect(rectF: RectF, op: Region.Op): Boolean
fun clipRect(left: Float, top: Float, right: Float, op: Region.Op): Boolean

// ใช้คำสั่งเหล่านี้แทน
fun clipOutPath(path: Path): Boolean
fun clipOutRect(rect: Rect): Boolean
fun clipOutRect(rectF: RectF): Boolean
fun clipOutRect(left: Float, top: Float, right: Float, bottom: Float): Boolean
fun clipOutRect(left: Int, top: Int, right: Int, bottom: Int): Boolean

Color

ชื่อเต็ม : android.graphics.Color

เพิ่มคำสั่งใหม่เข้ามาเยอะมากกกกกกกกกกกกกกก

Sensor

ชื่อเต็ม : android.hardware.Sensor

เพิ่มประเภทของ Sensor เข้ามาใหม่อีก 2 แบบ

Sensor.TYPE_ACCELEROMETER_UNCALIBRATED
Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT

Location

ชื่อเต็ม : android.location.Location

เพิ่มคำสั่งสำหรับ Bearing Accuracy, Speed Accuracy และ Vertical Accuracy

fun getBearingAccuracyDegrees(): Float
fun getSpeedAccuracyMetersPerSecond(): Float
fun getVerticalAccuracyMeters(): Float

fun hasBearingAccuracy(): Boolean
fun hasSpeedAccuracy(): Boolean
fun hasVerticalAccuracy(): Boolean

fun removeBearingAccuracy()
fun removeSpeedAccuracy()
fun removeVerticalAccuracy()

fun setBearingAccuracyDegrees(float bearingAccuracyDegrees)
fun setSpeedAccuracyMetersPerSecond(float speedAccuracyMeterPerSecond)
fun setVerticalAccuracyMeters(float verticalAccuracyMeters)

ExifInterface

ชื่อเต็ม : android.media.ExifInterface

เพิ่มคำสั่งสำหรับ Thumbnail ใน EXIF นั้นๆ

fun getThumbnailBitmap(): Bitmap 
fun getThumbnailBytes(): ByteArray 
fun isThumbnailCompressed(): Boolean 

และเพิ่ม Tag ใหม่เพื่อให้ครอบคลุมมากขึ้น

ExifInterface.TAG_DEFAULT_CROP_SIZE     
ExifInterface.TAG_DNG_VERSION     
ExifInterface.TAG_NEW_SUBFILE_TYPE     
ExifInterface.TAG_ORF_ASPECT_FRAME     
ExifInterface.TAG_ORF_PREVIEW_IMAGE_LENGTH     
ExifInterface.TAG_ORF_PREVIEW_IMAGE_START     
ExifInterface.TAG_ORF_THUMBNAIL_IMAGE     
ExifInterface.TAG_RW2_ISO     
ExifInterface.TAG_RW2_JPG_FROM_RAW     
ExifInterface.TAG_RW2_SENSOR_BOTTOM_BORDER     
ExifInterface.TAG_RW2_SENSOR_LEFT_BORDER     
ExifInterface.TAG_RW2_SENSOR_RIGHT_BORDER     
ExifInterface.TAG_RW2_SENSOR_TOP_BORDER     
ExifInterface.TAG_SUBFILE_TYPE

Media Recorder

มี Output Format แบบ MPEG_2_TS แล้วนะ

MediaRecorder.OutputFormat.MPEG_2_TS

Build

ชื่อเต็ม : android.os.Build

เปลี่ยนวิธีการดึงค่า Serial ที่เดิมจะเรียกจากตัวแปรโดยตรง ให้ไปเรียกผ่าน Getter ที่เพิ่มเข้ามาใหม่แทน

// Deprecated
var serial = Build.SERIAL

// ใช้คำสั่งนี้แทน
var serial = Build.getSerial()

และเพิ่ม Version Code สำหรับ Android 8.0 Oreo เข้ามาด้วย

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    // DO something
}

Bundle

ชื่อเต็ม : android.os.Bundle

เพิ่มคำสั่งสำหรับ Hard Copy เข้ามาเพื่อให้สามารถสร้าง Bundle จาก Bundle อีกตัวได้ โดยที่ทั้งสองตัวนั้นเป็น Object คนละตัวกัน

val oldBundle = Bundle()
oldBundle.putString(KEY_NAME, name)
oldBundle.putString(KEY_COUNTRY, country)

// สร้าง Bundle ตัวใหม่ โดยที่ยังมีค่าเดิมที่กำหนดไว้อยู่
val newBundle: Bundle = oldBundle.deepCopy()

Parcelable

Parcel รองรับ SparseIntArray แล้ว จากเดิมที่รองรับแค่ SparseArray กับ SparseBooleanArray (ทำไมไม่มี SparseLongArray ด้วยนะ)

PreferenceDataStore

เป็นคลาสใหม่ที่สร้างขึ้นมาใช้ใน Preference เวลาที่ผู้ใช้ตั้งค่าการทำงานต่างๆ (หน้าที่ใช้ PreferenceActivity หรือ PreferenceFragment) ซึ่งเป็นคลาสที่สามารถนำมาใช้แทนที่ SharedPreference เดิมได้เลย

class UserSettingFragment : PreferenceFragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        PreferenceManager.setDefaultValues(activity, R.xml.advanced_preferences, false)
        addPreferencesFromResource(R.xml.activity_preference)
    }

    fun updateNamePreference() {
        val dataStore = preferenceManager.preferenceDataStore
        dataStore.putString(KEY_NAME, etUsername.getText().toString())
    }
    ...
}

จากการนั่งดูแล้วก็ยังไม่เข้าใจเหมือนกันว่าทำไมถึงสร้างคลาสตัวนี้ขึ้นมาเพื่อใช้แทน SharedPreference เฉพาะใน Preference เท่านั้น…

แต่ถ้าเป็น Activity หรือ Fragment ทั่วๆไป ไม่ต้องสนใจครับ ใช้ SharedPreference เหมือนเดิมน่ะแหละ

Settings

ชื่อเต็ม : android.provider.Settings

เพิ่ม Action และ Extra ที่เกี่ยวกับ Notification เพิ่มเข้ามา

Settings.ACTION_APP_NOTIFICATION_SETTINGS     
Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS     
Settings.ACTION_MANAGE_EXTERNAL_SOURCES     
Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS     
Settings.EXTRA_APP_PACKAGE     
Settings.EXTRA_CHANNEL_ID

TelephonyManager

ชื่อเต็ม : android.telephony.TelephonyManager

มีหลายๆอย่างในนี้ที่เพิ่มเข้ามา แต่บางอย่างก็ไม่น่าสนใจ บางอย่างก็มีไว้สำหรับ System App ซึ่งที่น่าสนใจที่สุดก็คงจะเป็นการที่เปิดให้สามารถส่ง USSD และรับ Reponse ได้แล้ว เพราะเดิมต้องไปเรียกผ่าน Intent โดยใช้ ACTION_CALL ซึ่งไม่สามารถดักข้อมูลที่ส่งกลับมาได้

val ussdCode = "*103#"

val manager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
manager.sendUssdRequest(ussdCode, object : TelephonyManager.UssdResponseCallback() {
    fun onReceiveUssdResponse(request: String?, response: CharSequence?) {
        
    }

    fun onReceiveUssdResponseFailed(request: String?, failureCode: Int) {
        
    }
}, Handler())

TransitionListenerAdapter

ชื่อเต็ม : android.transition.TransitionListenerAdapter

เพิ่มเข้ามาเพื่อทดแทนการใช้ Transition.TransitionListener ที่ทำให้โค้ดยาวเหยียด (เหมือนกับ AnimatorListenerAdapter เลย)

InputDevice

ชื่อเต็ม : android.view.InputDevice

เพิ่ม Constant เข้ามาใหม่อีก 2 ตัว

InputDevice.SOURCE_MOUSE_RELATIVE InputDevice.SOURCE_ROTARY_ENCODER

มันมีใครควบคุมอุปกรณ์แอนดรอยด์ผ่าน Input Device พวกนี้ด้วยหรอเนี่ย…

MotionEvent

ชื่อเต็ม : android.view.MotionEvent

เพิ่ม Constant ที่ชื่อว่า AXIS_SCROLL เพื่อรองรับกับ Input Device ที่สามารถ Scroll ได้

MotionEvent.AXIS_SCROLL

ViewGroup

ชื่อเต็ม : android.view.ViewGroup

ประกาศ Deprecated บางคำสั่ง

// Deprecated 
fun invalidateChild(child: View, dirty: Rect)
fun invalidateChildInParent(location: IntArray, dirty: Rect): ViewParent

// ใช้คำสั่งนี้แทน
fun onDescendantInvalidated(child: View, target: View)

DatePicker

ชื่อเต็ม : android.widget.DatePicker

เพิ่ม Listener เมื่อมีการเปลี่ยนวันที่

val datePicker: DatePicker = ...
datePicker.setOnDateChangedListener { view, year, monthOfYear, dayOfMonth -> 
	// Do something when date changed    
}

ProgressBar

ชื่อเต็ม : android.widget.ProgressBar

สามารถกำหนด Minimum Progress ได้แล้วววววววววววววว

val progressBar: ProgressBar = ...

// กำหนดค่า Minimum Progress
val minimumProgress = 10
progressBar.min = minimumProgress

// ดังค่า Minimum Progress
val minProgress = progressBar.min

และเพิ่มคำสั่ง isAnimating เพื่อใช้แทน isIndeterminate ด้วย เพราะว่า isAnimating นั้นหมายถึง ProgressBar กำลังอยู่ใน Indeterminate Mode และ Visible อยู่ (นอกเหนือจากนี้จะเป็น False ทั้งหมด)

val isAnimating: Boolean = progressBar.isAnimating()

เก็บตกคำสั่งอื่นๆที่ถูก Remove และ Deprecate

  • คลาส ProgressDialog ถูกประกาศ Deprecate ทั้งคลาส ให้สร้าง Dialog ที่มี Progress Bar เอง
  • คลาส DialerFilter ถูกประกาศ Deprecate ทั้งคลาส ให้ใช้ Custom View หรือ Layout จัดการเองแทน
  • คลาส ZoomButton และ ZoomButtonsController ถูกประกาศ Deprecate ให้ใช้วิธีเขียนคำสั่งเพื่อควบคุมการทำงานเอง
  • คำสั่ง findViewTraversal และ findViewWithTagTraversal ในคลาส K ถูกลบออกไป
  • คลาส RasterizerSpan ถูกลบออกไป
  • คลาส LayerRasterizer ถูกลบออกไป
  • คลาส Rasterizer ถูกลบออกไป
  • คลาส PskKeyManager ถูกลบออกไป

สรุป

เยอะมากกกกกกกกก นี่ขนาดตัดหลายๆอันออกไปแล้วนะ (แถมเอามาแค่ Android เท่านั้น ยังไม่นับส่วนของ Java) ซึ่งหลายๆคำสั่งที่เปลี่ยนไปก็น่าจะกระทบกับนักพัฒนาหลายๆคนอยู่บ้าง (เจ้าของบล็อกก็ด้วย) ถ้าจะแก้ไขให้รองรับกับ Android 8.0 Oreo ก็อย่าลืมเรื่อง Backward Compatibility ด้วยนะ

สุดท้ายแล้วก็ให้หมั่นอัปเดตอยู่บ่อยๆ ว่าในแต่ละเวอร์ชันเนี่ยมีอะไรเพิ่มเข้ามาบ้าง เปลี่ยนอะไรบ้าง อันไหนเลิกใช้ ให้ใช้อันไหนแทน เพื่อให้แอปฯของผู้ที่หลงเข้ามาอ่านทำงานได้ปกติสุขบนอุปกรณ์แอนดรอยด์ที่ต่างเวอร์ชันกันครับ

แหล่งข้อมูลอ้างอิง ​