Fire OS 7搭載Fire TV対応アプリの開発について


Fire OS 7搭載Fire TV対応アプリの開発について

Fire OS 7はAndroid 9 Pieをベースにしています。以下のガイドラインを読んで、アプリとFire OS 7の互換性を確認してください。

Fire OS 7搭載デバイスと旧Fire OS搭載デバイス

Android 9 Pie(APIレベル28)をベースにしたFire OS 7は、2019年にFire TVデバイス向けにリリースされる予定です。

2018年に発売されたAmazon Fire TVデバイスのほとんどは、Android Nougat(Android 7.1.2、レベル25)をベースにしたFire OS 6を採用しています。それ以前のFire TVデバイスは、Fire OS 5(Android 5.1、レベル22)を搭載しています。通常、旧型デバイスのFire OSバージョンはアップグレードされません。

Fire TVデバイスおよびバージョンの詳細については、Fire OSのバージョンを参照してください。

Fire OS 7におけるAndroidの変更点

Fire OS 6からFire OS 7にアップグレードするには、Nougat(Android 7.1.2)からOreo(Android 8.0)またはPie(Android 9)への移行が必要です。

Android 8.0とAndroid 9で導入された変更点のため、アプリをFire OS 7デバイスで正常に動作させるにはコードを変更する必要があります。

Android 8.0の変更点

Android 8.0における主な変更点は以下のとおりです。

  • 通知チャネル: すべての通知(「おすすめ」パートナーが管理する「おすすめ」を含む)をチャネルに割り当てる必要があります。チャネルについて詳しくは、Androidドキュメントの通知チャネルを作成して管理するを参照してください。サンプルコードと追加情報については、後述のFire OS 7における通知セクションを参照してください。

  • バックグラウンドサービス: Android 8.0では、バックグラウンドサービスの実行が制限されています。そのため、Fire OS 7ではアプリがバックグラウンドサービスを使用しておすすめを更新することができません。このバックグラウンドサービスの制限を回避する方法として、AndroidではJobSchedulersの使用が推奨されています。詳細については、Androidドキュメントのバックグラウンド実行制限を参照してください。

  • ネイティブライブラリ: 書き込みと実行の両方が可能なロードセグメントが含まれているネイティブライブラリはロードされません。

  • パーミッション: 同じグループに属しているパーミッションでも、各パーミッションをアプリから明示的にリクエストする必要があります。

  • MediaSessionイベント: アプリでオーディオを処理するには、MediaSessionを適切に使用する必要があります。詳細については、Android 8.0ドキュメントのFinding a media session(英語のみ)を参照してください。Androidがイベントを処理する際、イベントを処理するためのアクティブなMedia Sessionがフォアグラウンドアクティビティになければ、AndroidはほかのMedia Sessionを探します。Fire TVにおけるマルチメディアアプリの要件も参照してください。

上記の変更点については、Android 8.0での動作変更点を参照してください。

Android 9の変更点

Android 9における主な変更点は以下のとおりです。

  • プライバシーに関する変更点: アプリからWi-Fiへのアクセスが制限されます。バックグラウンドで実行中のアプリからユーザー入力やセンサーデータにアクセスする機能が制限されています。

上記の変更点については、動作の変更点:すべてのアプリを参照してください。

Fire OS 7とAndroid 9の互換性

Fire OS 7で実装されたすべての機能は、Android 9と互換性があります。Android 9のすべての機能がFire OS 7で利用可能なわけではありませんが、Fire OS 7で実装された機能についてはAndroid 9でも同等に使用可能です。Android 9の一部の機能(分割画面、通知ドット、アダプティブアイコンなど)は、Fire OS 7ではサポートされていません。

また、Fire OS 7の機能とAndroid 9は互換性がありますが、Amazon FireデバイスでGoogleのサービスを使用することはできません。代わりに、必要なサービス(アプリ内課金など)に対応するAmazon Developer SDKを使用する必要があります。詳細については、Fire TVとAndroid TVにおける開発の違いを参照してください。

Fire OS 7デバイスをアプリの対象に指定

ユーザーがアプリを実行するデバイスとして、Fire OS 5、Fire OS 6、Fire OS 7が考えられます。デバイスのFire OSバージョンとアプリの互換性を最大限に高めるため、SDKレベルに基づいた対象デバイスの指定をお勧めします。

Fire OS 7デバイスを対象に指定するには、コードでBuild.VERSION.SDK_INT28(Android 9のAPIレベル)以上であることを確認してください。

詳細については、Fire TVデバイスの識別を参照してください。Androidドキュメントの異なるプラットフォームバージョンのサポートも参照してください。

Fire OS 7デバイスにおけるアプリの互換性テスト

