"Fly high but don't fly alone"
มาดูกันว่า Android O มีอะไรใหม่บ้าง ในแบบฉบับนักพัฒนา
25 Mar 2017 05:51   [47382 views]

Android N ยังมีเครื่องได้ใช้กันไม่ถึง 3% ทั่วโลก คืนก่อนอยู่ดี ๆ กูเกิลก็ประกาศเปิดตัว Android O มาเป็นที่เรียบร้อยแล้ว ซึ่งถามว่าแปลกมั้ย ? จริง ๆ ไม่แปลกนะ เพราะปีที่แล้วกูเกิลก็ปล่อย Android N แบบนี้ ช่วงเวลาประมาณนี้เหมือนกันเลย (เดือนมีนาคม) ซึ่งก็เป็นไปตามแผน Release ของกูเกิลนั่นเอง

อย่างไรก็ตาม Android O ยังไม่มีชื่ออย่างเป็นทางการแต่อย่างใด แต่เล่นคำด้วยชื่อ O-MG เรามาดูกันว่า Android O มีอะไรน่าสนใจบ้างในมุมมองของนักพัฒนาครับ

Notification Channels

Notification เป็นหนึ่งในสิ่งที่เป็นสุดยอดนวัตกรรมของแอนดรอยด์จนทุก OS ต้องลอกตาม ไม่ต้องแปลกใจว่าทุกครั้งที่แอนดรอยด์อัปเดตเวอร์ชันใหม่ เราก็จะได้พบฟีเจอร์ใหม่ ๆ เกี่ยวกับ Notification ทุกครั้งไป

Android O ก็มีการเปลี่ยนแปลงกับ Notification หลายอย่างมาก อย่างแรกที่จะพามารู้จักคือ Notification Channels หรือ การแบ่ง Notification ออกเป็นหลาย ๆ ประเภท

แรกเริ่มเดิมที ตั้งแต่แอนดรอยด์เวอร์ชันแรก Notification ของแต่แอป ฯ มีศักดิ์เท่ากัน เวลาเราตั้งปิด Notification ก็จะปิดไปหมด เวลาจะตั้งค่าอะไรก็จะถูกนำไปใช้กับ Notification ทั้งหมดของแอป ฯ นั้น

แต่ในแง่ของการใช้งานจริง Notification ของแต่ละแอป ฯ มีหลายรูปแบบมาก ยกตัวอย่างเช่น Instagram ก็มี Notification ของ Like, Comment, Mention ฯลฯ แล้วถ้าเกิดเราอยากจะปิดไม่รับเฉพาะ Notification ของ Like ส่วนที่เหลือยังเหมือนเดิมหละ ?

แต่ก่อนแต่ละแอป ฯ ต้องมานั่งเขียนเอง (ซึ่งเอาเข้าจริงก็แทบไม่มีแอป ฯ ไหนทำ) แต่ด้วยความสามารถของ Android O ตอนนี้เราสามารถแบ่งแยกประเภทของ Notification ได้แล้ว และแต่ละอันสามารถจัดการตั้งค่าต่าง ๆ แยกกันได้เลย และนี่เองที่เราเรียกว่า Notification Channels ครับ หน้าตาจะเป็นแบบนี้

วิธีการใช้งานก็ไม่ยาก แค่ "กดค้าง" ที่ Notification ของแอป ฯ นั้น ๆ แล้วรูปแบบของ Notification จะเปลี่ยนไปเพื่อให้เราตั้งค่าให้แต่ละ Channel ได้ (ในมุมมองผู้ใช้ เราจะเรียก Channel ว่า Category)

เมื่อกดที่ ALL CATEGORIES เราก็จะสามารถเข้าไปจัดการการตั้งค่าของแต่ละ Category ได้

อันนี้ลองกดเข้าไปดูใน Category นึง

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

แล้วถามว่าแต่ละ Category สามารถตั้งค่าอะไรได้บ้าง ? ... ตามนี้ครับ

Importance - ระดับความสำคัญของ Notification ใน Category นั้น ๆ (IMPORTANCE_NONE จนถึง IMPORTANCE_HIGH)

Sound - กำหนดเสียงเตือน

Lights - กำหนดรูปแบบของไฟ LED (เปิด/ปิด และ สีไฟ)

Vibration - กำหนดรูปแบบการสั่ง (เปิด/ปิด และ Pattern การสั่น)

Show on lockscreen - กำหนดว่าจะให้ Notification ใน Channel นี้ปรากฎบน Lockscreen หรือไม่

Override do not disturb - ถ้าเครื่องตั้งโหมด DND ไว้ จะให้ Notification นี้ทะลุโหมดนั้นได้มั้ย

โดยโค้ดที่ต้องทำก็ไม่มีอะไรมาก อย่างแรกคือต้องสร้าง Notification Channel ขึ้นมาก่อน พร้อมระบุว่ามันชื่ออะไร โค้ดตามนี้ ตรงไปตรงมามาก

NotificationManager mNotificationManager =
        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// The id of the channel.
String id = "my_channel_01";
// The user visible name of the channel.
CharSequence name = getString(R.string.channel_name);
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel mChannel = new NotificationChannel(id, name, importance);
// Configure the notification channel.
mChannel.enableLights(true);
// Sets the notification light color for notifications posted to this
// channel, if the device supports this feature.
mChannel.setLightColor(Color.RED);
mChannel.enableVibration(true);
mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
mNotificationManager.createNotificationChannel(mChannel);

ก็จะเห็นว่าสามารถกำหนดพวกการตั้งค่าต่าง ๆ ได้ง่าย ๆ เลย ซึ่งพอสร้าง Channel เสร็จแล้ว เราก็จะใช้เจ้าชื่อ Channel นี่แหละในการอ้างอิง Channel โค้ดส่วนอื่น ๆ ซึ่งในที่นี้คือ my_channel_01

คราวนี้โค้ดส่วนการแสดง Notification ก็จะเปลี่ยนไปเล็กน้อยด้วยการเพิ่มคำสั่ง .setChannel(...) เข้าไป

mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Sets an ID for the notification, so it can be updated.
int notifyID = 1;
// The id of the channel.
String CHANNEL_ID = "my_channel_01";
// Create a notification and set the notification channel.
Notification notification = new Notification.Builder(MainActivity.this)
        .setContentTitle("New Message")
        .setContentText("You've received new messages.")
        .setSmallIcon(R.drawable.ic_notify_status)
        .setChannel(CHANNEL_ID)
        .build();
// Issue the notification.
mNotificationManager.notify(id, notification);

และถ้าไม่ต้องการใช้ Notification Channel นั้น ๆ แล้วก็สามารถลบทิ้งได้เช่นกัน

NotificationManager mNotificationManager =
        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// The id of the channel.
String id = "my_channel_01";
NotificationChannel mChannel = mNotificationManager.getNotificationChannel(id);
mNotificationManager.deleteNotificationChannel(mChannel);

อันนี้เป็นการอธิบายพอสังเขป ยกมาเฉพาะส่วนที่สำคัญ ความจริงยังมีรายละเอียดอื่น ๆ อีก สามารถอ่านเพิ่มได้ใน Notification Channels ครับ =)

Notification Snoozing

เราสามารถตั้งให้ Notification ซ่อนไปก่อนแล้วค่อยโผล่ทีหลังได้ ซึ่งเหมือนกับที่เรา Snooze นาฬิกาปลุกนั่นเอง วิธีการใช้งานก็แค่ลาก Notification ไปทางขวาแล้วที่กดไอคอนนาฬิกา

