使用FileProvider在Android N上打开下载的文件

时间:2016-10-12 10:37:02

标签: android android-fileprovider android-7.0-nougat

由于FileProvider的更改,我必须修复我们的Android N应用程序。我基本上已经阅读了关于这个主题的所有内容,但我找不到任何解决方案。

这是我们之前的代码,它从我们的应用开始下载,将它们存储在Download文件夹中并调用ACTION_VIEW意图作为DownloadManager告诉他&#39的安慰; s完成下载:

BroadcastReceiver onComplete = new BroadcastReceiver() {
    public void onReceive(Context ctxt, Intent intent) {
        Log.d(TAG, "Download commplete");

        // Check for our download
        long referenceId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
        if (mDownloadReference == referenceId) {
            DownloadManager.Query query = new DownloadManager.Query();
            query.setFilterById(mDownloadReference);
            Cursor c = mDownloadManager.query(query);
            if (c.moveToFirst()) {
                int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
                if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
                    String localUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
                    String fileExtension = MimeTypeMap.getFileExtensionFromUrl(localUri);
                    String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension);

                    if (mimeType != null) {
                        Intent openFileIntent = new Intent(Intent.ACTION_VIEW);
                        openFileIntent.setDataAndTypeAndNormalize(Uri.parse(localUri), mimeType);
                        openFileIntent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);

                        try {
                            mAcme.startActivity(openFileIntent);
                        }
                        catch (ActivityNotFoundException e) {
                            // Ignore if no activity was found.
                        }
                    }
                }
            }
        }
    }
};

这适用于Android M,但由于受欢迎的FileUriExposedException而在N上中断。我现在尝试使用FileProvider解决此问题,但我无法让它发挥作用。当我尝试获取内容URI时它会中断:

Failed to find configured root that contains /file:/storage/emulated/0/Download/test.pdf

文件的localUri返回的DownloadManager为:

file:///storage/emulated/0/Download/test.pdf

Environment.getExternalStorageDirectory()返回/storage/emulated/0,这是转化的代码:

File file = new File(localUri);
Log.d(TAG, localUri + " - " + Environment.getExternalStorageDirectory());
Uri contentUri = FileProvider.getUriForFile(ctxt, "my.file.provider", file);

来自AndroidManifest.xml

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="my.file.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths"/>
    </provider>

file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_path" path="." />
</paths>

