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

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

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

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

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

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

يوضّح ما يلي كل نوع من أنواع التعديلات ويقدّم مقتطفات رمز لكل منها.

  • التحديث الكامل: اتصل على 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);
    

يمكنك استدعاء هذه الطرق من أي مكان في تطبيقك، شرط أن يكون للتطبيق UID مماثل لـ AppWidgetProvider.

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

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

تعديلها بصفة دورية

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

لا تتيح الدالة 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 من واجهة برمجة التطبيقات)، لا يمكن للتطبيقات تسجيل البث الضمني في بياناتها، مع بعض الاستثناءات.

الاعتبارات الواجب مراعاتها عند تعديل تطبيق مصغّر من BroadcastReceiver

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

مدة التحديث

وكقاعدة عامة، يسمح النظام لأجهزة استقبال البث، التي تعمل عادةً في سلسلت الرسائل الأساسية للتطبيق، بالعمل لمدة تصل إلى 10 ثوانٍ قبل اعتبارها غير مستجيبة والتسبب في خطأ "التطبيق لا يستجيب". إذا استغرق تعديل التطبيق المصغّر وقتًا أطول، ننصحك باتّباع الخطوات التالية:

  • حدِّد موعدًا لمهمة باستخدام 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>