สามารถตั้งให้ Snooze ได้สามเวลา 15 นาที 30 นาที และ 1 ชั่วโมง ครับ

Notification Timeouts

แต่ก่อนถ้าเราต้องการยกเลิก Notification ใด ๆ ภายในเวลาที่กำหนด เราต้องมาเขียนโปรแกรมเอง แต่ตอนนี้ไม่ต้องแล้วด้วยคำสั่ง Notification.Builder.setTimeout() ที่เปิดให้เราสามารถตั้ง Timeout ได้ทันที ใช้งานง่ายด้วย แค่ใส่คำสั่งเพิ่มไปใน Builder บรรทัดเดียวเท่านั้นเอง

Notification Dismissal

เราสามารถตรวจสอบได้แล้วว่า Notification ถูกกำจัดทิ้งด้วยการที่ผู้ใช้ปัดทิ้งหรือว่าโปรแกรมสั่งลบทิ้ง โดยตรงนี้จะถูกเพิ่มเข้ามาในคลาส NotificationListenerService ด้วย Callback ที่ชื่อว่า onNotificationRemoved(...)

Notification Background Colors

เราสามารถกำหนดสีพื้นหลังของ Notification ได้แล้วด้วยคำสั่ง Notification.Builder.setColor(int) อย่างไรก็ตาม ในแง่ของ UX เราควรจะเปลี่ยนสีพื้นหลังก็ต่อเมื่อจำเป็นจริง ๆ เท่านั้น อย่าเปลี่ยนมั่วซั่วเพราะผู้ใช้จะสับสนและไม่ชอบ

Background Execution Limits

ตั้งแต่ Android M เป็นต้นมา หนึ่งในมิชชั่นหลักของการพัฒนา Android OS คือ "การประหยัดพลังงาน" จะเห็นได้จากการที่ AOSP ผลักดันโปรเจคต่าง ๆ ที่เกี่ยวข้องกับการจำกัดการใช้พลังงานออกมามากมาย เช่น Doze ซึ่งไม่ต้องแปลกใจ ใน Android O ก็มีการเปลี่ยนแปลงบางอย่างเพิ่มเติมเข้ามาอีกเพื่อให้ประหยัดแบตลงไปอีกอย่างเจ้า Background Execution Limits

โดยแนวแนวคิดพื้นฐานของ Background Execution Limits คือ โดยปกติแอนดรอยด์จะอนุญาตให้แอป ฯ หลาย ๆ ตัวทำงานพร้อมกันได้ ไม่ว่าจะในรูปแบบ Activity หรือ Service แต่นั่นเองคือปัญหา เพราะเมื่อมีแอป ฯ รันขึ้นมาพร้อมกันหลาย ๆ ตัวเข้า มันก็จะยิ่งกินแบตเยอะขึ้นเรื่อย ๆ

ทำยังไงดีหละ ? ... Android O ก็เลยทำการจำกัดการทำงานของแอป ฯ ที่อยู่ใน Background เสียเลย โดยแบ่งเป็นสองอย่างด้วยกัน ได้แก่ Background Service Limitations และ Broadcast Limitations

Background Service Limitations

สำหรับแอป ฯ ที่ถูกตรวจจับว่าเป็น Background App บน Android O จะมีข้อจำกัดในการทำงานร่วมกับ Service อยู่หลายอย่างด้วยกัน แต่ก่อนจะถึงจุดนั้น ถามว่าอะไรคือนิยามของ Background App ? ... ขอตอบแบบนี้น่าจะง่ายกว่า ถ้าเกิดเข้าข่ายข้อใดข้อหนึ่งด้านล่างนี้จะถือว่าไม่เป็น Background App (หรือพูดง่าย ๆ คือเป็น Foreground App) ครับ

- มี Activity ของ App นั้น ๆ ปรากฏอยู่บนจอ ไม่ว่าจะอยู่ในสถานะ Started, Resumed หรือ Paused ก็ตาม (ถ้าเป็น Android Developer ที่แท้จริงต้องรู้ทันทีว่าสถานะ Started, Resumed และ Paused คือมีส่วนใดส่วนหนึ่งปรากฏอยู่บนจอ)

- App นั้น ๆ มี Foreground Service รันอยู่ ซึ่งก็คือ Service ที่จะไม่ถูกทำลายทิ้งเมื่อเมมโมรี่ต่ำและจะมี Notification ปรากฏอยู่บน Notification Bar เช่นพวก Service ของแอป ฯ เล่นเพลง เป็นต้น

- ถ้ามี Foreground App ใดก็ตามเชื่อมต่อกับแอป ฯ นั้น ๆ อยู่ ไม่ว่าจะทาง Service Binding หรือผ่านทาง Content Provider ก็ตาม

หากตกในข้อใดข้อหนึ่งในด้านบนนี้จะถือว่าเป็น Foreground App และจะมีสิทธิ์ทำงานเหมือนเดิมไม่มีอะไรเปลี่ยนแปลงครับ แต่ถ้าไม่มีสักข้อจะถือว่าเป็น Background App และจะมีข้อจำกัดดังนี้

หลังจากแอป ฯ ถูกย้ายไปเป็น Background มันจะยังใช้งานได้ปกติอีกหลายนาที (ไม่รู้ตัวเลขที่แน่นอน) ยังคงสามารถสร้าง Service และใช้งานได้เหมือนเดิมทุกประการ ก่อนที่มันจะถูกเปลี่ยนสถานะเป็น Idle ซึ่งในสถานะนี้ Service ที่ถูกสร้างขึ้นจะถูกสั่งให้หยุดโดยอัตโนมัติ เหมือนกับว่าเราสั่งคำสั่ง Service.stopSelf() เลยนั่นเอง

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

ซึ่งสิ่งที่ต้องโน้ตไว้อย่างนึงคือ

Bound Service จะยังคงทำงานได้เหมือนเดิมนะ ไม่มีข้อจำกัดอะไร แอป ฯ ยังคงสามารถ Bind เข้า Service ได้ไม่ว่าจะอยู่บน Background หรือ Foreground

ไม่งงเนอะ

แล้วถามว่าถ้าอยู่ดี ๆ Service ก็ตายไปแบบนี้ เราจะใช้อะไรแทนได้ ? คำตอบคือเราสามารถใช้ JobScheduler ในการทำงานได้ครับ ก็ตรงไปตรงมาคือตั้งเวลาแล้วให้มันทำงานที่เราต้องการตามช่วงเวลาที่กำหนดไว้นั่นเอง ... Back to the basic เนอะ ...

อย่างไรก็ตาม จะมีอยู่บางกรณีที่ถึงแม้แอป ฯ จะกลายเป็น Background App แล้ว แต่ก็จะยังคงสามารถสร้างและใช้งาน Service ได้อยู่ ก็คือแอป ฯ ที่ตั้งไว้ให้ Handle Task ที่เกี่ยวข้องโดยตรงกับผู้ใช้ เช่น

- เมื่อแอป ฯ ต้องจัดการข้อความที่เข้ามาทาง Firebase Cloud Messaging แบบ High Priority

- เมื่อแอป ฯ ได้รับ Broadcast ที่เกี่ยวข้องกับ SMS และ MMS Message

