比如说,我正在开发一个聊天应用程序,可以与其他人共享任何类型的文件(没有mimetype限制):比如图像、视频、文档,但也可以共享压缩文件,比如zip、rar、apk,或者更不常见的文件类型,比如photoshop或autocad文件。
在Android9或更低版本中,我直接将这些文件下载到下载目录,但在Android10中,如果不向用户显示下载它们的意图,就不可能做到这一点。。。
不可能的但为什么谷歌浏览器或其他浏览器能够做到这一点呢?事实上,在Android10中,他们仍然下载文件到下载目录,而不需要询问用户。
我首先分析了Whatsapp以了解它们是如何实现的,但它们利用了AndroidManifest上的requestLegacyExternalStorage属性。但后来我分析了Chrome,它针对的是Android 10,没有使用requestLegacyExternalStorage。这怎么可能?
我已经在谷歌上搜索了好几天了,应用程序可以直接下载一个文件到Android 10(Q)上的下载目录,而不必像Chrome那样询问用户把它放在哪里。
我已经阅读了android for developers文档,关于Stackoverflow的很多问题,互联网上的博客帖子和谷歌群组,但我仍然没有找到一种方法来保持与android 9完全相同的功能,甚至没有找到一个让我满意的解决方案。
到目前为止,我一直在尝试:
>
在应用程序开头使用SAF获得授权访问权限,uri指向下载内容的任何目录:
StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
i = sm.getPrimaryStorageVolume().createOpenDocumentTreeIntent();
请求用户许可是一种多么丑陋的行为,不是吗?尽管这不是谷歌Chrome的功能。
或者再次使用ACTION_CREATE_DOCUMENT,保存我在onActivityResult()中获得的Uri,并使用grantPersion()和getContentResolver()。但这并不创建目录,而是创建文件。
我还试图获得MediaStore。下载。INTERNAL_CONTENT_URI或MediaStore。下载。EXTERNAL_CONTENT_URI并使用Context.getContentResolver.insert()保存文件,但巧合的是,尽管它们被注释为@NonNull,但它们实际上返回...空
添加requestLegacyExternalStorage=“false”作为我的AndroidManifest的应用程序标签的属性。xml。但这只是开发人员的一个补丁,以便在他们做出更改和修改代码之前争取时间。除此之外,谷歌Chrome并不是这么做的。
getFilesDir()和getExtranalFilesDir()和getExtranalFilesDir()仍然可用,但当我的应用程序卸载时,存储在这些目录上的文件将被删除。用户希望在卸载我的应用程序时保留他们的文件。同样对我来说不是一个可行的解决方案。
我的临时解决方案:
我找到了一种解决方法,可以在不添加requestLegacyExternalStorage=“false”的情况下随时随地下载。
它包括通过以下方式从文件对象获取Uri:
val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val file = File(downloadDir, fileName)
val authority = "${context.packageName}.provider"
val accessibleUri = FileProvider.getUriForFile(context, authority, file)
有一个提供者。xml
<paths>
<external-path name="external_files" path="."/>
</paths>
并将其设置为AndroidManifest。xml:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
问题是:
它使用了getExternalStoragePublicDirectory方法,该方法在Android Q上被弃用,极有可能在Android 11上被删除。你可能认为你可以手动创建自己的路径,因为你知道真实的路径(/storage/simulated/0/Download/),并继续创建一个文件对象,但如果谷歌决定更改Android 11上的下载目录路径呢?
恐怕这不是长久之计,所以
我的问题是:
如何在不使用已弃用的方法的情况下实现这一点?还有一个额外的问题,谷歌Chrome如何访问下载目录?
我认为最好用java编写答案,因为有很多遗留代码需要更新。我也不知道你想存储什么样的文件,所以,在这里,我举了一个位图的例子。如果你想保存任何其他类型的文件,你只需要创建一个OutputStream,对于它的剩余部分,你可以按照下面我写的代码。现在,在这里,这个函数只处理Android 10的保存,以及注释。我希望你对如何在较低的Android版本中保存文件有必要的了解
@RequiresApi(api = Build.VERSION_CODES.Q)
public void saveBitmapToDownloads(Bitmap bitmap) {
ContentValues contentValues = new ContentValues();
// Enter the name of the file here. Note the extension isn't necessary
contentValues.put(MediaStore.Downloads.DISPLAY_NAME, "Test.jpg");
// Here we define the file type. Do check the MIME_TYPE for your file. For jpegs it is "image/jpeg"
contentValues.put(MediaStore.Downloads.MIME_TYPE, "image/jpeg");
contentValues.put(MediaStore.Downloads.IS_PENDING, true);
Uri uri = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
Uri itemUri = getContentResolver().insert(uri, contentValues);
if (itemUri != null) {
try {
OutputStream outputStream = getContentResolver().openOutputStream(itemUri);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
outputStream.close();
contentValues.put(MediaStore.Images.Media.IS_PENDING, false);
getContentResolver().update(itemUri, contentValues, null, null);
} catch (IOException e) {
e.printStackTrace();
}
}
}
现在,如果你想创建一个子目录,即下载文件夹中的文件夹,你可以将其添加到contentValues中
contentValues.put(MediaStore.Downloads.RELATIVE_PATH, "Download/" + "Folder Name");
这将创建一个名为“folder Name”的文件夹,并在该文件夹中存储“Test.jpg”。
另外请注意,这不需要清单中的任何权限,这意味着它也不需要运行时权限。如果你想要静态编程语言版本,请在评论中问我。我很乐意为它提供静态编程语言方法。
你可以使用Android下载管理器。请求
。在Android 9之前,它将需要写入外部存储
任务。从Android 10/Q及以上版本来看,它不需要任何权限(似乎它自己处理权限)。
如果你想在事后打开该文件,你需要获得用户的许可(如果你只想在外部应用程序(例如PDF阅读器)中打开该文件,也需要用户的许可)。
您可以这样使用下载管理器:
DownloadManager.Request request = new DownloadManager.Request(<download uri>);
request.addRequestHeader("Accept", "application/pdf");
// Save the file in the "Downloads" folder of SDCARD
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,filename);
DownloadManager downloadManager = (DownloadManager) getActivity().getSystemService(Context.DOWNLOAD_SERVICE);
downloadManager.enqueue(request);
这是参考:https://developer.android.com/reference/kotlin/android/app/DownloadManager.Request?hl=en#setDestinationInExternalPublicDir(科特林。弦乐Kotlin字符串)
https://developer.android.com/training/data-storage
10个多月过去了,但没有给我一个满意的答案。所以我会回答我自己的问题。
正如@commonware在评论中所说,“获取MediaStore.Downloads.INTERNAL_CONTENT_URI或MediaStore.Downloads.EXTERNAL_CONTENT_URI并使用Context.getContentResolver.insert()保存文件”应该是解决方案。我仔细检查了一下,发现这是真的,我说它不起作用是错的。但是
我发现使用ContentResolver很棘手,我无法让它正常工作。我会提出一个单独的问题,但我一直在调查,并找到了一个令人满意的解决方案。
我的解决方案:
基本上,你必须下载到你的应用拥有的任何目录,然后复制到下载文件夹。
>
配置你的应用程序:
>
添加提供者路径。xml到xml资源文件夹
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
在您的清单中添加FileProvider:
<application>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
准备将文件下载到应用程序拥有的任何目录,例如getFilesDir()、getExternalFilesDir()、getCacheDir()或getExternalCacheDir()。
val privateDir = context.getFilesDir()
下载文件时考虑其进度(DIY):
val downloadedFile = myFancyMethodToDownloadToAnyDir(url, privateDir, fileName)
下载后,如果愿意,可以对文件进行任何威胁。
复制到下载文件夹:
//This will be used only on android P-
private val DOWNLOAD_DIR = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val finalUri : Uri? = copyFileToDownloads(context, downloadedFile)
fun copyFileToDownloads(context: Context, downloadedFile: File): Uri? {
val resolver = context.contentResolver
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, getName(downloadedFile))
put(MediaStore.MediaColumns.MIME_TYPE, getMimeType(downloadedFile))
put(MediaStore.MediaColumns.SIZE, getFileSize(downloadedFile))
}
resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
} else {
val authority = "${context.packageName}.provider"
val destinyFile = File(DOWNLOAD_DIR, getName(downloadedFile))
FileProvider.getUriForFile(context, authority, destinyFile)
}?.also { downloadedUri ->
resolver.openOutputStream(downloadedUri).use { outputStream ->
val brr = ByteArray(1024)
var len: Int
val bufferedInputStream = BufferedInputStream(FileInputStream(downloadedFile.absoluteFile))
while ((bufferedInputStream.read(brr, 0, brr.size).also { len = it }) != -1) {
outputStream?.write(brr, 0, len)
}
outputStream?.flush()
bufferedInputStream.close()
}
}
}
进入下载文件夹后,您可以从应用程序中打开文件,如下所示:
val authority = "${context.packageName}.provider"
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(finalUri, getMimeTypeForUri(finalUri))
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
} else {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}
try {
context.startActivity(Intent.createChooser(intent, chooseAppToOpenWith))
} catch (e: Exception) {
Toast.makeText(context, "Error opening file", Toast.LENGTH_LONG).show()
}
//Kitkat or above
fun getMimeTypeForUri(context: Context, finalUri: Uri) : String =
DocumentFile.fromSingleUri(context, finalUri)?.type ?: "application/octet-stream"
//Just in case this is for Android 4.3 or below
fun getMimeTypeForFile(finalFile: File) : String =
DocumentFile.fromFile(it)?.type ?: "application/octet-stream"
优点:
>
还允许您在下载时了解其进度
一旦移动,你仍然可以从应用程序中打开它们,因为文件仍然属于你的应用程序。
Android Q不需要写入外部存储权限,仅用于此目的:
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
缺点:
如果这种方法对你来说足够了,那就试一试。
我正在尝试使用Selenium WebDriver自动执行文件下载功能。我正在使用谷歌浏览器,要下载的文件类型是PDF格式。当 WebDriver 单击下载(或打印)链接时,浏览器将显示 pdf 文件的预览,而不是直接下载。如何使chrome驱动程序直接下载pdf文件?我尝试了下面的代码,但没有运气 我知道这个问题已经在StackOverflow上问过了,包括这个,但这些解决方案都不适合我。 我正
希望有人能帮我, 场景:我是, 导航到有下载图标的页面 单击“下载”图标 .pdf文件开始下载到我的项目目录 注意:下载过程中不会显示确认消息,只要单击下载图标,文件就会被下载 观察到:当在本地运行测试用例时(从mvn命令和testng命令),它似乎正在工作并将文件下载到我的目录中。 然而,同样的案例似乎不适用于詹金斯。该文件不会下载到 Jenkins Directory。 本地: Windows
在我实现了一个Android应用程序的解决方案后,发布到web服务器并验证Google Order,然后发布一个下载链接。现在,我正在尝试编写一个应用程序的代码,以读取thankyou.php页面中的链接 该文件是一个“.dat”扩展名,来自某个链接。应用程序应该检索一个新页面中的链接,并让客户下载文件。
下载 <?php /** * 下载文件请求示例 */ require dirname(__DIR__) . '/vendor/autoload.php'; use Yurun\Util\HttpRequest; use Yurun\Util\YurunHttp; use Yurun\Util\YurunHttp\Handler\Swoole; $url = 'http://www.baid
问题内容: 目的是从Internet下载文件,并从中创建文件对象或类似文件的文件,而无需使其接触硬盘驱动器。这仅是出于我的知识,想知道它是否可能或可行,尤其是因为我想看看是否可以绕过必须编写文件删除行的代码。 通常,这就是我从网络上下载内容并将其映射到内存的方式: 问题答案: 这就是我最终要做的。
问题内容: 我刚刚开始学习来自Android的iOS的Apple Swift编程。我现在基本上可以阅读和操作swift代码,还学习了iOS swift编程中使用的一些通用类,但是仍然对语法和所有内容感到困惑。 我正在尝试下载文件。就像,让我们说来自这个网址 在一个按钮中单击。也许也有视觉进步 通过在stackoverflow中搜索,我偶然发现了Alamofire。我可以尝试一下,但是我不确定这是否