【发布时间】:2012-02-21 09:03:57
【问题描述】:
我尝试使用共享意图从我的应用中导出位图,而不为临时位置保存文件。我发现的所有例子都是两步的 1) 保存到 SD 卡并为该文件创建 Uri 2) 以此 Uri 开始意图
是否可以在不需要 WRITE_EXTERNAL_STORAGE 权限的情况下保存文件 [然后将其删除]?如何在没有 ExternalStorage 的情况下寻址设备?
【问题讨论】:
我尝试使用共享意图从我的应用中导出位图,而不为临时位置保存文件。我发现的所有例子都是两步的 1) 保存到 SD 卡并为该文件创建 Uri 2) 以此 Uri 开始意图
是否可以在不需要 WRITE_EXTERNAL_STORAGE 权限的情况下保存文件 [然后将其删除]?如何在没有 ExternalStorage 的情况下寻址设备?
【问题讨论】:
我尝试使用共享意图从我的应用中导出位图,而不为临时位置保存文件。
理论上,这是可能的。实际上,这可能是不可能的。
理论上,您只需要共享一个Uri,它将解析为位图。最简单的方法是,如果该文件是其他应用程序可以直接访问的文件,例如在外部存储上。
要根本不将其写入闪存,您需要实现自己的ContentProvider,弄清楚如何实现openFile() 以返回您的内存位图,然后传递一个代表该位图的Uri ACTION_SENDIntent。由于openFile() 需要返回ParcelFileDescriptor,我不知道如果没有磁盘上的表示你会怎么做,但我没有花太多时间搜索。
是否可以在不需要 WRITE_EXTERNAL_STORAGE 权限的情况下保存文件 [然后将其删除]?
如果您只是不希望它在外部存储上,您可以使用内部存储上的文件走ContentProvider 路线。 This sample project 演示了一个 ContentProvider,它通过 ACTION_VIEW 将 PDF 文件提供给设备上的 PDF 查看器;同样的方法可以用于ACTION_SEND。
【讨论】:
getCacheDir()吗?
我遇到了同样的问题。我不想要求读取和写入外部存储权限。此外,当手机没有 SD 卡或卡被卸载时,有时会出现问题。
以下方法使用称为FileProvider 的ContentProvider。从技术上讲,您仍然在共享之前保存位图(在内部存储中),但您不需要请求任何权限。此外,每次您共享位图时,图像文件都会被覆盖。并且由于它在内部缓存中,当用户卸载应用程序时它会被删除。所以在我看来,这与不保存图像一样好。这种方法也比将其保存到外部存储更安全。
文档非常好(请参阅下面的进一步阅读),但有些部分有点棘手。这是对我有用的摘要。
<manifest>
...
<application>
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.myapp.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
...
</application>
</manifest>
将com.example.myapp 替换为您的应用程序包名称。
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="shared_images" path="images/"/>
</paths>
这告诉 FileProvider 从哪里获取要共享的文件(在这种情况下使用缓存目录)。
// save bitmap to cache directory
try {
File cachePath = new File(context.getCacheDir(), "images");
cachePath.mkdirs(); // don't forget to make the directory
FileOutputStream stream = new FileOutputStream(cachePath + "/image.png"); // overwrites this image every time
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
File imagePath = new File(context.getCacheDir(), "images");
File newFile = new File(imagePath, "image.png");
Uri contentUri = FileProvider.getUriForFile(context, "com.example.myapp.fileprovider", newFile);
if (contentUri != null) {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // temp permission for receiving app to read this file
shareIntent.setDataAndType(contentUri, getContentResolver().getType(contentUri));
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
startActivity(Intent.createChooser(shareIntent, "Choose an app"));
}
【讨论】:
这用于将 CardView 共享为图像,然后将其保存在应用程序内部存储区域的缓存子目录中。 希望对您有所帮助。
@Override
public void onClick(View view) {
CardView.setDrawingCacheEnabled(true);
CardView.buildDrawingCache();
Bitmap bitmap = CardView.getDrawingCache();
try{
File file = new File(getContext().getCacheDir()+"/Image.png");
bitmap.compress(Bitmap.CompressFormat.PNG,100,new FileOutputStream(file));
Uri uri = FileProvider.getUriForFile(getContext(),"com.mydomain.app", file);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
shareIntent.setType("image/jpeg");
getContext().startActivity(Intent.createChooser(shareIntent, "Share"));
}catch (FileNotFoundException e) {e.printStackTrace();}
}
});
【讨论】:
为了解决 位图未更新 问题,我改进了 Suragch 的答案,使用 Gurupad Mamadapur 的评论并添加自己的修改。
这是 Kotlin 语言的代码:
private lateinit var myRootView:View // root view of activity
@SuppressLint("SimpleDateFormat")
private fun shareScreenshot() {
// We need date and time to be added to image name to make it unique every time, otherwise bitmap will not update
val sdf = SimpleDateFormat("yyyyMMdd_HHmmss")
val currentDateandTime = sdf.format(Date())
val imageName = "/image_$currentDateandTime.jpg"
// CREATE
myRootView = window.decorView.rootView
myRootView.isDrawingCacheEnabled = true
myRootView.buildDrawingCache(true) // maybe You dont need this
val bitmap = Bitmap.createBitmap(myRootView.drawingCache)
myRootView.isDrawingCacheEnabled = false
// SAVE
try {
File(this.cacheDir, "images").deleteRecursively() // delete old images
val cachePath = File(this.cacheDir, "images")
cachePath.mkdirs() // don't forget to make the directory
val stream = FileOutputStream("$cachePath$imageName")
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream) // can be png and any quality level
stream.close()
} catch (ex: Exception) {
Toast.makeText(this, ex.javaClass.canonicalName, Toast.LENGTH_LONG).show() // You can replace this with Log.e(...)
}
// SHARE
val imagePath = File(this.cacheDir, "images")
val newFile = File(imagePath, imageName)
val contentUri = FileProvider.getUriForFile(this, "com.example.myapp.fileprovider", newFile)
if (contentUri != null) {
val shareIntent = Intent()
shareIntent.action = Intent.ACTION_SEND
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // temp permission for receiving app to read this file
shareIntent.type = "image/jpeg" // just assign type. we don't need to set data, otherwise intent will not work properly
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri)
startActivity(Intent.createChooser(shareIntent, "Choose app"))
}
}
【讨论】:
如果有人仍在寻找简单而简短的解决方案没有任何存储权限(也支持牛轧糖 7.0)。在这里。
在清单中添加此内容
<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/provider_paths" />
</provider>
现在创建 provider_paths.xml
<paths>
<external-path name="external_files" path="."/>
</paths>
最后将此方法添加到您的活动/片段中(rootView 是您要共享的视图)
private void ShareIt(View rootView){
if (rootView != null && context != null && !context.isFinishing()) {
rootView.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(rootView.getDrawingCache());
if (bitmap != null ) {
//Save the image inside the APPLICTION folder
File mediaStorageDir = new File(AppContext.getInstance().getExternalCacheDir() + "Image.png");
try {
FileOutputStream outputStream = new FileOutputStream(String.valueOf(mediaStorageDir));
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if (ObjectUtils.isNotNull(mediaStorageDir)) {
Uri imageUri = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", mediaStorageDir);
if (ObjectUtils.isNotNull(imageUri)) {
Intent waIntent = new Intent(Intent.ACTION_SEND);
waIntent.setType("image/*");
waIntent.putExtra(Intent.EXTRA_STREAM, imageUri);
startActivity(Intent.createChooser(waIntent, "Share with"));
}
}
}
}
}
更新:
正如@Kathir 在 cmets 中提到的,
DrawingCache 已从 API 28+ 中弃用。使用下面的代码来使用Canvas。
Bitmap bitmap = Bitmap.createBitmap(rootView.getWidth(), rootView.getHeight(), quality);
Canvas canvas = new Canvas(bitmap);
Drawable backgroundDrawable = view.getBackground();
if (backgroundDrawable != null) {
backgroundDrawable.draw(canvas);
} else {
canvas.drawColor(Color.WHITE);
}
view.draw(canvas);
return bitmap;
【讨论】:
DrawingCache 已从 API 28+ 中弃用。使用Canvas 或PixelCopy 来完成此操作。 Example for Canvas usage。并且,不要忘记设置背景颜色(view 或 Bitmap),否则您将获得黑色背景