開源中國(OSChina)源碼解析(3)——Activity基類(舊版)

功能 / 分析Activity基類(舊版)
版本 / v4.1.7

1、前言

在分析主頁面(MainActivity)之前,我們需要知道Activity基類(BaseActivity)都封裝了哪些功能,對外提供了哪些共通的方法。

此處使用了設計模式中的模板方法。我們先來看看它的定義是什麼。定義一個功能的框架或者說骨架,一部分功能是確定的,一部分功能是不確定的,先把確定的部分確定下來,把不確定的部分延遲到子類中實現的設計模式稱之爲模板方法。

其優點是通過繼承基類,從而具備共通的屬性和行爲,減少重複的工作。缺點是提高了耦合度,同時由於有些功能在部分畫面中使用不到,畫面渲染的時間也會加長,犧牲部分的性能,所以需要權衡哪些功能通過繼承的方式(基類的形式)提供,哪些功能通過組合的方式(工具類的形式)提供。

2、功能列表

關聯代碼文件:

  • https://gitee.com/oschina/android-app/blob/v4.1.7/app/src/main/java/net/oschina/app/base/BaseActivity.java

通過閱讀源碼,我們可以知道BaseActivity主要封裝了以下功能。

  • 畫面初始化(onCreate)
  • 設置標題欄(ActionBar)
  • 設置返回按鈕(BackButton)
  • 設置友盟(umeng)打點服務
  • 封裝提示框(Toast)
  • 封裝加載框(WaitDialog)

2.1、畫面初始化(onCreate)

相關文件:

  • app/src/main/java/net/oschina/app/interf/BaseViewInterface.java

說明:

  • 接口View.OnClickListener :不是每一個界面都有點擊事件,所以此處不合理,應刪除
  • 方法 onBeforeSetContentLayout():在設置界面佈局之前的處理,比如說子頁面需要重新設置主題樣式,那麼就可以重寫此方法。默認處理爲空。
  • 方法 getLayoutId():獲取佈局文件的ResId,默認值爲0,ContentView爲Null。
public abstract class BaseActivity extends AppCompatActivity implements
        DialogControl, View.OnClickListener, BaseViewInterface {

    protected LayoutInflater mInflater;
    protected ActionBar mActionBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 講解:在清單文件中已經設置全局主題樣式了(App_Theme_Light),
        // 因爲此處優先級高,所以清單文件中Activity作用域下的主題將會被覆蓋
        // 此處應刪除
        setTheme(R.style.App_Theme_Light);

        onBeforeSetContentLayout();
        if (getLayoutId() != 0) {
            setContentView(getLayoutId());
        }
        mActionBar = getSupportActionBar();
        mInflater = getLayoutInflater();

        ......

        // 講解:可以使用JetPack的成員組件Databinding綁定組件,ButterKnife應廢棄
        // 通過註解綁定控件
        ButterKnife.bind(this);

        init(savedInstanceState);
        initView();
        // 講解:此處初始化數據並不合理,要優先加載頁面,數據加載後置。
        initData();
        ......
    }

    protected void onBeforeSetContentLayout() {
    }

    protected void init(Bundle savedInstanceState) {
    }

    protected int getLayoutId() {
        return 0;
    }
}

2.2、設置標題欄(ActionBar)

    protected ActionBar mActionBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ......
        mActionBar = getSupportActionBar();

        if (hasActionBar()) {
            initActionBar(mActionBar);
        }
        ......
    }

    // 講解:是否含有ActionBar,可以通過設置主題樣式便可以實現。
    protected boolean hasActionBar() {
        return getSupportActionBar() != null;
    }

    // 講解:代碼中mActionBar可以使用參數actionBar代替
    protected void initActionBar(ActionBar actionBar) {
        if (actionBar == null)
            return;
        if (hasBackButton()) {
            mActionBar.setDisplayHomeAsUpEnabled(true);
            mActionBar.setHomeButtonEnabled(true);
        } else {
            actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE);
            actionBar.setDisplayUseLogoEnabled(false);
            int titleRes = getActionBarTitle();
            if (titleRes != 0) {
                actionBar.setTitle(titleRes);
            }
        }
    }

    // 講解:通過資源Id設置標題
    public void setActionBarTitle(int resId) {
        if (resId != 0) {
            setActionBarTitle(getString(resId));
        }
    }

    // 講解:通過文本設置標題
    public void setActionBarTitle(String title) {
        if (TextUtils.isEmpty(title)) {
            title = getString(R.string.app_name);
        }
        if (hasActionBar() && mActionBar != null) {
            mActionBar.setTitle(title);
        }
    }

2.3、設置返回按鈕(BackButton)


    // 講解1:在子類中重寫該方法即可
    protected boolean hasBackButton() {
        return false;
    }

    protected void initActionBar(ActionBar actionBar) {
        if (actionBar == null)
            return;
        // 講解2:當設置有返回按鈕的時候,在ActionBar顯示返回按鈕
        if (hasBackButton()) {
            mActionBar.setDisplayHomeAsUpEnabled(true);
            mActionBar.setHomeButtonEnabled(true);
        } else {
            actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE);
            actionBar.setDisplayUseLogoEnabled(false);
            int titleRes = getActionBarTitle();
            if (titleRes != 0) {
                actionBar.setTitle(titleRes);
            }
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            // 講解3:點擊ActionBar上的返回按鈕時,返回上一級頁面
            case android.R.id.home:
                onBackPressed();
                break;

            default:
                break;
        }
        return super.onOptionsItemSelected(item);
    }

