الاقتراحات في Android N والإصدارات الأقدم

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

ملاحظة: استخدِم واجهات برمجة التطبيقات الموضّحة هنا لتقديم اقتراحات في التطبيقات التي تعمل بإصدارات Android حتى الإصدار Android 7.1 (المستوى 25 من واجهة برمجة التطبيقات) فقط. لتقديم اقتراحات للتطبيقات التي تعمل بنظام التشغيل Android 8.0 (المستوى 26 لواجهة برمجة التطبيقات) والإصدارات الأحدث، يجب أن يستخدم تطبيقك قنوات الاقتراحات.

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

الشكل 1. مثال على صف الاقتراحات

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

أفضل الممارسات المتعلّقة بالاقتراحات

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

أنواع الاقتراحات

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

  • اقتراحات محتوى مواصلة المشاهدة للحلقة التالية كي يتمكّن المستخدمون من مواصلة مشاهدة المسلسل ويمكنك بدلاً من ذلك استخدام اقتراحات لمواصلة مشاهدة الأفلام أو البرامج التلفزيونية أو ملفات البودكاست المتوقفة مؤقتًا حتى يتمكّن المستخدمون من العودة إلى مشاهدة المحتوى المتوقّف مؤقتًا ببضع نقرات فقط.
  • اقتراحات محتوى جديد، مثل حلقة جديدة يتم عرضها لأول مرة، إذا انتهى المستخدم من مشاهدة مسلسل آخر بالإضافة إلى ذلك، إذا كان تطبيقك يسمح للمستخدمين بالاشتراك في المحتوى أو متابعته أو تتبّعه، استخدِم اقتراحات محتوى جديدة للعناصر التي لم تتم مشاهدتها في قائمة المحتوى الذي يتم تتبّعه.
  • اقتراحات المحتوى ذو الصلة التي تستند إلى سلوكيات المشاهدة السابقة للمستخدمين

للحصول على مزيد من المعلومات حول كيفية تصميم بطاقات الاقتراحات لأفضل تجربة مستخدم، راجع صف الاقتراحات في مواصفات تصميم Android TV.

إعادة تحميل المحتوى المقترح

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

تخصيص الاقتراحات

يمكنك تخصيص بطاقات الاقتراحات لعرض معلومات حول العلامة التجارية من خلال ضبط عناصر واجهة المستخدم، مثل صورة المقدّمة والخلفية في البطاقة واللون ورمز التطبيق والعنوان والعنوان الفرعي. لمزيد من المعلومات، يمكنك الاطّلاع على صف الاقتراحات في مواصفات تصميم Android TV.

اقتراحات المجموعة

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

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

استخدِم NotificationCompat.Builder.setGroup() لضبط السلسلة الرئيسية للمجموعة لاقتراح. على سبيل المثال، لوضع علامة على اقتراح معيّن باعتباره ينتمي إلى مجموعة تتضمن محتوًى رائجًا جديدًا، يمكنك استخدام الاسم setGroup("trending").

إنشاء خدمة اقتراحات

يتم إنشاء اقتراحات المحتوى من خلال المعالجة في الخلفية. لكي يساهم تطبيقك في الاقتراحات، أنشِئ خدمة تضيف بشكل دوري بطاقات بيانات من كتالوج تطبيقك إلى قائمة اقتراحات النظام.

يوضِّح مثال الرمز التالي كيفية تمديد IntentService لإنشاء خدمة اقتراحات لتطبيقك:

Kotlin

class UpdateRecommendationsService : IntentService("RecommendationService") {
    override protected fun onHandleIntent(intent: Intent) {
        Log.d(TAG, "Updating recommendation cards")
        val recommendations = VideoProvider.getMovieList()
        if (recommendations == null) return

        var count = 0

        try {
            val builder = RecommendationBuilder()
                    .setContext(applicationContext)
                    .setSmallIcon(R.drawable.videos_by_google_icon)

            for (entry in recommendations.entrySet()) {
                for (movie in entry.getValue()) {
                    Log.d(TAG, "Recommendation - " + movie.getTitle())

                    builder.setBackground(movie.getCardImageUrl())
                            .setId(count + 1)
                            .setPriority(MAX_RECOMMENDATIONS - count)
                            .setTitle(movie.getTitle())
                            .setDescription(getString(R.string.popular_header))
                            .setImage(movie.getCardImageUrl())
                            .setIntent(buildPendingIntent(movie))
                            .build()
                    if (++count >= MAX_RECOMMENDATIONS) {
                        break
                    }
                }
                if (++count >= MAX_RECOMMENDATIONS) {
                    break
                }
            }
        } catch (e: IOException) {
            Log.e(TAG, "Unable to update recommendation", e)
        }
    }

