إنشاء تطبيق مصغّر متقدّم

توضّح هذه الصفحة الممارسات الموصى بها لإنشاء أداة أكثر تقدمًا لتحسين تجربة المستخدم.

تحسينات لتحديث محتوى التطبيق المصغّر

قد يكون تحديث محتوى التطبيق المصغّر مُكلفًا من الناحية الحسابية. ولتوفير استهلاك البطارية، عليك تحسين نوع التحديث ومعدّل تكراره وتوقيته.

أنواع تحديثات التطبيقات المصغّرة

هناك ثلاث طرق لتحديث التطبيق المصغَّر: تحديث كامل، وتحديث جزئي، وتحديث البيانات في حالة استخدام أداة المجموعة. لكل منها تكاليف حسابية وتبعات مختلفة.

في ما يلي وصف لكل نوع تحديث، كما يوفر مقتطفات رمز لكل نوع.

  • تحديث كامل: يمكنك الاتصال بالرقم 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 الذي تم توفيره سابقًا. يتم تجاهل هذه الطريقة إذا لم تتلقَّ الأداة تحديثًا كاملاً واحدًا على الأقل من خلال 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);
    

يمكنك طلب هذه الطرق من أي مكان في تطبيقك، طالما أنّ التطبيق يحتوي على المعرّف الفريد نفسه لفئة AppWidgetProvider المقابلة.

تحديد عدد مرات تحديث التطبيق المصغّر

يتم تعديل التطبيقات المصغّرة بشكل دوري استنادًا إلى القيمة المقدّمة للسمة updatePeriodMillis. ويمكن تحديث الأداة استجابةً لتفاعل المستخدم أو لبث التحديثات أو لكليهما.

التحديث بصفة دورية

يمكنك التحكّم في معدّل تكرار التعديلات الدورية من خلال تحديد قيمة AppWidgetProviderInfo.updatePeriodMillis في ملف appwidget-provider XML. يؤدي كل تحديث إلى ظهور الإجراء AppWidgetProvider.onUpdate()، وهو المكان الذي يمكنك فيه وضع الرمز لتحديث الأداة. ومع ذلك، ننصحك بالاطّلاع على بدائل تحديثات مستقبل البث الموضحة في القسم التالي إذا كانت الأداة بحاجة إلى تحميل البيانات بشكل غير متزامن أو إذا استغرق تحديثها أكثر من 10 ثوانٍ، لأن النظام يعتبر BroadcastReceiver غير مستجيب بعد 10 ثوانٍ.

لا يسمح updatePeriodMillis باستخدام القيم التي تقلّ عن 30 دقيقة. ومع ذلك، إذا كنت تريد إيقاف التحديثات الدورية، يمكنك تحديد 0.

يمكنك السماح للمستخدمين بتعديل معدل تكرار التعديلات في الإعدادات. على سبيل المثال، قد يرغب في تحديث مؤشر سهم كل 15 دقيقة أو أربع مرات فقط في اليوم. في هذه الحالة، اضبط السمة updatePeriodMillis على 0 واستخدِم WorkManager بدلاً من ذلك.

التعديل استجابةً لتفاعل مستخدم

في ما يلي بعض الطرق المُقترَحة لتحديث الأداة استنادًا إلى تفاعل المستخدم:

  • من نشاط التطبيق: يمكنك استدعاء AppWidgetManager.updateAppWidget مباشرةً استجابةً لتفاعل مستخدم، مثل نقرة المستخدم.

  • من التفاعلات عن بُعد، مثل إشعار أو تطبيق مصغّر: أنشئ PendingIntent، ثم حدِّث التطبيق المصغّر من العناصر التي تم استدعاؤها Activity أو Broadcast أو Service. ويمكنك اختيار أولويتك الخاصة. على سبيل المثال، إذا اخترت Broadcast لـ PendingIntent، يمكنك اختيار بث في المقدّمة لمنح الأولوية BroadcastReceiver.

التعديل استجابةً لحدث بث

مثال على حدث البث الذي يتطلب أداة للتحديث هو عندما يلتقط المستخدم صورة. في هذه الحالة، تريد تحديث الأداة عند اكتشاف صورة جديدة.

يمكنك جدولة مهمة باستخدام JobScheduler وتحديد البث كعامل التفعيل باستخدام الطريقة JobInfo.Builder.addTriggerContentUri.

يمكنك أيضًا تسجيل BroadcastReceiver للبث، على سبيل المثال، الاستماع إلى ACTION_LOCALE_CHANGED. مع ذلك، وبما أنّ هذا الإجراء يستهلك موارد الجهاز، استخدِمه بعناية واستمِع فقط إلى البث المحدّد. ومع طرح قيود البث في Android 7.0 (المستوى 24 لواجهة برمجة التطبيقات) وAndroid 8.0 (المستوى 26 لواجهة برمجة التطبيقات)، لن تتمكّن التطبيقات من تسجيل عمليات البث الضمنية في بياناتها، مع بعض الاستثناءات.

نقاط يجب أخذها في الاعتبار عند تحديث تطبيق مصغّر من جهاز BroadcastRecipient.

إذا تم تحديث التطبيق المصغَّر من BroadcastReceiver، بما في ذلك AppWidgetProvider، يجب الانتباه إلى الاعتبارات التالية المتعلقة بمدة تحديث التطبيق المصغَّر وأولويته.

مدة التحديث

كقاعدة عامة، يسمح النظام لأجهزة استقبال البث، التي تعمل عادةً في سلسلة التعليمات الرئيسية للتطبيق، بتشغيلها لمدة تصل إلى 10 ثوانٍ قبل اعتبارها غير مستجيبة وظهور خطأ التطبيق لا يستجيب (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:

<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>