Android开发:如何从相册选图到RecyclerView并实现持久化存储?
解决图片选择后重启消失+本地文件夹存储+RecyclerView展示的问题
嘿,我来帮你搞定这个问题!你当前的代码只是临时获取了系统相册的图片Uri,既没把图片存到应用可控的目录,也没持久化保存路径——这就是为什么重启应用后图片消失的核心原因。下面我带你一步步完成所有需求:
一、先搞定权限准备
首先要确保应用拥有必要的存储访问权限:
- 在
AndroidManifest.xml中添加权限(针对不同Android版本做适配):
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Android 10及以下需要写入权限,11+用Scoped Storage无需此权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
- 在代码中动态申请权限(Android 6.0+必须这么做):
private static final int REQUEST_PERMISSIONS = 100; private void checkPermissions() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_PERMISSIONS); } else { // 权限已获取,打开相册选择图片 openGallery(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_PERMISSIONS && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { openGallery(); } else { Toast.makeText(this, "需要存储权限才能选择图片哦", Toast.LENGTH_SHORT).show(); } }
二、修改相册选择逻辑,把图片复制到应用专属文件夹
我们需要把选中的图片复制到应用的私有目录(比如外部存储的Pictures子目录),这样图片完全受应用控制,不会因为系统变化或原图片被删除而丢失:
1. 打开相册的方法
private static final int PICK_IMAGE = 200; private void openGallery() { Intent galleryIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(galleryIntent, PICK_IMAGE); }
2. 处理选择结果并保存图片
private File appImageDir; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 创建应用专属的图片文件夹,卸载应用时会自动删除 appImageDir = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "MyAppAlbum"); if (!appImageDir.exists()) { appImageDir.mkdirs(); // 递归创建文件夹 } // 初始化RecyclerView等组件... } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK && requestCode == PICK_IMAGE && data != null) { Uri selectedUri = data.getData(); // 复制图片到应用专属文件夹 File savedImage = copyImageToLocalDir(selectedUri); if (savedImage != null) { // 保存图片路径到持久化存储 saveImagePath(savedImage.getAbsolutePath()); // 刷新RecyclerView展示新图片 refreshImageList(); // 临时预览图片(可选) imageView.setImageURI(Uri.fromFile(savedImage)); } } } // 复制图片到本地文件夹的工具方法 private File copyImageToLocalDir(Uri sourceUri) { try { InputStream inputStream = getContentResolver().openInputStream(sourceUri); if (inputStream == null) { Toast.makeText(this, "无法读取选中的图片", Toast.LENGTH_SHORT).show(); return null; } // 生成唯一文件名,避免重复 String fileName = "IMG_" + System.currentTimeMillis() + ".jpg"; File destFile = new File(appImageDir, fileName); OutputStream outputStream = new FileOutputStream(destFile); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } inputStream.close(); outputStream.close(); return destFile; } catch (IOException e) { e.printStackTrace(); Toast.makeText(this, "图片保存失败", Toast.LENGTH_SHORT).show(); return null; } }
三、持久化存储图片路径
重启后要能加载之前的图片,必须把图片路径保存起来。这里用SharedPreferences存储路径列表(如果图片数量多,建议用Room数据库):
private static final String PREFS_NAME = "MyAppImagePrefs"; private static final String KEY_IMAGE_PATHS = "savedImagePaths"; private void saveImagePath(String path) { SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); Set<String> pathsSet = prefs.getStringSet(KEY_IMAGE_PATHS, new HashSet<>()); pathsSet.add(path); prefs.edit().putStringSet(KEY_IMAGE_PATHS, pathsSet).apply(); } // 获取所有保存的图片路径 private List<String> getSavedImagePaths() { SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); Set<String> pathsSet = prefs.getStringSet(KEY_IMAGE_PATHS, new HashSet<>()); return new ArrayList<>(pathsSet); }
四、用RecyclerView展示所有图片
1. 创建RecyclerView的Adapter
建议用Glide/Picasso等图片加载库,它们会自动处理缓存和内存优化,比原生setImageURI更稳定:
public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ImageViewHolder> { private List<String> imagePaths; private Context context; public ImageAdapter(Context context, List<String> imagePaths) { this.context = context; this.imagePaths = imagePaths; } @NonNull @Override public ImageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.item_image, parent, false); return new ImageViewHolder(view); } @Override public void onBindViewHolder(@NonNull ImageViewHolder holder, int position) { String path = imagePaths.get(position); // 用Glide加载图片,记得添加依赖:implementation 'com.github.bumptech.glide:glide:4.15.1' Glide.with(context) .load(new File(path)) .centerCrop() .into(holder.imageView); } @Override public int getItemCount() { return imagePaths.size(); } public static class ImageViewHolder extends RecyclerView.ViewHolder { ImageView imageView; public ImageViewHolder(@NonNull View itemView) { super(itemView); imageView = itemView.findViewById(R.id.item_image_view); } } }
2. 在Activity中初始化RecyclerView并刷新数据
private RecyclerView recyclerView; private ImageAdapter imageAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // ... 之前的文件夹初始化代码 ... // 初始化RecyclerView recyclerView = findViewById(R.id.recycler_view); recyclerView.setLayoutManager(new GridLayoutManager(this, 2)); // 2列网格布局 // 加载已保存的图片路径,初始化Adapter List<String> imagePaths = getSavedImagePaths(); imageAdapter = new ImageAdapter(this, imagePaths); recyclerView.setAdapter(imageAdapter); } // 刷新RecyclerView的方法 private void refreshImageList() { List<String> updatedPaths = getSavedImagePaths(); imageAdapter.imagePaths = updatedPaths; imageAdapter.notifyDataSetChanged(); }
关键说明
- 为什么原来的代码重启后消失?因为你只临时获取了系统相册的Uri,这个Uri没有被持久化,而且系统相册的图片不属于应用可控范围,一旦原图片被删除或权限变化,就无法访问。复制到应用私有目录后,图片完全由应用管理,不会丢失。
- 应用私有目录的图片,在应用卸载时会被自动删除,符合Android的存储规范。
- 用Glide/Picasso加载图片能避免OOM(内存溢出)问题,还支持占位图、错误图等功能,体验更好。
内容的提问来源于stack exchange,提问作者Saad Iqbal
相关产品推荐
相关产品推荐

