Magren

Magren

Idealist & Garbage maker 🛸
twitter
jike

Android基底クラスの設計

この 2 日間、冬休みが始まり、家にいるのも退屈なので、以前の Android の大御所が書いたプロジェクトを学ぶことに決めました。他の人のコードの構造や、コードの結合度を下げる方法を見てみるためです。そして、先輩のプロジェクトで BaseActivity に触れたので、ここに記録します。

なぜ基底クラスを設計するのか#

  • コードの記述を便利にし、重複コードや冗長なロジックを減らし、コードを最適化するため
  • プログラムのアーキテクチャを最適化し、結合度を下げ、拡張や修正を容易にするため
  • レイアウトがよりクリーンになり、ライフサイクルログなどの重複ロジックによるレイアウトの占有を減らすため

設計の基本的な考え方#

  • ライフサイクルのデバッグログ出力
  • ビューのバインディング
  • よく使う OnClick メソッド
  • 戻るメソッド、トースト、アクティビティなどの操作
  • よく使うサードパーティツール(ButterKnife など)
  • タイトルバーの表示、全画面表示、データの初期化など

具体的な基底クラスのカプセル化#

BaseActivity#

public abstract class BaseActivity<P extends BasePresenter> extends AppCompatActivity
        implements BaseView<P> {

    @BindView(R.id.toolbar)
    Toolbar mToolbar;

    Unbinder bind;

    /**
     * プログレスダイアログ
     */
    protected ProgressDialog mProgressDialog;
    /**
     * ジェネリックでPresenterを確定
     */
    protected P mPresenter;

    @Override
    protected final void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 縦画面に設定
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        setContentView(bindLayout());
        // ButterKnifeでレイアウトをバインド
        bind = ButterKnife.bind(this);

        mPresenter = createPresenter();
        if (mPresenter != null) {
            // Presenterの初期化メソッドを呼び出す
            mPresenter.onStart();
        }

        // データの準備
        prepareData();
        // タイトルバーの初期化
        initToolbar();
        // ビューの初期化
        initView();
        // データの初期化
        initData(savedInstanceState);
        // イベントリスナーの初期化
        initEvent();
    }


    /**
     * Presenterを作成する
     *
     * @return ジェネリックPresenter
     */
    protected abstract P createPresenter();

    /**
     * BasePresenterインターフェースのsetPresenterメソッドを実装
     *
     * @param presenter createPresenter()で作成されたPresenter
     */
    @Override
    public void setPresenter(P presenter) {
        mPresenter = presenter;
    }

    /**
     * Toolbarを初期化する
     */
    private void initToolbar() {
        setSupportActionBar(mToolbar);
    }

    /**
     * Toolbarのタイトルを設定する
     *
     * @param title タイトル
     */
    protected void setToolbarTitle(String title) {
        if (getSupportActionBar() != null) {
            getSupportActionBar().setTitle(title);
        }
    }

    /**
     * Toolbarに戻るボタンとタイトルを表示する
     *
     * @param title タイトル
     */
    protected void setToolbarBackEnable(String title) {
        if (getSupportActionBar() != null) {
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
            getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_arrow_white_24dp);
            setToolbarTitle(title);
        }

    }

    /**
     * レイアウトをバインドする
     *
     * @return レイアウトファイルのリソースID
     */
    protected abstract int bindLayout();

    /**
     * データを準備する(Intentから前の画面から渡されたデータや他に初期化が必要なデータを取得)
     */
    protected abstract void prepareData();

    /**
     * ビューを初期化する、findViewByIdなど
     */
    protected abstract void initView();

    /**
     * データを初期化する、ローカルまたはサーバーからデータを取得する
     *
     * @param savedInstanceState 異常終了時に保存されたデータ
     */
    protected abstract void initData(Bundle savedInstanceState);

    /**
     * イベントリスナーを初期化する、setOnClickListenerなど
     */
    protected abstract void initEvent();

    /**
     * BaseViewのshowToast(CharSequence msg)を実装
     *
     * @param msg トーストに表示するメッセージ
     */
    @Override
    public void showToast(CharSequence msg) {
        ToastUtils.shortToast(this, msg);
    }

    /**
     * BaseViewのshowToast(int msgId)を実装
     *
     * @param msgId トーストに表示する文字列リソースID
     */
    @Override
    public void showToast(int msgId) {
        ToastUtils.shortToast(this, msgId);
    }

    /**
     * BaseViewのshowLoadingDialog(CharSequence msg)を実装
     * ローディングダイアログを表示する
     *
     * @param msg ダイアログのヒント内容
     */
    @Override
    public void showLoadingDialog(CharSequence msg) {
        if (mProgressDialog == null) {
            mProgressDialog = new ProgressDialog(this);
            mProgressDialog.setTitle(R.string.title_dialog_tips);
            mProgressDialog.setMessage(msg);
        } else {
            mProgressDialog.setTitle(R.string.title_dialog_tips);
        }
        mProgressDialog.show();
    }

    /**
     * BaseViewのhideLoadingDialog()を実装
     * ローディングダイアログを非表示にする
     */
    @Override
    public void hideLoadingDialog() {
        if (mProgressDialog != null && mProgressDialog.isShowing()) {
            mProgressDialog.dismiss();
        }
    }

    /**
     * Activityが破棄されるときにリソースをクリーンアップする
     */
    @Override
    protected void onDestroy() {
        // ButterKnifeのバインディングを解除
        bind.unbind();
        // Presenterを破棄
        if (mPresenter != null) {
            mPresenter.onDestroy();
        }
        super.onDestroy();
    }

    /**
     * キーボードを隠す
     */
    public void hideKeyboard() {
        View view = getCurrentFocus();
        if (view != null) {
            ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).
                    hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        // (ActivityのみのアプリではSDKが自動的に呼び出すため、個別に記述する必要はありません)
        // onPageEndがonPauseの前に呼び出されることを保証します。onPauseでは情報が保存されます。
        MobclickAgent.onPageEnd(this.getClass().getSimpleName());
        MobclickAgent.onPause(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        // ページを統計(ActivityのみのアプリではSDKが自動的に呼び出すため、個別に記述する必要はありません。)
        MobclickAgent.onPageStart(this.getClass().getSimpleName());
        // 統計の時間
        MobclickAgent.onResume(this);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            finish();
        }
        return super.onOptionsItemSelected(item);
    }
}

