Android MVP架构:如何在Presenter类中统一处理所有异常
嘿,我太懂你这种每次写if(mView != null)的烦躁了——重复的空检查代码真的能把人磨疯!针对你在Android MVP架构里遇到的这个问题,有几个实用的方案能帮你彻底摆脱这些烦人的判断,同时安全处理View已销毁的情况:
方案1:用View代理类封装空检查逻辑
这是最稳妥的方案,核心思路是把所有View的空检查逻辑统一封装到一个代理类里,Presenter只和代理类交互,不用自己做判断。
首先定义一个通用的代理基类,封装View的绑定/解绑和安全调用逻辑:
public class BaseViewProxy<T extends BaseView> { private T mRealView; // 绑定View public void attachView(T view) { this.mRealView = view; } // 解绑View(在onDestroy/onDestroyView时调用) public void detachView() { this.mRealView = null; } // 封装安全调用的核心方法 protected void safeExecute(ViewAction<T> action) { if (mRealView != null) { action.run(mRealView); } } // 定义接口传递View操作 public interface ViewAction<T> { void run(T view); } }
然后针对你的具体View接口,实现对应的代理类,把所有UI更新方法都用safeExecute包裹:
// 假设你的View接口是MainView public class MainViewProxy extends BaseViewProxy<MainView> { public void updateUISomehow() { safeExecute(MainView::updateUISomehow); } public void showLoading() { safeExecute(MainView::showLoading); } // 其他UI更新方法同理,全部封装在这里 }
最后在Presenter里使用这个代理类:
public class MainPresenter { private MainViewProxy mViewProxy; public void attachView(MainView view) { mViewProxy = new MainViewProxy(); mViewProxy.attachView(view); } public void detachView() { mViewProxy.detachView(); } public void loadData() { // 模拟耗时网络请求 new Thread(() -> { // 数据加载完成后,直接调用代理方法,不用写任何空检查 mViewProxy.updateUISomehow(); }).start(); } }
这样一来,所有空检查的逻辑都被封装在代理类里,Presenter的代码会清爽很多,而且完全避免了空指针风险。
方案2:自定义异常+全局异常处理器
如果你不想写额外的代理类,也可以通过自定义异常配合全局处理器来“忽略”View为空时的调用。这个方案的核心是:在Presenter调用View方法时,如果View为空就抛出一个特定的自定义异常,然后让全局异常处理器捕获它并跳过崩溃。
首先定义一个专属的异常类:
public class NullViewException extends RuntimeException { public NullViewException() { super("Presenter尝试调用已销毁的View方法"); } }
然后在BasePresenter里封装View调用的逻辑,为空时抛出异常:
public abstract class BasePresenter<T extends BaseView> { protected T mView; public void attachView(T view) { mView = view; } public void detachView() { mView = null; } // 统一处理View调用,确保在主线程执行UI操作 protected void callView(ViewAction<T> action) { if (Looper.myLooper() != Looper.getMainLooper()) { new Handler(Looper.getMainLooper()).post(() -> executeAction(action)); } else { executeAction(action); } } private void executeAction(ViewAction<T> action) { if (mView == null) { throw new NullViewException(); } action.run(mView); } public interface ViewAction<T> { void run(T view); } }
接下来实现全局异常处理器,捕获自定义异常并忽略:
public class GlobalExceptionHandler implements Thread.UncaughtExceptionHandler { private final Thread.UncaughtExceptionHandler defaultHandler; public GlobalExceptionHandler() { defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); } @Override public void uncaughtException(Thread t, Throwable e) { if (e instanceof NullViewException) { // 只做日志记录,不触发崩溃 Log.d("MVP", "忽略空View调用异常:" + e.getMessage()); } else { // 其他异常交给系统默认处理器处理 defaultHandler.uncaughtException(t, e); } } }
最后在Application里初始化全局处理器:
public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); Thread.setDefaultUncaughtExceptionHandler(new GlobalExceptionHandler()); } }
现在你的Presenter可以这样调用View方法,不用写空检查:
public class MainPresenter extends BasePresenter<MainView> { public void loadData() { new Thread(() -> { // 直接调用,不用判断mView是否为空 callView(MainView::updateUISomehow); }).start(); } }
⚠️ 注意:这个方案要谨慎使用,一定要确保只捕获NullViewException,避免误处理其他空指针异常导致隐藏bug。
可选方案:RxJava生命周期绑定(如果项目用了RxJava)
如果你的项目已经在用RxJava处理异步任务(比如网络请求),可以配合RxLifecycle或者AutoDispose这类库,把Observable的生命周期和View绑定。当View销毁时,自动取消订阅,这样就不会走到onNext回调里调用View方法了,从源头避免了空指针。
比如用RxLifecycle的话,Presenter里的代码大概是这样:
public class MainPresenter extends BasePresenter<MainView> { public void loadData() { ApiService.getInstance().fetchData() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) // 绑定View的生命周期,View销毁时自动取消订阅 .compose(mView.bindToLifecycle()) .subscribe(data -> { // 这里不用判断mView是否为空,因为订阅已经被取消了 mView.updateUISomehow(data); }, throwable -> { // 错误处理 }); } }
这个方案非常优雅,但前提是你的项目已经引入了RxJava相关依赖。
总的来说,方案1的代理类方式是最推荐的——它把空检查逻辑统一封装,让Presenter专注业务逻辑,没有额外风险。方案2适合快速解决问题,但要注意异常范围的控制。如果用RxJava的话,可选方案会更省心。
内容的提问来源于stack exchange,提问作者VSB




