通过Seekable管道或流与另一个Android应用共享吗?

问题描述 投票:31回答:4

很多Intent动作,例如ACTION_VIEW,将Uri指向要对该动作执行的内容。如果内容由文件支持,则Uri是直接指向该文件,还是指向为该文件提供服务的ContentProvidersee FileProvider),这通常是有效的。

在某些情况下,开发人员不想让内容驻留在文件中以便与其他应用程序共享。一种常见的情况是加密:解密的数据应驻留在RAM中,而不是磁盘上,以最大程度地降低有人获取解密数据的风险。

我从RAM共享的经典解决方案是FileProvider。但是,当响应use ParcelFileDescriptor and createPipe()(或其他)的活动在该管道上获得ParcelFileDescriptor时,与createPipe()从文件中提供内容时获得的流相比,生成的流受到限制。例如,ACTION_VIEW与Adobe Reader配合良好,会使QuickOffice崩溃。

基于InputStream ContentProvider问题,我的假设是this sample app确实在创建管道,而past。试图“倒带”或“快进”的客户端会因此而遇到问题。

我正在寻找一种可靠的解决方案,用于与第三方应用程序共享内存内容,从而克服了这一限制。具体来说:

  • 它必须使用客户端应用程序(即related实现者)可能会接受的createPipe()语法;涉及客户端应用程序不太可能识别的钝性解决方案(例如,通过pipes are non-seekable额外信息来回传送)不合格

  • 要共享的数据不能作为共享的一部分写入文件中(当然,客户端应用程序可以将接收到的字节保存到磁盘上,但是暂时忽略这种风险)

  • 理想情况下,它不涉及寻求共享数据以打开Uri或加剧安全风险的应用程序]] >>

  • 可能的建议思路包括:

    • 某种重新配置ACTION_VIEW的方法,导致可搜索的管道

  • 使用基于套接字的Intent的某种方式,该方式导致可搜索的管道

  • 某种类型的RAM磁盘或其他与Android其余部分相似的文件,但不是永久文件

  • 如果可以的话,一个可行的解决方案的关键条件是能否从QuickOffice可以读取的RAM中获取PDF。

    有什么建议吗?

    谢谢!

很多Intent操作,例如ACTION_VIEW,都采用Uri指向应对其执行操作的内容。如果内容由文件支持-Uri是直接指向文件还是...

您提出了非常困难的要求组合。

让我们看看您的想法以寻求解决方案:

可能的建议思路包括:

  • 某种重新配置createPipe()的方式,该方式导致可搜索的管道

  • 使用基于套接字的FileDescriptor的某种方式来生成可搜索的管道

  • 某种类型的RAM磁盘或其他与Android其余部分相似的文件,但不是永久文件

  • 第一个无效。这个问题是由OS实现的管道基元从根本上是不可寻求的。原因是支持寻求,这将要求OS缓冲整个管道的“内容” ...直到读取端关闭。这是无法实现的……除非您对可通过管道发送的数据量进行限制。

    由于几乎相同的原因,第二个也不起作用。操作系统级别的套接字不可搜索。

在一个层面上,最终构想(RAM文件系统)的工作与Android OS支持这种功能的模数有关。 (毕竟,可以找到Ramfs文件。)但是,文件流不是管道。特别是对于文件流和管道,关于文件结尾的行为是不同的。从读者的角度来看,要使文件流看起来像管道流,将需要在那一侧添加一些特殊的代码。 (该问题类似于在日志文件上运行ServerSocket的问题...)