アプリとFire OS 7の互換性は、実際のデバイスに接続することでテストが可能です。アプリテストサービスでは最小限の互換性テストしか行えないため、アプリにFire OS 7デバイスとの互換性があるかどうかはわかりません。

minSdkVersionとtargetSdkVersionの設定

一般的に、minSdkVersionは22以下に設定します(アプリに必要な最小APIレベルを考慮してください)。そうすることで、すべてのAmazon Fire TVデバイス(Fire OS 5で動作する古いデバイスを含む)と、ほとんどのFireタブレットデバイスでアプリが使用可能になります。targetSdkVersionは、アプリのテストに使用した最も高いAPIレベルに設定します。

minSdkVersionがサポート対象デバイスに与える影響

アプリが正常に動作するために必要な最小SDKレベルは、アプリのマニフェスト(またはbuild.gradleファイル)内のminSdkVersion属性で設定します(そのAPIレベルをサポートしていないデバイスでは、該当アプリのインストールはできません。詳細については、デバイスフィルタリングと互換性を参照してください)。

Fire OS 5デバイスはAPIレベル22(Lollipop 5.1)、Fire OS 7デバイスはAPIレベル28(Android 9)をベースにしています。minSdkVersionを22に設定した場合、アプリが正常に動作するにはデバイスのAPIレベルが22以上であることが必要になります。

Androidではレベルに下位互換性があるため、minSdkVersionを22に設定すると、より高いAPIレベル(28など)のデバイスにもアプリがインストール可能になります。APIレベル28には、通常、レベル1~28の全APIが含まれます(リリースごとに前回のレベルに追加されるため)。

しかし、Android 9(APIレベル28)のAPIを利用する場合、minSdkVersionを22に設定すると、APIレベルが28ではないFire OS 5デバイスにもアプリがインストールされてしまいます。そのため、防御的なコーディングを行う必要があります。デバイスのレベルを確認して、そのAPIレベルがサポートされていない場合は別のレベルにフォールバックするようなコード作成が必須となります。たとえば、以下のようなコードが考えられます。

targetSdkVersion

if (Build.VERSION.SDK_INT >= 28) {
 Log.v(TAG, "はい、これはAPIレベル28以上のデバイスです");
} else {
 Log.v(TAG, "いいえ、これはAPIレベル28以上のデバイスではありません");
}

このコードは、デバイスのAPIレベルが28以上であるかどうかを確認します。APIレベルが28以上の場合、コードが実行されます。そうでない場合、elseロジックでフォールバックします。

デフォルトでは、targetSdkVersionが指定されていない場合、minSdkVersionと同じ値が使用されます。targetSdkVersionには、アプリのテストに使用した最も高いAPIレベルを設定することができます。Androidは、アプリがこの値に基づいてデバイスで適切に動作するようにします。

たとえば、targetSdkVersionを23以上(Marshmallowのリリース)に設定すると、AndroidはMarshmallowに含まれるランタイムパーミッションチェックを適用します。しかし、targetSdkVersionが23以下(Marshmallowにランタイムパーミッションチェックが導入される前)の場合、Androidはこの動作をアプリに適用しません。

maxSdkVersion属性の使用は推奨されていません。ただし、アプリをFire OS 5デバイスのみで使用可能にする場合は、maxSdkVersionを22に設定しても構いません。アプリをFire OS 7デバイスのみで使用可能にする場合は、minSdkVersionを28に設定しても構いません。

詳細については、以下を参照してください。

サポート

Fire OS 7でアプリに問題が見つかった場合は、Amazon Fire TVとFire TV Stickに関するフォーラムでその問題を報告してください。

Fire OS 7における通知

Android 8.0(APIレベル26)では、アプリが送信する通知をチャネルに割り当てる必要があります(「おすすめ」は通知の一種です)。 アプリが通知またはおすすめを送信する場合、チャネルを作成し、そのチャネルを通知に関連付ける必要があります。チャネルIDがない通知はドロップされます。

通知にチャネルを追加する手順の概要は、以下のとおりです。

  1. 手順1: 通知チャネルを作成し、通知マネージャーに渡して登録する
  2. 手順2: 通知のチャネルIDを設定する(次のいずれかの方法を使用します)

以下のセクションでは、上記の内容について詳しく説明すると共に、コードサンプルを示します。

手順1: 通知チャネルを作成し、通知マネージャーに渡して登録する

以下は、Androidドキュメントの通知チャネルを作成するからの抜粋です。

通知チャネルを作成する

通知チャネルを作成する手順は次のとおりです。

  1. 固有のチャネル ID、ユーザーに表示される名前、重要度を指定して、NotificationChannelオブジェクトを作成します。
  2. 必要であれば、システム設定でユーザーに表示される説明をsetDescriptionで指定します。
  3. createNotificationChannelに通知チャネルを渡して登録します。

    private void createNotificationChannel() {
        //NotificationChannelを作成しますが、API 26以降でのみ使用します。
        //これは、NotificationChannelクラスが新しく、サポートライブラリにないためです
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            CharSequence name = getString(R.string.channel_name);
            String description = getString(R.string.channel_description);
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
            channel.setDescription(description);
            //チャネルをシステムに登録します。この後で重要度や
            //通知動作を変更することはできません
            NotificationManager notificationManager = getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);
        }
    }
    

