如何在API 29或Android Q中使用DownloadManager下载文件?

33

作为一名新手Android开发者,我正在尝试使用DownloadManager创建一个简单的应用程序。

以下是代码:

public class MainActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback{

Button btn;

private long referenceID;
private DownloadManager downloadManager;
private static final int PERMISSION_REQUEST_CODE = 1;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    btn = findViewById(R.id.btn);

    btn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {



            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){


                if (checkPermission())
                {

                    /*** If Storage Permission Is Given, Check External storage is available for read and write***/

                    Uri image_uri = Uri.parse("https://unifiedclothes.com/Unifiedclothes/App_Gallery/thumb_8_121432471036-1432471036-SC-505.jpg");

                    referenceID = DownloadImage(image_uri);




                } else {

                    requestPermission();
                }

            }

            else{
                Toast.makeText(MainActivity.this,"Permission Is Granted..",Toast.LENGTH_SHORT).show();

            }
        }
    });

    registerReceiver(receiver,new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}

private BroadcastReceiver receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {

        String action = intent.getAction();

        if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)){



            DownloadManager.Query ImageDownloadQuery = new DownloadManager.Query();
            //set the query filter to our previously Enqueued download
            ImageDownloadQuery.setFilterById(referenceID);

            //Query the download manager about downloads that have been requested.
            Cursor cursor = downloadManager.query(ImageDownloadQuery);

            if(cursor.moveToFirst()){

                Toast.makeText(MainActivity.this,DownloadStatus(cursor),Toast.LENGTH_SHORT).show();
            }



        }

    }
};

private String DownloadStatus(Cursor cursor){

    //column for download  status
    int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
    int status = cursor.getInt(columnIndex);
    //column for reason code if the download failed or paused
    int columnReason = cursor.getColumnIndex(DownloadManager.COLUMN_REASON);
    int reason = cursor.getInt(columnReason);



    String statusText = "";
    String reasonText = "";

    switch(status){
        case DownloadManager.STATUS_FAILED:
            statusText = "STATUS_FAILED";
            switch(reason){
                case DownloadManager.ERROR_CANNOT_RESUME:
                    reasonText = "ERROR_CANNOT_RESUME";
                    break;
                case DownloadManager.ERROR_DEVICE_NOT_FOUND:
                    reasonText = "ERROR_DEVICE_NOT_FOUND";
                    break;
                case DownloadManager.ERROR_FILE_ALREADY_EXISTS:
                    reasonText = "ERROR_FILE_ALREADY_EXISTS";
                    break;
                case DownloadManager.ERROR_FILE_ERROR:
                    reasonText = "ERROR_FILE_ERROR";
                    break;
                case DownloadManager.ERROR_HTTP_DATA_ERROR:
                    reasonText = "ERROR_HTTP_DATA_ERROR";
                    break;
                case DownloadManager.ERROR_INSUFFICIENT_SPACE:
                    reasonText = "ERROR_INSUFFICIENT_SPACE";
                    break;
                case DownloadManager.ERROR_TOO_MANY_REDIRECTS:
                    reasonText = "ERROR_TOO_MANY_REDIRECTS";
                    break;
                case DownloadManager.ERROR_UNHANDLED_HTTP_CODE:
                    reasonText = "ERROR_UNHANDLED_HTTP_CODE";
                    break;
                case DownloadManager.ERROR_UNKNOWN:
                    reasonText = "ERROR_UNKNOWN";
                    break;
            }
            break;
        case DownloadManager.STATUS_PAUSED:
            statusText = "STATUS_PAUSED";
            switch(reason){
                case DownloadManager.PAUSED_QUEUED_FOR_WIFI:
                    reasonText = "PAUSED_QUEUED_FOR_WIFI";
                    break;
                case DownloadManager.PAUSED_UNKNOWN:
                    reasonText = "PAUSED_UNKNOWN";
                    break;
                case DownloadManager.PAUSED_WAITING_FOR_NETWORK:
                    reasonText = "PAUSED_WAITING_FOR_NETWORK";
                    break;
                case DownloadManager.PAUSED_WAITING_TO_RETRY:
                    reasonText = "PAUSED_WAITING_TO_RETRY";
                    break;
            }
            break;
        case DownloadManager.STATUS_PENDING:
            statusText = "STATUS_PENDING";
            break;
        case DownloadManager.STATUS_SUCCESSFUL:
            statusText = "Image Saved Successfully";
            //reasonText = "Filename:\n" + filename;
            Toast.makeText(MainActivity.this, "Download Status:" + "\n" + statusText + "\n" + reasonText, Toast.LENGTH_SHORT).show();
            break;
    }

    return statusText + reasonText;


}


