Android MVP模式下Presenter对象空指针异常排查求助
问题根源
从错误日志的调用链就能定位到问题核心:
01-29 16:11:54.709 28540-28540/com.example.myfirstexamplemvp W/System.err: at com.example.myfirstexamplemvp.view.EmployeeListActivity.initView(EmployeeListActivity.java:35)
01-29 16:11:54.709 28540-28540/com.example.myfirstexamplemvp W/System.err: at com.example.myfirstexamplemvp.presenter.EmployeePresenter.initPresenter(EmployeePresenter.java:40)
01-29 16:11:54.709 28540-28540/com.example.myfirstexamplemvp W/System.err: at com.example.myfirstexamplemvp.presenter.EmployeePresenter.(EmployeePresenter.java:34)
01-29 16:11:54.709 28540-28540/com.example.myfirstexamplemvp W/System.err: at com.example.myfirstexamplemvp.view.EmployeeListActivity.onCreate(EmployeeListActivity.java:28)
当你在EmployeeListActivity的onCreate里执行presenter = new EmployeePresenter(this)时,Presenter的构造函数会触发initPresenter(),进而调用view.initView()。这时候Activity中的presenter变量还没完成赋值(毕竟new EmployeePresenter(this)这个构造过程还在跑),所以在initView()里调用presenter.getEmployees(listView)时,presenter还是null,直接抛出空指针异常。
而onItemClick里调用正常,是因为到那个时候Presenter的构造已经完成,Activity的presenter变量已经被正确赋值了。
解决方案
我们需要调整初始化顺序,确保presenter变量完全赋值后,再调用initView()方法。
步骤1:修改Presenter代码
把view.initView()从Presenter的初始化流程中移除,让Activity来控制调用时机:
public class EmployeePresenter implements EmployeeContract.AddPresenter, EmployeeContract.GetPresenter, EmployeeContract.EditPresenter { private EmployeeContract.View view; AddEmployeeModel model; SqliteController controller; public EmployeePresenter(EmployeeContract.View view) { this.view = view; initPresenter(); // 移除这里的view.initView()调用 } private void initPresenter() { model = new AddEmployeeModel(); controller = new SqliteController((Context) view); // 移除view.initView() } @Override public void getEmployees(final ListView listView) { List<AddEmployeeModel> list = controller.getEmployeeList(); ArrayList<String> empname = new ArrayList<String>(); if (list.size() > 0) { for (int i = 0; i < list.size(); i++) { empname.add(list.get(i).getFirstName() + " " + list.get(i).getLastName()); } listView.setAdapter(new ArrayAdapter<String>((Context) view, android.R.layout.simple_expandable_list_item_1, empname)); } } }
步骤2:调整Activity代码
在presenter赋值完成后,主动调用initView()方法,同时可以去掉onResume里重复创建Presenter的代码(除非你有特殊的复用需求):
public class EmployeeListActivity extends AppCompatActivity implements EmployeeContract.View { private static final String TAG = EmployeeListActivity.class.getSimpleName(); private ListView listView; private EmployeeContract.GetPresenter presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_employee_list); presenter = new EmployeePresenter(this); // Presenter初始化完成 initView(); // 现在调用initView,presenter已经不为null了 } @Override public void initView() { try { listView = findViewById(R.id.listview); presenter.getEmployees(listView); // 现在调用不会空指针 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { presenter.listViewClick(position); } }); } catch (Exception e) { e.printStackTrace(); String methodName = Thread.currentThread().getStackTrace()[2].getMethodName(); ErrorLog.saveLog(getBaseContext(), TAG, methodName, e.getMessage(), String.valueOf(e.getCause())); } } @Override protected void onResume() { super.onResume(); // 如果没有特殊需求,不需要重复创建Presenter,否则会覆盖之前的实例 // presenter = new EmployeePresenter(this); } }
额外建议
- MVP架构中,Presenter的构造函数尽量只做简单初始化(比如绑定View、初始化Model),不要触发View的方法调用,避免这种初始化顺序导致的空指针。
- 如果需要在页面可见时刷新数据,可以在
onResume里调用presenter.getEmployees(listView),而不是重新创建Presenter实例。
内容的提问来源于stack exchange,提问作者Ekta Bhawsar

