Skip to content

Commit 5a6ce43

Browse files
graycreateclaude
andauthored
fix: prevent crash when clicking null images in gallery (#110)
- Add validation in GalleryActivity.open() to check for null/invalid images before opening gallery - Add null safety checks in GalleryAdapter to handle null image objects - Add null checks in getCurrentImage() and menu actions to prevent NPE - Add ProGuard rules to keep ImagesInfo classes from being obfuscated - Show "图片不存在" toast when attempting to open gallery with invalid images This fixes the crash that occurred only in release builds when clicking on images in reply lists that had null or invalid image data. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2ec05e0 commit 5a6ce43

File tree

3 files changed

+56
-6
lines changed

3 files changed

+56
-6
lines changed

app/proguard-rules.pro

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@
202202

203203
# Keep data models used for JSON parsing (Gson/Fruit)
204204
-keep class me.ghui.v2er.network.bean.** { *; }
205+
# Keep ImagesInfo and related classes for Fruit HTML parsing
206+
-keep class me.ghui.v2er.module.imgviewer.ImagesInfo { *; }
207+
-keep class me.ghui.v2er.module.imgviewer.ImagesInfo$* { *; }
208+
-keep class me.ghui.v2er.module.imgviewer.ImagesInfo$*$* { *; }
205209

206210
# Keep classes with native methods
207211
-keepclasseswithmembernames class * {

app/src/main/java/me/ghui/v2er/module/gallery/GalleryActivity.java

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,26 @@ public class GalleryActivity extends BaseActivity implements SwipeToDismissTouch
4848
private ImagesInfo mData;
4949

5050
public static void open(ImagesInfo imgsData, Context context) {
51+
// Validate images data before opening gallery
52+
if (imgsData == null || imgsData.getImages() == null || imgsData.getImages().isEmpty()) {
53+
Voast.show("图片不存在");
54+
return;
55+
}
56+
57+
// Check if at least one valid image exists
58+
boolean hasValidImage = false;
59+
for (ImagesInfo.Images.Image img : imgsData.getImages()) {
60+
if (img != null && img.getUrl() != null) {
61+
hasValidImage = true;
62+
break;
63+
}
64+
}
65+
66+
if (!hasValidImage) {
67+
Voast.show("图片不存在");
68+
return;
69+
}
70+
5171
Navigator.from(context)
5272
.putExtra(EXTRA_IMG_DATA, imgsData)
5373
.to(GalleryActivity.class)
@@ -134,12 +154,22 @@ public void onPageScrollStateChanged(int state) {
134154

135155
private String getCurrentImage() {
136156
int index = mViewPager.getCurrentItem();
157+
if (mData == null || mData.getImages() == null ||
158+
index < 0 || index >= mData.getImages().size() ||
159+
mData.getImages().get(index) == null) {
160+
return null;
161+
}
137162
return mData.getImages().get(index).getUrl();
138163
}
139164

140165
private void saveImage() {
166+
String currentImage = getCurrentImage();
167+
if (currentImage == null) {
168+
Voast.show("图片不存在");
169+
return;
170+
}
141171
// Already have permission, do the thing
142-
glideRequest(getCurrentImage()).into(new SimpleTarget<File>() {
172+
glideRequest(currentImage).into(new SimpleTarget<File>() {
143173
@Override
144174
public void onResourceReady(File file, Transition<? super File> transition) {
145175
if (!FileUtils.isExternalStorageWritable()) {
@@ -148,7 +178,7 @@ public void onResourceReady(File file, Transition<? super File> transition) {
148178
}
149179
Observable.just(file)
150180
.compose(RxUtils.io_main())
151-
.map(f -> FileUtils.saveImg(f, Utils.getTypeFromImgUrl(getCurrentImage())))
181+
.map(f -> FileUtils.saveImg(f, Utils.getTypeFromImgUrl(currentImage)))
152182
.subscribe(new BaseConsumer<String>() {
153183
@Override
154184
public void onConsume(String path) {
@@ -166,19 +196,25 @@ public void onConsume(String path) {
166196

167197
@Override
168198
public boolean onMenuItemClick(MenuItem item) {
199+
String currentImage = getCurrentImage();
200+
if (currentImage == null) {
201+
Voast.show("图片不存在");
202+
return false;
203+
}
204+
169205
switch (item.getItemId()) {
170206
case R.id.action_open_in_browser:
171-
Utils.openInBrowser(getCurrentImage(), this);
207+
Utils.openInBrowser(currentImage, this);
172208
break;
173209
case R.id.action_save:
174210
//check write external permission then do save stuff
175211
saveImage();
176212
break;
177213
case R.id.action_share:
178-
glideRequest(getCurrentImage()).into(new SimpleTarget<File>() {
214+
glideRequest(currentImage).into(new SimpleTarget<File>() {
179215
@Override
180216
public void onResourceReady(File file, Transition<? super File> transition) {
181-
Utils.shareImg(file, Utils.getTypeFromImgUrl(getCurrentImage()), GalleryActivity.this);
217+
Utils.shareImg(file, Utils.getTypeFromImgUrl(currentImage), GalleryActivity.this);
182218
}
183219
});
184220
break;

app/src/main/java/me/ghui/v2er/module/gallery/GalleryAdapter.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ public void destroyItem(ViewGroup container, int position, Object object) {
4949
}
5050

5151
private ImagesInfo.Images.Image getItem(int postion) {
52+
if (mImagesInfo == null || mImagesInfo.getImages() == null ||
53+
postion < 0 || postion >= mImagesInfo.getImages().size()) {
54+
return null;
55+
}
5256
return mImagesInfo.getImages().get(postion);
5357
}
5458

@@ -59,7 +63,13 @@ public Object instantiateItem(ViewGroup container, int position) {
5963
root.setOnImageClicked(mOnImageClickedListener);
6064
container.addView(root);
6165
// TODO: 2019/1/4 support svg
62-
String url = getItem(position).getUrl();
66+
ImagesInfo.Images.Image image = getItem(position);
67+
if (image == null || image.getUrl() == null) {
68+
// This shouldn't happen now that we validate before opening gallery
69+
// But keep it as a safety net
70+
return root;
71+
}
72+
String url = image.getUrl();
6373
if (!Utils.isSVG(url)) {
6474
GlideApp.with(mContext)
6575
.load(url)

0 commit comments

Comments
 (0)