7

我之前的问题(Is it possible to share an image on Android via a data URL?)与这个问题有关。我已经想出了如何在没有权限将文件写入外部存储的情况下将图像从我的应用程序共享到另一个应用程序。但是,我仍然会遇到一些问题行为:

  1. 当我尝试从我的手机(Android 2.2.2)共享图像时,接收应用程序中出现致命错误,并且它们根本没有提供图像。(这可能是由于我的应用程序中的某些操作在 Android 2.2.2 上不受支持?或者这会导致我的应用程序而不是目标应用程序出现错误?)
  2. 当我尝试将图像共享到 Evernote 时,一切正常,但有时在保存笔记几秒钟后,我在应用程序屏幕底部(来自 Evernote 应用程序)收到一条消息:“java.lang.SecurityException:权限拒绝:从 ProcessRecord{413db6d0 1872:com.evernote/u0a10105}(pid=1872,uid=10105)打开提供程序 com.enigmadream.picturecode.PictureContentProvider 不是从 uid 10104 导出的“
  3. 当我尝试将图片分享到 Facebook 时,图片有一个矩形,但里面没有图片。

下面是我的 ContentProvider 代码。必须有一种更简单和/或更合适的方式来实现基于文件的 ContentProvider(尤其是查询功能)。我预计很多问题都来自查询实现。有趣的是,这在我的 Nexus 7 上运行 GMail 时效果很好它也会为附件选择正确的显示名称和大小。

public class PictureContentProvider extends ContentProvider implements AutoAnimate {
    public static final Uri CONTENT_URI = Uri.parse("content://com.enigmadream.picturecode.snapshot/picture.png");
    private static String[] mimeTypes = {"image/png"};
    private Uri generatedUri;

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
       throw new RuntimeException("PictureContentProvider.delete not supported");
    }

    @Override
    public String getType(Uri uri) {
       return "image/png";
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
       throw new RuntimeException("PictureContentProvider.insert not supported");
    }

    @Override
    public boolean onCreate() {
       generatedUri = Uri.EMPTY;
       return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
          String[] selectionArgs, String sortOrder) {
       long fileSize = 0;
       MatrixCursor result = new MatrixCursor(projection);
       File tempFile;
       try {
          tempFile = generatePictureFile(uri);
          fileSize = tempFile.length();
       } catch (FileNotFoundException ex) {
          return result;
       }
       Object[] row = new Object[projection.length];
       for (int i = 0; i < projection.length; i++) {

          if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.DISPLAY_NAME) == 0) {
             row[i] = getContext().getString(R.string.snapshot_displaystring);
          } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.SIZE) == 0) {
             row[i] = fileSize;
          } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.DATA) == 0) {
             row[i] = tempFile;
          } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.MIME_TYPE)==0) {
             row[i] = "image/png";
          }
       }

       result.addRow(row);
       return result;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
          String[] selectionArgs) {
       throw new RuntimeException("PictureContentProvider.update not supported");
    }

    @Override
    public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
       return mimeTypes;
    }

    private File generatePictureFile(Uri uri) throws FileNotFoundException {
       if (generatedUri.compareTo(uri)==0)
          return new File(getContext().getFilesDir(), "picture.png");;
          Context context = getContext();
          String query = uri.getQuery();
          String[] queryParts = query.split("&");
          String pictureCode = "016OA";
          int resolution = 36;
          int frame = 0;
          int padding = 0;
          for (String param : queryParts) {
             if (param.length() < 2)
                continue;
             if (param.substring(0,2).compareToIgnoreCase("p=") == 0) {             
                pictureCode = param.substring(2);
             } else if (param.substring(0,2).compareToIgnoreCase("r=") == 0) {
                resolution = Integer.parseInt(param.substring(2));              
             } else if (param.substring(0, 2).compareToIgnoreCase("f=") == 0) {
                frame = Integer.parseInt(param.substring(2));
             } else if (param.substring(0, 2).compareToIgnoreCase("a=") == 0) {
                padding = Integer.parseInt(param.substring(2));
             }
          }
          Bitmap picture = RenderPictureCode(pictureCode, resolution, frame, padding);
          File tempFile = new File(context.getFilesDir(), "picture.png");       
          FileOutputStream stream;
          stream = new FileOutputStream(tempFile);
          picture.compress(CompressFormat.PNG, 90, stream);
          try {
             stream.flush();
             stream.close();
          } catch (IOException e) {
             e.printStackTrace();
             throw new Error(e);
          }
          picture.recycle();
          generatedUri = uri;
          return tempFile;
    }

    @Override
    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
       File tempFile = generatePictureFile(uri);
       return ParcelFileDescriptor.open(tempFile, ParcelFileDescriptor.MODE_READ_ONLY);
    }
...
}

我在 AndroidManifest.xml 文件中也有这个作为<activity>元素的兄弟:

    <provider 
        android:name="PictureContentProvider"
        android:authorities="com.enigmadream.picturecode.snapshot"
        android:grantUriPermissions="true"
        android:readPermission="com.enigmadream.picturecode.snapshot"
        tools:ignore="ExportedContentProvider">
        <grant-uri-permission android:path="/picture.png" />
    </provider>

