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

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

تحسينات لتحديث محتوى الأدوات

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

أنواع تعديلات الأدوات

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

في ما يلي شرح لكل نوع من أنواع التعديل، بالإضافة إلى مقتطفات رموز لكل نوع.

  • التحديث الكامل: يمكنك الاتصال بالرقم 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 في ملف XML الخاص appwidget-provider. ويؤدي كل تعديل إلى تفعيل طريقة AppWidgetProvider.onUpdate()، وهي المكان الذي يمكنك فيه وضع الرمز لتعديل التطبيق المصغّر. ومع ذلك، يجب مراعاة بدائل تحديثات جهاز استقبال البث الموضَّحة في القسم التالي، وذلك إذا كانت الأداة بحاجة إلى تحميل البيانات بشكل غير متزامن أو تستغرق أكثر من 10 ثوانٍ للتحديث، لأنّه بعد 10 ثوانٍ، يعتبر النظام BroadcastReceiver غير مستجيب.

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

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

إذا تم تعديل التطبيق المصغّر من خلال 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>