- เมื่อแอป ฯ ต้องจัดการ PendingIntent จาก Notification

ซึ่งส่วนใหญ่กรณีด้านบน ๆ จะรับกันทาง BroadcastReceiver แต่ถ้ามีข้อจำกัดก็จะทำงานต่อไม่ได้เนอะ มันก็เลยถูกยกเว้นไว้ครับ

หากอยากรู้เรื่องราวเพิ่มเติมสามารถอ่านได้ที่ Background Service Limitations ครับ

Broadcast Limitations

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

ใน Android N (API Level 24) มีความพยายามในการอุดปัญหาตรงนี้ไว้แล้วด้วยการยกเลิกบาง Broadcast ที่มีการยิงบ่อย ๆ ทิ้งไปด้วย Background Optimization ส่วนบน Android O ก็เพิ่มบางอย่างเข้าไปอีกดังนี้

- เราไม่สามารถผูก Implicit Broadcast เข้ากับ BroadcastReceiver ผ่านการประกาศใน AndroidManifest ได้แล้ว ซึ่ง Implicit Broadcast คือ Action ที่ไม่ระบุเฉพาะเจาะจงว่าต้องส่งให้แอป ฯ นั้น ๆ ยกตัวอย่างเช่น ACTION_PACKAGE_REPLACED ที่เอาไว้ตรวจสอบว่ามีแอป ฯ ใด ๆ ถูกอัปเดต นี่จะประกาศผูกใน AndroidManifest ไม่ได้แล้ว

- แต่ถ้าเป็น Explicit Broadcast หรือ Action ที่ระบุเฉพาะเจาะจงว่าต้องเป็นแอป ฯ นั้น ๆ จะยังใช้งานได้อยู่ เช่น ACTION_MY_PACKAGE_REPLACED ที่เอาไว้ตรวจสอบอย่างเฉพาะเจาะจงว่าแอป ฯ เราถูกอัปเดต อันนี้จะยังประกาศใช้ได้อยู่

- คำสั่ง Context.registerReceiver() จะยังใช้ผูก Broadcast แบบ Runtime ได้อยู่ทั้งแบบ Implicit และ Explicit

ผลก็จะเห็นชัดเจน จากนี้จะมีแอป ฯ ถูกเรียกใช้งานจาก Broadcast น้อยลงมากอย่างมีนัยสำคัญ ซึ่งแน่นอน ก็จะประหยัดแบตลงมาก ๆ ด้วย

แล้วถามว่าถ้าใช้ Implicit Broadcast ไม่ได้ ทางออกคืออะไร ? ... คำตอบคือ ... JobScheduler อีกแล้วจ้าาา หลาย ๆ เงื่อนไขใน JobScheduler นี่มีฟังก์ชันเหมือนกับ Implicit Broadcast เลย เช่น การสั่งให้เริ่มทำงานเมื่อมีการเสียบสายชาร์จ บน Android O จะใช้ไม่ได้ละ ก็ต้องมาใช้ JobScheduler ตั้งเงื่อนไขการเสียบสายชาร์จแทน ใช้งานได้เหมือนกัน

ประมาณนี้ครับ ลองอ่านข้อมูลเพิ่มเติมได้ที่ Broadcast Limitations ได้เลย

Background Location Limits

การเข้าถึง GPS ก็เป็นอีกหนึ่งงานที่กินแบตเยอะมากกกกกกกก Android O เลยแก้ไขด้วยการจำกัดให้แอป ฯ ที่อยู่ใน Background สามารถได้รับการอัปเดตพิกัดได้น้อยลงมาก ๆ คือเหลือเพียงชั่วโมงละไม่กี่ครั้งเท่านั้น (ตัวเลขยังไม่แน่นอน ทางทีมพัฒนาแอนดรอยด์ยังคงศึกษาและทดลองเพื่อหาค่าที่เหมาะสมอยู่)

ซึ่งตรงนี้จริง ๆ แล้วใช้วิธีคิดเหมือน Background Service Limitations ทุกประการคือ ถ้าแอป ฯ อยู่ในสถานะเหล่านี้จะถือว่าเป็น Foreground

- มี Activity ของ App นั้น ๆ ปรากฏอยู่บนจอ ไม่ว่าจะอยู่ในสถานะ Started, Resumed หรือ Paused ก็ตาม (ถ้าเป็น Android Developer ที่แท้จริงต้องรู้ทันทีว่าสถานะ Started, Resumed และ Paused คือมีส่วนใดส่วนหนึ่งปรากฏอยู่บนจอ)

- App นั้น ๆ มี Foreground Service รันอยู่ ซึ่งก็คือ Service ที่จะไม่ถูกทำลายทิ้งเมื่อเมมโมรี่ต่ำและจะมี Notification ปรากฏอยู่บน Notification Bar เช่นพวก Service ของแอป ฯ เล่นเพลง เป็นต้น

- ถ้ามี Foreground App ใดก็ตามเชื่อมต่อกับแอป ฯ นั้น ๆ อยู่ ไม่ว่าจะทาง Service Binding หรือผ่านทาง Content Provider ก็ตาม

แต่ถ้าไม่อยู่ในกรณีใดเลยจะถือว่าเป็น Background App และข้อจำกัดเรื่องการได้รับอัปเดตพิกัดล่าสุดจะส่งผลทันที

แล้วถ้าจะทำแอป ฯ ที่ซีเรียสเรื่องพิกัดหละ แบบต้องอัปเดตตลอดเวลา จะต้องทำยังไง ? ... คำตอบคือ ทำให้แอป ฯ อยู่ในสถานะ Foreground ด้วยการรัน Foreground Service เพื่อขออัปเดตพิกัดนั่นเอง เพียงเท่านี้ก็จะใช้งานได้เหมือนเดิมแล้วครับ ซึ่งในแง่ User Experience ก็จะดีด้วย เพราะเราต้องสร้าง Notification บอกผู้ใช้ว่าแอป ฯ เราเรียกใช้ตัว Location อยู่นะ

ข้อมูลเพิ่มเติมอ่านได้ที่ Background Location Limits ครับ

Autofill Framework

ก็เป็นฟีเจอร์ที่น่าจะคุ้นเคยกันแล้วเพราะว่ามีให้ใช้นานแล้วบน Web Browser แต่ตอนนี้มีให้ใช้แล้วบนแอป ฯ แอนดรอยด์แบบ Native

หลักการทำงานดูเหมือนจะง่าย แต่เอาเข้าจริงก็แอบซับซ้อนนิดหน่อยเพราะ Autofill ไม่ได้เป็นฟีเจอร์ที่ใช้งานได้เลยพร้อมแอนดรอยด์ แต่แท้จริงแล้วมันเป็น "Framework" การจะเปิดใช้งานได้ เครื่องนั้น ๆ จะต้องติดตั้ง Autofill Service ก่อน สามารถลงได้หลายตัวและสามารถเลือกได้ว่าจะใช้ตัวไหนจากหน้า Settings -> Apps & Notifications -> Default apps -> Autofill app

หากนึกภาพไม่ออก มันก็เหมือนกับคีย์บอร์ดนั่นแหละ เราสามารถลงได้หลายตัวแล้วเลือกเอาว่าจะใช้ตัวไหน

และ Autofill Service ตัวนี้แหละที่จะช่วยจัดการเก็บข้อมูลต่าง ๆ ใน View ต่าง ๆ ให้เราแล้วก็จะ Autofill ให้ในการใช้งานครั้งถัดไป