private long DownloadImage(Uri uri){

    long downloadReference;

    downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);

    DownloadManager.Request request = new DownloadManager.Request(uri);
    //Setting title of request
    request.setTitle("Image Download");

    //Setting description of request
    request.setDescription("Image download using DownloadManager.");


    request.setDestinationInExternalPublicDir(getExternalFilesDir(Environment.DIRECTORY_PICTURES) + "/NewFile","sample2.jpg");
    //request.allowScanningByMediaScanner();
    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
    downloadReference = downloadManager.enqueue(request);


    return  downloadReference;
}


private boolean checkPermission() {
    int result = ContextCompat.checkSelfPermission(MainActivity.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
    if (result == PackageManager.PERMISSION_GRANTED) {
        return true;
    } else {
        return false;
    }
}


private void requestPermission() {

    ActivityCompat.requestPermissions(MainActivity.this, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE);
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    if (requestCode == PERMISSION_REQUEST_CODE) {

        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

            Uri image_uri = Uri.parse("https://www.dccomics.com/sites/default/files/Char_GetToKnow_Batman80_5ca54cb83a27a6.53173051.png");

            referenceID = DownloadImage(image_uri);


        }

        else {

            Toast.makeText(MainActivity.this, "Permission Denied... \n You Should Allow External Storage Permission To Download Images.", Toast.LENGTH_LONG).show();
        }
    }
}

我在任何API 29以下的设备上运行时都能正常工作(我的测试设备是Nexus 5X,API 28模拟器)。但是,当我在Nexus 5X上运行API 29时,应用程序会崩溃。以下是日志:

2019-09-24 20:51:46.354 11322-11344/? E/DatabaseUtils: Writing exception to parcel
java.lang.IllegalStateException: Not one of standard directories: /storage/emulated/0/Android/data/com.blz.prisoner.downloadmanager/files/Pictures/NewFile
    at com.android.providers.downloads.DownloadProvider.call(DownloadProvider.java:651)
    at android.content.ContentProvider.call(ContentProvider.java:2152)
    at android.content.ContentProvider$Transport.call(ContentProvider.java:477)
    at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:277)
    at android.os.Binder.execTransactInternal(Binder.java:1021)
    at android.os.Binder.execTransact(Binder.java:994)

2019-09-24 20:51:46.355 15023-15023/com.blz.prisoner.downloadmanager D/AndroidRuntime: 关闭VM

--------- beginning of crash
2019-09-24 20:51:46.360 15023-15023/com.blz.prisoner.downloadmanager E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.blz.prisoner.downloadmanager, PID: 15023
    java.lang.IllegalStateException: Not one of standard directories: /storage/emulated/0/Android/data/com.blz.prisoner.downloadmanager/files/Pictures/NewFile
        at android.os.Parcel.createException(Parcel.java:2079)
        at android.os.Parcel.readException(Parcel.java:2039)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:188)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:140)
        at android.content.ContentProviderProxy.call(ContentProviderNative.java:658)
        at android.content.ContentProviderClient.call(ContentProviderClient.java:558)
        at android.content.ContentProviderClient.call(ContentProviderClient.java:546)
        at android.app.DownloadManager$Request.setDestinationInExternalPublicDir(DownloadManager.java:567)
        at com.blz.prisoner.downloadmanager.MainActivity.DownloadImage(MainActivity.java:206)
        at com.blz.prisoner.downloadmanager.MainActivity.access$200(MainActivity.java:29)
        at com.blz.prisoner.downloadmanager.MainActivity$1.onClick(MainActivity.java:60)
        at android.view.View.performClick(View.java:7140)
        at android.view.View.performClickInternal(View.java:7117)
        at android.view.View.access$3500(View.java:801)
        at android.view.View$PerformClick.run(View.java:27351)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

