Android's Photo Picker的使用

108 阅读4分钟

谷歌对权限的要求日益严格,READ_MEDIA_IMAGES/WRITE_EXTERNAL_STORAGE/READ_MEDIA_VIDEOS这三个权限,必须是APP的核心功能代码才可以申请,如果只是某一个功能使用,谷歌要求使用Android's Photo Picker,否则APP上传GooglePlay时会被拒绝:

image.png

解决方法 :

从Android13版本开始,Android系统引入了系统级的照片选择器Photo Picker,无需申请权限即可使用;在Android13以下的版本,可以直接启用系统相册来选择图片和视频。

因为Android系统是在Android13版本之后才引入Photo Picker的,因此我们需要解决版本兼容性问题 :

  1. 在Android13及以上版本,使用Photo Picker,使用Intent: MediaStore.ACTION_PICK_IMAGES来启动照片选择器。
  2. 在Android13以下的版本,使用自定义图片选择器,或者通过Intent.ACTION_GET_CONTENT,并设置类型"image/或video/" 来启动系统相册。
  3. 在Android13及以上的版本,使用MediaStore.EXTRA_PICK_IMAGES_MAX来设置选择图片或视频的最大数量; Android13以下的版本,使用extra: Intent.EXTRA_ALLOW_MULTIPLE 来允许多选 编写如下工具类 :
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.util.Log;

import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;

import java.util.ArrayList;
import java.util.List;

/**
 * Android Photo Picker 工具类
 * 兼容 Android 13+ 的官方 Photo Picker 和低版本的传统文件选择
 */
public class PhotoPickerUtil {
    
    private static final String TAG = "PhotoPickerUtil";
    
    // 请求代码
    public static final int REQUEST_CODE_PICK_IMAGES = 1001;
    public static final int REQUEST_CODE_PICK_VIDEOS = 1002;
    public static final int REQUEST_CODE_PICK_MEDIA = 1003;
    
    // 结果键值
    public static final String EXTRA_SELECTED_URIS = "selected_uris";
    public static final String EXTRA_ERROR_MESSAGE = "error_message";
    
    private ActivityResultLauncher<Intent> mediaPickerLauncher;
    private PhotoPickerCallback callback;
    private final Context context;
    private int maxSelection = 1;
    private boolean allowMultiple = false;
    private String[] mimeTypes = {"image/*", "video/*"};
    
    public interface PhotoPickerCallback {
        void onMediaSelected(List<Uri> uris);
        void onError(String errorMessage);
    }
    
    public PhotoPickerUtil(Context context) {
        this.context = context;
        initActivityResultLauncher();
    }
    
    /**
     * 初始化 Activity Result Launcher
     */
    private void initActivityResultLauncher() {
        if (context instanceof FragmentActivity) {
            FragmentActivity activity = (FragmentActivity) context;
            mediaPickerLauncher = activity.registerForActivityResult(
                new ActivityResultContracts.StartActivityForResult(),
                new ActivityResultCallback<ActivityResult>() {
                    @Override
                    public void onActivityResult(ActivityResult result) {
                        handleActivityResult(result.getResultCode(), result.getData());
                    }
                });
        }
    }
    
    /**
     * 设置回调
     */
    public void setCallback(PhotoPickerCallback callback) {
        this.callback = callback;
    }
    
    /**
     * 设置最大选择数量
     */
    public void setMaxSelection(int maxSelection) {
        this.maxSelection = maxSelection;
        this.allowMultiple = maxSelection > 1;
    }
    
    /**
     * 设置 MIME 类型
     * @param mimeTypes 如 {"image/*"}, {"video/*"}, {"image/*", "video/*"}
     */
    public void setMimeTypes(String[] mimeTypes) {
        this.mimeTypes = mimeTypes;
    }
    
    /**
     * 打开媒体选择器
     */
    public void openMediaPicker() {
        if (isPhotoPickerAvailable()) {
            // Android 13+ 使用官方 Photo Picker
            openSystemPhotoPicker();
        } else {
            // 低版本使用传统 Intent
            openLegacyMediaPicker();
        }
    }
    