ในส่วนของแอป ฯ ที่ต้องการให้ใช้ Autofill ได้ พวก View มาตรฐานต่าง ๆ จะใช้งานได้อัตโนมัติทันที ไม่ต้องทำอะไรพิเศษ แต่ถ้าจะให้ Custom View ใช้งานได้อาจจะต้องเขียนโค้ดเพิ่มเติมเล็กน้อย

โดยสรุปแล้ว Autofill จะมีอยู่สองส่วนด้วยกัน

1) Autofill Service - อันนี้โค้ดค่อนข้างซับซ้อนมาก แต่เราไม่จำเป็นต้องเขียนเอง ถ้ามีคนทำปล่อยบน Play Store ก็โหลดมาลงและใช้งานได้ทันที (เหมือนคีย์บอร์ดนั่นแหละ) แต่ถึงเวลาจริงก็ต้องดูให้ดีนะ หากนักพัฒนาไว้ใจไม่ได้ พวกข้อมูลต่าง ๆ ก็จะถูกขโมยไปได้อย่างง่ายดายเลย อ้อยเข้าปากช้าง

2) แอป ฯ - ถ้าใช้ View มาตรฐานทั่วไปก็ไม่ต้องเขียนอะไรเพิ่มเติม ใช้งานได้ทันที

หากต้องการศึกษาเพิ่มเติมสามารถอ่านได้จาก Autofill ครับ ส่วนนี่เป็นโค้ดตัวอย่าง android-AutofillFramework มีทั้ง Autofill Service และแอป ฯ สำหรับทดสอบครับ

Fonts in XML

Custom Font เป็นอะไรที่ใช้งานกันจนเป็นปกติกันแล้ว แต่ก่อนหน้านี้กว่าจะใช้งานได้นี่เหนื่อยมาก ๆ ต้องประกาศโน่นนี่วุ่นวายในโค้ด Java แต่ด้วยฟีเจอร์ใหม่ ตอนนี้เราสามารถยัดฟอนต์เข้าไปใน Resource ได้แล้วจ้า โดยยัดไฟล์ ttf เข้าไปใน res/font ได้เลยทันที แถมยังพรีวิวบน Android Studio ได้อีกด้วย

จากนั้นก็สามารถรวบฟอนต์ตระกูลเดียวกันในแบบต่าง ๆ เช่น Normal, Bold, Italic รวมเป็น Font Family ได้ด้วย XML Resource File

<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android">
    <font
        android:fontStyle="normal"
        android:fontWeight="400"
        android:font="@font/lobster_regular" />
    <font
        android:fontStyle="italic"
        android:fontWeight="400"
        android:font="@font/lobster_italic" />
</font-family>

และเวลาเรียกใช้งานก็กำหนดผ่าน android:fontFamily ได้โดยตรง

<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:fontFamily="@font/lobster"/>

แน่นอนว่าพรีวิวบน Android Studio ได้ด้วย แสดงผลได้หมดตามสไตล์ที่กำหนด จะตัวหนาตัวเอียงก็ไม่หวั่น =)

รวมถึงการที่มันเป็น Resource ทำให้เราสามารถแยกฟอนต์ตาม Configuration Qualifier ได้อีกด้วย เปิดเครื่องภาษาไทยเป็นฟอนต์นี้ เปิดเครื่องอินเดียเป็นฟอนต์นี้อะไรงี้ ทำได้ง่าย ๆ เลย

มันเป็นอะไรที่เจ๋งมากจริง ๆ น่าเสียดายที่ใช้ได้แค่บน Android O ก็ต้องรอลุ้นกันว่าจะมีใน Android Support Library มั้ยครับ

ข้อมูลเพิ่มเติมอ่านได้ที่ Working With Fonts จ้าา

Autosizing TextViews

ก่อนหน้านี้ TextView สามารถแสดงฟอนต์ขนาดเดียวเท่านั้นตามที่กำหนดใน android:textSize แล้ว View จะขยายขนาดตามเองเพื่อให้แสดงผลได้ทั้งหมด

แต่หลาย ๆ ครั้งเราก็มีโจทย์ว่า ถ้าข้อความยาว ๆ เราอยากจะทำให้ฟอนต์มันเล็กลงแทนที่จะขยายขนาด View อ่ะ ทำได้มั้ย ?

ก่อนหน้านี้ก็ต้องใช้ Library เสริมเพื่อให้มันทำได้ตามโจทย์นี้ แต่ตอนนี้ไม่ต้องแล้วเพราะ Android O มาพร้อมฟีเจอร์นี้ในตัวเรียบร้อยยย โดยเราสามารถกำหนดได้สองรูปแบบ

Granularity

คือการกำหนดว่าขนาดฟอนต์ต่ำสุดและสูงสุดคือเท่าไหร่ และจะให้ลดหรือเพิ่มทีละกี่ sp หน้าตาของ XML ตามนี้เลย

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:autoSizeText="uniform"
  android:autoSizeMinTextSize="12sp"
  android:autoSizeMaxTextSize="100sp"
  android:autoSizeStepGranularity="2sp"
/>

ก็ตรงไปตรงมาเนอะ ไม่ต้องอธิบายเยอะ ^ ^

Preset Sizes

คือการกำหนดขนาดว่ามีฟอนต์ขนาดไหนบ้างที่เราอนุญาตให้ใช้งานได้ ใส่ไว้ใน res/values ตามนี้

<resources>
    <array name="autosize_text_sizes">
        <item>10sp</item>
        <item>12sp</item>
        <item>20sp</item>
        <item>40sp</item>
        <item>100sp</item>
    </array>
</resources>

จากนั้นก็เอา Resource ตัวนี้ไปกำหนดให้ TextView ซะ

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:autoSizeText="uniform"
  android:autoSizePresetSizes="@array/autosize_text_sizes"
/>

ก็เรียบร้อยครับ ง่าย ๆ แค่นี้แหละ TextView ตัวนี้ก็จะสามารถแสดงผลได้ 5 ขนาดฟอนต์ตามที่ลิสต์ไว้ด้านบนทันที

Adaptive Icons

เป็นการกำหนดไอคอนในรูปแบบใหม่ จากเดิมที่ใส่เป็นไฟล์เดียวแล้วได้เป็นไอคอนเลย กลายเป็นการสร้างไอคอนเป็นสองเลเยอร์ คือ ตัวไอคอน (Foreground) และ ตัวพื้นหลัง (Background) ขนาด 108 x 108 dp แล้วทั้งสองจะถูกรวมโดยระบบปฏิบัติการโดยอัตโนมัติ และแสดงผลออกมาในรูปทรงต่าง ๆ ตามที่เครื่องนั้น ๆ ตั้งไว้ ไม่ว่าจะเป็นวงกลม สี่เหลี่ยม หรืออะไรก็ตามแต่

ซึ่งประโยชน์ของการที่ไอคอนถูกแยกออกมาเป็นสองเลเยอร์คือ ระบบปฏิบัติการสามารถนำมันมาสร้างเป็นเอฟเฟ็กต์สำหรับแสดงผลได้หลายรูปแบบ เช่น Parallax หรือ Pulsing

