
写Android没几年的朋友大概率都有过这种体验一个新页面做完代码里塞满了setContentView、findViewById、状态栏再调一轮、Toast再来一个明明业务很简单却要写一堆和业务无关的样板代码。这个痛苦我太熟了早期我维护过一个十几个页面的项目每个Activity里都有一坨一模一样却不敢删的初始化代码那时候我就开始琢磨与其在每个页面里复制粘贴不如把所有公共逻辑抽到一个基类里。这就是我做BaseActivity封装的原动力也是今天这篇要聊透的事情。BaseActivity这个东西本质上不是什么高深技术它就是把Activity生命周期里那些每个页面都得干的事情收拢到一处让子类只关心自己的业务。但它真正值钱的地方在于封装的边界和度封装早了后面需求一变基类改起来牵一发动全身封装少了子类还是得写一堆重复代码等于没封。这篇文章我会结合自己维护多年的实战经验从头梳理一套可落地的BaseActivity设计方案你会看到布局加载怎么抽象、状态栏和软键盘怎么统一处理、Toast怎么封装才不会在各机型上翻车、权限请求怎么基于新API标准化以及MVP模式下的泛型基类怎么做最后还会把我踩过的内存泄漏的坑一并摊开。无论你是刚开始写Android的新人还是项目里已经有BaseActivity但觉得越写越乱的老人这篇文章应该都能给你一些直接的参考。1. 重复代码的真实成本为什么每个页面都必须做这件事很多初学者对封装的理解停留在减少代码量这个层面这个认知有些片面。减少代码量只是封装的结果封装的真正价值是把那些容易出错的、需要保持一致性的逻辑集中在一个地方管理。Activity的开发里这种逻辑占的比例相当高。拿一个非常典型的场景来说。你新建一个Activity不管页面多简单有几件事是躲不掉的指定布局文件、处理状态栏颜色和字体深浅、决定软键盘弹出时界面怎么变化、注册各种需要释放的监听器、处理页面进入和退出的公共逻辑。这些事情单拎出来每个都不难但它们有一个共同特点容易忘且行为必须全局一致。我曾经统计过一个项目20个Activity其中有12个用的是几乎相同的状态栏设置代码只是把颜色值换了一下有9个页面在Android 10以下和以上跑出来的软键盘表现压根不一样因为没统一处理windowSoftInputMode和fitsSystemWindows的配合。这类问题靠人肉在每个Activity里修今天是改不完的因为你根本不知道还有哪个页面漏了。BaseActivity最直接的价值就是把每个页面都必须做的初始化公共逻辑变成一个约定子类继承基类只需要告诉基类我用的布局是哪个状态栏要什么颜色我数据加载好了没有剩下的事情基类统一处理。这是一种面向约定的编程思路它的收益不在代码行数上而在维护心智上以后状态栏逻辑要调整改一个地方软键盘策略要变改一个地方所有页面自动生效。而且我说句实在话BaseActivity做得好不好直接影响团队协作效率。新成员接手项目看主Activity就知道整个项目的基调和约束条件他写的第一个页面天然就和全项目风格统一不需要去翻别的页面样例猜基类里到底帮我做了哪些事情。这比任何开发规范文档都有效。2. 第一步先把布局加载流程抽象干净ViewBinding与懒加载的落地2.1 从setContentView到抽象方法基类不要背业务包袱正常写Activity第一行业务代码大概率是setContentView(R.layout.activity_main)。既然每个子类都要调而且调用的时机是固定的——onCreate阶段、且在业务初始化之前——那这个动作就应该收进基类。我的做法是基类里定义两个抽象概念布局资源和视图对象。子类只需要声明自己用哪个布局基类负责加载public abstract class BaseActivity extends AppCompatActivity { LayoutRes protected abstract int getLayoutResId(); Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getLayoutResId()); initWindows(); initView(); initData(); } protected abstract void initView(); protected abstract void initData(); }这个设计模版方法模式的思路onCreate的骨架顺序由基类定死子类只负责填空。initView用来做findViewById、设置监听initData用来加载数据。为什么要把这两个拆开因为大多数页面里视图初始化和数据加载是两个时机做的事比如你需要先拿到控件引用才能异步请求回来更新它或者数据加载要在视图准备好之后才能进行。拆开的另一个好处是子类代码可读性更高新人扫一眼三个方法名就清楚页面结构了。这里有一个我踩过的坑很多基类里直接写abstractvoid initView()和void initData()但如果你的项目里有那种布局非常简单、不需要初始化数据的页面呢每个页面的调用点都要写一遍onCreate本身没问题但如果基类的抽象方法强迫每个子类都实现一个空白方法这个设计就偏硬了。后来我改成了非抽象方法默认空实现子类按需重写。抽象方法用来约束必须有的非抽象方法用来提供可以有的这个边界要牢记。2.2 用ViewBinding替代findViewById类型安全带来的连锁收益布局加载接完了紧接着就是控件绑定。老项目里findViewById满天飞Java这边稍不注意就是ClassCastExceptionKotlin的kotlin-android-extensions插件已经废弃所以现在的新项目基本都转向ViewBinding。ViewBinding有个特点每个布局都会生成一个对应的Binding类绑定操作是类型安全的。如果你还在用findViewById我个人建议借封装BaseActivity的机会把ViewBinding引入进来。用泛型可以很好地处理这件事public abstract class BaseBindingActivityVB extends ViewBinding extends BaseActivity { protected VB binding; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 这里用反射获取泛型VB类的inflate方法实现统一绑定 binding createBinding(getLayoutResId()); setContentView(binding.getRoot()); initView(); initData(); } }泛型的设计有一个小技巧通过Class 拿到泛型真实类型然后反射调用它的inflate方法。因为ViewBinding的每个子类都有统一的inflate(LayoutInflater)静态方法所以这层反射是安全且可控的。不过这里要提醒一句反射方案虽然能减少子类代码但在编译期无法检查泛型类型是否正确如果有人在继承时写错了泛型参数运行时才会暴露。考虑到BaseActivity通常是项目里层级最顶部的类这种风险完全可以通过Code Review和团队规范兜住。如果你不想用反射也可以让子类自己提供binding实例protected abstract VB createBinding(LayoutInflater inflater);这两种方式我都用过。反射方案子类更省事子类只要写一个getLayoutResId非反射方案更直白、编译期安全但每个子类都要写一行createBinding。就Android开发的现状来说我个人更倾向非反射方式因为它把不确定性控制在了可控范围内——毕竟封装基类的目的就是减少不确定性而不是引入新的不确定性。2.3 布局懒加载想要又不想显示先想清楚还有一种场景是这页面的布局要等条件满足才显示。比如闪屏页先显示logo等判断完登录状态再跳走或进入主界面再比如有些页面要根据网络返回决定显示A布局还是B布局。这种情况下一进onCreate就把布局膨胀出来是浪费的。我处理这类页面时BaseActivity里提供了一个特殊开关protected boolean isLazyLoad() { return false; }子类重写返回true后基类不会在onCreate里立即setContentView而是提供一个showLazyView()方法由子类在合适的时机调用触发绑定和初始化。这个设计能减少页面启动时无意义的measure/layout/draw。不过说句实话现在手机性能都不差这种优化更多是心理安慰除非你的布局很重或者页面很极端否则没必要为了懒加载给每个页面增加复杂度。把基类做简单点别把基类变成体系黑洞也是封装的重要原则。3. 状态栏、软键盘与主题色那些最容易被忽略的隐形统一3.1 状态栏适配的版本分裂问题状态栏是一个让Android开发者吐槽了无数次的东西因为不同Android版本、不同国内厂商ROM对状态栏的处理方式完全不同。从Android 4.4开始有沉浸式Android 6.0支持状态栏字体变深色Android 8.0新增手势栏小米、华为又有各自的白名单机制。这些适配逻辑如果散落在每个Activity里基本等于项目里埋了无数颗雷。我的BaseActivity里专门有一个状态栏处理模块核心代码如下protected void initWindows() { if (Build.VERSION.SDK_INT Build.VERSION_CODES.LOLLIPOP) { Window window getWindow(); window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(getStatusBarColor()); } setStatusBarTextDark(isStatusBarTextDark()); }这里有个关键点状态栏的颜色和字体深浅是两码事。颜色是背景深浅是图标和文字的明暗。浅色背景配深色图标深色背景配浅色图标这组配合做不好页面顶部就会糊成一片。基类默认提供 getStatusBarColor() 接口子类按需覆盖即可。真实项目里我碰到最多的坑是fitsSystemWindows与状态栏重叠的问题。当你把状态栏设置为透明、希望内容延伸上去时会用FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS和fitsSystemWindows做配合。这个组合涉及Activity根布局的padding处理稍不注意就会导致页面顶部控件被状态栏遮住。后来我统一在一个基类的initWindows里处理默认状态栏不透明、设置指定颜色只有需要全屏沉浸的子类才主动调整window flags。先保证不出错再追求炫酷效果。3.2 软键盘与adjustResize一句话就能改变体验软键盘处理是另一个平时没人看出了问题用户直接卸载的细节点。最经典的场景聊天界面输入框被键盘顶起页面底部按钮被键盘挡住或者键盘弹起时背景被压缩变形。Android里软键盘相关行为的开关是windowSoftInputMode它在AndroidManifest里配置。问题在于全局配置会影响所有页面而不同页面的软键盘交互诉求完全不同聊天页希望adjustResize键盘弹出时重新调整布局大小列表页通常希望adjustPan键盘弹起时把焦点控件滚动到可视区域。我的做法是BaseActivity提供一个可覆盖的方法来设置软键盘模式并在onCreate时写入window属性替代在Manifest里写死protected SoftInputMode getSoftInputMode() { return SoftInputMode.ADJUST_PAN; } private void applySoftInputMode() { getWindow().setSoftInputMode(getSoftInputMode().getValue()); }Manifest里不要写windowSoftInputMode全部交给基类运行时指定。子类对软键盘有特殊要求时重写getSoftInputMode即可其他页面默认走adjustPan入口处再根据实际情况覆盖。这样软键盘行为就和页面代码放一起了改起来不用翻Manifest也不用担心Manifest里配了全局值导致某些页面异常。顺带一提用enableWindowContentOverlay或adjustResize时如果Activity里使用了CoordinatorLayout某些机型上会触发布局重复测量的问题这属于框架间的兼容问题没有银弹只能靠真机多测。真机适配这件事封装再好的基类都替不了你。3.3 全局主题切换基类里设置一次theme页面自动继承现在的App大多有日间/夜间模式或者至少有一套品牌色的动态切换机制。如果我们每写一个页面都要重新设置一遍背景色、标题栏颜色、控件主题色那这种视觉体系注定是维持不下去的。BaseActivity里可以做一层统一的主题应用逻辑。比较直接的做法是基类提供 getThemeResId() 方法子类声明自己用的是哪个主题基类在super.onCreate之前调用setTheme。但是Android的setTheme必须在setContentView之前调用才生效所以这段逻辑必须放在onCreate的最前面。另一种思路是把主题色抽成一个全局配置基类在initWindows里去读取应用当前的深浅色模式动态计算状态栏颜色、导航栏颜色、默认背景色。这种做法把日夜间模式的适配从各个页面收敛到了基类与全局配置子类几乎感知不到。当然真正的页面内部控件颜色还是得靠资源系统values-night去处理这是BaseActivity做不了的部分一定要分清边界。4. 统一Toast封装审美之外还有系统机制的门道4.1 原生Toast的重复显示问题Toast可能是大家最早接触的Android组件之一但很多人不知道Android原生Toast有一个著名的历史问题同一个Toast连续show会排队一个个弹用户手机上会出现连续好几条toast排着队冒出来的情况如果在textView.setText之后只改内容不改显示时长在某些低版本系统上还可能因为资源复用导致提示文本一直不刷新。我自己在上一个项目里就被这个坑害过用户快速点击某个按钮触发校验提示屏幕上会连弹五六个请输入正确格式体验极差。后来我用一个自定义Toast类替换了原生Toast的调用核心思路是全局只维护一个Toast实例show的时候先cancel之前的再show新的。public class CommonToast { private static Toast sToast; public static void show(CharSequence message) { if (sToast null) { sToast Toast.makeText(App.getContext(), , Toast.LENGTH_SHORT); } sToast.setText(message); sToast.show(); } }这个封装带来的改进非常明显用户看到的永远是最新一条提示不会堆积重复调用时旧Toast被取消不会排队。而在BaseActivity里我会再包一层showToast(String msg)方法让子类用起来更顺手也方便以后插入埋点或统一日志。4.2 Android 12的新坑通知权限与Toast如果你以为Toast封装到这里就完事了那还是太年轻。Android 12API 31之后应用弹出Toast的行为变了。从Android 12开始系统会在某些情况下限制后台应用弹出Toast的权限并且如果你的App targetSdk达到31及以上首次弹出Toast时会有一个系统应用显示通知的权限提示需要处理。这个变化直接影响了App退回后台后通过Toast提示用户的场景。比如下载任务完成了App切到后台你想toast提示一下结果发现完全没弹因为Toast被系统限了。实际项目里我在BaseActivity的onStop和onPause阶段会特殊处理Toast的调用时机或者干脆在那种场景下改用系统通知栏Notification提示用户。这个适配细节是写在系统文档里但大多数业务开发者根本没注意到的。顺带补充一个和Toast配套的封装思路所有提示信息统一走BaseActivity的showToast方法后可以顺带做一层短时间内的去重。比如同一秒内多次请求同一文案直接丢弃后面的。这种在基类层面的全局去重能有效防止频繁点击按钮导致Toast轰炸属于加分的细节。5. 权限请求与ActivityResult的路由新API下标准化封装5.1 为什么老一套onRequestPermissionsResult已经不够了Android运行时权限从6.0引入到如今已经十多年了。早期写法是requestPermissions onRequestPermissionsResult回调这套API本身没什么硬伤但它有个比较麻烦的组织问题回调结果和触发请求的上下文是分离的一个Activity里如果在多个地方发起了不同权限的请求onRequestPermissionsResult里就要写一堆判断来区分到底是哪个请求的回调代码容易乱。后来Google推出了Activity Result API用registerForActivityResult ActivityResultContracts.RequestMultiplePermissions来替代startActivityForResult和onActivityResult这算是一次根本性的重构。使用这套API之后权限请求的结果可以直接进入一个回调方法并且通过传入的请求码可以精确区分来源。BaseActivity建议基于这套新API做封装因为Activity Result API天然适合在基类层注册回调然后子类按需调用。5.2 封装出一套请求权限只写两行的方法我的BaseActivity里权限模块的设计大致如下private final ActivityResultLauncherString[] permissionLauncher registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result - { onPermissionResult(result); }); protected void requestPermission(String permission, int requestCode) { // 6.0以下直接放行 if (Build.VERSION.SDK_INT Build.VERSION_CODES.M) { onPermissionGranted(requestCode); return; } if (checkSelfPermission(permission) PackageManager.PERMISSION_GRANTED) { onPermissionGranted(requestCode); return; } permissionLauncher.launch(new String[]{permission}); } protected void onPermissionResult(MapString, Boolean result) { // 基类统一做状态分发 }这里最大的好处是子类请求权限不用自己new launcher、不用自己维护requestCode到回调方法的映射只需要调用requestPermission(permission, code)并在基类提供的空方法onPermissionResult(int requestCode, boolean granted, boolean shouldShowRationale)里处理结果。关键信息被拒绝、被永久拒绝、应该展示原因弹窗也可以在基类里统一解析后传给子类子类不用关心权限映射的具体细节。5.3 onResume里的权限兜底处理拒绝后重新请求的判断权限封装的坑通常在被拒绝之后怎么办。权限这东西用户第一次点了拒绝系统会允许我们再次弹窗询问但如果用户勾选了不再询问再次requestPermissions会直接被系统拒绝而且没有任何回调可以用来区分用户拒绝了还是系统不让我们弹窗了。正确的判断方式是在回调里检查 shouldShowRequestPermissionRationale(permission)。这个方法返回true说明用户拒绝过但没勾选不再询问可以继续弹返回false并且权限未授予说明用户勾了不再询问或者系统限制这时候应该引导用户去设置页手动开关而不是硬弹。为了处理这种用户跳去设置页改完权限返回App的场景我的BaseActivity里还会在onResume做一次权限检查兜底。思路是如果子类的某个权限请求还在等待状态从设置页回来时重新检查一下如果已经授权了就回调onPermissionGranted。这样用户从设置页授权回来后页面会立即响应不需要用户再次触发操作。实现时注意用标志位避免onResume里重复回调这个细节容易忽略。另外还有一个老生常谈的点权限请求的launcher必须是在Activity已经started之后才能launch否则会抛异常。很多人在onCreate里直接launch就崩了。解决办法是把请求时机放到onResume之后或者用Handler post一下延后执行。我在基类里对这类调用做了一层防护记录待请求的权限在started状态时再真正launch。6. 进阶方案泛型MVP基类与内存泄漏的防线6.1 泛型Presenter基类绑定和解绑的固定节奏项目做到后面很多人会把MVP或MVVM模式引入。如果项目用的是MVPBaseActivity往往就扩展成了BaseMvpActivity 。这个类的核心职责不是管理View而是管理Presenter的创建和销毁时机。我长期的实践模式是这样的public abstract class BaseMvpActivityP extends BasePresenter extends BaseActivity { protected P mPresenter; Override protected void onCreate(Bundle savedInstanceState) { mPresenter createPresenter(); if (mPresenter ! null) { mPresenter.attach(this); } super.onCreate(savedInstanceState); } Override protected void onDestroy() { if (mPresenter ! null) { mPresenter.detach(); } super.onDestroy(); } protected abstract P createPresenter(); }这个封装的动作看起来很小但它保住了MVP最重要的约定View层销毁时Presenter必须解绑避免Presenter继续持有Activity引用导致的内存泄漏。如果你的项目里有些页面没有PresentercreatePresenter返回null即可基类做了空判断不会Crash。6.2 Activity生命周期泄漏不止是Presenter那点事基于BaseActivity我们能做的内存泄漏防护远不止Presenter。Activity的泄漏在Android开发里是一个永恒话题最常见的有静态变量持有Activity引用、内部类Handler持有外部Activity、RxJava订阅未取消、EventBus未注销、匿名线程在Activity销毁后还在跑。封装层面的做法是在基类里统一提供注册和注销的时机Override protected void onDestroy() { if (mPresenter ! null) { mPresenter.detach(); } if (compositeDisposable ! null) { compositeDisposable.clear(); } if (EventBus.getDefault().isRegistered(this)) { EventBus.getDefault().unregister(this); } super.onDestroy(); }用一个RxCompositeDisposable统一收集所有订阅Activity销毁时统一clear这个方案是我给所有使用RxJava的项目推荐的做法。EventBus的注册也可以用注解反射的方式在基类里自动判断并注销子类只需要调用RxBus.register(this)即可完全不用关心注销的事。这些统一的清理动作有巨大的隐藏价值它把防泄漏从每个开发者自觉变成了基类强制处理。哪怕有个新同事忘了解绑Presenter或忘了注销订阅只要他继承的是我们的BaseActivity最外层的保护就还在。我反复和团队说这句话基类的作用就是让你的队友少犯你以前犯过的错。6.3 Lifecycle组件如何让封装的边界更清晰如果你的项目已经全面Architecture Components化那现代的做法不是用MVP基类而是用ViewModel LiveData Lifecycle。Java的LifecycleOwner框架天然解决了生命周期绑定问题Activity本身就是LifecycleOwnerViewModel自己会在onDestroy时自动clear。这种情况下BaseActivity的作用就变了它不再是MVP的依附点而是公共UI逻辑的统一入口。比如我们要监听App前后台切换可以在BaseActivity里通过ProcessLifecycleOwner注册一个全局监听器页面自动感知前后台事件做暂停轮询、恢复刷新等操作。这种借助Lifecycle的封装方式代码比手动在onResume/onPause里管理要简洁很多而且天然支持可空安全不会出现那种页面已经销毁了异步回调还来更新UI的崩溃。我个人现在的项目里BaseActivity的定位是公共UI策略的模板类而不是业务逻辑的容器。所有和业务强相关的逻辑尽量下沉到ViewModel或者Presenter基类只处理生命周期和全局UI策略。这个定位想清楚之后BaseActivity的设计就不会失控了。7. 功能开关而不是方法爆炸BaseActivity的维护演进BaseActivity封装到一定规模后最常见的毛病是方法越来越多。今天加一个showLoading明天加一个hideKeyboard后天又加一个openWebView基类变成一个大杂烩子类继承它要面对的是一堆自己根本用不到的方法这对新人的认知负担是很大的。我有一个喜欢的优化思路把互相关联的功能折叠成一个开关方法对外暴露的是开关级别的API而不是一堆琐碎操作。比如页面功能聚合这个场景可以设计成protected boolean isUseDefaultTitleBar() { return false; } protected boolean isNeedRegisterEventBus() { return false; } protected boolean isUseTransparentStatusBar() { return false; }子类只需要按需重写这些布尔方法而不是自己去手动调节各种Flags、手动注册EventBus。这种开关式设计的优点是子类代码里的意图非常明确读代码的人一眼就能看出这个页面的特殊之处而不需要去猜测基类体系里有哪些隐藏行为。当然功能开关多了之后这些开关之间的组合关系也需要在基类内部理清楚。比如isUseTransparentStatusBar和isUseDefaultTitleBar同时为true时状态栏的颜色处理就会冲突这时基类要做一层优先级裁决明确告知子类后定义的规则。这种隐性约束建议写进基类的注释文档里否则团队里出现为什么我打开了透明状态栏标题栏不见了的疑惑是必然的。还有一点要牢记基类的代码应该保持可读自文档化。方法名要语义明确每个开关变量最好在基类内部有一个默认行为子类覆盖时必须知道覆盖后会有什么连锁反应。不要为了省几个字让方法命名变得难以理解比如isUseTitleBar和needTitleBar这种几乎没区别的命名过三个月你自己再看都猜不准区别。8. 实战收尾我调BaseActivity最后悔和最后怕的三件事文章讲到这儿我猜你手头已经在想那我项目里的BaseActivity该怎么重构了。最后分享三个我从实践中得来的教训尤其是我们这代开发者项目里几乎人人都手写过BaseActivity有些决定做了之后想返工代价是真不小。第一件事是不要在BaseActivity里做太多通用网络请求的假设。有一阵子我的基类里塞了一个统一的网络请求方法子类调用后自动弹loading、自动处理错误码。看起来很美但实际情况是每个页面的loading样式不一样、错误重试策略不一样、token过期处理更是不一样的最后这个方法的代码里全是if/else分支专门处理各种特例。后来我把它彻底移出了基类网络层由专门的Repository和ViewModel负责基类只保留页面级生命周期管理与公共界面策略。这个回退过程花了三个版本迭代非常痛苦。基类里放的应该是几乎不会变的逻辑而不是看起来大家都会用的逻辑。第二件事是必须给BaseActivity写注释和约束文档。你可能会觉得基类方法名都写得很清楚了还要啥文档但真实情况是团队里有个基础稍弱的同事一直在重写onCreate而不是调用基类提供的initView/initData导致基类的软键盘处理和状态栏适配在他写的页面上全都没生效他排查了一天还不知道为什么。后来我在基类的类注释里把继承本类的固定姿势、必须重写哪些方法、禁止重写哪些方法写了三行这种问题才绝迹。基类是团队的契约契约不写清楚总会有人无意识违约。第三件事也是我最坚持的一条经验BaseActivity不要做得太聪明。不要试图在基类里做过多隐式的逻辑。很多开发者有个误区认为基类越强大子类就越简单。但实际工程里基类越隐式debug的成本越高。比如基类自动给每个页面加了一个统计上报结果数据平台收到的页面访问数对不上你查起来要从Activity的onCreate一路往下翻翻到基类的某个角落才发现原来基类在帮每个页面偷偷上报事件。如果这个行为做成了显式的方法调用子类页面上有一行reportPageView(this)那排查起来就快多了新人读代码也更友好。所以让基类的每个行为都可以被看见——这是我的BaseActivity封装哲学里最重要的一条。写基类这件事往小了说是抽公共代码往大了说是在定义整个项目的编码契约。一套设计良好的BaseActivity能让团队里的每个人都少写几百行重复代码更重要的是让跨页面的行为统一到不会出错的轨道上。但也要记得基类是服务于业务的而不是业务服务于基类的。当某一天你发现自己的基类开始为了某个特例页面疯狂堆参数的时候就该停下来想一想是不是这个特殊页面根本不该走基类的框架而是应该单独另起炉灶了。这是我几年间在BaseActivity上栽过最多次跟头的地方希望你少走一些弯路。