【问题标题】:Android Share Intent for a Bitmap - is it possible not to save it prior sharing?位图的 Android 共享意图 - 是否可以在共享之前不保存它?
【发布时间】:2012-02-21 09:03:57
【问题描述】:

我尝试使用共享意图从我的应用中导出位图,而不为临时位置保存文件。我发现的所有例子都是两步的 1) 保存到 SD 卡并为该文件创建 Uri 2) 以此 Uri 开始意图

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

【问题讨论】:

    标签: android android-intent


    【解决方案1】:

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

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

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

    要根本不将其写入闪存,您需要实现自己的ContentProvider,弄清楚如何实现openFile() 以返回您的内存位图,然后传递一个代表该位图的Uri ACTION_SENDIntent。由于openFile() 需要返回ParcelFileDescriptor,我不知道如果没有磁盘上的表示你会怎么做,但我没有花太多时间搜索。

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

    如果您只是不希望它在外部存储上,您可以使用内部存储上的文件走ContentProvider 路线。 This sample project 演示了一个 ContentProvider,它通过 ACTION_VIEW 将 PDF 文件提供给设备上的 PDF 查看器;同样的方法可以用于ACTION_SEND

    【讨论】:

    • 不能按照this SO answer中的建议使用getCacheDir()吗?
    • @Suragch:在应用的内部存储上创建全球可读的文件并不是一个好主意。
    • 这是有道理的。发表评论后,我阅读了another one of your answers,您推荐使用FileProvider,这就是我现在正在努力的方向。
    • 每次共享屏幕截图时,缓存大小都会增加,但从另一个答案来看,它将替换以前的图像,因此它不应该增加缓存大小。请解释一下。
    【解决方案2】:

    我遇到了同样的问题。我不想要求读取和写入外部存储权限。此外,当手机没有 SD 卡或卡被卸载时,有时会出现问题。

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

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

    在 Manifest 中设置 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"));
    }
    

    进一步阅读

    【讨论】:

    • 很好的解决方案!为什么你使用 getCacheDir() 而不是 getFilesDir(),甚至是 getExternalCacheDir()
    • 另外,这需要 AsyncTask 还是 IntentService?你认为什么最适合这种情况?您是否拥有(或知道)任何将您的解决方案封装在 AsyncTask 或 IntentService 中的完整代码?
    • @Jamrelian,因为目的只是分享图片而不是保存,所以我选择了缓存目录。缓存目录中的东西不会持久。不过,我想,使用另一个目录会很好。
    • 很好的答案! 供未来的读者使用 - 大多数应用会保存与图像文件名相关的缩略图缓存。如果文件名保持不变,则不会更新缓存。我通过删除目录的内容并将文件名设置为当前日期(以毫秒为单位)解决了这个问题。
    • @GurupadMamadapur 你是对的。您需要删除缓存并使用其他文件名保存...
    【解决方案3】:

    这用于将 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();}
    
            }
        });
    

    【讨论】:

      【解决方案4】:

      这是为自己的应用程序截屏并通过任何信使或电子邮件客户端分享图像的工作方法。

      为了解决 位图未更新 问题,我改进了 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"))
          } 
      }
      

      【讨论】:

      • Zakir,请解释您的代码以帮助其他人使用它。顺便说一句,可以在同一个 Android Studio 项目中使用 Kotlin 和 Java 代码是件好事。有些人可能不会考虑您的解决方案,因为他们不知道。谢谢!
      • 嗨,zakir,你能提供java代码吗?我需要捕获滚动视图数据。成功捕获图像并能够存储在 /storage 中,但在共享时未获取文件路径。你能帮帮我吗
      【解决方案5】:

      如果有人仍在寻找简单而简短的解决方案没有任何存储权限(也支持牛轧糖 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+ 中弃用。使用CanvasPixelCopy 来完成此操作。 Example for Canvas usage。并且,不要忘记设置背景颜色(viewBitmap),否则您将获得黑色背景
      猜你喜欢
      • 1970-01-01
      • 2015-09-08
      • 1970-01-01
      • 1970-01-01
      • 2023-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多