สร้างวิดเจ็ตขั้นสูง

หน้านี้จะอธิบายแนวทางปฏิบัติที่แนะนำในการสร้างวิดเจ็ตขั้นสูงยิ่งขึ้นเพื่อประสบการณ์ของผู้ใช้ที่ดียิ่งขึ้น

การเพิ่มประสิทธิภาพสำหรับการอัปเดตเนื้อหาวิดเจ็ต

การอัปเดตเนื้อหาวิดเจ็ตอาจใช้พลังงานในการประมวลผลมาก หากต้องการประหยัดแบตเตอรี่ ให้เพิ่มประสิทธิภาพประเภท ความถี่ และเวลาการอัปเดต

ประเภทการอัปเดตวิดเจ็ต

การอัปเดตวิดเจ็ตทำได้ 3 วิธี ได้แก่ การอัปเดตทั้งหมด การอัปเดตบางส่วน และรีเฟรชข้อมูล (ในกรณีที่เป็นวิดเจ็ตคอลเล็กชัน) แต่ละวิธีมีต้นทุนการประมวลผลและผลลัพธ์ที่แตกต่างกัน

ข้อมูลต่อไปนี้จะอธิบายการอัปเดตแต่ละประเภทและแสดงข้อมูลโค้ดของแต่ละประเภท

  • การอัปเดตแบบสมบูรณ์: โทร AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews) เพื่ออัปเดตวิดเจ็ตอย่างสมบูรณ์ การดำเนินการนี้จะแทนที่ RemoteViews ที่ระบุไว้ก่อนหน้านี้ด้วย RemoteViews ใหม่ การอัปเดตนี้เป็นงานที่ต้องใช้การประมวลผลมากที่สุด

    Kotlin

    val appWidgetManager = AppWidgetManager.getInstance(context)
    val remoteViews = RemoteViews(context.getPackageName(), R.layout.widgetlayout).also {
    setTextViewText(R.id.textview_widget_layout1, "Updated text1")
    setTextViewText(R.id.textview_widget_layout2, "Updated text2")
    }
    appWidgetManager.updateAppWidget(appWidgetId, remoteViews)

    Java

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widgetlayout);
    remoteViews.setTextViewText(R.id.textview_widget_layout1, "Updated text1");
    remoteViews.setTextViewText(R.id.textview_widget_layout2, "Updated text2");
    appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
  • การอัปเดตบางส่วน: เรียกใช้ AppWidgetManager.partiallyUpdateAppWidget เพื่ออัปเดตบางส่วนของวิดเจ็ต ซึ่งจะผสาน RemoteViews ใหม่เข้ากับ RemoteViews ที่ระบุไว้ก่อนหน้านี้ ระบบจะละเว้นวิธีการนี้หากวิดเจ็ตไม่ได้รับอัปเดตอย่างเต็มรูปแบบอย่างน้อย 1 ครั้งผ่าน updateAppWidget(int[], RemoteViews)

    Kotlin

    val appWidgetManager = AppWidgetManager.getInstance(context)
    val remoteViews = RemoteViews(context.getPackageName(), R.layout.widgetlayout).also {
    setTextViewText(R.id.textview_widget_layout, "Updated text")
    }
    appWidgetManager.partiallyUpdateAppWidget(appWidgetId, remoteViews)

    Java

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widgetlayout);
    remoteViews.setTextViewText(R.id.textview_widget_layout, "Updated text");
    appWidgetManager.partiallyUpdateAppWidget(appWidgetId, remoteViews);
  • การรีเฟรชข้อมูลคอลเล็กชัน: เรียกใช้ AppWidgetManager.notifyAppWidgetViewDataChanged เพื่อทำให้ข้อมูลของมุมมองคอลเล็กชันในวิดเจ็ตเป็นโมฆะ ซึ่งจะทริกเกอร์RemoteViewsFactory.onDataSetChanged ในระหว่างนี้ ข้อมูลเก่าจะแสดงในวิดเจ็ต คุณสามารถทำงานที่ราคาแพงแบบซิงค์กันได้อย่างปลอดภัยด้วยวิธีนี้

    Kotlin

    val appWidgetManager = AppWidgetManager.getInstance(context)
    appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_listview)

    Java

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_listview);