我认为问题出在DownloadImage(Uri uri)函数中的代码行"request.setDestinationInExternalPublicDir(getExternalFilesDir(Environment.DIRECTORY_PICTURES) + "/NewFile","sample2.jpg");",如何解决?

另一个问题是,当我在API版本低于29的设备上运行应用程序时,它可以正常运行,但是当我完成下载后点击通知时,它无法在图库/保存的文件夹中打开图片。


3
Android Q采用了作用域储存,需要以不同的方式访问,这是我所知道的,而且有点烂(s**tty)。参考链接:https://commonsware.com/blog/2019/03/29/death-external-storage-where-google.html - Pemba Tamang
哦我的天,我和你一样。我觉得DownloadManager在这里是无用的,因为我不能使用外部公共存储(已弃用),显然也不能使用内部存储(DownloadManager没有权限(会抛出安全异常))。如果有人知道解决方案,请务必发布。 - New Guy
6个回答

19

使用下载管理器在Android Q及以下版本中下载文件:

如果您的目标是Android Q(29),则无需退出受限存储。(android:requestLegacyExternalStorage="true"不需要)

清单文件

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

代码:

 private fun onDownload() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        downloadFile("xxx.jpg", "File Desc", "url")
    }else{
        val result = requestRuntimePermission(
            this,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
        )
        result.success {
             downloadFile("xxx.jpg", "File Desc", "url")
        }
    }

}

private fun downloadFile(fileName : String, desc :String, url : String){
    // fileName -> fileName with extension
    val request = DownloadManager.Request(Uri.parse(url))
        .setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
        .setTitle(fileName)
        .setDescription(desc)
        .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
        .setAllowedOverMetered(true)
        .setAllowedOverRoaming(false)
        .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,fileName)
    val downloadManager= getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
    val downloadID = downloadManager.enqueue(request)
}

将文件存储在外部应用程序专用目录中

.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_MUSIC,fileName)

将文件存储在外部公共目录中

.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,fileName)

4
你从哪里获取"android.permission.ACCESS_DOWNLOAD_MANAGER"权限?我找不到任何相关信息。 - Evgeny
@Evgeny 抱歉没有提供代码,但它很简单,所以我没有添加。 此外,我认为不再需要它,因为在我的应用程序中,DownloadManager可以在没有权限的情况下工作。 祝你有美好的一天。 - Z3R0

14

我只是使用以下方法解决了问题:

setDestinationInExternalFilesDir(context, relativePath, filename);

改为:

setDestinationInExternalPublicDir(relativePath, filename);

我的相对路径是:

Environment.getExternalStorageDirectory().getPath() + "MyExternalStorageAppPath"

我在我的清单文件中也有:

android:requestLegacyExternalStorage="true"

为了使用传统的存储管理 (Shared Storage) 而不是从 Android 10 开始使用的新存储管理 (Scoped Storage),请注意,使用 "setDestinationInExternalFilesDir" 将文件下载到专门为您的应用程序分配的外部内存中,因此: "external/Android/data/your_app_name/path_you_used_on_function"。如果您想将它下载到另一个地方,您需要在使用输入和输出流下载后将其移动。要在 Android 10 或更高版本中使用其他应用程序打开文件,您必须使用 FileProvider。

如果有人需要,这是将文件从一个位置移动到另一个位置的代码(移动而不是复制。因此原文件将被删除。如果您想复制文件而不删除源文件,请删除 "source.delete();" ):

public static boolean moveFile(File source, String destPath){
        if(source.exists()){
            File dest = new File(destPath);
            checkMakeDirs(dest.getParent());
            try (FileInputStream fis = new FileInputStream(source);
                 FileOutputStream fos = new FileOutputStream(dest)){
                if(!dest.exists()){
                    dest.createNewFile();
                }
                writeToOutputStream(fis, fos);
                source.delete();
                return true;
            } catch (IOException ioE){
                Log.e(TAG, ioE.getMessage());
            }
        }
        return false;
    }

