位图的Android共享意图-是否可以在共享之前不保存它?


71

我尝试使用共享意图从我的应用程序导出位图,而不将文件保存为临时位置。我发现的所有示例都分两个步骤:1)保存到SD卡并为该文件创建Uri 2)使用此Uri启动意图

是否可以在不需要WRITE_EXTERNAL_STORAGE权限的情况下进行保存,保存文件[然后将其删除]?没有外部存储的设备如何寻址?

Answers:


256

我有同样的问题。我不想要求读取和写入外部存储权限。另外,有时手机没有SD卡或卡未卸下时也会出现问题。

以下方法使用一个称为FileProviderContentProvider。从技术上讲,共享之前仍要保存位图(在内部存储中),但是不需要请求任何权限。同样,每次共享位图时,图像文件都会被覆盖。并且由于它位于内部缓存中,因此当用户卸载应用程序时,它将被删除。因此,我认为这与不保存图像一样好。此方法比将其保存到外部存储更安全。

该文档非常好(请参见下面的“进一步阅读”),但是某些部分有些棘手。这是对我有用的摘要。

在清单中设置FileProvider

<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为您的应用包名称。

创建res / xml / filepaths.xml

<?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"));
}

进一步阅读


1
很好的解决方案!为什么要使用getCacheDir()而不是getFilesDir()甚至getExternalCacheDir()的任何原因?
Alex Vang

另外,这是否需要AsyncTask或IntentService?您认为哪种情况最适合这种情况?您是否拥有(或知道)将您的解决方案包装在AsyncTask或IntentService中的完整代码?
Alex Vang

3
@Jamrelian,由于目的只是共享图像而不是保存图像,因此选择了缓存目录。缓存目录中的内容不会持久。不过,我想使用另一个目录会很好。
Suragch '16

3
好答案!对于未来的读者-大多数应用会保存相对于图像文件名的缩略图缓存。如果文件名保持不变,则不会更新缓存。我通过删除目录的内容并将文件名设置为当前日期(以毫秒为单位)来解决此问题。
Gurupad Mamadapur

1
@GurupadMamadapur你是对的。您需要删除缓存并使用其他文件名保存...
Hoang Nguyen

10

我尝试使用共享意图从我的应用程序导出位图,而不将文件保存为临时位置。

从理论上讲,这是可能的。实际上,这是不可能的。

从理论上讲,您需要共享的只是一个Uri可以解析为位图的对象。最简单的方法是,如果该文件是其他应用程序可以直接访问的文件,例如在外部存储上。

要完全不将其写入Flash,您需要实现自己的实现ContentProvider,弄清楚如何实现openFile()返回内存中的位图,然后在中传递Uri表示该位图的表示ACTION_SEND Intent。由于openFile()需要返回a ParcelFileDescriptor,所以我不知道如果没有磁盘上的表示该怎么做,但是我没有花太多时间进行搜索。

是否可以在不需要WRITE_EXTERNAL_STORAGE权限的情况下进行保存,保存文件[然后将其删除]?

如果您只是不想在外部存储上使用它,则可以ContentProvider使用内部存储上的文件进行操作。此示例项目演示了如何ContentProvider通过设备ACTION_VIEW上的PDF查看器提供PDF文件。相同的方法可以用于ACTION_SEND


难道一个人都不能getCacheDir()按照这个答案中的建议使用吗?
苏拉奇2015年

1
@Suragch:在应用程序的内部存储上创建世界可读的文件不是一个好主意。
CommonsWare,2015年

1
这就说得通了。发表评论后,我阅读了您的另一个答案,建议您使用FileProvider,这就是我现在正在努力的方向。
苏拉奇2015年

每次共享屏幕快照时,高速缓存的大小都会增加,但是从其他答案开始,它将替换前一个图像,因此不应增加高速缓存的大小。请解释一下。
GvSharma '16

5

如果仍然有人在没有任何存储许可的情况下寻求简便的解决方案(也支持Nougat 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在评论中提到的那样,

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;

1
DrawingCache已从API 28+中弃用。使用CanvasPixelCopy完成此操作。画布用法示例。并且,不要忘记设置背景颜色(为您的view或设置Bitmap),否则您将得到黑色背景
Kathir

3

这用于将CardView作为图像共享,然后将其保存在应用程序内部存储区域的cache子目录中。希望对您有所帮助。

        @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();}

        }
    });

1

这是制作自己的应用程序的屏幕截图共享的有效方法通过任何Messenger或电子邮件客户端其为图像的。

为了解决位图不更新的问题,我使用Gurupad Mamadapur的注释并添加了自己的修改来改善Suragch的答案。


这是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"))
    } 
}

1
Zakir,请解释您的代码以帮助其他人使用它。顺便说一句,可以说Kotlin和Java代码可以在同一Android Studio项目中使用。有些人可能不考虑您的解决方案,因为他们不知道。谢谢!
威尔

嗨,扎基尔,请您提供Java代码。我需要捕获scrollview数据。成功捕获图像并能够存储在/ storage中,但共享时未获取文件路径。您能帮我吗
塔拉
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.