    private fun buildPendingIntent(movie: Movie): PendingIntent {
        val detailsIntent = Intent(this, DetailsActivity::class.java)
        detailsIntent.putExtra("Movie", movie)

        val stackBuilder = TaskStackBuilder.create(this)
        stackBuilder.addParentStack(DetailsActivity::class.java)
        stackBuilder.addNextIntent(detailsIntent)

        // Ensure a unique PendingIntents, otherwise all
        // recommendations end up with the same PendingIntent
        detailsIntent.setAction(movie.getId().toString())

        val intent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
        return intent
    }

    companion object {
        private val TAG = "UpdateRecommendationsService"
        private val MAX_RECOMMENDATIONS = 3
    }
}

Java

public class UpdateRecommendationsService extends IntentService {
    private static final String TAG = "UpdateRecommendationsService";
    private static final int MAX_RECOMMENDATIONS = 3;

    public UpdateRecommendationsService() {
        super("RecommendationService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Log.d(TAG, "Updating recommendation cards");
        HashMap<String, List<Movie>> recommendations = VideoProvider.getMovieList();
        if (recommendations == null) return;

        int count = 0;

        try {
            RecommendationBuilder builder = new RecommendationBuilder()
                    .setContext(getApplicationContext())
                    .setSmallIcon(R.drawable.videos_by_google_icon);

            for (Map.Entry<String, List<Movie>> entry : recommendations.entrySet()) {
                for (Movie movie : entry.getValue()) {
                    Log.d(TAG, "Recommendation - " + movie.getTitle());

                    builder.setBackground(movie.getCardImageUrl())
                            .setId(count + 1)
                            .setPriority(MAX_RECOMMENDATIONS - count)
                            .setTitle(movie.getTitle())
                            .setDescription(getString(R.string.popular_header))
                            .setImage(movie.getCardImageUrl())
                            .setIntent(buildPendingIntent(movie))
                            .build();

                    if (++count >= MAX_RECOMMENDATIONS) {
                        break;
                    }
                }
                if (++count >= MAX_RECOMMENDATIONS) {
                    break;
                }
            }
        } catch (IOException e) {
            Log.e(TAG, "Unable to update recommendation", e);
        }
    }

    private PendingIntent buildPendingIntent(Movie movie) {
        Intent detailsIntent = new Intent(this, DetailsActivity.class);
        detailsIntent.putExtra("Movie", movie);

        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
        stackBuilder.addParentStack(DetailsActivity.class);
        stackBuilder.addNextIntent(detailsIntent);
        // Ensure a unique PendingIntents, otherwise all
        // recommendations end up with the same PendingIntent
        detailsIntent.setAction(Long.toString(movie.getId()));

        PendingIntent intent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
        return intent;
    }
}

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

<manifest ... >
  <application ... >
    ...

    <service
            android:name="com.example.android.tvleanback.UpdateRecommendationsService"
            android:enabled="true" />
  </application>
</manifest>

تقديم اقتراحات

بعد بدء تشغيل خدمة الاقتراحات، يجب إنشاء اقتراحات وتمريرها إلى إطار عمل Android. يتلقّى إطار العمل الاقتراحات باعتبارها عناصر Notification تستخدم نموذجًا معيّنًا ويتم تمييزها بفئة معيّنة.

تعيين القيم

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

Kotlin

class RecommendationBuilder {
    ...

    fun setTitle(title: String): RecommendationBuilder {
        this.title = title
        return this
    }

    fun setDescription(description: String): RecommendationBuilder {
        this.description = description
        return this
    }

    fun setImage(uri: String): RecommendationBuilder {
        imageUri = uri
        return this
    }

    fun setBackground(uri: String): RecommendationBuilder {
        backgroundUri = uri
        return this
    }

...

Java

public class RecommendationBuilder {
    ...

    public RecommendationBuilder setTitle(String title) {
            this.title = title;
            return this;
        }

        public RecommendationBuilder setDescription(String description) {
            this.description = description;
            return this;
        }

        public RecommendationBuilder setImage(String uri) {
            imageUri = uri;
            return this;
        }

        public RecommendationBuilder setBackground(String uri) {
            backgroundUri = uri;
            return this;
        }
...

إنشاء الإشعار

بعد ضبط القيم، يمكنك إنشاء الإشعار وتحديد القيم من فئة أداة الإنشاء للإشعار وإرسال طلب بقيمة NotificationCompat.Builder.build().

احرص أيضًا على طلب الرقم setLocalOnly() لكي لا يظهر إشعار "NotificationCompat.BigPictureStyle" على الأجهزة الأخرى.

يوضح مثال الرمز البرمجي التالي كيفية إنشاء توصية.

Kotlin

class RecommendationBuilder {
    ...

