Android Studio: BaseActivity基类设计——万能的家政保姆

一、博文导读

        本文是基于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 的主要功能和作用

  1. 统一布局加载逻辑

    • 子类只需要告诉 BaseActivity“我的布局是啥”BaseActivity 就帮你加载,不需要每个 Activity 都写 setContentView()
  2. 减少重复代码

    • 比如:Toast 提示、进度条加载、日志打印等常见操作,如果每个 Activity 都需要写一遍,会很麻烦,放到 BaseActivity 就能直接调用。
  3. 方便管理通用逻辑

    • 比如设置状态栏颜色、检查权限、统一的事件处理等等,这些逻辑通常每个页面都要做,放到 BaseActivity 后只写一次就行了。
  4. 代码结构清晰

    • 子类只负责自己的“业务逻辑”(比如点击一个按钮做某件事),而不关心“通用功能”(比如显示加载框、打印日志等)。

2.2 哪些项目需要写 BaseActivity?

需要写的场景:

  1. 项目中有多个 Activity,而且它们有许多共同的功能或逻辑
    • 例如:一个电商项目,多个页面都需要显示加载框、打印日志或者检查权限。
  2. 想提高代码复用性,避免重复写同样的代码
    • 比如:Toast 提示、网络请求失败的统一处理、加载进度条等。
  3. 项目较大,需要统一管理样式和逻辑
    • 比如:统一设置所有 Activity 的状态栏样式。

不需要写的场景:

  1. 项目比较小,只有一两个 Activity,每个页面功能简单,没有太多公共逻辑时,没必要写 BaseActivity
  2. 项目中几乎所有页面的逻辑都不一样,公共功能很少,写了 BaseActivity 反而复杂化。

不能写的场景:

  1. 如果项目需要支持某种特殊的 Activity 类型(比如:DialogFragmentPreferenceActivity),它们可能无法继承 BaseActivity,需要单独处理。
  2. 当不同页面需要的功能完全不一样,强行用 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

除了基础的布局加载,还可以扩展以下功能:

功能列表

  1. 通用工具方法:例如 Toast 提示、日志打印。
  2. 统一状态栏样式:处理状态栏的颜色、样式。
  3. 加载进度框:封装通用的 Loading 提示框。
  4. 权限请求:集中处理运行时权限。
  5. 日志跟踪:自动打印当前 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,所有子类都会自动应用。
  • 清晰分离:子类专注于业务逻辑,基类处理通用功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

剑客狼心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值