Fire TV対応アプリのパフォーマンスとユーザーエクスペリエンスの最適化は、最優先事項です。その際、考慮すべきポイントの一つが、バックグラウンドメモリの使用量です。これは、アプリの起動時間と全体的なユーザーエクスペリエンスに多大な影響を与えます。バックグラウンドメモリの使用量に既定のしきい値はありませんが、Fire OSでリソースの解放が必要な場合、バックグラウンドメモリをより多く使用しているFire TV対応アプリが最初にクリアされます。アプリの「コールドスタート」では、関連するプロセス、アクティビティ、ビュー、アセットをすべてメモリに読み込む必要があります。この読み込み時間は、ユーザーがアプリを起動して実行するまでにかかる時間に影響します。
バックグラウンドメモリの使用量改善に関するこの開発者向けチュートリアルでは、Androidのナビゲーションの原則を基に、Fire OSデバイスのアプリにどのように影響するかについて詳しく見ていきましょう。
次の図は、Fire App Builderのサンプルアプリの4つの状態を示しています。
アプリを最初に起動したら、Fire TVのホーム画面からアプリのホーム画面に切り替わります。その後、ユーザーがコンテンツを選択すると、コンテンツの詳細画面に遷移します。ユーザーがコンテンツを再生すると、コンテンツの再生画面が表示されます。ここで、ユーザーがリモコンのホームボタンを押した場合、アプリはバックグラウンドに送られます。
Androidアプリでは、デフォルトのアクティビティのライフサイクルイベント(「onPause」など)に応答した後、アプリがバックグラウンド状態に移行しているときに、メモリリソースを解放することがあります。例えば、ユーザーがストリーミングアプリで動画を一時停止したり、ホームボタンを押したり、誤ってアプリを閉じたりすることがあります。このとき、中断した場所からすぐに再生できることが理想的ですが、アプリが長時間バックグラウンドで置かれていると、特にバックグラウンドメモリを大幅に消費している場合、オペレーティングシステムがアプリをシャットダウンすることがあります。
この問題に対処するために、AndroidではシステムコールバックとしてonTrimMemoryが用意されています。これにより、アプリがシステムレベルでメモリ関連のイベントに応答してメモリを解放できるようになります。Fire TV対応アプリのコンテキストでは、onTrimMemoryを使用することで、バックグラウンドメモリの使用量を管理して、パフォーマンスを最適化することができます。以下は、Fire TV対応アプリのサンプルでのonTrimMemoryの使用例です。このアプリはJavaでのみ使用できますが、以下の例にはKotlinベースのアプリで活用できるKotlinのサンプルも含まれています。
このサンプルは、メモリ関連のイベントに応答する方法を示す簡単なデモです。アプリのアーキテクチャに応じて、データコンテナからキャッシュメモリを解放したり、バックグラウンド状態では不要となるグラフィックリソースの読み込みを解除したりなど、さまざまなアクションから選択することができます。また、アプリで可能な限りメモリを解放しても引き続きメモリ不足になる場合は、予期せぬシャットダウンに備えて、アプリの状態を保存することを検討してください。
手順1: こちらの手順に従って、Fire App Builderをダウンロードし、Fire TV対応アプリをビルド・実行します。これでサンプルアプリのインスタンスが実行中になったので、次の変更を行います。
手順2: onTrimMemoryコールバックをModularApplicationに追加します。
<path to sample app>/fire-app-builder-master/ModuleInterface/src/main/java/com/amazon/android/module/ModularApplication.java
ModularApplication.javaを開き、このファイルにonTrimMemoryコールバックを追加します。以下のコードスニペットに参考例を示します。
Java
import android.content.ComponentCallbacks2;
/**
* モジュール型アプリの基本クラス。
*/
public abstract class ModularApplication extends Application implements ComponentCallbacks2 {
...
...
...
...
@Override
public void onCreate() {
super.onCreate();
...
...
}
...
...
...
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
//このコールバックはメモリ関連のシステムコールバックについてアプリに通知します。
switch (level) {
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
/* アプリがバックグラウンド状態に移行中です。*/
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
/* アプリがフォアグラウンドにある場合のシステムメモリ不足のコールバックです。
このブログではこの部分は省略します。 */
break;
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
/*
アプリがバックグラウンドにある場合のメモリ不足のイベントです。
メッセージタイプに応じて、ここで解放するメモリの内容と量を決定
できます。
*/
break;
default:
break;
}
}
}
Kotlin
import android.content.ComponentCallbacks2
/**
* モジュール型アプリの基本クラス。
*/
class ModularApplication : Application() , ComponentCallbacks2{
...
...
...
override fun onCreate() {
super.onCreate();
...
...
}
...
...
...
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
//このコールバックはメモリ関連のシステムコールバックについてアプリに通知します。
when (level) {
ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
// アプリがバックグラウンド状態に移行中です。
}
ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE,
ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
/* アプリがフォアグラウンドにある場合のシステムメモリ不足のコールバックです。
このブログではこの部分は省略します。 */
}
ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
ComponentCallbacks2.TRIM_MEMORY_MODERATE,
ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
/*
アプリがバックグラウンドにある場合のメモリ不足のイベントです。
メッセージタイプに応じて、ここで解放するメモリの内容と量を決定
できます。
*/
}
else -> {
}
}
}
}
手順3: アプリのUIをナビゲートするとアクティビティが別のアクティビティの上にスタックされ、上記の図に示すように複数のアクティビティがスタックに追加されることがあります。すべてのアクティビティの参照をModularApplicationのリスト変数に格納し、エントリの追加および削除を行うメソッドも追加します。このサンプル実装内では、アプリがバックグラウンド状態でメモリを解放する必要がある場合、ホーム画面を除くすべてのアクティビティを終了することでメモリの解放を開始します。アクティビティを破棄すると、それに関連するメモリも解放されます。
Java
public abstract class ModularApplication extends Application implements ComponentCallbacks2 {
...
...
private List<Activity> arrayActivities = new ArrayList<>();
...
...
public void addActivity(Activity activity){
arrayActivities.add(activity);
}
public void removeActivity(Activity activity){
arrayActivities.remove(activity);
}
}
Kotlin
class ModularApplication : Application() , ComponentCallbacks2{
...
...
private var arrayActivities: ArrayList<Activity> = ArrayList ();
...
...
fun addActivity(activity: Activity) {
arrayActivities.add(activity);
}
fun removeActivity(activity: Activity) {
arrayActivities.remove(activity);
}
}
手順4: 次に以下のファイルに変更を加えます。
<path to sample app>/fire-app-builder-master/TVUIComponent/lib/src/main/java/com/amazon/android/tv/tenfoot/ui/activities/
ContentBrowseActivity.java
ContentDetailsActivity.java
ContentSearchActivity.java
FullContentBrowseActivity.java
SplashActivity.java
VerticalContentGridActivity.java
<path to sample app>/fire-app-builder-master/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/ui/
PlaybackActivity.java
上記のすべてのファイルで、ActivitiyのonCreate
メソッドとonDestroy
メソッドを変更します。以下は各アクティビティのクラス内での変更例です。
Java
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.full_content_browse_activity_layout);
...
...
((ModularApplication)getApplication()).addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
...
...
((ModularApplication)getApplication()).removeActivity(this);
}
Kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.full_content_browse_activity_layout);
...
...
(application as ModularApplication).addActivity(this);
}
override fun onDestroy() {
super.onDestroy()
(application as ModularApplication).removeActivity(this);
}
手順5: 次にModularApplication.onTrimMemory内でデバッグログを使用し、アプリで使用可能なメモリを出力します。デバッグログは、onTrimMemoryが呼び出された際の特定のインスタンスを把握するのに役立ちます。これにより、各呼び出し時のメモリ使用量と実行中のアクティビティを確認できます。また、システムのリソースが不足してきた場合のアプリの終了も監視します。
Java
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
//このコールバックはメモリ関連のシステムコールバックについてアプリに通知します。
switch (level) {
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
/* アプリがバックグラウンド状態に移行中です。*/
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
/* アプリがフォアグラウンドにある場合のシステムメモリ不足のコールバックです。このブログではこの部分は省略します。 */
break;
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
/*
アプリがバックグラウンドにある場合のメモリ不足のイベントです。
メッセージタイプに応じて、ここで解放するメモリの内容と量を決定
できます。
*/
Log.e("onTrimMemory", "Total activities :" + arrayActivities.size());
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
activityManager.getMemoryInfo(mi);
double sizeInMB = mi.availMem / 0x100000L;
Log.e("onTrimMemory", "Available Memory:" + sizeInMB);
break;
default:
break;
}
}
Kotlin
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
//このコールバックはメモリ関連のシステムコールバックについてアプリに通知します。
when (level) {
ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
// アプリがバックグラウンド状態に移行中です。
}
ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE,
ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
/* アプリがフォアグラウンドにある場合のシステムメモリ不足のコールバックです。
このブログではこの部分は省略します。 */
}
ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
ComponentCallbacks2.TRIM_MEMORY_MODERATE,
ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
/*
アプリがバックグラウンドにある場合のメモリ不足のイベントです。
メッセージタイプに応じて、ここで解放するメモリの内容と量を決定
できます。
*/
Log.e("onTrimMemory", "Total activities :" + arrayActivities.size)
val mi = ActivityManager.MemoryInfo()
val activityManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
activityManager.getMemoryInfo(mi)
val sizeInMB = (mi.availMem / 0x100000L).toDouble()
Log.e("onTrimMemory", "Available Memory:$sizeInMB")
}
else -> {
}
}
}
手順6: (ホーム画面のアイコンをクリックするか、Android Studioを使用して)サンプルアプリを起動し、サンプルビデオをいくつか再生します。テスト中は、ターミナルウィンドウ(MACの場合はターミナル、Windowsの場合はコマンドコンソール)を開き、次のコマンドを入力してlogcatを監視できます。
adb logcat | grep "OnTrimMemory"
手順7: ビデオの再生中に、ホームボタンを押してアプリをバックグラウンドに送ります。ここで、別のアプリ(Prime Videoなど)を開き、HDコンテンツを再生します。ほかにもアプリをいくつか開き、logcatに以下の行が表示されるまで同じ操作を実行します。
03-24 22:21:35.587 29110 29110 E onTrimMemory: Total activities :3
03-24 22:21:42.462 29110 29110 E onTrimMemory: Available Memory :408.0
手順8: ここまでの手順で、OSのリソースが不足してきたときにサンプルアプリがバックグラウンドで強制終了することを確認しました。バックグラウンドアプリ内でのリソース使用量の順位をさらに下げて、アプリがバックグラウンド状態でより長く動作し続けるにはどうすればよいかを考えてみましょう。 このサンプルアプリで実行できる簡単な変更方法としては、アクティビティリストのサイズを確認し、ホーム画面(最上位のアクティビティ)以外のすべてのアクティビティを破棄する方法があります。これらのアクティビティを解放することで、関連するリソースも解放され、サンプルアプリによる全体的なメモリ使用量が削減されます。以下は、最上位のアクティビティ(ホーム画面)を除くアクティビティのリストをスタックから解放するサンプルコードスニペットです。
Java
public void onTrimMemory(int level) {
...
...
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
while(arrayActivities.size() > 1){
Activity activity = arrayActivities.get(arrayActivities.size() - 1);
arrayActivities.remove(activity);
activity.finish();
}
...
...
}
Kotlin
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
...
...
ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
ComponentCallbacks2.TRIM_MEMORY_MODERATE,
ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
while (arrayActivities.size > 1) {
val activity = arrayActivities[arrayActivities.size - 1]
arrayActivities.remove(activity)
activity.finish()
}
....
....
}
...
...
}
バックグラウンドメモリの解放を行う上記のサンプル実装では、ユーザーが短時間でアプリに戻った場合はビデオの再生を再開できます。アプリが長時間バックグラウンドに置かれ、OSからメモリ不足のコールバックを受け取った場合は、ビデオ再生とビデオ詳細ページのアクティビティを解放することでメモリが解放されます。ユーザーがアプリに戻り、アプリがフォアグラウンドに移動すると、アプリのホーム画面が表示され、そこから引き続きナビゲーションを行うことができます。このセクションの冒頭で触れたように、アプリのデザインはそれぞれ異なります。アプリではデザインに応じて、システムが開始したメモリコールバックイベントに応答してバックグラウンドメモリの使用量を削減するための最適なアクションを実行することができます。
ユーザーの利便性向上のため、Fire TV対応アプリをぜひ最適化しましょう。Fire OSのこれらの機能を活用することにより、読み込み時間やアプリの状態について、迅速で優れたパフォーマンスを実現できます。