Multiple Ways to Download Files to an Android SD Card
This article explains the evolution of Android storage permissions and presents three practical approaches—direct SD‑card permission, DocumentFile API, and Storage Access Framework—for reliably downloading files to external SD cards across various Android versions.
Android File Download to SD Card: Several Approaches
Introduction
Recent Android releases have tightened storage permissions, making it harder to download files directly to an external SD card. This guide shows how to implement a simple file download that works on different Android versions.
Permission Changes Over Android Versions
Key milestones include:
Android 5.0 – Storage Access Framework (SAF) introduced for fine‑grained file access.
Android 6.0 – Runtime permissions require requesting storage access at runtime.
Android 7.0 – File URI restrictions; apps must use ContentProvider to share files.
Android 10 – Scoped Storage limits apps to their own directories and specific public folders.
Android 11 – Further Scoped Storage tightening and the new MANAGE_EXTERNAL_STORAGE permission (restricted by Google Play).
Android 12 – More granular media‑type permissions.
Android 13/14 – Permission model stabilised; legacy flag android:requestLegacyExternalStorage="true" is obsolete.
Method 1: Direct SD‑Card Permission
The simplest solution is to request the write‑external‑storage permission and write to the public Download directory.
File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
String downloadPath = downloadDir.getAbsolutePath();
MyOkhttpUtil.okHttpDownloadFile("http://www.baidu.com/income-eligibility-criteria.pdf",
new CallBackUtil.CallBackFile(downloadPath, fileName) {
@Override
public void onFailure(Call call, Exception e) {
LoadingDialogManager.get().dismissLoading();
YYLogUtils.e("downLoadMessageFile--onFailure");
ToastUtils.get().showFailText(CommUtils.getContext(), "File download failed");
}
@Override
public void onProgress(float progress, long total) {
super.onProgress(progress, total);
}
@Override
public void onResponse(Call call, File response) {
LoadingDialogManager.get().dismissLoading();
YYLogUtils.w("downLoadMessageFile--Success--path=" + response.getAbsolutePath());
ToastUtils.get().showSuccessText(CommUtils.getContext(), "File download successful, save path: " + response.getAbsolutePath());
}
});This works on Android 7 devices (fails) and Android 13 devices (succeeds) when the appropriate permission is granted.
Permission Request Example
PermissionEngine.get().requestPermission(activity, new PermissionEngine.OnSuccessCallback() {
@Override
public void onSuccess() {
}
}, Manifest.permission_group.STORAGE);Or request the specific read/write permissions:
PermissionEngine.get().requestPermission(activity, new PermissionEngine.OnSuccessCallback() {
@Override
public void onSuccess() {
}
}, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE);Method 2: Using DocumentFile API
For Android 10+ where direct File access is restricted, wrap the target path with DocumentFile and write via an OutputStream obtained from the Uri.
private void downloadFile(DocumentFile selectedDir, String fileName) {
String url = "http://example.com/file.pdf";
File parent = new File(selectedDir.getPath());
DocumentFile documentFile = DocumentFile.fromFile(parent);
DocumentFile subDocumentFile = documentFile.createFile("application/pdf", fileName);
Uri uri = subDocumentFile.getUri();
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) { }
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
InputStream inputStream = response.body().byteStream();
OutputStream outputStream = null;
try {
outputStream = getContentResolver().openOutputStream(uri);
if (outputStream != null) {
copyInputStreamToOutputStream(inputStream, outputStream);
}
} finally {
if (inputStream != null) inputStream.close();
if (outputStream != null) outputStream.close();
}
}
}
});
}
private void copyInputStreamToOutputStream(InputStream inputStream, OutputStream outputStream) throws IOException {
byte[] buffer = new byte[4096];
int len;
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
}
}This approach works on Android 10 and later for custom directories.
Method 3: Using SAF (Storage Access Framework) for User‑Chosen Folder
Let the user pick a destination folder via Intent.ACTION_OPEN_DOCUMENT_TREE , then obtain the Uri and write the file similarly to Method 2.
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
activity.startActivityForResult(intent, 1027);Handle the result:
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1027 && resultCode == Activity.RESULT_OK) {
if (data != null) {
Uri treeUri = data.getData();
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
if (pickedDir != null) {
// Use pickedDir to download the file
Uri uri = pickedDir.getUri();
// proceed with stream copy as shown earlier
}
}
}
}Conclusion
The article reviews Android's tightening storage permissions and presents three viable solutions for writing files to external storage: direct permission request (simplest), DocumentFile API (for Android 10+), and SAF with user‑chosen folders (most flexible). Developers should avoid dangerous permissions like MANAGE_DOCUMENTS and prefer the approaches above to ensure compatibility across Android versions.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.