【Android】Activityを肥大化させないためのデザインパターンを紹介します。

androidデザインパターン

こんにちは。良昌です。

Androidのライブラリはすごく便利なのですが、内部的に複数のスレッドを走らせる機能などは、終了時のコールバック関数をターゲットのActivityで実装するため、インスタンスの生成自体もターゲットのActivityで行うことが多いと思います。

そのため、少し機能をリッチにしてしまうと、肥大化されたActivityが作られてしまうのではないでしょうか。

効率良くアプリを量産するためには、使い回しの効く独自のライブラリを作っていくことが重要だと思いますので、今回は簡単な例を挙げて、Activityを肥大化させないためのデザインを紹介します。

[Android]Activityを肥大化させないためのデザインパターンを紹介します。


■ Activityを肥大化させる原因の例

スレッドの終了を通知する機能は色々とありますが、今回はandroid.os.CountDownTimerクラスを使い、カスタムビューを作ってみたいと思います。

よく見かけるのは、以下のように匿名クラスを実装するか、CountDownTimerクラスを継承したサブクラスを実装しているパターンです。

public class HogeActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.hoge);
        // 30秒カウントダウン
        this.countDown(3000);
    }

    private void countDown(long millisInFuture) {
        final TextView tv = (TextView)findViewById(R.id.textView1);
        new CountDownTimer(millisInFuture, 1000) {
            @Override
            public void onTick(long millisUntilFinished) {
                // TextViewで1秒毎にカウントを表示
                tv.setText(String.valueOf(TimeUnit.MILLISECONDS
                    .toSeconds(millisUntilFinished)));
            }
            @Override
            public void onFinish() {
                // カウントダウンタイマー終了時の処理
                HogeActivity.this.fuga();
            }
        }.start();
    }

    private void fuga() {
        // カウントダウンタイマー終了時の処理
    }
}

上記のようが実装が、Activityを肥大化させる原因となります。これを解消するため、10-25行目の処理をカスタムビューとして切り出したいと思います。


■ Activityを肥大化させないためのデザイン

・ リスナを作成

まずは、スレッドの終了を通知するためのリスナを作成します。どこから通知されたかをターゲットのActivityでハンドリングする必要があるので、callback()メソッドの引数にはandroid.view.Viewを設定しています。

public interface CallbackListener {

    void callback(View v);
}

・ カスタムビューを作成

次に先ほどの処理を切り出したカスタムビューを作成します。継承するViewクラスは何でも良いですが、今回はandroid.widget.LinearLayoutを使用しています。

public class ExtensionCountDownTimer extends LinearLayout {

    private Activity activity;

    private TextView tv;

    private CountDownTimer countDownTimer;

    public ExtensionCountDownTimer(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.init(context);
        this.activity = (Activity)context;
    }

    public void start(long millisInFuture) {
        this.countDownTimer = this.getCountDownTimer(millisInFuture);
        this.countDownTimer.start();
    }

    public void cancel() {
        this.countDownTimer.cancel();
    }

    private void init(Context context) {
        this.tv = new TextView(context);
        this.addView(this.tv);
    }

    private CountDownTimer getCountDownTimer(long millisInFuture) {

        return new CountDownTimer(millisInFuture, 1000) {
            @Override
            public void onTick(long millisUntilFinished) {
                // TextViewで1秒毎にカウントを表示
                tv.setText(String.valueOf(TimeUnit.MILLISECONDS
                    .toSeconds(millisUntilFinished)));
            }
            @Override
            public void onFinish() {
                // スレッドの終了をリスナに通知
                ((CallbackListener)activity).callback(ExtensionCountDownTimer.this);
            }
        };
    }
}

特に難しいところは無いと思いますが、ポイントはCountDownTimer#onFinish()で先ほど作成したCallbackListener#callback()を呼出している部分です。この処理により、ターゲットのActivityに対し、スレッドの終了を通知しています。

@Override
public void onFinish() {
    // スレッドの終了をリスナに通知
    ((CallbackListener)activity).callback(ExtensionCountDownTimer.this);
}

・ リスナを実装したActivityを作成

最後に作成したリスナを実装したActivityを作成します。callback()メソッドで引数のインスタンスを判定して処理を実装すれば、複数のViewからの通知にも耐えられるようになっています。

public class HogeActivity extends Activity implements CallbackListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.hoge);
        // 30秒カウントダウン
        ((ExtensionCountDownTimer)findViewById(R.id.countDownTimer)).start(3000);
    }

    @Override
    public void callback(View v) {
        if (v instanceof ExtensionCountDownTimer) {
            // カウントダウンタイマー終了時の処理
            this.fuga();
        }
    }

    private void fuga() {
        // カウントダウンタイマー終了時の処理
    }
}

・ デザインの応用と展開

この方法は、アニメーションの終了通知に対しても使えます。アニメーションが実装された複数のViewを使いたい場合でも、いわゆるコールバックの入れ子地獄に悩まされることもない筈です。

animation.setAnimationListener(new AnimationListener() {
    @Override
    public void onAnimationEnd(Animation animation) {
        ((CallbackListener)activity).callback(HogeView.this);
    }
});

■ まとめ

やっていることは何てこと無いJavaのインターフェースの実装ですが、Androidのサンプルではあまり見かけないパターンで、匿名クラスやサブクラスを実装したパターンが多いです。サンプルを見る度に「なんで?それだと新しいアプリ作る度に同じようなコード書かなきゃいけないじゃん。」と思うのは僕だけでしょうか?

docomoがiPhoneのリリースを発表しましたね。これで、ますますAndroidアプリの需要が低くなってしまうのでは。。。そんなことにはめげずに明日もAndroidアプリを開発していきたいと思います。

では、また。

コメントを残す

メールアドレスが公開されることはありません。