Android 7.0(Nougat)で追加されたマルチウィンドウについて学習します。最新OSではマルチウィンドウ機能をOS上でサポートしているため、マルチウィンドウに対応する・しないどちらにせよ、アプリを正しく動作させるためにどういう対処が必要か知っておく必要があります。
まず手を動かして手法を掴みたい方は下記「実習」から、先にマルチウィンドウについて詳しく知りたい方は次ページの「講義」から読み進めてみてください。
実習 マルチウィンドウの画面分割を体験する
1 サンプルプロジェクトをインポートする
サンプルプロジェクト「Chapter09/Lesson37/before」をインポートします。[Welcome to Android Studio]画面から[Import project (Eclipse ADT, Gradle,etc.)]を選択します(図1(1))。[ファイル選択]ダイアログが表示されるので、インポートしたいプロジェクトのフォルダ(Lesson37/before)を選択して(2)、[OK]ボタンをクリックします(3)。読み込みが完了するとプロジェクトを開いた状態になるので、[Android]から[Project]に変更しておきます(4)。
2 MainActivityのレイアウトを編集する
app/src/main/res/layout/activity_main.xmlを開いて、リスト1のように編集してください。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp" tools:context="com.kayosystem.honki.chapter09.lesson37.MainActivity"> <Button android:id="@+id/launch_activity" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="16dp" android:text="@string/launch_second_activity" /> <Button android:id="@+id/launch_activity_new_window" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="16dp" android:text="@string/launch_second_activity_new_window" /> <fragment xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/loglist_fragment" android:name="com.kayosystem.honki.chapter09.lesson37.LogListFragment" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="16dp" android:background="@android:color/white" /> </LinearLayout>
3 アプリ内で使用する文字列を編集する
app/src/main/res/values/strings.xmlを開いて、リスト2のように編集してください。
<resources> <string name="app_name">Lesson37</string> <string name="name_second_activity">これはSecondActivityです</string> <string name="launch_second_activity">SecondActivityを起動</string> <string name="launch_second_activity_new_window">SecondActivityを別ウィンドウで起動</string> </resources>
4 MainActivityのJavaプログラムを編集する
app/src/main/java/(Company Domain名)/MainActivity.javaを開いて、リスト3のように編集してください。
(省略) import android.content.res.Configuration; import android.util.Log; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); findViewById(R.id.launch_activity).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(MainActivity.this, SecondActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } }); findViewById(R.id.launch_activity_new_window).setOnClickListener(newView.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(MainActivity.this, SecondActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } }); } (省略)
5 SecondActivityを追加する
app/src/main/java/(Company Domain名)/MainActivity.javaを右クリックし(図2(1))、メニューから[New](2)→[Activity](3)→[Empty Activity]を選択します(4)。[Configure Activity]画面が表示されるので「ActivityName」を「SecondActivity」に変更し(5)、[Generate LayoutFile]のチェックを外して(6)、[Finish]ボタンをクリックします(7)*。
*SecondActivityのレイアウトファイルは事前に準備しています。
6 SecondActivityのJavaプログラムを編集する
app/src/main/java/(Company Domain名)/SecondActivity.javaを開いて、リスト4のように編集してください。
(省略) public class SecondActivity extends LogActivity { @Override public int getActivityLayoutId() { return R.layout.activity_second; } }
7 AndroidManifest.xmlを編集する
app/src/main/AndroidManifest.xmlファイルを開いて、リスト5のように編集してください。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.kayosystem.honki.chapter09.lesson37"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name="com.kayosystem.honki.chapter09.lesson37.MainActivity" android:label="@string/app_name"> <intent-fi lter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-fi lter> </activity> <activity android:name=".SecondActivity" /> </application> </manifest>
8 アプリを実行する
アプリを実行すると図3のようなメイン画面が表示されます。オーバービューボタンを長押しすると画面が分割するので(1)、[SECONDACTIVITYを別ウィンドウで起動]ボタンをクリックすると(2)、SecondActivityが画面下半分に表示されます(3)。メイン画面とSecondActivityには、それぞれのライフサイクルが表示され、画面をクリックするとアクティブになるのでライフサイクルの変化を確認してください。
講義 マルチウィンドウについて
マルチウィンドウとは
Android 7.0(Nougat)ではマルチタスク機能を強化するためにマルチウィンドウが追加されました。もともとAndroid OSは、画面上に表示・動作できるActivityは1つまででしたが、マルチウィンドウを使うと複数のActivityを1画面で同時に表示できます。わかりやすい例を挙げると、ドキュメントアプリで資料を見ながらメールアプリを使う、Twitterアプリを使いながらブラウザアプリでWebページの閲覧、といった操作がマルチウィンドウでは可能になります。
とはいえ、Androidユーザーだった方にはそれほど革新的な機能ではないかもしれません。なぜならSAMSUNG、Huawei、LG等の一部のAndroidデバイスメーカーでは、数年前から独自機能としてアプリのマルチウィンドウ機能がサポートされていました。あるいはシステムオーバーレイ上にLayoutやViewを追加してActivityを使わずに複数の画面を表示するテクニックも存在していたためです。いずれにせよ、Android 7.0(Nougat)で正式にマルチウィンドウがサポートされたのは大きな変化といえます。
マルチウィンドウの操作方法
マルチウィンドウには専用の切り替え操作が必要です。切り替え方法には表1の方法があります。
なお、マルチウィンドウモードを解除する場合はオーバービューボタンを再度長押しするか、分割線を画面端までドラッグしてどちらか片方のアプリを全画面にすることで解除できます。
マルチウィンドウの種類
マルチウィンドウはデバイスの種類に応じて画面分割モード、ピクチャーインピクチャー(以下PinP)モード、フリーフォームモードの3種類が用意されています。「デバイスの種類に応じて」というところがミソであり、ややこしいところなのですが、Androidはスマートフォンだけに限らず、タブレット、TV(テレビ)、Wear(ウェアラブル)、Auto(カーナビ)とさまざまなプラットフォームが存在するため、プラットフォームに応じて最適なマルチウィンドウが選ばれるようになっています。各マルチウィンドウの特徴は以下のようになっています。
画面分割モード
画面分割モードはスマートフォン・タブレットで利用できるマルチウィンドウです。ディスプレイの向きに応じてポートレートでは上下、ランドスケープでは左右に2つ並べて表示できます(図6)。分割領域にある分割線をドラッグすることで片方のアプリを拡大、もう片方を縮小といったようにサイズ比率を変更することもできます。後述するPinPモード、フリーフォームモードは特殊なので、Androidのマルチウィンドウ機能といえばこの画面分割モードがスタンダードなモードといえます。
ピクチャーインピクチャー(PinP)モード
PinPモードはAndroid TVで利用できるマルチウィンドウです。テレビ番組のワイプのように、メインとなるアプリを表示しつつ、4隅のいずれかのコーナーに別のアプリを表示することができます(図7)。ただしPinPのウィンドウは240x135dpとサイズが小さく限られていることもあり、表示できるUIやレイアウトは限られたものになります。実際の使用用途として動画再生アプリのPinP表示で使うのがほとんどかもしれません。またPinPモードに限ってはユーザー操作でマルチウィンドウに切り替えるのではなくenterPictureInPictureModeメソッドを使用してプログラムで切り替える必要があります。
フリーフォームモード
フリーフォームモードはスマートフォン・タブレットで利用できるマルチウィンドウです。アプリをウィンドウ化させてPCのようにディスプレイ上に自由に配置できるようになります(図8)。またこのモードに限ってアプリを2つよりも多く配置できます。なお、執筆している2016年9月現在、フリーフォームモードは販売するAndroidデバイスメーカーが機能を有効にした場合に利用できるモードとされています。フリーフォームモードはディスプレイサイズが大きくないとかえって使い勝手が良くないので、オプション的な位置付けとなっているようです。
ちなみにN Preview のシステムイメージではデフォルトは無効となっていますが、特定の操作を行うことでフリーフォームモードを有効にすることができます(最後の補講で紹介)。
マルチウィンドウのライフサイクル
Android開発者でマルチウィンドウ機能を目にした時に、まずライフサイクルを気にする人も多いのではないでしょうか。筆者もその1人でした。Androidでは表示中のActivityだけがアクティブ状態になるのが今までの常識で、開発者はこのルールにしたがってアプリの初期化、再開、中断、終了等の処理を実装していました。そのためマルチウィンドウで画面に2つもアプリが表示されるとActivityのライフサイクルは一体どう管理すれば良いのか疑問に思ったのです。
でも大丈夫です。マルチウィンドウであってもActivityのライフサイクルに変更はありません。たとえば画面分割モードの場合、現在操作しているActivityだけがアクティブ状態となり、もう片方のActivityは表示されていても、onPauseメソッド(一時停止状態)となります。実習で作成したサンプルはライフサイクルの流れがわかるようにログをリスト表示できるようにしているので、実際に動かしてみるとActivityのライフサイクルがどうなっているのか確認しやすいでしょう。
ただし、ライフサイクル自体に変更はないもののライフサイクルの流れには変化があるという点に注意してください。通常Activityを切り替えた場合、Activityの表示・非表示フェーズが存在するためonStart(Activity表示)、onStopメソッド(Activity非表示)が呼ばれます。しかしマルチウィンドウで画面上に表示しているActivityを切り替えた場合は、Activityの表示状態が変化するわけではないのでonStart、onStopメソッドが呼ばれません。これが意味することは、もし初期化処理や再開処理をonStartメソッド、停止処理や終了処理をonStopメソッドで実装している場合は、マルチウィンドウ時のみ動作しないということです。たとえばGPSセンサーを利用するアプリでonStopメソッドにGPSの停止処理を実装していたとしたら、マルチウィンドウでアプリを切り替えてもGPSセンサーは動きっぱなしになります。もちろんそれが正しい動作であるなら良いのですが、非アクティブ時にGPSセンサーを動作させる必要がないのであれば、バッテリー消費が激しい間違った実装になってしまいます。マルチウィンドウでも正しくアプリを動作させたい場合は、どのライフサイクルでどの処理が必要か検討した上で実装するように心がけてください。
マルチウィンドウ向けアプリの構築
マルチウィンドウの制御に関して細かく設定する場合は、Android 7.0(Nougat)であるAPIレベル24をターゲットSDKに指定する必要があります。APIレベル23以下をターゲットにした場合は、android:screenOrientation属性を指定していなければ強制的にマルチウィンドウに、指定していればマルチウィンドウにならず全画面で表示されます(表2)。
マルチウィンドウの種類に関する属性
APIレベル24ではマルチウィンドウの有効/無効、および、どのモードを利用するかの設定を属性で指定できます。表3はそれらの属性を説明したものです。
表3の属性をtrueに設定した場合はマルチウィンドウモードのサポートを有効にします。falseを指定した場合は無効にします。この属性は
表4の属性をtrueに設定した場合、PinPモードのサポートが有効になります。前述したようにPinPモードはAndroidTVプラットフォームで有効なモードなので普段スマートフォン・タブレットを対象にアプリを開発する場合は利用することのない属性です。
マルチウィンドウのレイアウトに関する属性
同じくAPIレベル24ではマルチウィンドウ時のActivityサイズを指定できる属性が追加されています。表5はそれら属性を説明したものです。
フリーフォームモード時のデフォルトのWidth(幅)、Height(高さ)をdpで指定します。フリーフォームモードにするとアプリがウィンドウ化する都合上、画面サイズの幅が約90%、高さが70%ほどに縮みます(図9)。あらかじめフリーフォームモード時のサイズを指定したい場合は、この属性を指定することで最適なサイズでフリーフォームモードへ移行できます。なお、2016年9月現在、N Previewではこの属性は機能していません。
表6の属性はフリーフォームモード時にアプリが配置される位置(gravity)を指定します。フリーフォームモードの場合、アプリを画面上の好きな位置に配置できるため、gravity属性を使用してデフォルトの表示位置を指定できるようになっています。この属性も2016年9月現在、N Previewでは機能していません。
表7の属性は画面分割モード・フリーフォームモード時の最小幅(Width)、高さ(Height)をdpで指定します。これ以上サイズを縮めるとアプリのUI/UXに支障をきたすといった場合は、この属性で最小サイズを指定することで回避できます。2016年9月現在、筆者が試しているN Previewではフリーフォームモードのみ機能しています。
なお、表5、6、7のサイズに関する属性をActivityにセットしたい場合はAndroid Manifest.xml上でリスト6のように設定します。
<activity android:name=".MainActivity" android:label="@string/app_name"> <layout android:defaultWidth="320dp" android:defaultHeight="320dp" android:gravity="end" android:minHeight="160dp" android:minWidth="160dp" /> </activity>
マルチウィンドウを実装する際に利用する代表的なメソッド
表8のisInMultiWindowModeメソッドを使うと現在Activityがマルチウィンドウかどうかを調べることができます。戻り値がtrueの場合はマルチウィンドウ、falseの場合は全画面(通常の起動)と判断することができます。IsInPictureInPictureModeメソッドはPinPモードかどうかを調べるメソッドで、こちらもtrueであればPinPモード、falseであればPinPモードになっていないと判定することができます。現在の画面モードに応じて処理を切り分けたい場合はこのメソッドを使用して判定しましょう。
表9のonMultiWindowModeChangedメソッドは全画面とマルチウィンドウが切り替えるたびに呼ばれます。引数がboolean型になっていてisInMultiWindowModeの値がtrueならマルチウィンドウ、falseなら全画面モードと判定できるようになっています。もし画面モードが切り替わった直後に、画面モードに応じて特定の処理を実装したい場合はこのメソッドをオーバーライドすると実現できます。onPictureInPictureModeChangedメソッドも同様の使い方ができ、こちらはPinPモードの切り替えを検出することができます。
新しいウィンドウでActivityを起動する方法
画面分割モード時に、IntentフラグにIntent.FLAG_ACTIVITY_LAUNCH_ADJACENT | Intent.FLAG_ACTIVITY_NEW_TASKを指定してstartActivityメソッドを呼び出すと隣の分割領域に新たにActivityを起動することができます(実習のリスト3)。ただしこの操作はシステムで保証されるわけではなく、可能であればという制約の上での動作となっています。なお、本サンプルでも[SECONDACTIVITYを別ウィンドウで起動]ボタンにてこの動作を実装しています*。
*正直なところ自身のアプリと他のアプリをマルチウィンドウで動かすことにはあまり利点を感じないかもしれませんが、自身のアプリ内で複数のActivityを隣に並べながら利用するというケースであれば便利な使い方があるかもしれません。
fi ndViewById(R.id.launch_activity_new_window).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(MainActivity.this,SecondActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT |Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } });
まとめ
- マルチウィンドウの種類には、画面分割モード、ピクチャーインピクチャーモード、フリーフォームモードがあります。
- マルチウィンドウに表示しているActivity はonStart、onStopメソッドが呼ばれないことを考慮した実装が必要です。
- マルチウィンドウを細かく設定するには、APIレベル24をターゲットSDKに指定する必要があります。
補講
フリーフォームモードを有効にする方法
N Previewではadbコマンドを使用してフリーフォームモードに切り替えることができます。もしフリーフォームモードでアプリのテストをしたい際にはAPIレベル24のエミュレータでリスト8のコマンドを実行してください。
adb shell settings put global enable_freeform_support 1
コマンドを実行したら一度OSを再起動します。エミュレータの場合はエミュレータを起動し直してください。フリーフォームが正しく適用されている場合は、オーバービュー画面に表示されるアプリのツールバー部分に専用のアイコンが追加されます(図11)。
フリーフォームモードはマルチタスクの自由度が高く、ほぼPCのようなウィンドウ操作が可能になります(図12)。ただし、これらをうまく活用するにはディスプレイが大画面である必要があるため、今後もあまり利用する機会のないマルチウィンドウモードかもしれません。