วิธีการกำหนดใน XML ก็ไม่ยากอะไร แค่สร้าง XML สำหรับไอคอนขึ้นมาหนึ่งตัวไว้ภายใต้ res/mipmap เช่นไฟล์นี้คือ ic_launcher.xml

<maskable-icon>
    <background android:drawable="@color/ic_background"/>
    <foreground android:drawable="@mipmap/ic_foreground"/>
</maskable-icon>

และเราก็สามารถนำไปกำหนดเป็นไอคอนของแอป ฯ ได้ทันที

<application
    …
    android:icon="@mipmap/ic_launcher"
    android:roundIcon="@mipmap/ic_launcher_round"
    …>
</application>

ง่าย ๆ และเท่ =D

ข้อมูลเพิ่มเติมอ่านได้จาก Adaptive Icons จ้า

Picture in Picture (PIP) for Handsets

อันนี้ไม่มีอะไรมาก โหมด Picture-in-Picture หรือ PIP ที่ก่อนหน้านี้มีให้ใช้บน Android TV ตอนนี้สามารถใช้ได้บนทุกอุปกรณ์ที่เป็น Android O แล้ว รวมถึงโทรศัพท์มือถือด้วยนั่นเอง

API มีการเปลี่ยนแปลงนิดหน่อยแต่เราคงไม่เอามาเขียนเพราะจะละเอียดไป ลองไปอ่านเพิ่มได้ที่ Picture-in-Picture mode จ้า

Color Management

จอขออุปกรณ์ใหม่ ๆ บางรุ่นตอนนี้สามารถแสดงผลภาพด้วย Color Space แบบ wide-gamut ได้แล้ว (มันคือ RGB Color Space แบบเฉพาะที่ Adobe คิดค้นขึ้น) ยกตัวอย่างเช่น AdobeRGB ซึ่งเราก็สามารถนำภาพที่บันทึกมาด้วย Color Space เหล่านี้มาใช้งานได้แล้วบน Android O

วิธีการใช้งาน เราจะต้องเปิดใช้งานเป็น Activity ไปโดยระบุ Flag ไว้ใน AndroidManifest ครับ แค่นี้แหละ ไม่มีอะไรซับซ้อน เบา ๆ ขำ ๆ

Seekable AnimatorSet

AnimatorSet ตอนนี้สามารถ Seek ไปตามเวลาที่กำหนด รวมถึง เล่นย้อนหลัง ได้แล้ว ไม่ต้องประกาศ AnimatorSet สองตัวอีกต่อไป ใช้ตัวเดียวแล้วสั่งเล่นย้อนเอาได้เลย

Cached Data Quota

คลาส StorageManager เพิ่มคำสั่ง getCacheQuotaBytes(...) ขึ้นมาเพื่อบอกว่าแอป ฯ เราควรจะใส่ไฟล์ลงไปใน Cache ไม่เกินเท่าไหร่

คือยังใส่เกินได้นะ มันเป็นแค่ตัวเลขที่แนะนำไว้ แล้วถ้าใส่เกินหละ ? ผลลัพธ์คือ ถ้าเกิดเครื่องต้องการเคลียร์พื้นที่ ไฟล์ใน Cache ของเราจะถูกเคลียร์เป็นไฟล์แรก ๆ นั่นเอง ข้อหาที่ใส่ไฟล์เกินที่แนะนำไว้

นอกจากนี้ยังมีฟังก์ชันเพิ่มมาอีกสองตัวเพื่อกำหนดว่าอยากให้ระบบเคลียร์ไฟล์ใน Cache ในรูปแบบที่เราต้องการ

setCacheBehaviorAtomic() - เป็นการระบุว่าถ้าระบบจะเคลียร์อะไรใน Directory ที่กำหนดนี้ ให้เคลียร์หมดเลยทั้ง Directory นะ ไม่ใช่แค่บางไฟล์

setCacheBehaviorTombstone() - เป็นการระบุว่าถ้าระบบจะเคลียร์อะไรใน Directory ที่กำหนด ให้มันตัดไฟล์เหลือ 0 ไบต์แทนที่จะลบไฟล์ใน Directory นั้นทิ้งไป ประโยชน์คือเอาไว้แยกแยะทางโปรแกรมมิ่งว่าก่อนหน้าที่ Cache จะถูกเคลียร์ ไฟล์นี้เคยมีอยู่หรือไม่นั่นเอง

ก็ประมาณนี้ครับผม

Managing WebViews

WebView มาพร้อม API ใหม่สี่กลุ่มด้วยกันได้แก่

Version API

เอาไว้ตรวจสอบเวอร์ชันของ WebView Package โดยใช้โค้ดดังนี้

PackageInfo webViewPackageInfo = WebView.getCurrentWebViewPackage();
Log.d(TAG, "WebView version: " + webViewPackageInfo.versionName);

ประโยชน์คือบางทีเราต้องการแสดงผลต่างกันไปตามเวอร์ชันของ WebView Package ก็ใช้วิธีนี้ตรวจสอบเอาครับ

Google Safe Browser API

เป็นการเปิดให้ WebView ตรวจสอบความปลอดภัยของเว็บผ่าน Google Safe Browsing ก่อนจะเข้าถึงเว็บนั้น ๆ เหมือนกับที่เราใช้อยู่บน Chrome นั่นเอง วิธีการเปิดก็กำหนดผ่าน meta-data ของ AndroidManifest ได้ทันทีครับ

<manifest>
    <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
               android:value="true" />
    ...
    <application> ... </application>
</manifest>

แค่นี้เลย ง่ายดี

Termination Handle API

WebView ตอนนี้ทำงานในโหมด Multiprocess คือตัวประมวลผลเว็บจะถูกแยกโปรเซสออกมาทำงานโดยเฉพาะ ไม่ได้อยู่รวมกับโปรเซสของแอป ฯ ที่เรียกใช้งานแล้ว ดังนั้นรูปแบบการ Handle สิ่งต่าง ๆ จะเปลี่ยนแปลงไปเล็กน้อย อย่างหนึ่งที่สำคัญคือเวลาที่ Renderer ของ WebView ถูกทำลายทิ้งเพื่อทวงคืนหน่วยความจำ เราก็ต้องเขียนคอยดักจับที่ Callback ที่ชื่อว่า onRenderProcessGone เอาครับ

public class MyRendererTrackingWebViewClient extends WebViewClient {
    private WebView mWebView;

    @Override
    public boolean onRenderProcessGone(WebView view,
            RenderProcessGoneDetail detail) {
        if (!detail.didCrash()) {
            // Renderer was killed because the system ran out of memory.
            // The app can recover gracefully by creating a new WebView instance
            // in the foreground.
            Log.e("MY_APP_TAG", "System killed the WebView rendering process " +
                    "to reclaim memory. Recreating...");

            if (mWebView != null) {
                ViewGroup webViewContainer =
                        (ViewGroup) findViewById(R.id.my_web_view_container);
                webViewContainer.removeView(mWebView);
                mWebView.destroy();
                mWebView = null;
            }

            // By this point, the instance variable "mWebView" is guaranteed
            // to be null, so it's safe to reinitialize it.

            return true; // The app continues executing.
        }

        // Renderer crashed because of an internal error, such as a memory
        // access violation.
        Log.e("MY_APP_TAG", "The WebView rendering process crashed!");

        // In this example, the app itself crashes after detecting that the
        // renderer crashed. If you choose to handle the crash more gracefully
        // and allow your app to continue executing, you should 1) destroy the
        // current WebView instance, 2) specify logic for how the app can
        // continue executing, and 3) return "true" instead.
        return false;
    }
}