BaseFragment#

public abstract class BaseFragment<P extends BasePresenter> extends Fragment
        implements BaseView<P> {

    @BindView(R.id.toolbar)
    Toolbar mToolbar;

    private Unbinder mUnbinder;
    protected P mPresenter;

    protected ProgressDialog mProgressDialog;

    private ActionBar mActionbar;

    @Nullable
    @Override
    public final View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mPresenter = createPresenter();
        if (mPresenter != null) {
            mPresenter.onStart();
        }
        View root = inflater.inflate(bindLayout(), container, false);
        mUnbinder = ButterKnife.bind(this, root);
        prepareData(savedInstanceState);
        initToolbar();
        // ビューの初期化
        initView(root);
        initData(savedInstanceState);
        initEvent();
        return root;
    }


    protected abstract P createPresenter();


    @Override
    public void setPresenter(P presenter) {
        mPresenter = presenter;
    }

    /**
     * データを準備する
     *
     * @param savedInstanceState
     */
    protected abstract void prepareData(Bundle savedInstanceState);

    /**
     * フラグメントのレイアウトファイルをバインドする
     *
     * @return
     */
    protected abstract int bindLayout();

    /**
     * データを初期化する
     *
     * @param savedInstanceState
     */
    protected abstract void initData(Bundle savedInstanceState);

    /**
     * インターフェースを初期化する
     *
     * @param rootView
     */
    protected abstract void initView(View rootView);

    /**
     * イベントリスナーを初期化する
     */
    protected abstract void initEvent();


    /**
     * Toolbarを初期化する
     */
    private void initToolbar() {
        ((AppCompatActivity) getActivity()).setSupportActionBar(mToolbar);
        mActionbar = ((AppCompatActivity) getActivity()).getSupportActionBar();
    }

    /**
     * Toolbarのタイトルを設定する
     *
     * @param title タイトル
     */
    protected void setToolbarTitle(String title) {
        if (mActionbar != null) {
            mActionbar.setTitle(title);
        }
    }

    /**
     * Toolbarに戻るボタンとタイトルを表示する
     *
     * @param title タイトル
     */
    protected void setToolbarBackEnable(String title) {
        if (mActionbar != null) {
            mActionbar.setDisplayHomeAsUpEnabled(true);
            mActionbar.setHomeAsUpIndicator(R.drawable.ic_arrow_white_24dp);
        }
    }

    @Override
    public void showToast(CharSequence msg) {
        ToastUtils.shortToast(APP.getAppContext(), msg);
    }

    @Override
    public void showToast(int msgId) {
        ToastUtils.shortToast(APP.getAppContext(), msgId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public void showLoadingDialog(CharSequence msg) {
        if (mProgressDialog == null) {
            mProgressDialog = new ProgressDialog(getContext());
            mProgressDialog.setMessage(msg);
        } else {
            mProgressDialog.setTitle(R.string.title_dialog_tips);
        }
        mProgressDialog.show();
    }

    @Override
    public void hideLoadingDialog() {
        if (mProgressDialog != null && mProgressDialog.isShowing()) {
            mProgressDialog.dismiss();
        }
    }

    @Override
    public void onDetach() {
        mUnbinder.unbind();
        super.onDetach();
    }

    @Override
    public void onDestroyView() {
        if (mPresenter != null) {
            mPresenter.onDestroy();
        }
        super.onDestroyView();
    }

    @Override
    public void onResume() {
        super.onResume();
        MobclickAgent.onPageStart(this.getClass().getSimpleName());
    }

    @Override
    public void onPause() {
        super.onPause();
        MobclickAgent.onPageEnd(this.getClass().getSimpleName());
    }

}

BasePresenter#

public interface BasePresenter {

    void onStart();

    void onDestroy();

}

BaseView#

public interface BaseView<P> {

    void setPresenter(P presenter);

    void showToast(CharSequence msg);

    void showToast(int msgId);

    void showLoadingDialog(CharSequence msg);

    void hideLoadingDialog();

}

Impl クラス#

public abstract class BasePresenterImpl implements BasePresenter {
    protected CompositeDisposable mSubscriptions;

    @Override
    public void onStart() {
        if (mSubscriptions == null) {
            mSubscriptions = new CompositeDisposable();
        }
    }

    @Override
    public void onDestroy() {
        if (mSubscriptions != null) {
            mSubscriptions.dispose();
            mSubscriptions.clear();
        }
    }
}

APP クラス#

グローバルなコンテキストを取得するためのクラス

public class APP extends Application {

    private static Context appContext;
    private static long exitTime = 0;

    /**
     * ApplicationのContextを取得する
     *
     * @return グローバルContext
     */
    public static Context getAppContext() {
        return appContext;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        appContext = getApplicationContext();
    }

    /**
     * アプリを終了する
     */
    public static void exitApp() {
        if (System.currentTimeMillis() - exitTime > 2000) {
            ToastUtils.shortToast(getAppContext(), appContext.getString(R.string.text_press_again));
            exitTime = System.currentTimeMillis();
        } else {
            android.os.Process.killProcess(android.os.Process.myPid());
        }
    }
}

具体的な使用法#

Contract 契約クラス#

契約クラスは、同じ画面の view と presenter のインターフェースを定義するために使用され、メソッドの命名やコメントを規定することで、ページ全体のロジックを明確に見ることができます。

public interface myContract {

    interface View extends BaseView<Presenter> {
    }

    interface Presenter extends BasePresenter {

    }
}

Presenter#

public class SamplePresenter extends BasePresenterImpl implements myContract.Presenter {

    private final myContract.View mView;
    public SamplePresenter(myContract.View view) {
        mView = view;
        this.mView.setPresenter(this);
    }
}

Activity#

public class SampleActivity extends BaseActivity<myContract.Presenter> implements myContract.View {

    @BindView(R.id.tv_sample_text)
    TextView mTvSample;

    @Override
    protected myContract.Presenter createPresenter() {
        return new SamplePresenter(this);
    }

    @Override
    protected int bindLayout() {
        //TODO:ビューを追加、androidmanifestに追加することを忘れないでください
        return R.layout.activity_sample;
    }

    @Override
    protected void prepareData() {
        //TODO:データを準備する 例えば:データベースからデータを読み込む、またはネットワークリクエストデータなど
    }

    @Override
    protected void initView() {
        //TODO:ビューを初期化する 例えば:recycleviewの準備、adapterの追加など
        mTvSample.setText("これはサンプルです");
    }

    @Override
    protected void initData(Bundle savedInstanceState) {
        //TODO:データを初期化する 例えば:データをビューに追加する
    }

    @Override
    protected void initEvent() {
        //TODO:イベントリスナーを初期化する 例えば:リスナーを追加、プルダウンリフレッシュ、もっと読み込むなど
    }
}
読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。