    @Throws(IOException::class)
    fun build(): Notification {
        ...

        val notification = NotificationCompat.BigPictureStyle(
        NotificationCompat.Builder(context)
                .setContentTitle(title)
                .setContentText(description)
                .setPriority(priority)
                .setLocalOnly(true)
                .setOngoing(true)
                .setColor(context.resources.getColor(R.color.fastlane_background))
                .setCategory(Notification.CATEGORY_RECOMMENDATION)
                .setLargeIcon(image)
                .setSmallIcon(smallIcon)
                .setContentIntent(intent)
                .setExtras(extras))
                .build()

        return notification
    }
}

Java

public class RecommendationBuilder {
    ...

    public Notification build() throws IOException {
        ...

        Notification notification = new NotificationCompat.BigPictureStyle(
                new NotificationCompat.Builder(context)
                        .setContentTitle(title)
                        .setContentText(description)
                        .setPriority(priority)
                        .setLocalOnly(true)
                        .setOngoing(true)
                        .setColor(context.getResources().getColor(R.color.fastlane_background))
                        .setCategory(Notification.CATEGORY_RECOMMENDATION)
                        .setLargeIcon(image)
                        .setSmallIcon(smallIcon)
                        .setContentIntent(intent)
                        .setExtras(extras))
                .build();

        return notification;
    }
}

تشغيل خدمة الاقتراحات

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

Kotlin

class BootupActivity : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Log.d(TAG, "BootupActivity initiated")
        if (intent.action.endsWith(Intent.ACTION_BOOT_COMPLETED)) {
            scheduleRecommendationUpdate(context)
        }
    }

    private fun scheduleRecommendationUpdate(context: Context) {
        Log.d(TAG, "Scheduling recommendations update")
        val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
        val recommendationIntent = Intent(context, UpdateRecommendationsService::class.java)
        val alarmIntent = PendingIntent.getService(context, 0, recommendationIntent, 0)
        alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                INITIAL_DELAY,
                AlarmManager.INTERVAL_HALF_HOUR,
                alarmIntent
        )
    }

    companion object {
        private val TAG = "BootupActivity"
        private val INITIAL_DELAY:Long = 5000
    }
}

Java

public class BootupActivity extends BroadcastReceiver {
    private static final String TAG = "BootupActivity";

    private static final long INITIAL_DELAY = 5000;

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "BootupActivity initiated");
        if (intent.getAction().endsWith(Intent.ACTION_BOOT_COMPLETED)) {
            scheduleRecommendationUpdate(context);
        }
    }

    private void scheduleRecommendationUpdate(Context context) {
        Log.d(TAG, "Scheduling recommendations update");

        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        Intent recommendationIntent = new Intent(context, UpdateRecommendationsService.class);
        PendingIntent alarmIntent = PendingIntent.getService(context, 0, recommendationIntent, 0);

        alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                INITIAL_DELAY,
                AlarmManager.INTERVAL_HALF_HOUR,
                alarmIntent);
    }
}

يجب أن يتم تنفيذ هذه الفئة BroadcastReceiver بعد بدء تشغيل جهاز التلفزيون الذي تم تثبيته عليه. لتحقيق ذلك، يجب تسجيل هذه الفئة في بيان التطبيق باستخدام فلتر أهداف يرصد اكتمال عملية تشغيل الجهاز. يوضح الرمز النموذجي التالي كيفية إضافة هذه الإعدادات إلى البيان:

<manifest ... >
  <application ... >
    <receiver android:name="com.example.android.tvleanback.BootupActivity"
              android:enabled="true"
              android:exported="false">
      <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
      </intent-filter>
    </receiver>
  </application>
</manifest>

ملاحظة مهمّة: إنّ تلقّي إشعار اكتمال التشغيل يتطلّب أن يطلب تطبيقك إذن RECEIVE_BOOT_COMPLETED. لمزيد من المعلومات، يُرجى الاطّلاع على "ACTION_BOOT_COMPLETED".

في طريقة onHandleIntent() لفئة خدمة الاقتراحات، انشر الاقتراح إلى المدير على النحو التالي:

Kotlin

val notification = notificationBuilder.build()
notificationManager.notify(id, notification)

Java

Notification notification = notificationBuilder.build();
notificationManager.notify(id, notification);