我已经尝试了在xml文件中找到的所有值。 :(

5 个答案:

答案 0 :(得分:8)

感谢@greenaps,我能够解决这个问题。从DownlodManager检索到的本地URI前缀为file://。必须删除新FileProvider

if (localUri.substring(0, 7).matches("file://")) {
    localUri =  localUri.substring(7);
}
File file = new File(localUri);

答案 1 :(得分:6)

此处更改AndroidManifest.xml

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths"/>
</provider>

更改文件路径

Uri contentUri;
if(Build.VERSION.SDK_INT == 24){
         contentUri = FileProvider.getUriForFile(MainActivity.this,
              getApplicationContext().getPackageName() + ".provider",
                    file);
 } else{
         contentUri = Uri.fromFile(file);
        }

答案 2 :(得分:2)

如何获取文件的确切路径文件名。较低和较高的设备。

公共类RealPathUtil {

public static String getRealPath(Context context, Uri fileUri) {
    String realPath;
    // SDK < API11
    if (Build.VERSION.SDK_INT < 11) {
        realPath = RealPathUtil.getRealPathFromURI_BelowAPI11(context, fileUri);
    }
    // SDK >= 11 && SDK < 19
    else if (Build.VERSION.SDK_INT < 19) {
        realPath = RealPathUtil.getRealPathFromURI_API11to18(context, fileUri);
    }
    // SDK > 19 (Android 4.4) and up
    else {
        realPath = RealPathUtil.getRealPathFromURI_API19(context, fileUri);
    }
    return realPath;
}


@SuppressLint("NewApi")
public static String getRealPathFromURI_API11to18(Context context, Uri contentUri) {
    String[] proj = {MediaStore.Images.Media.DATA};
    String result = null;

    CursorLoader cursorLoader = new CursorLoader(context, contentUri, proj, null, null, null);
    Cursor cursor = cursorLoader.loadInBackground();

    if (cursor != null) {
        int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();
        result = cursor.getString(column_index);
        cursor.close();
    }
    return result;
}

public static String getRealPathFromURI_BelowAPI11(Context context, Uri contentUri) {
    String[] proj = {MediaStore.Images.Media.DATA};
    Cursor cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
    int column_index = 0;
    String result = "";
    if (cursor != null) {
        column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();
        result = cursor.getString(column_index);
        cursor.close();
        return result;
    }
    return result;
}

/**
 * Get a file path from a Uri. This will get the the path for Storage Access
 * Framework Documents, as well as the _data field for the MediaStore and
 * other file-based ContentProviders.
 *
 * @param context The context.
 * @param uri     The Uri to query.
 * @author paulburke
 */
@SuppressLint("NewApi")
public static String getRealPathFromURI_API19(final Context context, final Uri uri) {

    final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

    // DocumentProvider
    if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
        // ExternalStorageProvider
        if (isExternalStorageDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            if ("primary".equalsIgnoreCase(type)) {
                return Environment.getExternalStorageDirectory() + "/" + split[1];
            }

            // TODO handle non-primary volumes
        }
        // DownloadsProvider
        else if (isDownloadsDocument(uri)) {

            final String id = DocumentsContract.getDocumentId(uri);
            final Uri contentUri = ContentUris.withAppendedId(
                    Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

            return getDataColumn(context, contentUri, null, null);
        }
        // MediaProvider
        else if (isMediaDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            Uri contentUri = null;
            if ("image".equals(type)) {
                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } else if ("video".equals(type)) {
                contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            } else if ("audio".equals(type)) {
                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            }

            final String selection = "_id=?";
            final String[] selectionArgs = new String[]{
                    split[1]
            };

            return getDataColumn(context, contentUri, selection, selectionArgs);
        }
    }
    // MediaStore (and general)
    else if ("content".equalsIgnoreCase(uri.getScheme())) {

        // Return the remote address
        if (isGooglePhotosUri(uri))
            return uri.getLastPathSegment();

        return getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }

    return null;
}

/**
 * Get the value of the data column for this Uri. This is useful for
 * MediaStore Uris, and other file-based ContentProviders.
 *
 * @param context       The context.
 * @param uri           The Uri to query.
 * @param selection     (Optional) Filter used in the query.
 * @param selectionArgs (Optional) Selection arguments used in the query.
 * @return The value of the _data column, which is typically a file path.
 */
public static String getDataColumn(Context context, Uri uri, String selection,
                                   String[] selectionArgs) {

    Cursor cursor = null;
    final String column = "_data";
    final String[] projection = {
            column
    };

    try {
        cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                null);
        if (cursor != null && cursor.moveToFirst()) {
            final int index = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(index);
        }
    } finally {
        if (cursor != null)
            cursor.close();
    }
    return null;
}


/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is ExternalStorageProvider.
 */
public static boolean isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is DownloadsProvider.
 */
public static boolean isDownloadsDocument(Uri uri) {
    return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is MediaProvider.
 */
public static boolean isMediaDocument(Uri uri) {
    return "com.android.providers.media.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is Google Photos.
 */
public static boolean isGooglePhotosUri(Uri uri) {
    return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}

}

这是精确的解决方案。刚打电话 Uri selectedResume = data.getData(); 字符串mediaResumePath = RealPathUtil.getRealPath(this,selectedResume);

答案 3 :(得分:1)

我遇到了类似的问题并通过不自动打开文件解决了这个问题,但显示了“下载完成”#39;通知然后让Android系统在用户点击通知时打开文件。

为了另外通知用户,我在下载完成时显示Toast。

DownloadManager mManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);

String url = "your URL";
String filename = "file.pdf";

// Set up the request.
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url))
            .setTitle("Test")
            .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename)
            .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
            .setDescription("Downloading...")
            .setMimeType("application/pdf");

request.allowScanningByMediaScanner();
mManager.enqueue(request);

广播接收器:

public class DownloadReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        switch (intent.getAction()) {
            case DownloadManager.ACTION_DOWNLOAD_COMPLETE:
                Toast.makeText(context, "Download completed", Toast.LENGTH_SHORT).show();
                break;
        }
    }

}

答案 4 :(得分:0)

这可以是更清晰的解决方案吗?

Uri uriParser = Uri.parse(downloadedPackageUriString);
File downloadedFile = new File(uriParser.getPath());

  

文件:///storage/emulated/0/Download/ZS%20Gac%C3%ADkov%C3%A1.pdf

之后

  

/ storage / emulated / 0 /下载/ZSSacíková.pdf