คุณสามารถเรียกใช้เมธอดเหล่านี้ได้จากทุกที่ในแอป ตราบใดที่แอปมี UID เดียวกับคลาส AppWidgetProvider ที่เกี่ยวข้อง

กำหนดความถี่ในการอัปเดตวิดเจ็ต

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

อัปเดตเป็นระยะๆ

คุณควบคุมความถี่ของการอัปเดตเป็นระยะได้โดยระบุค่าสำหรับ AppWidgetProviderInfo.updatePeriodMillis ใน appwidget-provider XML การอัปเดตแต่ละครั้งจะทริกเกอร์เมธอด AppWidgetProvider.onUpdate() ซึ่งคุณวางโค้ดเพื่ออัปเดตวิดเจ็ตได้ อย่างไรก็ตาม ให้พิจารณาทางเลือกในการอัปเดตตัวรับสัญญาณการออกอากาศที่อธิบายไว้ในส่วนถัดไป หากวิดเจ็ตต้องโหลดข้อมูลแบบไม่พร้อมกันหรือใช้เวลาอัปเดตนานกว่า 10 วินาที เนื่องจากหลังจากผ่านไป 10 วินาที ระบบจะถือว่า BroadcastReceiver ไม่ตอบสนอง

updatePeriodMillis ไม่รองรับค่าที่น้อยกว่า 30 นาที แต่หากต้องการปิดใช้การอัปเดตเป็นระยะ ให้ระบุ 0

คุณสามารถอนุญาตให้ผู้ใช้ปรับความถี่ในการอัปเดตในการกําหนดค่าได้ เช่น ผู้ชมอาจต้องการให้มีการอัปเดตข้อมูลทิกเกอร์หุ้นทุก 15 นาทีหรือวันละ 4 ครั้งเท่านั้น ในกรณีนี้ ให้ตั้งค่า updatePeriodMillis เป็น 0 และใช้ WorkManager แทน

การอัปเดตเพื่อตอบสนองต่อการโต้ตอบของผู้ใช้

ต่อไปนี้เป็นวิธีแนะนำในการอัปเดตวิดเจ็ตตามการโต้ตอบของผู้ใช้

  • จากกิจกรรมของแอป: เรียกใช้ AppWidgetManager.updateAppWidget โดยตรงเพื่อตอบสนองต่อการโต้ตอบของผู้ใช้ เช่น การแตะ

  • จากการโต้ตอบระยะไกล เช่น การแจ้งเตือนหรือวิดเจ็ตแอป ให้สร้าง PendingIntent แล้วอัปเดตวิดเจ็ตจาก Activity, Broadcast หรือ Service ที่เรียกใช้ คุณเลือกลําดับความสําคัญของคุณเองได้ เช่น หากเลือก Broadcast สำหรับ PendingIntent คุณจะเลือกการออกอากาศที่ทำงานอยู่เบื้องหน้าเพื่อจัดลำดับความสำคัญของ BroadcastReceiver ได้

การอัปเดตเพื่อตอบสนองต่อเหตุการณ์การออกอากาศ

ตัวอย่างเหตุการณ์การออกอากาศที่กําหนดให้วิดเจ็ตอัปเดตคือเมื่อผู้ใช้ถ่ายภาพ ในกรณีนี้ คุณต้องการอัปเดตวิดเจ็ตเมื่อตรวจพบรูปภาพใหม่

คุณสามารถกําหนดเวลางานด้วย JobScheduler และระบุการออกอากาศเป็นทริกเกอร์ได้โดยใช้เมธอด JobInfo.Builder.addTriggerContentUri