ก็ Handle กันไปครับว่าจะทำอะไร =)

Renderer Importance API

โดยปกติ WebView หลาย ๆ ตัวจะใช้ Renderer ร่วมกัน เราสามารถกำหนดความสำคัญได้ว่า WebView ไหนมีความสำคัญมากกว่ากันโดยใช้คำสั่งตามนี้

WebView myWebView;
myWebView.setRendererPriorityPolicy(RENDERER_PRIORITY_BOUND, true);

อย่างไรก็ตาม หากเซตค่านี้แบบไม่เข้าใจก็อาจทำให้แอป ฯ มีปัญหาได้ ดังนั้นโปรดใช้ด้วยความระมัดระวังจ้า

MediaPlayer Enhancements

MediaPlayer มาพร้อมความสามารถเพิ่มเติมสามกลุ่ม

Buffering

เราสามารถควบคุมรูปแบบการ Buffering มีเดียได้สามค่า

initial - เป็นการบอกว่าเราจะ Buffering มีเดียเท่าไหร่ก่อนจะเริ่มเล่นครั้งแรก

low - เป็นการระบุขนาดข้อมูลขั้นต่ำที่จะ Buffering ระหว่างเล่น

high - เป็นการระบุเพดานของข้อมูลที่จะ Buffering ระหว่างเล่น

โดยระหว่างที่เล่นอยู่นั้น MediaPlayer จะพยายาม Buffering มีเดียให้อยู่ระหว่าง low และ high ครับ

โดยค่าต่าง ๆ นั้นเราสามารถกำหนดได้หลายรูปแบบทั้ง "เวลา (กี่มิลลิวินาที)" หรือ "ขนาด (กี่กิโลไบต์)" สำหรับคนที่ทำงานด้านนี้น่าจะคุ้นเคยกับเรื่องพวกนี้อยู่แล้ว นั่นแหละ ตอนนี้เราควบคุมสิ่งเหล่านี้บนแอนดรอยด์ได้แล้วโดยไม่ต้องใช้ไลบรารี่นอก =)

Seek modes

MediaPlayer เพิ่มคำสั่ง seekTo(int, int) ขึ้นมา โดยเราสามารถระบุได้ว่าจะให้ Seek ไปยัง Keyframe ในรูปแบบไหนโดยระบุไว้ในพารามิเตอร์ที่สอง ตามนี้

SEEK_PREVIOUS_SYNC - เลื่อนไปยัง Keyframe ที่อยู่ก่อนหน้าเวลาที่กำหนด

SEEK_NEXT_SYNC - เลื่อนไปยัง Keyframe ถัดไปนับจากเวลาที่กำหนด

SEEK_CLOSEST_SYNC - เลื่อนไปยัง Keyframe ที่ใกล้กับเวลาที่กำหนดที่สุด

SEEK_CLOSEST - เลื่อนไปยังเวลาที่กำหนด ตรงไหนก็ได้ ไม่จำเป็นต้องเป็น Keyframe ก็ได้

ก็ถือว่ามีประโยชน์ทีเดียวสำหรับคนที่ทำงานด้านแอป ฯ เล่นวีดีโอ

DRM support

ไม่มีอะไรมากครับ ตรงไปตรงมา ตอนนี้ MediaPlayer สามารถเล่นไฟล์ที่ถูก DRM ไว้ได้แล้ว เย้


ข้อมูลเพิ่มเติมสำหรับเรื่องนี้ อ่านเพิ่มได้ที่ MediaPlayer Enhancements ครับ

Content Provider Paging

บน Android O เราสามารถทำการแบ่งผลลัพธ์ที่ได้จาก Content Provider ออกเป็นหน้า ๆ ได้แล้ว จะมีประโยชน์มากตอนที่ผลลัพธ์ของการร้องขอ Content นั้นเยอะมาก ๆ เช่น การขอลิสต์ของรูปทั้งหมดในแอป ฯ Photo แต่เราต้องการจะแสดงผลลัพธ์เป็นหน้า ๆ

โดยผลลัพธ์จะถูกส่งมาเป็น Cursor 1 อันต่อหนึ่งชุดผลลัพธ์ ทำให้เราสามารถใช้งานมันได้อย่างสะดวกมากมาย โดยเฉพาะอย่างยิ่งแอป ฯ ที่มีการใช้ Cursor ในการแสดงผลโดยตรงผ่านพวก CursorAdapter

อย่างไรก็ตาม ฟีเจอร์นี้ไม่ได้ทำได้เลยโดยอัตโนมัติ แต่ทั้ง Content Provider และ Client จะต้องอิมพลิเม้นท์โค้ดตรงนี้ด้วยถึงจะใช้งานได้ครับ เพียงแต่ Android O ระบุ Interface มาให้ว่าควรจะทำยังไงเท่านั้นเอง

Wi-Fi Aware

Wi-Fi Aware คือความสามารถของ Android O ที่เปิดให้อุปกรณ์มองหากันเองผ่าน Wi-Fi และเชื่อมต่อเพื่อคุยกันได้โดยตรงแบบ Peer-to-Peer

หลักการทำงานค่อนข้างคล้ายบลูทูธมาก ๆ คือ จะต้องมีเครื่องหนึ่งเปิด Service ที่ค้นหาได้ไว้ (Discoverable Service) ซึ่งเราจะเรียกเครื่องนี้ว่า Publisher แล้วก็จะให้เครื่องอื่นสแกนหา ซึ่งเราจะเรียกมันว่า Subscriber

หลังจากค้นหากันเจอแล้ว เครื่องทั้งสองก็จะสามารถสร้างการเชื่อมต่อและคุยกันแบบสองทางได้ทันทีโดยไม่ต้องมี Wi-Fi Access Point อยู่แถวนั้นเลย เท่เฟร่ออออออ

ฟัง ๆ ดูแล้วเหมือน Wi-Fi P2P ที่มีให้ใช้ตั้งแต่ Android 4.0 เนอะ แต่ความจริงแล้ว Wi-Fi Aware นั้นเสถียรกว่ากันมาก มากแค่ไหนยังบอกไม่ได้ แต่เชื่อว่ามากโขอยู่เพราะเทคโนโลยีมันต่างกันหลายปีมาก ๆ

สำหรับโค้ดมันค่อนข้างยาว ขอไม่แปะไว้ให้ดูละกันนะ หากใครอยากศึกษาเพิ่มเติมลองกดดูที่ Wi-Fi Aware ได้จ้า

Pinning Shortcuts

App Shortcuts ถูกประกาศเปิดตัวมาตอน Android 7.1 กับความสามารถที่เลือกฟังก์ชันที่ต้องการเรียกใช้ภายในแอป ฯ โดยตรงจากไอคอนได้ทันที (ดูจากภาพด้านบน)

บน Android O ก็เพิ่มความสะดวกขึ้นมาอีกนิดด้วยการเปิดให้นักพัฒนาสามารถสร้างคำสั่งเหล่านั้นมาแปะบน Launcher ในรูปแบบของ Pinned Shortcut ได้ ซึ่งการทำงานจะเหมือนกับ App Shortcuts ต่างกันที่การแสดงผลว่า Pinned Shortcut จะถูกแสดงในรูปแบบไอคอนแยกออกมาเลยหนึ่งตัวต่อหนึ่ง Shortcut นั่นเอง (ดูภาพประกอบด้านบน)

