首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

Android 從相簿選取圖片 Bitmap 空指標解決方案

     編輯:程式設計綜合問答

問題現象

使用 Intent.ACTION_GET_CONTENT 選取相簿圖片,在 onActivityResult 中嘗試從 data.getExtras().get("data") 取得 Bitmap,結果為 null 導致崩潰。

錯誤代碼

// 啟動選圖
Intent gallIntent = new Intent(Intent.ACTION_GET_CONTENT);
gallIntent.setType("image/*");
startActivityForResult(gallIntent, 10);

// 處理結果 - 錯誤寫法
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 10 && resultCode == RESULT_OK) {
        Bundle extras = data.getExtras(); // 這裡通常為 null 或無 "data" 鍵
        Bitmap b = (Bitmap) extras.get("data"); // 返回 null
        imgView.setImageBitmap(b); // NPE
        // ...
    }
}

根本原因

  • ACTION_GET_CONTENT / ACTION_PICK 返回的是內容 URI(data.getData()),而非 Bitmap
  • data.getExtras().get("data") 僅在 ACTION_IMAGE_CAPTURE(相機拍照)且未指定輸出 URI 時才會包含縮圖 Bitmap
  • 相簿選圖**永遠不會**在 Extras 中返回 Bitmap

正確解決方案

核心修正:使用 ContentResolver 從 URI 載入 Bitmap


@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    
    if (requestCode == 10 && resultCode == RESULT_OK && data != null) {
        Uri imageUri = data.getData(); // 取得內容 URI
        if (imageUri != null) {
            try {
                // 正確方式:透過 ContentResolver 解碼
                Bitmap bitmap = MediaStore.Images.Media.getBitmap(
                    this.getContentResolver(), imageUri);
                
                imgView.setImageBitmap(bitmap);
                
                // 後續處理:上傳、儲存、壓縮等
                // ...
                
            } catch (IOException e) {
                Log.e(TAG, "載入圖片失敗", e);
                Toast.makeText(this, "圖片載入失敗", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

進階:載入大圖避免 OOM(採用 inSampleSize)


private Bitmap loadBitmapSafely(Uri uri, int maxWidth, int maxHeight) {
    try {
        // 1. 先解析尺寸不載入像素
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        InputStream is = getContentResolver().openInputStream(uri);
        BitmapFactory.decodeStream(is, null, options);
        is.close();
        
        // 2. 計算縮放比例
        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
        
        // 3. 實際載入
        options.inJustDecodeBounds = false;
        is = getContentResolver().openInputStream(uri);
        Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
        is.close();
        return bitmap;
    } catch (IOException e) {
        Log.e(TAG, "載入圖片失敗", e);
        return null;
    }
}

private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    int inSampleSize = 1;
    if (options.outHeight > reqHeight || options.outWidth > reqWidth) {
        int halfH = options.outHeight / 2;
        int halfW = options.outWidth / 2;
        while ((halfH / inSampleSize) >= reqHeight && (halfW / inSampleSize) >= reqWidth) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}

現代方案:使用 Glide / Coil 圖片載入庫


// Glide (Java/Kotlin)
Glide.with(this)
    .load(uri)
    .override(800, 800) // 限制最大尺寸
    .centerCrop()
    .into(imgView);

// Coil (Kotlin 專用,協程支援)
imgView.load(uri) {
    crossfade(true)
    transformations(CenterCropTransformation())
}

權限與兼容性注意事項

Android 版本注意事項
Android 10 (API 29)+分區儲存,直接檔案路徑無效,必須用 ContentResolver / MediaStore API
Android 11 (API 30)+更嚴格的儲存權限,ACTION_GET_CONTENT 仍可用,ACTION_PICK 推薦用 MediaStore.Images.Media.EXTERNAL_CONTENT_URI
所有版本Manifest 宣告 READ_EXTERNAL_STORAGE (API 33+ 改 READ_MEDIA_IMAGES)

完整範例:相簿選圖 + 相機拍照雙模式


public class ImagePickerActivity extends AppCompatActivity {
    private static final int REQUEST_GALLERY = 10;
    private static final int REQUEST_CAMERA = 20;
    private ImageView imgView;
    private Uri cameraImageUri; // 相機拍照需預先建立輸出 URI
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_image_picker);
        imgView = findViewById(R.id.img_view);
    }
    
    // 從相簿選取
    public void pickFromGallery(View view) {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*");
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        startActivityForResult(intent, REQUEST_GALLERY);
    }
    
    // 相機拍照
    public void takePhoto(View view) {
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        // 建立輸出檔案 URI (使用 FileProvider 兼容 Android 7+)
        File photoFile = createImageFile();
        cameraImageUri = FileProvider.getUriForFile(this, 
            getPackageName() + ".fileprovider", photoFile);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, cameraImageUri);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        startActivityForResult(intent, REQUEST_CAMERA);
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode != RESULT_OK) return;
        
        try {
            Bitmap bitmap = null;
            if (requestCode == REQUEST_GALLERY && data != null) {
                // 相簿:從 URI 載入
                bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), data.getData());
            } else if (requestCode == REQUEST_CAMERA) {
                // 相機:從預先指定的 URI 載入
                bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), cameraImageUri);
            }
            
            if (bitmap != null) {
                imgView.setImageBitmap(bitmap);
            }
        } catch (IOException e) {
            Log.e(TAG, "圖片處理失敗", e);
        }
    }
    
    private File createImageFile() throws IOException {
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
        File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        return File.createTempFile("IMG_" + timeStamp + "_", ".jpg", storageDir);
    }
}

總結

相簿選圖返回的是 **URI**,必須用 MediaStore.Images.Media.getBitmap(ContentResolver, Uri) 或 ContentResolver.openInputStream(Uri) 載入。切勿嘗試從 data.getExtras() 取得 Bitmap。現代開發強烈建議使用 Glide/Coil 等圖片庫自動處理縮放、快取、生命週期等複雜問題。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved