Android 3.0(Honeycomb)以降に追加されたandroidのコンポーネント化技術である"Fragment(フラグメント)"だが、GUIの有無に関わらず今後のandroidプログラミングにおいて非常に重要な位置を占めるだろう。
Fragments | Android Developers
Fragmentを使うことでActivityクラスのかなりの機能を置き換えることができるせいか、ネットでも冗談めかして「Activityはオワコン」とか「Activity終了のお知らせ」等の極論を見かけるが、Activityにはコードをできるだけ書かないのはいいとしても、それ自体が不要なものになるのだろうか?
まさかそんなことはない。
ActivityはJFC/SwingでいえばJFrameクラス、.NET WindowsFormsでいえばFormクラスであり、GUIアプリケーションを構成するクラスのメインとなるコンポーネントなのだ。
Fragmentが幾ら便利になった所でActivityが不要になること等あり得ない。
FragmentがActivityの代わりになれない理由は以下の二点に集約される。
Fragmentはメッセージを捕捉できない
Fragmentは普通のクラスでありContextを継承しておらずWindowクラスのインスタンスも持たない。Fragmentのイベントの殆どはActivity由来のものである。Windowクラスも所持しておらず自らがWindowメッセージをハンドルすることができない。
Fragmentはインテントを捕捉できない
インテントはActivityThreadを経由して対象のActivityを起動するか又は起動済みのActivityに渡されるが、Fragmentはインテントを受け取る手段が無い。
私が特に重要だと思っているのは後者。Androidといえばインテント、インテントといえばAndroidである。
FragmentをActivityの代わりに使おうと思う時に一番困る問題である。
例えば拙作のAbstractNfcTagFragment抽象クラスはAndroid NFC FrameworkのACTION_TAG_DISCOVERED(NFCタグ発見)やACTION_TECH_DISCOVERED(タグ・テクノロジ発見)のインテントを処理する必要があるが、前述した通りでFragmentはインテントを直接処理することができないため、Actitityを経由してインテントをもらう必要がある。
AbstractNfcTagFragment.java
/**
* インテントを捕捉する
* @param intent アクティビティで捕捉したインテントがセットされます
*/
public void onNewIntent(Intent intent) {
String action = intent.getAction();
//TECHDISCOVERED
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {
mNfcTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if ( mNfcTag != null ) {
String tagTechs = mNfcTag.getTechList();
for (String filterTechs : mTechList) {
if (ArrayUtil.containArray(tagTechs, filterTechs)) {
for ( INfcTagListener listener : mListnerList ) {
listener.onTagDiscovered(intent, mNfcTag, this);
}
}
}
}
}
}
onNewIntentはActivityのようにAndroidシステムから自動的に呼び出されるコールバックではない。以下のようにActivity(FragmentActivityの継承クラス)のonNewIntentをオーバライドして、そこから明示的な呼び出さなくてはならないのである。
NFCTagReader.java
private NfcFeliCaTagFragment mFeliCafragment;
private ISO15693TagFragment mISO15693Fragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
:
:
//FeliCa, FeliCaLite用フラグメント
mFeliCafragment = new NfcFeliCaTagFragment(this);
mFeliCafragment.addNfcTagListener(this);
//ISO15693用フラグメント
mISO15693Fragment = new ISO15693TagFragment(this);
mISO15693Fragment.addNfcTagListener(this);
//インテントから起動された際の処理
Intent intent = this.getIntent();
this.onNewIntent(intent);
}
:
:
@Override
protected void onNewIntent(Intent intent) {
if ( mFeliCafragment != null ) {
mFeliCafragment.onNewIntent(intent);
}
if ( mISO15693Fragment != null ) {
mISO15693Fragment.onNewIntent(intent);
}
}
せっかくActivityの処理を軽くシンプルにするためにFragmentを導入したのにFragmentを使うためにonNewIntentメソッドをわざわざActivity側に書かなくてはならないのは本末転倒だ。また、NfcFeliCaTagFragmentとISO15693TagFragmentはインスタンス変数にセットをしているが、これもonNewIntentで参照する必要があるからであり、これが無ければ変数に格納する必要すらない。(Activityライフサイクルへの組込みとForegroundDisptchはFragment側で処理している)
FragmentはActivityとライフサイクルイベントを同期するとドキュメントにはあるが、それは一般的なイベントに限った場合である。
便利でスマートなFragmentだが、あくまでActivityが主であり、Fragmentは従であることを忘れてはいけない。