2.4、設置友盟(umeng)打點服務

此處應先定義接口,然後根據需要提供實現。同時只能觀測到用戶什麼時候打開頁面,什麼時候關閉頁面。用戶點擊什麼按鈕等事件沒有觀測到,收集的數據比較有限,不能給運維提供強有力的支持,需要改進。

private final String packageName4Umeng = this.getClass().getName();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ......

    // 講解初始化代理
    MobclickAgent.setDebugMode(false);
    MobclickAgent.openActivityDurationTrack(false);
    MobclickAgent.setScenarioType(this, MobclickAgent.EScenarioType.E_UM_NORMAL);
}

@Override
protected void onPause() {
    super.onPause();
    MobclickAgent.onPageEnd(this.packageName4Umeng);
    // 講解:此處設置錯誤,應爲MobclickAgent.onPause(this)
    MobclickAgent.onResume(this);
    ......
}

@Override
protected void onResume() {
    super.onResume();
    MobclickAgent.onPageStart(this.packageName4Umeng);
    MobclickAgent.onResume(this);
}

路徑:app/src/main/AndroidManifest.xml

<manifest>
    <application>
        <meta-data
            android:name="UMENG_APPKEY"
            android:value="53cb520c56240bbd7d076ce5" />
        <meta-data
            android:name="UMENG_CHANNEL"
            android:value="${UMENG_CHANNEL}" />
    </application>
</manifest>

2.5、封裝提示框(Toast)

系統在逐步迭代後,使用了至少四種提示框,SimplexToast應向上抽取成全局工具類,
以組合的方式在需要的位置調用。此處的代碼應刪除比較合理。

  • android.widget.Toast
  • app/src/main/java/net/oschina/app/base/BaseApplication.java
  • app/src/main/java/net/oschina/app/ui/dialog/CommonToast.java
  • app/src/main/java/net/oschina/app/improve/widget/SimplexToast.java
public void showToast(int msgResid, int icon, int gravity) {
    showToast(getString(msgResid), icon, gravity);
}

public void showToast(String message, int icon, int gravity) {
    CommonToast toast = new CommonToast(this);
    toast.setMessage(message);
    toast.setMessageIc(icon);
    toast.setLayoutGravity(gravity);
    toast.show();
}

2.6、封裝加載框(WaitDialog)

相關文件:

  • app/src/main/java/net/oschina/app/ui/dialog/DialogControl.java
  • app/src/main/java/net/oschina/app/improve/utils/DialogHelper.java

成員變量說明:

  • _isVisible:確保加載框在畫面實例化完成以後纔可以表示或者隱藏
  • _waitDialog:加載框定義
    private boolean _isVisible;
    private ProgressDialog _waitDialog;

    @Override
    public ProgressDialog showWaitDialog() {
        return showWaitDialog(R.string.loading);
    }

    @Override
    public ProgressDialog showWaitDialog(int resid) {
        return showWaitDialog(getString(resid));
    }

    @Override
    public ProgressDialog showWaitDialog(String message) {
        if (_isVisible) {
            if (_waitDialog == null) {
                _waitDialog = DialogHelper.getProgressDialog(this, message);
            }
            if (_waitDialog != null) {
                _waitDialog.setMessage(message);
                _waitDialog.show();
            }
            return _waitDialog;
        }
        return null;
    }

    @Override
    public void hideWaitDialog() {
        if (_isVisible && _waitDialog != null) {
            try {
                _waitDialog.dismiss();
                _waitDialog = null;
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

路徑:app/src/main/res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="loading">加載中…</string>
</resources>

3、總結

分析完以後才發現,原來是舊版Activity基類,已經被廢棄了,但是代碼文件仍然被保留沒有被刪除,也沒有添加已過時註解,建議改進。

  • 舊版:app/src/main/java/net/oschina/app/base/BaseActivity.java
  • 新版:app/src/main/java/net/oschina/app/improve/base/activities/BaseActivity.java

通過舊版Activity基類源碼的分析,筆者有以下的觀點供大家參考:

  • 生命週期(onCreate)中,界面佈局文件的設置setTheme(), setContentView() 可以刪除,因爲這些都是簡單的邏輯,沒有封裝的必要。
  • 設置標題欄(ActionBar),設置返回按鈕(BackButton)作爲所有界面的界面元素封裝在基類中是合理。
  • 封裝提示框(Toast),封裝加載框(WaitDialog),可以用組合的方式提供服務,降低界面邏輯的耦合度。
  • 友盟(umeng)打點服務,應該通過接口的方式提供服務,便於從友盟變成Google等其他移動服務。不光是埋點這種服務,第三方的服務都應該以接口的形式提供服務,便於後期進行拓展和調整。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章