Android应用HashMap/ArrayList持久化异常:添加读写代码后无法登录
Hey there, let's break down why your app is failing to log in after adding persistence code, and how to fix it step by step.
Core Problem Analysis
From your code and description, the key issues likely stem from incorrect serialization setup, missing equality checks for HashMap keys, or flawed error handling that hides critical issues. Let's dive into each possible cause:
1. Missing Serializable Implementation
All classes stored in your collections (User, EventManager, Parent, Event) must implement the Serializable interface to be saved/loaded via ObjectOutputStream/ObjectInputStream. If any class (or its nested custom members) doesn't implement this, serialization will fail silently (caught by your catch blocks) and return null when loading, leaving your static collections empty or corrupted.
Fix:
Add Serializable to all relevant classes, e.g.:
public class User implements Serializable { // Your existing fields and methods }
2. Broken equals() and hashCode() for HashMap Keys
Your UsersDB uses User as a HashMap key. Without properly overriding equals() and hashCode() in the User class, HashMap will compare object memory addresses instead of meaningful identifiers (like email). This means the User object you create during login won't match the one stored in the HashMap, even if the email is the same.
Fix:
Override equals() and hashCode() using a unique identifier (e.g., user email):
public class User implements Serializable { private String mail; // Other fields @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return Objects.equals(mail, user.mail); } @Override public int hashCode() { return Objects.hash(mail); } }
3. Unsafe Type Casting & Poor Error Handling
Your readArrayListFromSD method name is misleading (you save both HashMaps and ArrayLists), and you cast the returned object directly without type checks. If the loaded object isn't the expected type, a ClassCastException is caught, returns null, and your static collections may end up empty or null.
Fix:
Rename the method to readObjectFromSD, add type checks, and initialize empty collections if loading fails:
private Object readObjectFromSD(Context mContext, String filename){ try { FileInputStream fis = mContext.openFileInput(filename + ".dat"); ObjectInputStream ois = new ObjectInputStream(fis); Object obj = ois.readObject(); fis.close(); return obj; } catch (Exception e) { Log.e("Persistence", "Failed to read file: " + filename, e); // Use Log.e for visibility return null; } } // Update loadDataToDB with type checks public void loadDataToDB(){ try { // Load EventManagers Object emObj = readObjectFromSD(context, EVENTMANAGERDB); if (emObj instanceof HashMap) { EventManagersDB.loadAllManagers((HashMap<EventManager, String>) emObj); } else { Log.w("Persistence", "EventManager data invalid, initializing empty"); EventManagersDB.initEmpty(); } // Load Users Object userObj = readObjectFromSD(context, USERSDB); if (userObj instanceof HashMap) { UsersDB.loadFromFile((HashMap<User, String>) userObj); } else { Log.w("Persistence", "User data invalid, initializing empty"); UsersDB.initEmpty(); } // Repeat similar checks for ParentsDB and EventsDB Object parentObj = readObjectFromSD(context, PARENTSDB); if (parentObj instanceof HashMap) { ParentsDB.loadFromFile((HashMap<Parent, String>) parentObj); } else { Log.w("Persistence", "Parent data invalid, initializing empty"); ParentsDB.initEmpty(); } Object eventObj = readObjectFromSD(context, EVENTSDB); if (eventObj instanceof ArrayList) { EventsDB.setAllEvents((ArrayList<Event>) eventObj); } else { Log.w("Persistence", "Event data invalid, initializing empty"); EventsDB.initEmpty(); } } catch (Exception e){ Log.e("Persistence", "Failed to load all data", e); // Fallback to empty collections EventManagersDB.initEmpty(); UsersDB.initEmpty(); ParentsDB.initEmpty(); EventsDB.initEmpty(); } }
Then add initEmpty() methods to your DB classes to ensure collections are never null:
public class UsersDB { private static HashMap<User, String> users; public static void loadFromFile(HashMap<User, String> loadedUsers) { users = loadedUsers != null ? loadedUsers : new HashMap<>(); } public static void initEmpty() { users = new HashMap<>(); } // Rest of your methods }
4. Missing Save Call After Registration
Your current code only saves data after login, but not after registration. This means newly registered users are only stored in memory, not persisted to disk. Even worse, if you restart the app, the loaded (empty) data will overwrite the in-memory users.
Fix:
Call saveDataToDB() immediately after a successful registration:
btnRegister.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { boolean isRegistered = presenter.register(/* your registration params */); if (isRegistered) { presenter.saveDataToDB(); Toast.makeText(Login_activity.this, "Registration successful!", Toast.LENGTH_SHORT).show(); } } });
Final Checks
- Check Logcat: Filter for the
Persistencetag to see any hidden errors (like serialization failures or type mismatches). - Test Flow: Register a user, verify the
.datfiles are created in your app's internal storage, then restart the app and attempt login.
内容的提问来源于stack exchange,提问作者Paris Constantinides