สำหรับโค้ดก็ค่อนข้างตรงไปตรงมา เรียกคำสั่งต่าง ๆ ผ่าน ShortcutManager ได้เลย

ShortcutManager mShortcutManager =
        context.getSystemService(ShortcutManager.class);

if (mShortcutManager.isRequestPinShortcutSupported()) {
    // Assumes there's already a shortcut with the ID "my-shortcut".
    // The shortcut must be enabled.
    ShortcutInfo pinShortcutInfo = ShortcutInfo.Builder(context, "my-shortcut");

    // Create the PendingIntent object only if your app needs to be notified
    // that the user allowed the shortcut to be pinned. Note that, if the
    // pinning operation fails, your app isn't notified. We assume here that the
    // app has implemented a method called createShortcutResultIntent() that
    // returns a broadcast intent.
    Intent pinnedShortcutCallbackIntent =
            createShortcutResultIntent(pinShortcutInfo);

    // Configure the intent so that your app's broadcast receiver gets
    // the callback successfully.
    PendingIntent successCallback = PendingIntent.createBroadcast(context, 0,
            pinnedShortcutCallbackIntent);

    mShortcutManager.requestPinShortcut(pinShortcutInfo,
            successCallback.getIntentSender());
}

อ่านเพิ่มเติมได้ที่ Pinning Shortcuts จ้า

Pinning Widgets

เหมือนกับ Pinned Shortcut ทุกประการ ต่างกันแค่ว่าอันนี้เราจะ Pin เจ้า Widget แทน คำสั่งก็เหมือนด้านบนทุกประการเลยไม่ขอเอามาแปะนะ ใครสนใจไปอ่านเพิ่มได้ที่ Pinning Widgets ครับผม

Keyboard Navigation

ตอนนี้แอป ฯ แอนดรอยด์ก็ทยอยไปอยู่บนอุปกรณ์อื่น ๆ ที่ไม่ใช่มือถือเต็มไปหมด เช่น Chrome OS เอย หรือ Tablet เอย ซึ่งอุปกรณ์เหล่านี้มักจะมีคีย์บอร์ดเป็นตัวควบคุมหลัก ซึ่งที่ผ่านมาคีย์บอร์ดไม่สามารถสร้าง User Experience ที่ดีกับแอป ฯ แอนดรอยด์เหล่านี้ได้เพราะว่าแอป ฯ ส่วนใหญ่ออกแบบมาเพื่อจอทัชสกรีนนั่นเอง

บน Android O ทางทีมแอนดรอยด์จะโฟกัสเรื่องการปรับปรุง User Experience ให้การ Navigate ด้วยคีย์บอร์ดทำงานได้สมบูรณ์ขึ้นและเป็นไปตามที่ผู้ใช้คาดหวัง

อันนี้ต้องรอดูการเปลี่ยนแปลง =D

รับสายอัตโนมัติด้วย ANSWER_PHONE_CALLS Permission

Android O เปิดตัว Permission ใหม่ชื่อว่า ANSWER_PHONE_CALLS มีเอาไว้สำหรับขอสิทธิ์การรับสายอัตโนมัติผ่านโปรแกรมมิ่ง ซึ่งถือเป็นครั้งแรกที่แอนดรอยด์เปิดให้ทำอะไรแบบนี้ได้

โดย Permission ตัวนี้อยู่ในกลุ่ม Dangerous หากจะเปิดให้ใช้งานได้จะต้องร้องขอการอนุญาตเข้าถึง Permission จากทางผู้ใช้ผ่านทาง Runtime Permission ก่อนด้วย ส่วนคำสั่งที่ใช้รับสายคือ acceptRingingCall() ในคลาส TelecomManager ครับ

Smart Sharing

อีกหนึ่งความหล่อความเท่ของแอนดรอยด์ที่ใส่ AI เข้าไปเพื่อเรียนรู้พฤติกรรมผู้ใช้และนำมาใช้งานจริง โดยฟีเจอร์ Smart Sharing จะเรียนรู้ว่า Intent ที่ส่งเพื่อแชร์ไปยังแอป ฯ อื่นนั้นเป็นข้อมูลประเภทไหน และเหมาะกับการแชร์ไปยังแอป ฯ ไหน ยกตัวอย่างเช่น ถ้าเราถ่ายรูปอาหาร ตอนยิง Intent เพื่อแชร์รูปก็จะมีแอป ฯ ที่เหมาะกับการนำภาพอาหารไปทำงานต่อเด้งขึ้นมาให้ผู้ใช้เลือก หรือถ้าถ่ายรูปเซลฟี่ ก็จะมีแอป ฯ สำหรับแต่งหน้าโผล่มาให้เลือก เท่มั้ยล่าาาา

ถามว่าเบื้องหลังทำงานยังไง ? ความจริงเราต้องกำหนดข้อความกำกับ (Annotation) 3 คำให้กับ Intent ที่เราจะแชร์ก่อนครับ ตามนี้

ArrayList<String> annotations = new ArrayList<>();

annotations.add("topic1");
annotations.add("topic2");
annotations.add("topic3");

intent.putStringArrayListExtra(
    Intent.EXTRA_CONTENT_ANNOTATIONS,
    annotations
);

แล้วถ้าเกิดผู้ใช้เลือกแอป ฯ ไหน มันก็จะจำว่าแอป ฯ นั้นเหมาะกับ Annotation แบบไหนสำหรับผู้ใช้คนนี้ แล้วคราวหลังหากมี Intent ที่กำหนด Annotation แบบเดียวกันโผล่มา แอป ฯ ที่ผู้ใช้เคยเลือกก็จะถูกเหวี่ยงมาให้เลือกก่อนเป็นแอป ฯ แรก ๆ นั่นเอง เย้ เย

สำหรับ Annotation จริง ๆ เราสามารถกำหนดเป็นคำอะไรก็ได้ แต่ทางแอนดรอยด์ก็มีไกด์แนะนำอยู่ว่าควรจะใช้คำว่าอะไรบ้าง ตามนี้เลย EXTRA_CONTENT_ANNOTATIONS

Multi-display Support

อีกหนึ่งสิ่งที่เจ๋งมากบน Android O คือมันสนับสนุนการทำงานแบบหลายจอ (Multi-display) แล้ว ก็คล้าย ๆ กับที่เราต่อจอคอมพ์ไว้หลาย ๆ จอแล้วใช้งานจอซ้ายจอขวานั่นแหละ

ในแง่การใช้งานก็คงปรับตัวไม่ยากอะไร แต่ในแง่ของนักพัฒนาก็ต้องจัดการเรื่อง Lifecycle ให้ดีหน่อย เพราะผู้ใช้สามารถลาก Activity จากจอนึงไปแสดงผลบนอีกจอนึงได้ และในกรณีนั้นพวก Lifecycle Event ก็จะเกิดขึ้นตามที่เหมาะสม ดังนั้น ... นักพัฒนาทั้งหลาย อย่าลืมทดสอบแอป ฯ บน Multi-display ด้วยว่าใช้งานได้สมบูรณ์หรือไม่จ้าา (ไว้ค่อยทดสอบอีกทีตอนที่ทุกอย่างมันลงตัวแล้วก็ได้)

ลาก่อน SSLv3

