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

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

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

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

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

คุณสามารถอัปเดตวิดเจ็ตได้ 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 ใน XML ของ appwidget-provider ชิ้น อัปเดตจะทริกเกอร์เมธอด AppWidgetProvider.onUpdate() ซึ่งคุณ สามารถวางโค้ดเพื่ออัปเดตวิดเจ็ต อย่างไรก็ตาม ลองพิจารณาอีกทางเลือกสำหรับ ข้อมูลอัปเดตจาก Broadcast Receiver ที่อธิบายไว้ใน ต่อไปนี้ ถ้าวิดเจ็ตของคุณจำเป็นต้องโหลดข้อมูลไม่พร้อมกันหรือใช้เวลามากกว่า มากกว่า 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 ของ Google ข้อยกเว้น

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

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

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

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

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

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

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

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

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

ตัวอย่างเช่น เพิ่มส่วน 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>