Thread safe data structures in UserProfileImagesBatchLoader.

This commit is contained in:
Docile-Alligator 2025-07-28 17:07:15 -04:00
parent db138b6523
commit dcdb00760b

View File

@ -11,6 +11,7 @@ import org.json.JSONObject;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@ -38,6 +39,11 @@ public class UserProfileImagesBatchLoader {
private final Map<String, ViewPostDetailActivityViewModel.LoadIconListener> mAuthorFullNameToListenerMap;
private final List<Comment> mCallingComments;
private final Set<String> mLoadingAuthorFullNames;
private final Object mImageMapLock = new Object();
private final Object mCommentQueueLock = new Object();
private final Object mListenerMapLock = new Object();
private final Object mCallingCommentsLock = new Object();
private final Object mLoadingSetLock = new Object();
private boolean mIsLoadingBatch = false;
public UserProfileImagesBatchLoader(Executor executor, Handler handler, RedditDataRoomDatabase redditDataRoomDatabase,
@ -55,14 +61,24 @@ public class UserProfileImagesBatchLoader {
public void loadAuthorImages(List<Comment> comments, @NonNull ViewPostDetailActivityViewModel.LoadIconListener loadIconListener) {
String authorFullName = comments.get(0).getAuthorFullName();
if (mAuthorFullNameToImageMap.containsKey(authorFullName)) {
loadIconListener.loadIconSuccess(authorFullName, mAuthorFullNameToImageMap.get(authorFullName));
return;
synchronized (mImageMapLock) {
if (mAuthorFullNameToImageMap.containsKey(authorFullName)) {
loadIconListener.loadIconSuccess(authorFullName, mAuthorFullNameToImageMap.get(authorFullName));
return;
}
}
mAuthorFullNameToListenerMap.put(authorFullName, loadIconListener);
mCommentQueue.addAll(comments);
mCallingComments.add(comments.get(0));
synchronized (mListenerMapLock) {
mAuthorFullNameToListenerMap.put(authorFullName, loadIconListener);
}
synchronized (mCommentQueueLock) {
mCommentQueue.addAll(comments);
}
synchronized (mCallingCommentsLock) {
mCallingComments.add(comments.get(0));
}
if (!mIsLoadingBatch) {
loadNextBatch();
@ -70,62 +86,97 @@ public class UserProfileImagesBatchLoader {
}
private void loadNextBatch() {
if (mCommentQueue.isEmpty()) {
return;
synchronized (mCommentQueueLock) {
if (mCommentQueue.isEmpty()) {
return;
}
}
mIsLoadingBatch = true;
mExecutor.execute(() -> {
if (!mCallingComments.isEmpty()) {
int index = 0;
while (mCallingComments.size() > index) {
Comment c = mCallingComments.get(index);
ViewPostDetailActivityViewModel.LoadIconListener loadIconListener = mAuthorFullNameToListenerMap.get(c.getAuthorFullName());
synchronized (mCallingCommentsLock) {
Iterator<Comment> iterator = mCallingComments.iterator();
while (iterator.hasNext()) {
Comment c = iterator.next();
String authorFullName = c.getAuthorFullName();
ViewPostDetailActivityViewModel.LoadIconListener loadIconListener;
synchronized (mListenerMapLock) {
loadIconListener = mAuthorFullNameToListenerMap.get(authorFullName);
}
if (loadIconListener != null) {
if (mAuthorFullNameToImageMap.containsKey(c.getAuthorFullName())) {
loadIconListener.loadIconSuccess(c.getAuthorFullName(), mAuthorFullNameToImageMap.get(c.getAuthorFullName()));
mCallingComments.remove(index);
} else {
UserDao userDao = mRedditDataRoomDatabase.userDao();
UserData userData = userDao.getUserData(c.getAuthor());
if (userData != null) {
String iconImageUrl = userData.getIconUrl();
String authorFullName = c.getAuthorFullName();
mAuthorFullNameToImageMap.put(authorFullName, iconImageUrl);
mHandler.post(() -> loadIconListener.loadIconSuccess(authorFullName, iconImageUrl));
mAuthorFullNameToListenerMap.remove(authorFullName);
mCallingComments.remove(index);
} else {
index++;
synchronized (mImageMapLock) {
if (mAuthorFullNameToImageMap.containsKey(authorFullName)) {
String url = mAuthorFullNameToImageMap.get(authorFullName);
mHandler.post(() -> loadIconListener.loadIconSuccess(authorFullName, url));
iterator.remove();
continue;
}
}
UserData userData = mRedditDataRoomDatabase.userDao().getUserData(c.getAuthor());
if (userData != null) {
String iconImageUrl = userData.getIconUrl();
synchronized (mImageMapLock) {
mAuthorFullNameToImageMap.put(authorFullName, iconImageUrl);
}
mHandler.post(() -> loadIconListener.loadIconSuccess(authorFullName, iconImageUrl));
synchronized (mListenerMapLock) {
mAuthorFullNameToListenerMap.remove(authorFullName);
}
iterator.remove();
}
} else {
mCallingComments.remove(index);
iterator.remove();
}
}
}
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < BATCH_SIZE && !mCommentQueue.isEmpty(); i++) {
String authorFullName = mCommentQueue.poll().getAuthorFullName();
if (!mAuthorFullNameToImageMap.containsKey(authorFullName)) {
stringBuilder.append(authorFullName).append(",");
mLoadingAuthorFullNames.add(authorFullName);
} else if (i == 0) {
ViewPostDetailActivityViewModel.LoadIconListener loadIconListener = mAuthorFullNameToListenerMap.get(authorFullName);
if (loadIconListener != null) {
mHandler.post(() -> {
loadIconListener.loadIconSuccess(authorFullName, mAuthorFullNameToImageMap.get(authorFullName));
});
mAuthorFullNameToListenerMap.remove(authorFullName);
synchronized (mCommentQueueLock) {
for (int i = 0; i < BATCH_SIZE && !mCommentQueue.isEmpty(); i++) {
Comment comment = mCommentQueue.poll();
if (comment == null) {
continue;
}
for (int j = 0; j < BATCH_SIZE - 1 && !mCommentQueue.isEmpty(); j++) {
mCommentQueue.poll();
String authorFullName = comment.getAuthorFullName();
boolean alreadyCached;
synchronized (mImageMapLock) {
alreadyCached = mAuthorFullNameToImageMap.containsKey(authorFullName);
}
if (!alreadyCached) {
stringBuilder.append(authorFullName).append(",");
synchronized (mLoadingSetLock) {
mLoadingAuthorFullNames.add(authorFullName);
}
} else if (i == 0) {
ViewPostDetailActivityViewModel.LoadIconListener loadIconListener;
synchronized (mListenerMapLock) {
loadIconListener = mAuthorFullNameToListenerMap.get(authorFullName);
}
if (loadIconListener != null) {
String url;
synchronized (mImageMapLock) {
url = mAuthorFullNameToImageMap.get(authorFullName);
}
mHandler.post(() -> {
loadIconListener.loadIconSuccess(authorFullName, url);
});
synchronized (mListenerMapLock) {
mAuthorFullNameToListenerMap.remove(authorFullName);
}
}
for (int j = 0; j < BATCH_SIZE - 1 && !mCommentQueue.isEmpty(); j++) {
mCommentQueue.poll();
}
break;
}
break;
}
}
if (stringBuilder.length() > 0) {
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
try {
@ -151,11 +202,16 @@ public class UserProfileImagesBatchLoader {
private void parseUserProfileImages(String response) {
try {
JSONObject jsonResponse = new JSONObject(response);
for (String s : mLoadingAuthorFullNames) {
try {
mAuthorFullNameToImageMap.put(s, jsonResponse.getJSONObject(s).getString(JSONUtils.PROFILE_IMG_KEY).replaceAll("&amp;","&"));
} catch (JSONException e) {
e.printStackTrace();
synchronized (mLoadingSetLock) {
for (String s : mLoadingAuthorFullNames) {
try {
String imageUrl = jsonResponse.getJSONObject(s).getString(JSONUtils.PROFILE_IMG_KEY).replaceAll("&amp;","&");
synchronized (mImageMapLock) {
mAuthorFullNameToImageMap.put(s, imageUrl);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
} catch (JSONException e) {
@ -164,20 +220,36 @@ public class UserProfileImagesBatchLoader {
}
private void callListenerAndLoadNextBatch(boolean loadSuccessful) {
for (String s : mLoadingAuthorFullNames) {
ViewPostDetailActivityViewModel.LoadIconListener loadIconListener = mAuthorFullNameToListenerMap.get(s);
if (loadIconListener != null) {
mHandler.post(() -> {
loadIconListener.loadIconSuccess(s, mAuthorFullNameToImageMap.get(s));
});
mAuthorFullNameToListenerMap.remove(s);
}
if (!loadSuccessful && !mAuthorFullNameToImageMap.containsKey(s)) {
mAuthorFullNameToImageMap.put(s, null);
synchronized (mLoadingSetLock) {
for (String s : mLoadingAuthorFullNames) {
ViewPostDetailActivityViewModel.LoadIconListener loadIconListener;
synchronized (mListenerMapLock) {
loadIconListener = mAuthorFullNameToListenerMap.get(s);
}
if (loadIconListener != null) {
String imageUrl;
synchronized (mImageMapLock) {
imageUrl = mAuthorFullNameToImageMap.get(s);
}
mHandler.post(() -> {
loadIconListener.loadIconSuccess(s, imageUrl);
});
synchronized (mListenerMapLock) {
mAuthorFullNameToListenerMap.remove(s);
}
}
if (!loadSuccessful) {
synchronized (mImageMapLock) {
if (!mAuthorFullNameToImageMap.containsKey(s)) {
mAuthorFullNameToImageMap.put(s, null);
}
}
}
}
mLoadingAuthorFullNames.clear();
}
mLoadingAuthorFullNames.clear();
mIsLoadingBatch = false;
loadNextBatch();
}