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

تجربة طريقة "الكتابة"
‫Jetpack Compose هي مجموعة أدوات واجهة المستخدم التي يُنصح باستخدامها على Android. تعرَّف على كيفية إنشاء أدوات باستخدام واجهات برمجة التطبيقات المتوافقة مع Compose.

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

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

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

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

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

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

  • التحديث الكامل: اتّصِل بالرقم 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 في ملف appwidget-provider XML. يؤدي كل تحديث إلى تشغيل الطريقة 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. ومع ذلك، بما أنّ هذه العملية تستهلك موارد الجهاز، يجب استخدامها بحذر والاستماع فقط إلى البث المحدّد. مع طرح قيود البث في الإصدار 7.0 من نظام التشغيل Android (المستوى 24 من واجهة برمجة التطبيقات) والإصدار 8.0 (المستوى 26 من واجهة برمجة التطبيقات)، لا يمكن للتطبيقات تسجيل عمليات البث الضمنية في بياناتها، مع بعض الاستثناءات.

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

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

مدة التحديث

كقاعدة عامة، يسمح النظام لمستقبِلات البث، التي تعمل عادةً في سلسلة التعليمات الرئيسية للتطبيق، بالعمل لمدة تصل إلى 10 ثوانٍ قبل اعتبارها غير مستجيبة وتفعيل خطأ التطبيق لا يستجيب (ANR). لتجنُّب حظر سلسلة التعليمات الرئيسية أثناء معالجة البث، استخدِم طريقة goAsync. إذا استغرق تحديث التطبيق المصغّر وقتًا أطول، ننصحك بتحديد موعد لمهمة باستخدام WorkManager.

Caution: Any work you do here blocks further broadcasts until it completes,
so it can slow the receiving of later events.

يمكنك الاطّلاع على اعتبارات الأمان وأفضل الممارسات للحصول على مزيد من المعلومات.

أولوية التحديث

تُنفَّذ عمليات البث تلقائيًا كعمليات في الخلفية، بما في ذلك عمليات البث التي تتم باستخدام 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>