[不幸的是,我不认为有任何other

方法来获取文件描述符,该描述符相对于文件结束而言的行为类似于管道,并且也是可寻求的……根本不对操作系统进行根本性的修改。

如果您可以更改从流中读取的应用程序,则可以解决此问题。事实是fd需要由QuickOffice读取和查找(我假设您无法修改)的事实排除在外。 (但是,如果您可以

更改应用程序,则有多种方法可以使这项工作...)

顺便说一句,我认为您在Linux或Windows上的这些要求会有一些问题。而且它们不是特定于Java的。


UPDATE

对此有很多有趣的评论,我想在这里解决:

  1. OP已经解释了激发他的问题的用例。基本上,他想要一种方案,即如果在应用程序实际运行时用户设备被盗(或没收),则通过应用程序之间“通道”传递的数据将不会受到攻击。

    可以实现吗?

    • 理论上,没有。如果假设高度的技术复杂性(以及公众可能不了解的技术……),那么“坏蛋”可能会闯入操作系统并从共享内存中读取数据,而“通道”仍处于活动状态。] >

    • 我怀疑这样的攻击在实践中(当前)是可能的。

    • 但是,即使我们假设“通道”未向“光盘”写入任何内容,也可能still

      内存中通道的痕迹:例如
      • 仍然安装的RAMfs或仍然活动的共享内存段,或

      • 以前的RAMfs的剩余部分/共享内存。

        理论上,只要“坏蛋”没有打开或重新启动设备,就可以从理论上检索此数据。

  2. 已建议在此上下文中使用createPipe()

    • 没有公共Java API的问题可以解决(例如,通过编写第三方API)]] >>

    • 真正的绊脚石是需要流API。根据“ ashmem”文档,它们具有类似文件的API。但是我认为这仅意味着它们符合“文件描述符”模型。这些FD可以从一个应用程序传递到另一个应用程序(跨fork / exec),您可以使用“ ioctl”对其进行操作。但是没有迹象表明它们实现了“读取”和“写入”……更不用说“寻找”了。

  3. 现在,您可以使用通道两端的本机库和Java库在ashmem之上实现读/写/可搜索流。但是,两个应用程序都需要“意识到”该过程,可能要达到提供用于设置通道的命令行选项的级别。

  4. 这些问题也适用于旧式FileDescriptor ...,但是通道设置可能更困难。

  5. 另一个可能的选择是使用RAM fs。

  • 这更容易实现。 RAMfs中的文件的行为类似于“普通”文件。当由应用程序打开时,您将获得一个文件描述符,该文件描述符可以被打开,读取和写入。并且(我认为)您应该能够通过fork / exec传递RAMfs文件的可搜索FD。

  • 问题是RAMfs需要由操作系统“挂载”以使用它。挂载后,另一个(特权)应用程序也可以打开和读取文件。而且,当某些应用程序已打开RAMfs文件的fds时,操作系统将不允许您卸载RAMfs。

  • (一种(假设的)方案可以部分缓解上述情况。

    1. 源应用程序创建并挂载“私有” RAMfs。
    2. 源应用程序创建/打开文件以进行读取/写入,然后取消链接。
    3. 源应用程序从头开始使用fd写入文件。
    4. 源应用程序派生/执行接收器应用程序,并通过fd。
    5. 接收器应用程序从(我认为)仍可搜索的fd中读取,根据需要进行搜索。
    6. 当源应用程序注意到(子)接收器应用程序进程已退出时,它将卸载并销毁RAMfs。
    7. 这不需要修改读取(接收)应用程序。

      但是,第三个(特权)应用程序可能still可能进入RAMfs,在内存中找到未链接的文件,然后读取它。

    但是,回顾了以上所有内容之后,最实际的解决方案仍然是修改读取(接收器)应用程序,将整个输入流读取到tail -f中,然后在其中打开ashmem缓冲的数据。核心应用程序可以随意查找和重置它。

    这不是您问题的一般解决方案,但是使用以下代码(根据您的示例)在QuickOffice中打开PDF对我有用:

    ashmem 

    我一直在尝试@josias代码。我发现某些shmem调用带有byte[]的投影。包括该列的数据并设置实际长度意味着可以在更多应用程序中打开更多文件类型。即使不在传递的投影中始终包含[​​C0],也可以打开更多文件类型。

    这是我最后得到的:

    ByteArrayInputStream

    [我相信您正在寻找@Override public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException { try { byte[] data = getData(uri); long size = data.length; ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); new TransferThread(new ByteArrayInputStream(data), new AutoCloseOutputStream(pipe[1])).start(); return new AssetFileDescriptor(pipe[0], 0, size); } catch (IOException e) { e.printStackTrace(); } return null; }; private byte[] getData(Uri uri) throws IOException { AssetManager assets = getContext().getResources().getAssets(); InputStream is = assets.open(uri.getLastPathSegment()); ByteArrayOutputStream os = new ByteArrayOutputStream(); copy(is, os); return os.toByteArray(); } private void copy(InputStream in, OutputStream out) throws IOException { byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } in.close(); out.flush(); out.close(); } @Override public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, String sort) { if (projection == null) { projection = new String[] { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE }; } String[] cols = new String[projection.length]; Object[] values = new Object[projection.length]; int i = 0; for (String col : projection) { if (OpenableColumns.DISPLAY_NAME.equals(col)) { cols[i] = OpenableColumns.DISPLAY_NAME; values[i++] = url.getLastPathSegment(); } else if (OpenableColumns.SIZE.equals(col)) { cols[i] = OpenableColumns.SIZE; values[i++] = AssetFileDescriptor.UNKNOWN_LENGTH; } } cols = copyOf(cols, i); values = copyOf(values, i); final MatrixCursor cursor = new MatrixCursor(cols, 1); cursor.addRow(values); return cursor; } private String[] copyOf(String[] original, int newLength) { final String[] result = new String[newLength]; System.arraycopy(original, 0, result, 0, newLength); return result; } private Object[] copyOf(Object[] original, int newLength) { final Object[] result = new Object[newLength]; System.arraycopy(original, 0, result, 0, newLength); return result; } ,API 26中添加了该函数。这将为您提供query(...),这是_data工作所需的。但是,您也可以获取其文件描述符并在文件I / O中使用它:_data

    在功能描述中为:

    这在您要提供对磁盘上没有真实文件支持的大文件的快速访问时非常有用,例如网络共享,云存储服务等。例如,您可以回应ContentResolver#openFileDescriptor(android.net.Uri,字符串)请求,方法是返回以此创建的ParcelFileDescriptor方法,然后根据要求按需流式传输内容。另一个一个有用的例子可能是您拥有一个加密文件愿意按需解密,但要避免持久存在明文版本。

    它与private static final String[] PROJECTION = {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE, "_data"}; @Override public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, String sort) { byte[] data = getData(mSourcePath, url); final MatrixCursor cursor = new MatrixCursor(PROJECTION, 1); cursor.newRow() .add(url.getLastPathSegment()) .add(data.length) .add(data); return cursor; } 一起使用,这是您提供I / O的功能,主要从各种偏移量读取文件的片段。

    正如我测试过的那样,它也非常适合通过content://方案进行视频播放,因为查找是高效的,并且不像基于管道的方法那样按读取查找,但是Android确实会询问文件的相关片段。

    Android内部使用一些保险丝驱动程序在进程之间传输数据。

    android android-intent android-contentprovider
    4个回答
    8
    投票

    您提出了非常困难的要求组合。


    3
    投票

    这不是您问题的一般解决方案,但是使用以下代码(根据您的示例)在QuickOffice中打开PDF对我有用:

    ashmem 

    我一直在尝试@josias代码。我发现某些shmem调用带有byte[]的投影。包括该列的数据并设置实际长度意味着可以在更多应用程序中打开更多文件类型。即使不在传递的投影中始终包含[​​C0],也可以打开更多文件类型。

    这是我最后得到的:

    ByteArrayInputStream

    0
    投票

    我一直在尝试@josias代码。我发现某些shmem调用带有byte[]的投影。包括该列的数据并设置实际长度意味着可以在更多应用程序中打开更多文件类型。即使不在传递的投影中始终包含[​​C0],也可以打开更多文件类型。


    0
    投票

    [我相信您正在寻找@Override public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException { try { byte[] data = getData(uri); long size = data.length; ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); new TransferThread(new ByteArrayInputStream(data), new AutoCloseOutputStream(pipe[1])).start(); return new AssetFileDescriptor(pipe[0], 0, size); } catch (IOException e) { e.printStackTrace(); } return null; }; private byte[] getData(Uri uri) throws IOException { AssetManager assets = getContext().getResources().getAssets(); InputStream is = assets.open(uri.getLastPathSegment()); ByteArrayOutputStream os = new ByteArrayOutputStream(); copy(is, os); return os.toByteArray(); } private void copy(InputStream in, OutputStream out) throws IOException { byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } in.close(); out.flush(); out.close(); } @Override public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, String sort) { if (projection == null) { projection = new String[] { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE }; } String[] cols = new String[projection.length]; Object[] values = new Object[projection.length]; int i = 0; for (String col : projection) { if (OpenableColumns.DISPLAY_NAME.equals(col)) { cols[i] = OpenableColumns.DISPLAY_NAME; values[i++] = url.getLastPathSegment(); } else if (OpenableColumns.SIZE.equals(col)) { cols[i] = OpenableColumns.SIZE; values[i++] = AssetFileDescriptor.UNKNOWN_LENGTH; } } cols = copyOf(cols, i); values = copyOf(values, i); final MatrixCursor cursor = new MatrixCursor(cols, 1); cursor.addRow(values); return cursor; } private String[] copyOf(String[] original, int newLength) { final String[] result = new String[newLength]; System.arraycopy(original, 0, result, 0, newLength); return result; } private Object[] copyOf(Object[] original, int newLength) { final Object[] result = new Object[newLength]; System.arraycopy(original, 0, result, 0, newLength); return result; } ,API 26中添加了该函数。这将为您提供query(...),这是_data工作所需的。但是,您也可以获取其文件描述符并在文件I / O中使用它:_data

    © www.soinside.com 2019 - 2024. All rights reserved.