一、博文导读
本文是基于Android Studio真实项目,通过解析源码了解真实应用场景,写文的视角和读者是同步的,想到看到写到,没有上帝视角。
上期回顾,本文是第三期。简单来说,前两期的内容分别是代码解耦合和视图绑定,今天来看一下BaseActivity的全貌。
public abstract class BaseActivity extends AppCompatActivity{
private Display mDisplay;
protected Context mContext;
private Unbinder mUnbinder;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutId());
mUnbinder = ButterKnife.bind(this);
mContext = this;
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
if(null != toolbar){
TextView tv_title = (TextView) findViewById(R.id.tv_title);
if(null != tv_title){
tv_title.setText(getToolbarTitle());
}
setToolbar(toolbar);
}}
protected String getToolbarTitle(){
return getString(R.string.app_name);
}
protected final void setToolbar(Toolbar toolbar) {
setSupportActionBar(toolbar);
getDisplay().setSupportActionBar(toolbar);
}
protected abstract int getLayoutId();
public Display getDisplay(){
if(null == mDisplay){
mDisplay = new AndroidDisplay(this);
}
return mDisplay;
}
public void setDisplay(Display mDisplay) {
this.mDisplay = mDisplay;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
break;
}
return super.onOptionsItemSelected(item);
}
/**
* 登录状态发生改变
*/
protected void onErrorLoginStateChange(){
}
@Override
protected void onDestroy() {
setDisplay(null);
if(null != mCommonMessageDialog){
mCommonMessageDialog.dismiss();
}
mUnbinder.unbind();
super.onDestroy();
}
CommonMessageDialog mCommonMessageDialog;
protected void showAlertDialog(String msge, View.OnClickListener onClickListener){
if(mCommonMessageDialog != null && mCommonMessageDialog.isShowing()){
return;
}
mCommonMessageDialog = new CommonMessageDialog(mContext, msge, onClickListener);
mCommonMessageDialog.show();
}
}
二、代码解读——BaseActivity
BaseActivity
是一个在 Android 项目中用来“省事儿”的工具类,它的核心作用是把多个 Activity
公共的代码提取出来,让开发者不需要每次都重复写这些代码。通俗点讲,就是为了“偷懒”,把能一次性解决的事提前干好,其他地方直接用就行了。
2.1 BaseActivity 的主要功能和作用
-
统一布局加载逻辑:
- 子类只需要告诉
BaseActivity
:“我的布局是啥”,BaseActivity
就帮你加载,不需要每个Activity
都写setContentView()
。
- 子类只需要告诉
-
减少重复代码:
- 比如:
Toast
提示、进度条加载、日志打印等常见操作,如果每个Activity
都需要写一遍,会很麻烦,放到BaseActivity
就能直接调用。
- 比如:
-
方便管理通用逻辑:
- 比如设置状态栏颜色、检查权限、统一的事件处理等等,这些逻辑通常每个页面都要做,放到
BaseActivity
后只写一次就行了。
- 比如设置状态栏颜色、检查权限、统一的事件处理等等,这些逻辑通常每个页面都要做,放到
-
代码结构清晰:
- 子类只负责自己的“业务逻辑”(比如点击一个按钮做某件事),而不关心“通用功能”(比如显示加载框、打印日志等)。
2.2 哪些项目需要写 BaseActivity?
需要写的场景:
- 项目中有多个
Activity
,而且它们有许多共同的功能或逻辑:- 例如:一个电商项目,多个页面都需要显示加载框、打印日志或者检查权限。
- 想提高代码复用性,避免重复写同样的代码:
- 比如:Toast 提示、网络请求失败的统一处理、加载进度条等。
- 项目较大,需要统一管理样式和逻辑:
- 比如:统一设置所有
Activity
的状态栏样式。
- 比如:统一设置所有
不需要写的场景:
- 项目比较小,只有一两个
Activity
,每个页面功能简单,没有太多公共逻辑时,没必要写BaseActivity
。 - 项目中几乎所有页面的逻辑都不一样,公共功能很少,写了
BaseActivity
反而复杂化。
不能写的场景:
- 如果项目需要支持某种特殊的
Activity
类型(比如:DialogFragment
、PreferenceActivity
),它们可能无法继承BaseActivity
,需要单独处理。 - 当不同页面需要的功能完全不一样,强行用
BaseActivity
可能会导致“越写越复杂”,甚至需要额外禁用不需要的功能。
一句话总结:项目大、页面多、逻辑重复的地方多,就用 BaseActivity
;项目小、页面少、需求简单,就没必要用。
举几个例子:
2.3 基础版 BaseActivity
public abstract class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 加载布局
setContentView(getLayoutId());
// 初始化操作
initView();
initData();
initListener();
}
/**
* 子类必须实现此方法,返回对应的布局资源 ID
*/
@LayoutRes
protected abstract int getLayoutId();
/**
* 初始化视图(由子类实现)
*/
protected void initView() {
// 子类可根据需要重写
}
/**
* 初始化数据(由子类实现)
*/
protected void initData() {
// 子类可根据需要重写
}
/**
* 初始化监听器(由子类实现)
*/
protected void initListener() {
// 子类可根据需要重写
}
}
2.3.1 什么是 @LayoutRes
?
@LayoutRes
其实就是一个注解,它告诉编译器和程序员这个方法返回的值是一个布局文件的 ID。你可以把它理解为一种“标签”,它帮助我们更清晰地表明,这个返回值是专门用来指定界面布局的资源。
在 Android 开发中,界面是通过 XML 文件来设计的,比如 activity_main.xml
,这些 XML 文件会被转化成一个数字 ID(比如:R.layout.activity_main
)。在 Java 代码中,会用 R.layout.activity_main
来引用这个布局文件。
@LayoutRes
protected int getLayoutId() {
return R.layout.activity_main; // 这个返回值是一个布局资源ID
}
@LayoutRes
是用来标记返回值是布局资源的 ID(例如:R.layout.activity_main
)。
简单来说: @LayoutRes
是告诉你,“这个方法返回的值是用来设置界面的布局”。它的作用是帮助开发者理解代码和提高代码的准确性。
如果你没有加上这个注解,虽然程序是能运行的,但别人看你的代码时,可能不太清楚这个方法到底是在做什么。加了 @LayoutRes
注解,别人就可以明确知道这个方法返回的值是“布局资源的 ID”。
举个例子:
假设我们有一个基类 BaseActivity
,它用来处理一些公共操作,比如设置布局。在这个基类里,我们希望子类提供他们自己的布局文件:
public abstract class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 通过子类返回的布局资源 ID 加载布局
setContentView(getLayoutId());
}
// 子类必须实现这个方法,返回自己的布局 ID
@LayoutRes
protected abstract int getLayoutId();
}
在子类中,我们就需要实现 getLayoutId()
方法,并返回布局的资源 ID:
public class MainActivity extends BaseActivity {
@Override
protected int getLayoutId() {
return R.layout.activity_main; // 返回布局资源 ID
}
}
- IDE(比如 Android Studio)会检查你是否正确返回布局资源 ID。如果你不小心返回了一个字符串资源 ID(
R.string.some_string
),IDE 会提醒你错误。这样可以避免很多小错误。
这个注解在第一段BaseActivity中没有使用,虽然不影响功能,但是会降低代码清晰度。
2.3.2 子类示例:MainActivity.java
public class MainActivity extends BaseActivity {
private TextView textView;
private Button button;
@Override
protected int getLayoutId() {
return R.layout.activity_main; // 返回当前 Activity 的布局文件
}
@Override
protected void initView() {
// 绑定视图
textView = findViewById(R.id.textView);
button = findViewById(R.id.button);
}
@Override
protected void initData() {
// 设置默认文本
textView.setText("Welcome to BaseActivity!");
}
@Override
protected void initListener() {
// 设置按钮点击事件
button.setOnClickListener(v ->
Toast.makeText(this, "Button clicked!", Toast.LENGTH_SHORT).show()
);
}
}
2.4 扩展版 BaseActivity
除了基础的布局加载,还可以扩展以下功能:
功能列表
- 通用工具方法:例如 Toast 提示、日志打印。
- 统一状态栏样式:处理状态栏的颜色、样式。
- 加载进度框:封装通用的 Loading 提示框。
- 权限请求:集中处理运行时权限。
- 日志跟踪:自动打印当前 Activity 的名称。
import android.app.ProgressDialog;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.LayoutRes;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public abstract class BaseActivity extends AppCompatActivity {
private ProgressDialog progressDialog;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 打印日志
Log.d("BaseActivity", "Current Activity: " + getClass().getSimpleName());
// 加载布局
setContentView(getLayoutId());
// 初始化操作
initView();
initData();
initListener();
}
/**
* 子类必须实现此方法,返回对应的布局资源 ID
*/
@LayoutRes
protected abstract int getLayoutId();
/**
* 初始化视图(由子类实现)
*/
protected void initView() {
// 子类可根据需要重写
}
/**
* 初始化数据(由子类实现)
*/
protected void initData() {
// 子类可根据需要重写
}
/**
* 初始化监听器(由子类实现)
*/
protected void initListener() {
// 子类可根据需要重写
}
/**
* 显示 Toast
*/
protected void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
/**
* 显示加载进度框
*/
protected void showLoading(String message) {
if (progressDialog == null) {
progressDialog = new ProgressDialog(this);
progressDialog.setCancelable(false);
}
progressDialog.setMessage(message);
progressDialog.show();
}
/**
* 隐藏加载进度框
*/
protected void hideLoading() {
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.dismiss();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// 释放 ProgressDialog
if (progressDialog != null) {
progressDialog.dismiss();
progressDialog = null;
}
}
}
实现扩展功能:
import android.os.Handler;
import android.widget.Button;
public class MainActivity extends BaseActivity {
private Button button;
@Override
protected int getLayoutId() {
return R.layout.activity_main; // 返回当前布局文件
}
@Override
protected void initView() {
button = findViewById(R.id.button);
}
@Override
protected void initListener() {
// 设置按钮点击事件
button.setOnClickListener(v -> {
// 显示加载框
showLoading("Loading, please wait...");
// 模拟耗时操作
new Handler().postDelayed(() -> {
// 隐藏加载框
hideLoading();
// 显示 Toast
showToast("Button clicked!");
}, 2000);
});
}
}
BaseActivity优势分析
- 代码复用:将公共逻辑封装在
BaseActivity
中,减少重复代码。 - 灵活扩展:当需要修改通用功能时,只需更改
BaseActivity
,所有子类都会自动应用。 - 清晰分离:子类专注于业务逻辑,基类处理通用功能。