private static void writeToOutputStream(InputStream is, OutputStream os) throws IOException {
        byte[] buffer = new byte[1024];
        int length;
        if (is != null) {
            while ((length = is.read(buffer)) > 0x0) {
                os.write(buffer, 0x0, length);
            }
        }
        os.flush();
    }

用法("源"是您需要移动的文件,“路径”是目标位置):

使用方法(“source”是您需要移动的文件,“path”是目标位置):

if(FilesUtils.moveFile(source, path)) {
     // Success Moving File, do what you need with it
}

DownloadManager完成下载时的广播接收器:

private static class DownloadFileReceiver extends BroadcastReceiver {

        private DownloadManager mDownloadManager;
        private String mPath;

        private DownloadFileReceiver(DownloadManager dManager, String path){
            mDownloadManager = dManager;
            mPath = path;
        }

        /** Override BroadcastReceiver Methods **/
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
                Bundle extras = intent.getExtras();
                DownloadManager.Query q = new DownloadManager.Query();
                q.setFilterById(extras.getLong(DownloadManager.EXTRA_DOWNLOAD_ID));
                Cursor c = mDownloadManager.query(q);
                if (c.moveToFirst()) {
                    int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
                    if (status == DownloadManager.STATUS_SUCCESSFUL) {
                        String fullPath = null; File source = null;
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                            fullPath = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
                            source = new File(Uri.parse(fullPath).getPath());
                        } else {
                            fullPath = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
                            source = new File(fullPath);
                        }
                    }
                }
                c.close();
            }
            Objects.requireNonNull(context).unregisterReceiver(this);
        }
    }

将其注册到DownloadManager实例中:

context.registerReceiver(new DownloadFileReceiver(downloadManager, path),
                new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));

checkMakeDirs(这个函数用于检查目录是否存在或者能否成功创建目录),而makeDirs(只是直接创建目录而不做检查)的代码:

public static boolean checkMakeDirs(String dirPath){
        try {
            File dir = new File(dirPath);
            return dir.exists() || dir.mkdirs();
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
        }
        return false;
    }

    public static void makeDirs(String dirPath){
        try {
            File dir = new File(dirPath);
            if(!dir.exists()){
                dir.mkdirs();
            }
        } catch (Exception e){
            Log.e(TAG, e.getMessage());
        }
    }

重要提示:
自2021年05月07日起,如果您的应用程序在Google Play商店中,则理论上您必须将targetSdk设置为30,且只能使用Scoped Storage来访问您的文件(因此仅使用应用程序特定目录访问您的文件)。 这意味着您需要使用:

context.getFilesDir();


如何获取源路径?我不明白? - Attaullah
源路径是您在下载管理器上设置的路径,例如: request.setDestinationInExternalFilesDir(context, null, file.getPath());您还可以在用于获取DownloadManager结果的BroadcastReceiver中获取它。我在上面的答案中添加了代码,就在接收器内部。 - Z3R0
1
@Attaullah 看一下上面的 DownloadFileReceiver 代码, 希望这能帮到你 祝你有个愉快的一天 - Z3R0
checkMakeDirs是什么? - Mustansir
1
@Mustansi 如果目录不存在,它会创建这些目录。我将添加代码(: - Z3R0
我可以在 context.getFilesDir(); 中下载文件,但无法在设备文件浏览器中看到或使用代码访问它,但我可以在我的手机下载应用程序中看到它。 - Raunit Verma

3
private void DownloadImage(String url){
    String filename=url.substring(url.lastIndexOf("/")+1);
    File file=new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES ).getPath()+"/UnifiedClothes/" + filename);
    Log.d("Environment", "Environment extraData=" + file.getPath());

    DownloadManager.Request request=new DownloadManager.Request(Uri.parse(url))
            .setTitle(filename)
            .setDescription("Downloading")
            .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
            .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
            .setDestinationUri(Uri.fromFile(file))
            .setAllowedOverMetered(true)
            .setAllowedOverRoaming(true);
    downloadManager= (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
    referenceID = downloadManager.enqueue(request);

}