创建意图的代码如下所示:

        resolution = mPicView.getWidth();
        if (mPicView.getHeight() > resolution)
            resolution = mPicView.getHeight();
        String paddingText = mPadding.getEditableText().toString();
        int padding;
        try {
            padding = Integer.parseInt(paddingText);
        } catch (NumberFormatException ex) {
            padding = 0;
        }
        Uri uri = Uri.parse(PictureContentProvider.CONTENT_URI 
            + "?p=" + Uri.encode(mPicView.getPictureCode()) + "&r=" + Integer.toString(resolution) 
            + "&f=" + Integer.toString(mPicView.getFrame()) + "&a=" + Integer.toString(padding));
        Intent share = new Intent(Intent.ACTION_SEND);
        share.setType("image/png");
        share.putExtra(Intent.EXTRA_STREAM, uri);
        share.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_subject_made));
        share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        startActivity(Intent.createChooser(share, getString(R.id.menu_share)));

编辑 当我的手机发生错误时,这是​​堆栈跟踪的前两行:

04-07 13:56:24.423: E/DatabaseUtils(19431): java.lang.SecurityException: Permission Denial: reading com.enigmadream.picturecode.PictureContentProvider uri content://com.enigmadream.picturecode.snapshot/picture.png? p=01v131&r=36&f=0&a=0 from pid=19025, uid=10062 需要 com.enigmadream.picturecode.snapshot

04-07 13:56:24.423: E/DatabaseUtils(19431): 在 android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:271)

4

3 回答 3

2

由于该query方法,我在与某些电子邮件和消息传递客户端共享时遇到了问题。一些收件人应用程序发送null参数projection。发生这种情况时,您的代码会抛出一个NullPointerException. NPE 很容易解决。但是,发送的应用程序null仍然需要返回一些信息。我仍然无法分享到 Facebook,但我可以分享到我测试过的所有其他应用:

编辑我也不能让它与谷歌环聊一起工作。至少,我得到了一个祝酒词You can't send this file on Hangouts. Try using a picture。另请参阅此问题:图片在通过 Google 环聊发送后被删除。我认为这是因为我正在使用私人内容,而环聊由于某种原因不能/不会接受它。

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
        String sortOrder) {
    if (projection == null) {
        projection = new String[] {
                MediaStore.MediaColumns.DISPLAY_NAME,
                MediaStore.MediaColumns.SIZE,
                MediaStore.MediaColumns._ID,
                MediaStore.MediaColumns.MIME_TYPE
        };
    }

    final long time = System.currentTimeMillis();
    MatrixCursor result = new MatrixCursor(projection);
    final File tempFile = generatePictureFile(uri);

    Object[] row = new Object[projection.length];
    for (int i = 0; i < projection.length; i++) {

       if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.DISPLAY_NAME) == 0) {
          row[i] = uri.getLastPathSegment();
       } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.SIZE) == 0) {
          row[i] = tempFile.length();
       } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.DATA) == 0) {
          row[i] = tempFile;
       } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.MIME_TYPE)==0) {
          row[i] = _mimeType;
       } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.DATE_ADDED)==0 ||
               projection[i].compareToIgnoreCase(MediaStore.MediaColumns.DATE_MODIFIED)==0 ||
               projection[i].compareToIgnoreCase("datetaken")==0) {
           row[i] = time;
       } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns._ID)==0) {
           row[i] = 0;
       } else if (projection[i].compareToIgnoreCase("orientation")==0) {
           row[i] = "vertical";
       }
    }

    result.addRow(row);
    return result;
}
于 2014-03-10T20:55:49.730 回答
1

我不熟悉我需要或不需要的权限或如何禁用它们

尝试更换:

<provider 
    android:name="PictureContentProvider"
    android:authorities="com.enigmadream.picturecode.snapshot"
    android:grantUriPermissions="true"
    android:readPermission="com.enigmadream.picturecode.snapshot"
    tools:ignore="ExportedContentProvider">
    <grant-uri-permission android:path="/picture.png" />
</provider>

和:

<provider 
    android:name="PictureContentProvider"
    android:authorities="com.enigmadream.picturecode.snapshot"
    android:exported="true"
    tools:ignore="ExportedContentProvider">
</provider>

你真的应该android:exported="true"在第一个中也有,但我所指的权限更改代表删除android:readPermission<grant-uri-permissions>。

然后,摆脱addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);你的 Java 代码。

这使您ContentProvider成为世界可读的。从长远来看,这可能不是正确的答案。短期来看,这将有助于确定您的 Android 2.2.2 问题是否是因为FLAG_GRANT_READ_URI_PERMISSION没有被兑现。

于 2013-04-07T19:19:27.957 回答
0

有同样的问题,经过大量的研究和调试,这里是我的 5 美分:(在我的 android 2.3.3 和 4.2 设备上)

  1. Facebook 应用程序不会使用openFile(Uri uri, String mode),因此我们必须为其提供具有读取权限的真实 URI(在 projection[i] == MediaStore.MediaColumns.DATA 中) 。
  2. 你必须给 Facebook 它要求你的每一个投影,否则 FB-app 只会重复query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

这可能是一种黑客攻击,并且不适用于某些敏感数据,但您可以file.setReadable(true, false);在代码中的某处制作内部文件,facebook 将能够查看和发送 img。无需安卓外部存储权限。如果 img 可读,则可能不需要内容提供程序。可以在 Intent 中发送链接。但是我在没有提供者的情况下有奇怪的 google+ 预览行为,所以决定保留它。没有很多设备可以测试,只使用 Twitter、FB、Google+、Skype、Gmail。目前工作正常

这是GitHub 演示

于 2015-04-02T01:11:27.310 回答