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

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

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

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

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

คุณสามารถอัปเดตวิดเจ็ตได้ 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 อย่างไรก็ตาม เนื่องจากการดำเนินการนี้ใช้ทรัพยากรของอุปกรณ์มาก โปรดใช้ความระมัดระวังและรอฟัง ไปยังการออกอากาศที่เฉพาะเจาะจงเท่านั้น ด้วยการเปิดตัว broadcast ข้อจำกัดใน 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>