【问题标题】:Why Android DownloadManager throws SecurityException after opening a deleted file?为什么 Android DownloadManager 在打开已删除的文件后会抛出 SecurityException?
【发布时间】:2020-07-29 18:33:33
【问题描述】:

为什么当我尝试通过 DownloadManager 打开已删除的下载文件时收到安全异常?

这是我的代码:

import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.app.AlertDialog;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.util.Patterns;
import android.view.View;
import android.webkit.MimeTypeMap;
import android.webkit.URLUtil;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    public static final int EXTERNAL_STORAGE_RC = 101;

    private ImageView mDownloadedImageIv;
    private Button mDownloadBtn;
    private Button mShowImageBtn;
    private EditText mUrlInputEt;

    private long mDownloadId = -1;
    private DownloadReceiver mDownloadReceiver;
    private DownloadManager mDownloadManager;

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

        initUi();

        requestPermissions();

        mDownloadBtn.setOnClickListener(this);
        mShowImageBtn.setOnClickListener(this);

        mDownloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);

        mDownloadReceiver = new DownloadReceiver();
        registerReceiver(mDownloadReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
    }

    @Override
    protected void onDestroy() {
        unregisterReceiver(mDownloadReceiver);

        super.onDestroy();
    }

    private void requestPermissions() {
        if (!isExternalStoragePermissionGranted()) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    EXTERNAL_STORAGE_RC);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == EXTERNAL_STORAGE_RC && grantResults.length == 1 &&
                grantResults[0] != PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                AlertDialog.Builder builder = new AlertDialog.Builder(this)
                        .setTitle(R.string.perm_denied_ad_title)
                        .setMessage(R.string.perm_denied_ad_message)
                        .setNegativeButton(R.string.perm_denied_negative_btn_text, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                requestPermissions();
                            }
                        });

                builder.show();
            }
        }
    }

    private boolean isExternalStoragePermissionGranted() {
        return ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
                PackageManager.PERMISSION_GRANTED;
    }

    private void initUi() {
        mDownloadedImageIv = findViewById(R.id.downloaded_image_iv);
        mDownloadBtn = findViewById(R.id.download_btn);
        mShowImageBtn = findViewById(R.id.show_image_btn);
        mUrlInputEt = findViewById(R.id.url_input_et);
    }

    private boolean isValidExtension(String url) {
        String fileExt = MimeTypeMap.getFileExtensionFromUrl(url);

        if (fileExt.matches("jpeg|png|bmp")) {
            return true;
        }

        return false;
    }

    private void startDownloadingFromUrl() {
        if (!isExternalStoragePermissionGranted()) {
            showToast(R.string.permission_is_not_granted_err);
            return;
        }

        String url = mUrlInputEt.getText().toString();

        if (!Patterns.WEB_URL.matcher(url).matches()) {
            showToast(R.string.invalid_url_err);
            return;
        }

        if (!isValidExtension(url)) {
            showToast(R.string.invalid_url_extension_err);
            return;
        }

        String filenameToSave = URLUtil.guessFileName(url, null, null);

        DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(url))
                .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filenameToSave);

        mDownloadId = ((DownloadManager)getSystemService(DOWNLOAD_SERVICE)).enqueue(downloadRequest);
    }


    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.download_btn:
                startDownloadingFromUrl();
                break;
            case R.id.show_image_btn:
                showDownloadedImage();
                break;
            default:
                break;
        }
    }

    private void showDownloadedImage() {
        if (mDownloadId == -1) {
            return;
        }

        ParcelFileDescriptor parcelFd = null;

        try {
            parcelFd = mDownloadManager.openDownloadedFile(mDownloadId);
            if (parcelFd == null) {
                throw new FileNotFoundException();
            }
            FileDescriptor fd = parcelFd.getFileDescriptor();

            Bitmap image = BitmapFactory.decodeFileDescriptor(fd);

            if (image != null) {
                mDownloadedImageIv.setImageBitmap(image);
            } else {
                showToast(R.string.downloaded_image_does_not_exists_err);
            }
        } catch (FileNotFoundException e) {
            showToast(R.string.downloaded_image_does_not_exists_err);
        } finally {
            if (parcelFd != null) {
                try {
                    parcelFd.close();
                } catch (IOException ignored) { }
            }
        }
    }

    private void showToast(@StringRes int stringId) {
        Toast.makeText(this, stringId, Toast.LENGTH_SHORT).show();
    }

    private class DownloadReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);

            if (mDownloadId == id) {
                mShowImageBtn.setEnabled(true);
            }
        }
    }
}

还有我的布局:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <EditText
        android:id="@+id/url_input_et"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:ems="10"
        android:hint="@string/url_et_hint"
        android:inputType="textUri"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/show_image_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:enabled="false"
        android:text="@string/show_image_btn_text"
        app:layout_constraintEnd_toStartOf="@+id/download_btn"
        app:layout_constraintTop_toTopOf="@+id/download_btn" />

    <Button
        android:id="@+id/download_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="@string/download_btn_text"
        app:layout_constraintEnd_toEndOf="@+id/cardview_image"
        app:layout_constraintTop_toBottomOf="@+id/url_input_et" />


    <androidx.cardview.widget.CardView
        android:id="@+id/cardview_image"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginTop="16dp"
        android:layout_marginBottom="16dp"
        app:cardElevation="4dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="@+id/url_input_et"
        app:layout_constraintStart_toStartOf="@+id/url_input_et"
        app:layout_constraintTop_toBottomOf="@+id/download_btn">

        <ImageView
            android:id="@+id/downloaded_image_iv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"
            app:srcCompat="@drawable/ic_baseline_image_24" />

    </androidx.cardview.widget.CardView>


</androidx.constraintlayout.widget.ConstraintLayout>

重现步骤:

  1. 输入以 .png、.jpeg 或 .bmp 结尾的网址
  2. 按DOWNLOAD按钮下载图片
  3. 通过 Android 文件资源管理器删除图像
  4. 按显示图像按钮

代码在这一行失败,我无法处理这个异常:

parcelFd = mDownloadManager.openDownloadedFile(mDownloadId);

完整的异常文本是:

java.lang.SecurityException: Permission Denial: 从 pid=5300 读取 com.android.providers.downloads.DownloadProvider uri content://downloads/all_downloads/5, uid=10080 需要 android.permission.ACCESS_ALL_DOWNLOADS 或 grantUriPermission( )

【问题讨论】:

  • Why I am receiving Security Exception when I try to open a deleted downloaded file via DownloadManager? 你希望收到什么?
  • I can't handle this exception:。为什么不?抓住它。

标签: java android


【解决方案1】:

当您保持文件不变时它是否工作(打开)?它看起来像 Downloader 中的一些限制/错误,但您可以通过替换此行来捕获此异常

} catch (FileNotFoundException e) {

更一般的

} catch (Exception e) {

SecurityExceptionFileNotFoundException 都扩展了 Exception,因此两者都会被捕获

您也可以将其拆分为两个单独的catches,当SecurityException 被捕获时,还要检查您是否仍然具有适当的权限 - 如果用户离开应用程序一段时间并手动删除此文件(您的测试用例)然后他还可能在应用程序系统设置中进行操作并撤销权限

【讨论】:

  • 感谢您的回答。如果我保持文件不变,它可以正常工作。我通过在尝试打开文件之前检查 Uri 的空值来处理它 - mDownloadManager.getUriForDownloadedFile(mDownloadId) == null。但奇怪的是,下载管理器自己不做这个检查
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-28
  • 1970-01-01
  • 2018-01-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多