开源中国(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


<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等其他移动服务。不光是埋点这种服务,第三方的服务都应该以接口的形式提供服务,便于后期进行拓展和调整。

你可能感兴趣的:(源码分析(OSChina))