在安卓9版本中无法运行,请更新答案。 - Anand Savjani

2

根据我们面临的问题和文档,我得出结论:从API 29级开始,他们不允许我们直接创建任何非标准目录(用户定义),但我找到了一种解决该问题的有效方法。

这是我的清单文件。

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

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:requestLegacyExternalStorage="true"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

这是我的build.gradle文件,只是为了确保您知道我正在使用目标SDK 29。
apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
    applicationId "com.example.saveimage"
    minSdkVersion 16
    targetSdkVersion 29
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
  }
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

以下是MainActivity.kt文件的内容:
package com.example.saveimage

import android.app.DownloadManager
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File


class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    btnSave.setOnClickListener {
        downloadFile("https://homepages.cae.wisc.edu/~ece533/images/airplane.png") //Your URL
    }

}


fun downloadFile(uRl: String) {
    val direct = File(getExternalFilesDir(null), "/KrishanImages")

    if (!direct.exists()) {
        direct.mkdirs()
    }

    val mgr = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager

    val downloadUri = Uri.parse(uRl)
    val request = DownloadManager.Request(
        downloadUri
    )

    request.setAllowedNetworkTypes(
        DownloadManager.Request.NETWORK_WIFI or     
     DownloadManager.Request.NETWORK_MOBILE
    )
        .setAllowedOverRoaming(false).setTitle("Krishan Demo") //Download Manager Title
        .setDescription("Downloading...") //Download Manager description
        .setDestinationInExternalPublicDir(
            Environment.DIRECTORY_PICTURES,
            "/KrishanImages/sample2.jpg"  //Your User define(Non Standard Directory name)/File Name
        )

    mgr.enqueue(request)

    }
}

2
在将您的应用程序更新为针对Android 11时,系统会忽略requestLegacyExternalStorage标志。 - Matthias

2

我认为问题就在你提到的那一行代码里。你设置的字符串拼接导致文件目录变成了一个非标准的路径。

request.setDestinationInExternalPublicDir(getExternalFilesDir(Environment.DIRECTORY_PICTURES) + "/NewFile","sample2.jpg")

错误显示如下。请注意,该行告诉您它正在尝试保存到哪个目录。
/storage/emulated/0/Android/data/com.blz.prisoner.downloadmanager/files/Pictures/NewFile

因此,当您这样做时,它会尝试保存为:

/storage/emulated/0/Android/data/com.blz.prisoner.downloadmanager/files/Pictures/NewFile/sample2.jpg

解决这个问题的方法是将文件保存到标准目录中。只需要删除连接操作即可。

request.setDestinationInExternalPublicDir(getExternalFilesDir(Environment.DIRECTORY_PICTURES),"sample2.jpg")

然后它将尝试保存到标准目录。
/storage/emulated/0/Android/data/com.blz.prisoner.downloadmanager/files/Pictures/sample2.jpg

但是我想在Pictures文件夹中创建一个名为“NewFile”的目录,并且当我在API 29以下的设备上运行时,该目录已被创建。 - Bishal Imtiaz
这根本行不通。它可能会下载没有问题,但您将无法拉取并查看文件是否存在于您的应用程序中(在api 29(Q)上)。 - New Guy
有这些限制是很好的。让开发人员集中精力于创造性的功能。 - Kimi Chiu

-4

确保在您的清单文件中包含此标签:

    android:requestLegacyExternalStorage="true"

在Android 10、11中需要使用此标签


为什么有4个负面评价,但没有评论来解释为什么给出负面评价? - MindRoasterMir
1
@MindRoasterMir,因为这只是一种解决方法,无法在你的应用程序目标为Android 11及以上版本时工作。在将应用程序更新为针对Android 11(API级别30)的目标后,在Android 11设备上运行您的应用程序时,系统将忽略requestLegacyExternalStorage属性,因此您的应用程序必须准备支持范围存储并迁移那些设备上的用户的应用数据。 - Mike

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接