FragmentはActivityを置き換えない
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 ( INfcTagListenerlistener : 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は従であることを忘れてはいけない。