You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android 10及以上版本无法保存应用生成图片至图库求助

Hey there! Let's get your avatar saving working on newer Android versions. The root problem here is scoped storage—introduced in Android 10 (API 29)—which restricts direct access to shared external storage directories like the ones you're using with Environment.getExternalStorageDirectory(). Your current code works on Android 7 because it doesn't enforce these restrictions, but we need to adjust it to comply with modern Android storage rules.

What's Wrong with Your Current Code?

  • On Android 10+, direct writes to Environment.getExternalStorageDirectory() are blocked by scoped storage, even with requestLegacyExternalStorage="true" (this is a temporary workaround that doesn't work reliably on Android 11+).
  • You've added unnecessary permissions (like READ_INTERNAL_STORAGE, MANAGE_INTERNAL_STORAGE) that don't help and can confuse permission handling.
  • MediaScannerConnection.scanFile might not trigger correctly on newer versions when using direct file paths, since the system can't track the file properly.

Step-by-Step Fix

1. Clean Up Your AndroidManifest Permissions

Keep only the necessary permissions, and limit WRITE_EXTERNAL_STORAGE to Android 9 and below (since it's irrelevant for scoped storage):

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<!-- Only needed for Android 9 (API 28) and lower -->
<uses-permission
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="28" />

<!-- Remove all internal storage and MANAGE_EXTERNAL_STORAGE permissions unless you have a specific, approved use case -->

<application
    android:allowBackup="true"
    android:hardwareAccelerated="false"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:largeHeap="true"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme">
    <!-- Keep your activities and FileProvider as is -->
</application>

2. Replace Your Save Logic with MediaStore

Instead of writing directly to a file, use MediaStore to insert the image into the gallery. This is the official, recommended way to save media to shared storage on Android 10+:

public void SaveClick(View view) {
    // Handle permission check first (added in next step)
    ConstraintLayout savingLayout = findViewById(R.id.layoutavatar);
    Bitmap bitmap = getBitmapFromView(savingLayout);
    
    if (saveImageToGallery(this, bitmap)) {
        showSuccessDialog();
    } else {
        Toast.makeText(this, "Failed to save avatar", Toast.LENGTH_SHORT).show();
    }
}

private boolean saveImageToGallery(Context context, Bitmap bitmap) {
    String displayName = "AMONGUS-AVATAR_" + System.currentTimeMillis() + ".jpg";
    String mimeType = "image/jpeg";
    ContentValues values = new ContentValues();
    
    values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName);
    values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
    
    // For Android 10+, set relative path to Pictures directory
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/AMONGUS-AVATAR");
        values.put(MediaStore.MediaColumns.IS_PENDING, 1);
    } else {
        // For Android 9 and below, use external storage path (requires WRITE_EXTERNAL_STORAGE permission)
        File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
        File avatarDir = new File(picturesDir, "AMONGUS-AVATAR");
        if (!avatarDir.exists() && !avatarDir.mkdirs()) {
            return false;
        }
        File imageFile = new File(avatarDir, displayName);
        values.put(MediaStore.MediaColumns.DATA, imageFile.getAbsolutePath());
    }

    ContentResolver resolver = context.getContentResolver();
    Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
    
    if (uri == null) {
        return false;
    }

    try (OutputStream outputStream = resolver.openOutputStream(uri)) {
        return bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
    } catch (IOException e) {
        e.printStackTrace();
        // Delete the invalid entry if save fails
        resolver.delete(uri, null, null);
        return false;
    } finally {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            values.clear();
            values.put(MediaStore.MediaColumns.IS_PENDING, 0);
            resolver.update(uri, values, null, null);
        }
    }
}

// Keep your getBitmapFromView method as it is
private Bitmap getBitmapFromView(View view) {
    Bitmap returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(returnedBitmap);
    Drawable bgDrawable = view.getBackground();
    if (bgDrawable != null) {
        bgDrawable.draw(canvas);
    } else {
        canvas.drawColor(Color.TRANSPARENT);
    }
    view.draw(canvas);
    return returnedBitmap;
}

private void showSuccessDialog() {
    AlertDialog.Builder builder = new AlertDialog.Builder(fut.this);
    builder.setCancelable(true);
    builder.setTitle("AVATAR HAS BEEN SAVED");
    builder.setMessage("YOUR AVATAR HAS BEEN SAVED SUCCESSFULLY TO YOUR IMAGES DIRECTORY");
    builder.setPositiveButton("OK", (dialogInterface, i) -> alertTextView.setVisibility(View.VISIBLE));
    builder.show();
}

3. Add Dynamic Permission Handling (For Android 6-9)

On Android 6.0 (API 23) to Android 9 (API 28), you need to request WRITE_EXTERNAL_STORAGE permission at runtime before saving the image. Update your SaveClick method and add the permission result handler:

public void SaveClick(View view) {
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
            return;
        }
    }
    
    // Proceed with save logic
    ConstraintLayout savingLayout = findViewById(R.id.layoutavatar);
    Bitmap bitmap = getBitmapFromView(savingLayout);
    
    if (saveImageToGallery(this, bitmap)) {
        showSuccessDialog();
    } else {
        Toast.makeText(this, "Failed to save avatar", Toast.LENGTH_SHORT).show();
    }
}

// Handle permission request result
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == 100) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Permission granted, retry save
            SaveClick(findViewById(R.id.layoutavatar));
        } else {
            Toast.makeText(this, "Permission required to save avatar", Toast.LENGTH_SHORT).show();
        }
    }
}

Why This Works

  • MediaStore is the official API for sharing media with the system gallery, which automatically handles scoped storage rules on Android 10+.
  • We're using Environment.DIRECTORY_PICTURES to save the image in the standard Pictures directory, which is visible to the gallery app.
  • The IS_PENDING flag on Android 10+ ensures the image isn't visible to the gallery until we finish writing it.

内容的提问来源于stack exchange,提问作者Ayoub E Cher

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:52:17