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 ( 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は従であることを忘れてはいけない。