You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动