    /**
     * 检查是否支持官方 Photo Picker
     */
    private boolean isPhotoPickerAvailable() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
    }
    
    /**
     * 打开系统 Photo Picker (Android 13+)
     */
    private void openSystemPhotoPicker() {
        try {
            Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
            
            // 设置多选
            if (allowMultiple && maxSelection > 1) {
                intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, maxSelection);
            }
            
            // 设置 MIME 类型过滤
            if (mimeTypes != null && mimeTypes.length > 0) {
                if (mimeTypes.length == 1) {
                    intent.setType(mimeTypes[0]);
                } else {
                    // 多类型选择
                    intent.setType("*/*");
                    intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
                }
            }
            
            if (mediaPickerLauncher != null) {
                mediaPickerLauncher.launch(intent);
            } else if (context instanceof Activity) {
                ((Activity) context).startActivityForResult(intent, REQUEST_CODE_PICK_MEDIA);
            }
            
        } catch (Exception e) {
            Log.e(TAG, "打开系统 Photo Picker 失败", e);
            // 降级到传统方式
            openLegacyMediaPicker();
        }
    }
    
    /**
     * 打开传统媒体选择器 (Android 12 及以下)
     */
    private void openLegacyMediaPicker() {
        try {
            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
            intent.addCategory(Intent.CATEGORY_OPENABLE);
            
            // 设置 MIME 类型
            if (mimeTypes != null && mimeTypes.length == 1) {
                intent.setType(mimeTypes[0]);
            } else {
                intent.setType("*/*");
                if (mimeTypes != null && mimeTypes.length > 1) {
                    intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
                }
            }
            
            // 设置多选
            if (allowMultiple) {
                intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
            }
            
            // 创建选择器 Intent
            Intent chooser = Intent.createChooser(intent, "选择图片或视频");
            
            if (mediaPickerLauncher != null) {
                mediaPickerLauncher.launch(chooser);
            } else if (context instanceof Activity) {
                ((Activity) context).startActivityForResult(chooser, REQUEST_CODE_PICK_MEDIA);
            }
            
        } catch (Exception e) {
            Log.e(TAG, "打开传统媒体选择器失败", e);
            if (callback != null) {
                callback.onError("无法打开媒体选择器: " + e.getMessage());
            }
        }
    }
    
    /**
     * 处理选择结果
     */
    private void handleActivityResult(int resultCode, Intent data) {
        if (resultCode != Activity.RESULT_OK || data == null) {
            if (callback != null) {
                callback.onError("用户取消选择或选择失败");
            }
            return;
        }
        
        try {
            List<Uri> selectedUris = new ArrayList<>();
            
            // 处理多选结果
            if (data.getClipData() != null) {
                int count = Math.min(data.getClipData().getItemCount(), maxSelection);
                for (int i = 0; i < count; i++) {
                    Uri uri = data.getClipData().getItemAt(i).getUri();
                    if (uri != null) {
                        selectedUris.add(uri);
                    }
                }
            } 
            // 处理单选结果
            else if (data.getData() != null) {
                selectedUris.add(data.getData());
            }
            
            if (selectedUris.isEmpty()) {
                if (callback != null) {
                    callback.onError("未选择任何文件");
                }
            } else {
                if (callback != null) {
                    callback.onMediaSelected(selectedUris);
                }
            }
            
        } catch (Exception e) {
            Log.e(TAG, "处理选择结果失败", e);
            if (callback != null) {
                callback.onError("处理文件时出错: " + e.getMessage());
            }
        }
    }
    
    /**
     * 在 Activity 或 Fragment 的 onActivityResult 中调用
     */
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_CODE_PICK_MEDIA) {
            handleActivityResult(resultCode, data);
        }
    }
    
    /**
     * 释放资源
     */
    public void release() {
        callback = null;
    }
}

使用方法:

public class MainActivity extends AppCompatActivity {
    private PhotoPickerUtil photoPickerUtil;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 初始化 Photo Picker
        photoPickerUtil = new PhotoPickerUtil(this);
        photoPickerUtil.setCallback(new PhotoPickerUtil.PhotoPickerCallback() {
            @Override
            public void onMediaSelected(List<Uri> uris) {
                // 处理选择的媒体文件
                for (Uri uri : uris) {
                    Log.d("PhotoPicker", "Selected URI: " + uri.toString());
                    // 在这里显示图片或视频
                }
            }
            
            @Override
            public void onError(String errorMessage) {
                Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
            }
        });
        
        // 设置最多选择 5 个文件
        photoPickerUtil.setMaxSelection(5);
        
        // 只选择图片
        // photoPickerUtil.setMimeTypes(new String[]{"image/*"});
        
        // 只选择视频
        // photoPickerUtil.setMimeTypes(new String[]{"video/*"});
        
        // 选择图片和视频(默认)
        photoPickerUtil.setMimeTypes(new String[]{"image/*", "video/*"});
        
        // 打开选择器
        findViewById(R.id.btn_pick_media).setOnClickListener(v -> {
            photoPickerUtil.openMediaPicker();
        });
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        // 如果使用传统 onActivityResult 方式
        photoPickerUtil.onActivityResult(requestCode, resultCode, data);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (photoPickerUtil != null) {
            photoPickerUtil.release();
        }
    }
}