นอกจากนี้ คุณยังลงทะเบียน BroadcastReceiver สำหรับออกอากาศได้ เช่น การฟัง ACTION_LOCALE_CHANGED อย่างไรก็ตาม โปรดใช้ฟีเจอร์นี้อย่างระมัดระวังและฟังเฉพาะการออกอากาศที่ต้องการ เนื่องจากฟีเจอร์นี้จะใช้ทรัพยากรของอุปกรณ์ เมื่อมีข้อจำกัดเกี่ยวกับประกาศใน Android 7.0 (API ระดับ 24) และ Android 8.0 (API ระดับ 26) แอปจะลงทะเบียนประกาศโดยนัยในไฟล์ Manifest ไม่ได้ โดยมีข้อยกเว้นบางประการ

ข้อควรพิจารณาเมื่ออัปเดตวิดเจ็ตจาก BroadcastReceiver

หากวิดเจ็ตได้รับการอัปเดตจาก BroadcastReceiver ซึ่งรวมถึง AppWidgetProvider โปรดคำนึงถึงข้อควรพิจารณาต่อไปนี้เกี่ยวกับระยะเวลาและลำดับความสำคัญของการอัปเดตวิดเจ็ต

ระยะเวลาการอัปเดต

โดยทั่วไปแล้ว ระบบจะอนุญาตให้ตัวรับการออกอากาศซึ่งมักจะทำงานในเธรดหลักของแอปทำงานได้สูงสุด 10 วินาทีก่อนที่จะถือว่าไม่ตอบสนองและทำให้เกิดข้อผิดพลาดแอปพลิเคชันไม่ตอบสนอง (ANR) หากการอัปเดตวิดเจ็ตใช้เวลานานขึ้น ให้ลองใช้ทางเลือกต่อไปนี้

  • กำหนดเวลางานโดยใช้ WorkManager

  • ให้ผู้รับมีเวลามากขึ้นในการใช้วิธี goAsync ซึ่งจะช่วยให้ผู้รับดำเนินการเป็นเวลา 30 วินาที

ดูข้อมูลเพิ่มเติมที่ข้อควรพิจารณาและแนวทางปฏิบัติแนะนำด้านความปลอดภัย

ลำดับความสำคัญของการอัปเดต

โดยค่าเริ่มต้น การออกอากาศ รวมถึงการออกอากาศที่ทำโดยใช้ AppWidgetProvider.onUpdate จะทำงานเป็นกระบวนการเบื้องหลัง ซึ่งหมายความว่าทรัพยากรของระบบที่ทำงานหนักเกินไปอาจทำให้เกิดความล่าช้าในการเรียกใช้ตัวรับสัญญาณการออกอากาศ หากต้องการจัดลำดับความสำคัญของการออกอากาศ ให้กำหนดให้กระบวนการดังกล่าวเป็นกระบวนการที่ทำงานอยู่เบื้องหน้า

เช่น เพิ่ม Flag Intent.FLAG_RECEIVER_FOREGROUND ลงใน Intent ที่ส่งไปยัง PendingIntent.getBroadcast เมื่อผู้ใช้แตะส่วนใดส่วนหนึ่งของวิดเจ็ต

สร้างตัวอย่างที่ถูกต้องซึ่งมีรายการแบบไดนามิก

รูปที่ 1: ตัวอย่างวิดเจ็ตที่ไม่แสดงรายการในรายการ

ส่วนนี้จะอธิบายแนวทางที่แนะนำในการแสดงรายการหลายรายการในตัวอย่างวิดเจ็ตสำหรับวิดเจ็ตที่มีมุมมองคอลเล็กชัน ซึ่งก็คือวิดเจ็ตที่ใช้ ListView, GridView หรือ StackView

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

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

ตัวอย่างสำหรับ ListView เริ่มต้นด้วยไฟล์เลย์เอาต์แยกต่างหาก

