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

Android平台下如何修改现有代码实现文件下载至可移除USB存储设备?

How to Download Files to Removable USB Storage on Android

I see you're looking to switch your download target from internal storage to a removable USB drive. Let's break down the changes you need to make, covering path handling, permissions, and full code adjustments.

1. Adjust the Download Path

Your current code uses a hardcoded internal storage path (/sdcard/fastDl). To target the USB drive, you have two options:

  • Hardcoded approach (for quick testing): Replace your existing path with your specific USB directory:
    val downloadPath = "/storage/08E7-324HI/Downloads"
    
  • Dynamic path detection (better for production): USB volume IDs vary by device, so use this helper function to automatically find mounted removable USB drives:
    private fun getRemovableUsbPath(): String? {
        val storageManager = getSystemService(STORAGE_SERVICE) as StorageManager
        val storageVolumes = storageManager.storageVolumes
        for (volume in storageVolumes) {
            // Check if the volume is removable and accessible
            if (volume.isRemovable && volume.directory != null) {
                return volume.directory?.absolutePath + "/Downloads"
            }
        }
        return null // No USB drive found
    }
    
    Use this function in downloadStart() with a fallback to internal storage:
    val downloadPath = getRemovableUsbPath() ?: run {
        txtPercent.text = "USB drive not found, falling back to internal storage"
        this.getExternalFilesDir("fastDownload")!!.absolutePath
    }
    

2. Update Permissions for Android 10+

Your current WRITE_EXTERNAL_STORAGE permission is deprecated on Android 10 (API 29) and above. Here's how to adapt:

First, update your AndroidManifest.xml to include the necessary permissions:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

Then modify your permission check and request logic:

Replace checkPermission():

private fun checkPermission(): Boolean {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        // For Android 11+, check if we have full storage access
        Environment.isExternalStorageManager()
    } else {
        // For older versions, use the legacy permission
        ContextCompat.checkSelfPermission(applicationContext, WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
    }
}

Replace requestPermission():

private fun requestPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        // Launch system settings to request full storage access
        val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
        intent.data = Uri.parse("package:$packageName")
        startActivityForResult(intent, PERMISSION_REQUEST_CODE)
    } else {
        // Request legacy write permission for older Android versions
        ActivityCompat.requestPermissions(
            this,
            arrayOf(WRITE_EXTERNAL_STORAGE),
            PERMISSION_REQUEST_CODE
        )
    }
}

Update onRequestPermissionsResult() and add onActivityResult() to handle the Android 11+ permission flow:

override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    when (requestCode) {
        PERMISSION_REQUEST_CODE -> {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                if (Environment.isExternalStorageManager()) {
                    Snackbar.make(window.decorView.rootView, "Storage access granted", Snackbar.LENGTH_LONG).show()
                    downloadStart()
                } else {
                    Snackbar.make(window.decorView.rootView, "Storage access denied", Snackbar.LENGTH_LONG).show()
                }
            } else {
                val writeAccepted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
                if (writeAccepted) {
                    Snackbar.make(window.decorView.rootView, "Storage access granted", Snackbar.LENGTH_LONG).show()
                    downloadStart()
                } else {
                    Snackbar.make(window.decorView.rootView, "Storage access denied", Snackbar.LENGTH_LONG).show()
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && shouldShowRequestPermissionRationale(WRITE_EXTERNAL_STORAGE)) {
                        showMessageOKCancel("You need storage access to download files") { _, _ ->
                            requestPermissions(arrayOf(WRITE_EXTERNAL_STORAGE), PERMISSION_REQUEST_CODE)
                        }
                    }
                }
            }
        }
    }
}

// Handle result of the Android 11+ storage permission intent
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == PERMISSION_REQUEST_CODE) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            if (Environment.isExternalStorageManager()) {
                downloadStart()
            } else {
                Snackbar.make(window.decorView.rootView, "Storage access denied", Snackbar.LENGTH_LONG).show()
            }
        }
    }
}

3. Full Modified Code

Here's the complete adjusted MainActivity.kt with all changes:

import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.content.Intent
import android.content.DialogInterface
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.Settings
import android.net.Uri
import android.os.storage.StorageManager
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.aghamiri.fastdl.DownloadManager
import com.aghamiri.fastdl.OnDownloadProgressListener
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    private val PERMISSION_REQUEST_CODE = 200

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btn.setOnClickListener {
            if (!checkPermission()) {
                requestPermission()
            } else {
                downloadStart()
            }
        }
    }

    private fun downloadStart() {
        val downloadLink = "http://server.info/news.txt"
        val downloadPath = getRemovableUsbPath() ?: run {
            txtPercent.text = "USB drive not found, falling back to internal storage"
            this.getExternalFilesDir("fastDownload")!!.absolutePath
        }
        
        val dlManager = DownloadManager(downloadLink, downloadPath, object : OnDownloadProgressListener {
            override fun downloadStart() {
                logi("downloadStart")
            }

            override fun downloadedSuccess() {
                logi("downloadedSuccess")
                txtPercent.text = "Download successful to: $downloadPath"
            }

            override fun downloadCancel() {
                loge("downloadCancel")
            }

            override fun downloadFail(error: String?) {
                loge("downloadFail:$error")
                txtPercent.text = "Download failed: $error"
            }

            override fun percent(percent: Int) {
                runOnUiThread {
                    progressBar.progress = percent
                    txtPercent.text = "$percent%"
                    logd("downloadProgress:$percent")
                }
            }
        })
        dlManager.execute()
    }

    private fun getRemovableUsbPath(): String? {
        val storageManager = getSystemService(STORAGE_SERVICE) as StorageManager
        val storageVolumes = storageManager.storageVolumes
        for (volume in storageVolumes) {
            if (volume.isRemovable && volume.directory != null) {
                return volume.directory?.absolutePath + "/Downloads"
            }
        }
        return null
    }

    private fun checkPermission(): Boolean {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            Environment.isExternalStorageManager()
        } else {
            ContextCompat.checkSelfPermission(applicationContext, WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
        }
    }

    private fun requestPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
            intent.data = Uri.parse("package:$packageName")
            startActivityForResult(intent, PERMISSION_REQUEST_CODE)
        } else {
            ActivityCompat.requestPermissions(
                this,
                arrayOf(WRITE_EXTERNAL_STORAGE),
                PERMISSION_REQUEST_CODE
            )
        }
    }

    private fun showMessageOKCancel(message: String, okListener: DialogInterface.OnClickListener) {
        AlertDialog.Builder(this@MainActivity)
            .setMessage(message)
            .setPositiveButton("Agree", okListener)
            .setNegativeButton("Cancel", null)
            .create()
            .show()
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            PERMISSION_REQUEST_CODE -> {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                    if (Environment.isExternalStorageManager()) {
                        Snackbar.make(window.decorView.rootView, "Storage access granted", Snackbar.LENGTH_LONG).show()
                        downloadStart()
                    } else {
                        Snackbar.make(window.decorView.rootView, "Storage access denied", Snackbar.LENGTH_LONG).show()
                    }
                } else {
                    val writeAccepted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
                    if (writeAccepted) {
                        Snackbar.make(window.decorView.rootView, "Storage access granted", Snackbar.LENGTH_LONG).show()
                        downloadStart()
                    } else {
                        Snackbar.make(window.decorView.rootView, "Storage access denied", Snackbar.LENGTH_LONG).show()
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && shouldShowRequestPermissionRationale(WRITE_EXTERNAL_STORAGE)) {
                            showMessageOKCancel("You need storage access to download files") { _, _ ->
                                requestPermissions(arrayOf(WRITE_EXTERNAL_STORAGE), PERMISSION_REQUEST_CODE)
                            }
                        }
                    }
                }
            }
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == PERMISSION_REQUEST_CODE) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                if (Environment.isExternalStorageManager()) {
                    downloadStart()
                } else {
                    Snackbar.make(window.decorView.rootView, "Storage access denied", Snackbar.LENGTH_LONG).show()
                }
            }
        }
    }

    // Helper log functions (replace with Android's Log class if you don't have these)
    private fun logi(message: String) {
        android.util.Log.i("DownloadLog", message)
    }

    private fun loge(message: String) {
        android.util.Log.e("DownloadLog", message)
    }

    private fun logd(message: String) {
        android.util.Log.d("DownloadLog", message)
    }
}

4. Key Notes

  • USB Mount Status: Ensure the USB drive is properly mounted before testing. The helper function will return null if no removable USB is detected.
  • Google Play Policy: The MANAGE_EXTERNAL_STORAGE permission is restricted for most apps. For Play Store publication, consider using the Storage Access Framework (SAF) to let users manually select the USB folder, which is more policy-compliant.
  • Path Variability: USB paths differ across devices (e.g., /storage/XXXX-XXXX where XXXX is a volume ID), so dynamic detection is preferred over hardcoding for production apps.

内容的提问来源于stack exchange,提问作者Arge

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 06:54:53