Android Activity来回切换时Intent传值为空触发NPE问题求解
故障根因
- 核心逻辑错误:底部导航页面切换实现有误。从ProfileActivity点击Home按钮时,代码通过
startActivity()新建了全新的ProjectActivity实例,而非回到登录成功后创建的原有ProjectActivity实例。新实例的Intent中没有携带authenticatedUser参数,调用getIntent().getStringExtra()取到的值必然为null,后续依赖该参数的数据库查询全部失效,再次跳转ProfileActivity时传递的参数也是null,直接触发空指针异常。 - 架构设计缺陷:登录态完全依赖Intent逐层传递,没有全局兜底存储机制,只要出现跳转漏传参数、Activity被系统回收重建的场景,就会丢失用户信息。
- 代码健壮性不足:数据库操作方法未做入参空值校验,Cursor查询完成后未及时关闭释放资源,查库返回null对象后直接调用其方法,没有做空判断兜底。
- 语法错误:DaoHelper中
getProjectCount方法的rawQuery语句行尾缺少分号,会直接导致编译失败。
可行修复方案
1. 修正导航跳转逻辑,避免重复创建Activity实例
给跨页面跳转的Intent添加启动Flag,复用任务栈中已存在的Activity实例,不要反复创建新实例导致参数丢失。
修正ProjectActivity跳转ProfileActivity的逻辑
case R.id.nav_profile: Intent goToProfileActivityIntent = new Intent(ProjectActivity.this, ProfileActivity.class); // 添加Flag:栈中存在ProfileActivity实例时直接复用,清空其上的其他页面 goToProfileActivityIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); goToProfileActivityIntent.putExtra("authenticatedUser", authenticatedUser); startActivity(goToProfileActivityIntent); overridePendingTransition(0,0); return true;
修正ProfileActivity返回ProjectActivity的逻辑
case R.id.nav_home: Intent goHomeIntent = new Intent(ProfileActivity.this, ProjectActivity.class); // 添加Flag:直接回到栈中已存在的ProjectActivity实例,不会重新走onCreate创建新页面 goHomeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(goHomeIntent); overridePendingTransition(0,0); finish(); // 销毁当前ProfileActivity,避免返回栈堆积 return true;
2. 增加全局登录态兜底存储,不单独依赖Intent传参
登录成功后将用户邮箱存入SharedPreferences,所有需要获取登录用户信息的页面优先读Intent参数,取不到时从SharedPreferences读兜底,从根源避免参数丢失。
登录成功后(LoginActivity中校验账号通过后执行)存储登录态
SharedPreferences sp = getSharedPreferences("app_config", MODE_PRIVATE); sp.edit().putString("authenticatedUser", 登录用户邮箱字符串).apply();
所有需要获取当前用户的页面,统一做参数兜底读取
以ProjectActivity和ProfileActivity中获取用户信息的逻辑为例:
SharedPreferences sp = getSharedPreferences("app_config", MODE_PRIVATE); // 优先取Intent传递的参数,取不到读sp的兜底值 if (getIntent().getStringExtra("authenticatedUser") != null) { authenticatedUser = getIntent().getStringExtra("authenticatedUser"); // 同步更新sp中存储的值,保证数据一致 sp.edit().putString("authenticatedUser", authenticatedUser).apply(); } else { authenticatedUser = sp.getString("authenticatedUser", null); } // 极端场景下还是没拿到用户信息,直接跳回登录页,不执行后续逻辑 if (authenticatedUser == null) { startActivity(new Intent(this, LoginActivity.class)); finish(); return; }
3. 补全数据库操作的健壮性逻辑
修正DaoHelper的查询方法,增加入参判空、Cursor资源释放
getUserDetails方法修正后代码:
public User getUserDetails(String theEmailAddress) { // 入参为空直接返回,不执行查询 if (TextUtils.isEmpty(theEmailAddress)) { return null; } SQLiteDatabase db = this.getReadableDatabase(); User user = null; Cursor cursor = null; try { cursor = db.query(USER_TABLE, new String[]{COLUMN_USER_ID, COLUMN_USER_FIRST_NAME, COLUMN_USER_LAST_NAME, COLUMN_USER_EMAIL_ADDRESS, COLUMN_USER_PASSWORD}, COLUMN_USER_EMAIL_ADDRESS + " = ?", new String[]{theEmailAddress}, null, null, null); // 用moveToFirst取第一条匹配数据,比moveToNext逻辑更准确 if(cursor.moveToFirst()) { long userId = cursor.getLong(0); String firstName = cursor.getString(1); String lastName = cursor.getString(2); String emailAddress = cursor.getString(3); String password = cursor.getString(4); user = new User(userId, firstName, lastName, emailAddress, password); } } catch (Exception e) { e.printStackTrace(); } finally { // 用完关闭Cursor,避免内存泄漏 if (cursor != null && !cursor.isClosed()) { cursor.close(); } } return user; }
getProjectCount方法修正语法错误+补全判空逻辑:
public int getProjectCount(long userId) { if (userId <= 0) { return 0; } SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor = null; int count = 0; try { cursor = db.rawQuery("SELECT * FROM " + PROJECT_TABLE + " WHERE " + COLUMN_USER_PROJECT_FK + " = ?", new String[]{String.valueOf(userId)}); count = cursor.getCount(); } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null && !cursor.isClosed()) { cursor.close(); } } return count; }
查库返回对象使用前做空判断
以ProfileActivity的loadUserDetails方法为例:
private void loadUserDetails() { User user = daoHelper.getUserDetails(authenticatedUser); if (user == null) { Toast.makeText(this, "用户信息获取失败,请重新登录", Toast.LENGTH_SHORT).show(); startActivity(new Intent(this, LoginActivity.class)); finish(); return; } profileFirstNameEditText.setText(user.getFirstName()); profileLastNameEditText.setText(user.getLastName()); profileEmailAddressEditText.setText(user.getEmailAddress()); }
4. 可选优化:配置Activity启动模式
在AndroidManifest.xml中给ProjectActivity、ProfileActivity添加配置android:launchMode="singleTask",保证整个应用任务栈中两个页面各只有一个实例,从系统层面避免反复创建实例导致的参数丢失问题。
内容的提问来源于stack exchange,提问作者ServletException
相关产品推荐
相关产品推荐