// res/layout/widget_preview.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:background="@drawable/widget_background"
   android:orientation="vertical">

    // Include the actual widget layout that contains ListView.
    <include
        layout="@layout/widget_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    // The number of fake items you include depends on the values you provide
    // for minHeight or targetCellHeight in the AppWidgetProviderInfo
    // definition.

    <TextView android:text="@string/fake_item1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginVertical="?attr/appWidgetInternalPadding" />

    <TextView android:text="@string/fake_item2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginVertical="?attr/appWidgetInternalPadding" />

</LinearLayout>

ระบุไฟล์เลย์เอาต์ตัวอย่างเมื่อระบุแอตทริบิวต์ previewLayout ของข้อมูลเมตา AppWidgetProviderInfo คุณยังคงระบุเลย์เอาต์วิดเจ็ตจริงสำหรับแอตทริบิวต์ initialLayout และใช้เลย์เอาต์วิดเจ็ตจริงเมื่อสร้าง RemoteViews ขณะรันไทม์

<appwidget-provider
    previewLayout="@layout/widget_previe"
    initialLayout="@layout/widget_view" />

รายการในลิสต์ที่ซับซ้อน

ตัวอย่างในส่วนก่อนหน้าแสดงรายการในรายการจำลอง เนื่องจากรายการในรายการคือออบเจ็กต์ TextView การให้รายการจำลองอาจซับซ้อนขึ้นหากรายการเป็นเลย์เอาต์ที่ซับซ้อน

พิจารณารายการในลิสต์ที่กําหนดไว้ใน widget_list_item.xml และประกอบด้วยออบเจ็กต์ TextView 2 รายการ ดังนี้

<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <TextView android:id="@id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/fake_title" />

    <TextView android:id="@id/content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/fake_content" />
</LinearLayout>

หากต้องการระบุรายการในรายการจำลอง คุณสามารถใส่เลย์เอาต์หลายครั้งได้ แต่จะทำให้รายการในรายการแต่ละรายการเหมือนกัน หากต้องการระบุรายการรายการที่ไม่ซ้ำกัน ให้ทำตามขั้นตอนต่อไปนี้

  1. สร้างชุดแอตทริบิวต์สำหรับค่าข้อความ ดังนี้

    <resources>
        <attr name="widgetTitle" format="string" />
        <attr name="widgetContent" format="string" />
    </resources>
    
  2. ใช้แอตทริบิวต์ต่อไปนี้เพื่อตั้งค่าข้อความ

    <LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
        <TextView android:id="@id/title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="?widgetTitle" />
    
        <TextView android:id="@id/content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="?widgetContent" />
    </LinearLayout>
    
  3. สร้างสไตล์ได้มากเท่าที่ต้องการสำหรับตัวอย่างเพลง กำหนดค่าใหม่ในสไตล์แต่ละแบบ

    <resources>
    
        <style name="Theme.Widget.ListItem">
            <item name="widgetTitle"></item>
            <item name="widgetContent"></item>
        </style>
        <style name="Theme.Widget.ListItem.Preview1">
            <item name="widgetTitle">Fake Title 1</item>
            <item name="widgetContent">Fake content 1</item>
        </style>
        <style name="Theme.Widget.ListItem.Preview2">
            <item name="widgetTitle">Fake title 2</item>
            <item name="widgetContent">Fake content 2</item>
        </style>
    
    </resources>
    
  4. ใช้รูปแบบกับรายการจำลองในเลย์เอาต์ตัวอย่าง โดยทำดังนี้

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" ...>
    
        <include layout="@layout/widget_view" ... />
    
        <include layout="@layout/widget_list_item"
            android:theme="@style/Theme.Widget.ListItem.Preview1" />
    
        <include layout="@layout/widget_list_item"
            android:theme="@style/Theme.Widget.ListItem.Preview2" />
    
    </LinearLayout>