手順2: 通知のチャネルIDを設定する

通知のチャネルIDを設定する方法には、2つのオプションがあります。ContentRecommendationとリフレクションを使用するオプションと、Notification.Builderを使用するオプションです。

オプション1: createContentRecommendationとリフレクションを使用してチャネルIDを設定する

Notification notification = createContentRecommendation(largeIcon, notificationId);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    Log.d(TAG, "SDKバージョンはAndroid O以上です");

    try {
        Field channel = notification.getClass().getDeclaredField("mChannelId");
        channel.setAccessible(true);
        channel.set(notification, StringTerms.CHANNEL_ID);
    }
    catch (Exception e) {
        Log.d(TAG, "チャネルIDを設定できません", e);
    }
}

オプション2: Notification.BuilderとチャネルIDを使用する

以下のコードは、Google Gitに記載されたAndroidオープンソースプロジェクトのコードを適宜変更したものです。

public Notification getNotificationObject(Context context) {
    Notification.Builder builder = new Notification.Builder(context, "channelId");
    RecommendationExtender recExtender = new RecommendationExtender();

    //通知オブジェクト内にあるすべてのおすすめコンテンツデータをエンコードします

    builder.setCategory(Notification.CATEGORY_RECOMMENDATION);
    builder.setContentTitle(mTitle);
    builder.setContentText(mText);
    builder.setContentInfo(mSourceName);
    builder.setLargeIcon(mContentImage);
    builder.setSmallIcon(mBadgeIconId);
    if (mBackgroundImageUri != null) {
        builder.getExtras().putString(Notification.EXTRA_BACKGROUND_IMAGE_URI, mBackgroundImageUri);
    }
    builder.setColor(mColor);
    builder.setGroup(mGroup);
    builder.setSortKey(mSortKey);
    builder.setProgress(mProgressMax, mProgressAmount, false);
    builder.setAutoCancel(mAutoDismiss);

    if (mContentIntentData != null) {
        PendingIntent contentPending;
        if (mContentIntentData.mType == INTENT_TYPE_ACTIVITY) {
            contentPending = PendingIntent.getActivity(context, mContentIntentData.mRequestCode,
            mContentIntentData.mIntent, PendingIntent.FLAG_UPDATE_CURRENT,
            mContentIntentData.mOptions);
        }
        else if (mContentIntentData.mType == INTENT_TYPE_SERVICE) {
            contentPending = PendingIntent.getService(context, mContentIntentData.mRequestCode,
            mContentIntentData.mIntent, PendingIntent.FLAG_UPDATE_CURRENT);
            }
            else { // Default:INTENT_TYPE_BROADCAST{
            contentPending = PendingIntent.getBroadcast(context,
            mContentIntentData.mRequestCode,
            mContentIntentData.mIntent, PendingIntent.FLAG_UPDATE_CURRENT);
            }
        builder.setContentIntent(contentPending);
    }

    if (mDismissIntentData != null) {
        PendingIntent dismissPending;
        if (mDismissIntentData.mType == INTENT_TYPE_ACTIVITY) {
            dismissPending = PendingIntent.getActivity(context, mDismissIntentData.mRequestCode,
            mDismissIntentData.mIntent, PendingIntent.FLAG_UPDATE_CURRENT,
            mDismissIntentData.mOptions);
        }
        else if (mDismissIntentData.mType == INTENT_TYPE_SERVICE) {
            dismissPending = PendingIntent.getService(context, mDismissIntentData.mRequestCode,
            mDismissIntentData.mIntent, PendingIntent.FLAG_UPDATE_CURRENT);
            }
            else { // Default:INTENT_TYPE_BROADCAST{
                dismissPending = PendingIntent.getBroadcast(context,
                mDismissIntentData.mRequestCode,
                mDismissIntentData.mIntent, PendingIntent.FLAG_UPDATE_CURRENT);
            }
        builder.setDeleteIntent(dismissPending);
    }

    recExtender.setContentTypes(mContentTypes);
    recExtender.setGenres(mContentGenres);
    recExtender.setPricingInformation(mPriceType, mPriceValue);
    recExtender.setStatus(mStatus);
    recExtender.setMaturityRating(mMaturityRating);
    recExtender.setRunningTime(mRunningTime);

    builder.extend(recExtender);
    Notification notif = builder.build();
    return notif;
}

詳細については、Androidドキュメントの通知チャネルを作成して管理するを参照してください。