SSLv3 ที่มีรูรั่วทางด้านความปลอดภัยจนคนรณรงค์ให้เลิกใช้ไปตลอดปีที่ผ่านมา ตอนนี้ก็ได้ฤกษ์ถูกนำออกไปจาก Android O เรียบร้อยแล้วคร้าบผม

Java 8 Language APIs

มีการเพิ่มฟีเจอร์ใน OpenJDK 8 ใส่เพิ่มเข้าไปในแอนดรอยด์หลายตัว รวมถึงคลาส/แพคเกจ java.time, java.nio.file และ java.lang.invoke ด้วย

Runtime optimizations

Android Runtime ทำงานเร็วขึ้นสูงสุดถึง 2 เท่า !Behavior ที่เปลี่ยนแปลงเมื่อ targetSdkVersion เป็น O

ทุกครั้งที่เราอัปเดต targetSdkVersion เป็นเวอร์ชันใหม่ก็จะมี Behavior บางอย่างเปลี่ยนไป สำหรับกรณีที่เราอัปเดตเป็น O สิ่งที่เปลี่ยนแปลงไปจะมีดังต่อไปนี้

- เราจะต้องสร้าง Notification Channel อย่างน้อยหนึ่งเพื่อให้ Notification ใช้งานได้บน Android O

- Background Service Limitations และ Broadcast Limitations จะถูกนำมาใช้

- WebView จะเชื่อมต่อผ่าน HTTPS ได้เท่านั้น ไม่สามารถทำงานผ่าน HTTP ได้อีกต่อไป นอกจากจะเซตให้ใช้งานได้บนโดเมนที่กำหนด

- หากผู้ใช้อนุญาต Permission ใด ๆ แล้ว Permission อื่น ๆ ใน Permission Group เดียวกันจะไม่ได้รับอนุญาตโดยอัตโนมัติเหมือนแอนดรอยด์เวอร์ชันก่อน ๆ อีกต่อไป อย่างไรก็ตาม หากมีการร้องขอ Permission เพิ่มเติมที่เผอิญอยู่ใน Permission Group เดียวกับที่เคยได้รับอนุญาตแล้ว Permission นั้นจะได้รับอนุมัติทันทีโดยไม่ต้องร้องขอไปยังผู้ใช้อีก

ยังไม่หมดเท่านี้เพราะมันมีเยอะมาก เราคัดมาเฉพาะตัวสำคัญ ๆ ก่อน ใครอยากอ่านละเอียดเชิญที่ Apps targeting Android O จ้าาา

Behavior ที่ไม่เปลี่ยนแปลง ถ้าไม่เปลี่ยน targetSdkVersion

จริง ๆ ก็คืออะไรที่ตรงข้ามกับที่ลิสต์ไว้บนหัวข้อด้านบนนั่นแหละ แค่อยากจะมาสรุปในเรื่องสำคัญ ๆ ให้ชัดเจนว่า ถ้า targetSdkVersion ยังคงเดิม สิ่งที่จะยังคงทำงานเหมือนเดิมไม่เปลี่ยนแปลงแม้จะรันบน Android O จะมีดังต่อไปนี้

- Notification จะทำงานโดยปราศจาก Notification Channel เหมือนกับแอนดรอยด์รุ่นก่อน ๆ

- Background Service Limitations และ Broadcast Limitations จะไม่มีผลใด ๆ กับแอป ฯ นั้น ๆ จะยังคงเหมือนแอนดรอยด์รุ่นก่อน ๆ ทุกประการ

ส่วนที่เหลือก็อ่านลิงก์เดิมด้านบนครับ ^ ^

Behavior ที่เปลี่ยนแปลงถึงแม้จะไม่ไดัเปลี่ยน targetSdkVersion

แน่นอนว่าจะมีบาง Behavior ที่เปลี่ยนไปถึงแม้จะเป็น targetSdkVersion เดิม

- Background Location Limits จะมีผลทันที ใครทำแอป ฯ ที่มีการดึงพิกัดใน Background อย่าลืมอัปเดตและทดสอบแอป ฯ บน Android O ด้วย

ยังมีอีกเยอะเช่นกัน ไปอ่านเพิ่มเติมได้ที่ Apps targeting all API levels ครับผม

Timeline

สำหรับ Timeline ของ Android O มีดังนี้ครับ

ก็คงได้เห็นรายละเอียดอะไรเพิ่มเติมในงาน I/O เดือนพฤษภาคมนี้อยู่ ส่วนตัวจริงคงได้ใช้กันใน Q3 ครับ ... ถ้าใช้ Pixel อ่านะ

ทดสอบ Android O ผ่าน Android Studio 2.4

ตอนนี้มี SDK ของ Android O และ Emulator ให้โหลดมาทดสอบแล้ว ใครสนใจอยากโหลดมาลองเล่นก็ให้โหลด Android Studio 2.4 มาก่อน จากนั้นกดที่ SDK Manager แล้วกดโหลด Android O Preview มาได้เลยครับ

สำหรับโปรเจค ถ้าอยากจะทดสอบการคอมไพล์และ Behavior ของ targetSdkVersion ที่เป็น O ก็สามารถกำหนด build.gradle ได้ดังนี้

android {
  compileSdkVersion 'android-O'
  buildToolsVersion '26.0.0-rc1'

  defaultConfig {
    targetSdkVersion 'O'
  }

  ...
}

รวมถึง Emulator ก็สามารถสร้าง AVD ขึ้นมาทดสอบได้ทันที อันนี้ไม่ต้องสอนเนอะ เหมือนเดิมทุกประการ

ทั้งนี้ทั้งนั้นเอาไว้ทดสอบอย่างเดียวพอนะครับ อย่าเพิ่งซีเรียสจริงจังมากเพราะจากนี้อีกหลายเดือนมันจะยังเปลี่ยนไปอีกมาก นี่แค่ Alpha เอง ^ ^

Android Support Library v26.0.0

เนื่องจากว่า Android Support Library ต้องมีเวอร์ชันเดียวกับ compileSdkVersion ดังนั้นกูเกิลก็เลยจัด Android Support Library v26.0.0 มาให้เรียบร้อย เท่าที่ดูยังไม่มีอะไรใหม่ ๆ เน้นเอาไว้ให้คอมไพล์สำหรับ O ได้เท่านั้นเอง

ก็อัปเดต Android Support Library มาให้เรียบร้อยผ่านทาง SDK Manager แล้วก็เปลี่ยนเวอร์ชันของ Dependency ใน build.gradle ดังนี้

dependencies {
  compile 'com.android.support:appcompat-v7:26.0.0-alpha1'
}

เรียบร้อยครับ ขอให้มีความสุขกับ Android O จ้า

ก็หวังว่าบล็อกยาว ๆ นี้จะมีประโยชน์กับมนุษยชาติจ่ะ จุ๊บส์ บายยยย

บทความที่เกี่ยวข้อง

Jan 29, 2017, 16:07
55327 views
แอปหรือเว็บมีปัญหา ไม่ต้องใส่ชุดนักศึกษา แค่สมัครมาขอคำปรึกษากับงาน GDE Clinic ฟรี !
Jan 28, 2017, 19:06
75862 views
โครงสร้างพื้นฐาน(มาก)ของการติดต่อแอป ฯ กับ Database ฝั่ง Server สำหรับผู้เริ่มต้น
0 Comment(s)
Loading