从assets文件夹分享Android图片

24

我正在尝试分享我的assets文件夹中的一张图片。我的代码如下:

Intent share = new Intent(Intent.ACTION_SEND);
share.setType("image/jpg");
share.putExtra(Intent.EXTRA_STREAM, Uri.parse("file:///assets/myImage.jpg"));
startActivity(Intent.createChooser(share, "Share This Image"));

但是它没有起作用。你有什么想法吗?

7个回答

18

你可以通过自定义的ContentProvider来共享位于assets文件夹中的文件(包括图像文件)。

你需要扩展ContentProvider,在清单文件中注册它并实现openAssetFile方法。之后,你就可以通过Uri访问这些文件。

    @Override
    public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
        AssetManager am = getContext().getAssets();
        String file_name = uri.getLastPathSegment();

        if(file_name == null) 
            throw new FileNotFoundException();
        AssetFileDescriptor afd = null;
        try {
            afd = am.openFd(file_name);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return afd;
    }

1
你能否添加一些更多的细节,说明我们如何从另一个应用程序中调用它? - Lalit Poptani

16

补充一下@intrepidis的回答:

您需要覆盖像上面示例类中的方法:

package com.android.example;

import android.content.ContentProvider;
import android.net.Uri;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import java.io.FileNotFoundException;
import android.content.ContentValues;
import android.database.Cursor;
import java.io.IOException;
import android.os.CancellationSignal;

public class AssetsProvider extends ContentProvider
{

        @Override
        public AssetFileDescriptor openAssetFile( Uri uri, String mode ) throws FileNotFoundException
        {
                Log.v( TAG, "AssetsGetter: Open asset file" );
                AssetManager am = getContext( ).getAssets( );
                String file_name = uri.getLastPathSegment( );
                if( file_name == null )
                        throw new FileNotFoundException( );
                AssetFileDescriptor afd = null;
                try
                {
                        afd = am.openFd( file_name );
                }
                catch(IOException e)
                {
                        e.printStackTrace( );
                }
                return afd;//super.openAssetFile(uri, mode);
        }

        @Override
        public String getType( Uri p1 )
        {
                // TODO: Implement this method
                return null;
        }

        @Override
        public int delete( Uri p1, String p2, String[] p3 )
        {
                // TODO: Implement this method
                return 0;
        }

        @Override
        public Cursor query( Uri p1, String[] p2, String p3, String[] p4, String p5 )
        {
                // TODO: Implement this method
                return null;
        }

        @Override
        public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal )
        {
                // TODO: Implement this method
                return super.query( uri, projection, selection, selectionArgs, sortOrder, cancellationSignal );
        }

        @Override
        public Uri insert( Uri p1, ContentValues p2 )
        {
                // TODO: Implement this method
                return null;
        }

        @Override
        public boolean onCreate( )
        {
                // TODO: Implement this method
                return false;
        }

        @Override
        public int update( Uri p1, ContentValues p2, String p3, String[] p4 )
        {
                // TODO: Implement this method
                return 0;
        }
}

我需要两次覆盖查询方法。 并在androidmanifest.xml中的标签上方添加以下行:

<provider
  android:name="com.android.example.AssetsProvider"
  android:authorities="com.android.example"
  android:grantUriPermissions="true"
  android:exported="true" />

这样,一切都像魔法般运作得很好 :D


有人可以帮帮我吗?我正在从assets文件夹中获取图片——>文件夹——>image1.png,并且我无法从assets子文件夹中获取图像,请建议我如何编辑这一行代码:String file_name = uri.getLastPathSegment(); - user3233280
2
抱歉回复晚了。你是怎么将文件发送到Intent的?我认为代码没问题。你需要像这样通过地址调用图像:file:///android_asset/folder/image1.png如果你想分享它,你需要使用以下代码: String file = "folder/image1.png"; Uri theUri = Uri.parse( "content://com.example.yourproject/" + file ); Intent theIntent = new Intent( Intent.ACTION_SEND ); theIntent.setType( "image/*" ); theIntent.putExtra( Intent.EXTRA_STREAM, theUri ); startActivity( Intent.createChooser( theIntent, "Send to:" ); - Kikuto
2
@Kikuto,如果您的资产中有子文件夹,则应将AssetsProvider openAssetFile方法中的file_name更改为String file_name = uri.getPath().substring(1, uri.getPath().length()); - Ragaisis

14
此博客将为您解释全部:
http://nowherenearithaca.blogspot.co.uk/2012/03/too-easy-using-contentprovider-to-send.html

基本上,这个放在manifest文件中:
<provider android:name="yourclass.that.extendsContentProvider"                android:authorities="com.yourdomain.whatever"/>

内容提供程序类具有以下特点:

@Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
    AssetManager am = getContext().getAssets();
    String file_name = uri.getLastPathSegment();
    if(file_name == null) 
        throw new FileNotFoundException();
    AssetFileDescriptor afd = null;
    try {
        afd = am.openFd(file_name);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return afd;//super.openAssetFile(uri, mode);
}

调用代码执行以下操作:

Uri theUri = Uri.parse("content://com.yourdomain.whatever/someFileInAssetsFolder");
Intent theIntent = new Intent(Intent.ACTION_SEND);
theIntent.setType("image/*");
theIntent.putExtra(Intent.EXTRA_STREAM,theUri);
theIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,"Subject for message");                        
theIntent.putExtra(android.content.Intent.EXTRA_TEXT, "Body for message");
startActivity(theIntent);

请确保URI以“content://”开头。 - intrepidis
我在am.openFd(file_name)上遇到了"java.io.FileNotFoundException: This file can not be opened as a file descriptor; it is probably compressed"的错误。 - GozzoMan
@GozzoMan 在调试时,file_name 变量的实际值是多少? - intrepidis

5

由于其他答案在我这里(2019年)都没有用,所以我通过将资产复制到应用程序的内部文件目录,然后共享此文件来解决了问题。 在我的情况下,我需要共享位于assets文件夹中的PDF文件。

在AndroidManifest.xml中添加一个文件提供程序(无需使用自定义提供程序):

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/filepaths" />
</provider>

在res/xml/目录下创建一个filepaths.xml文件。
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path
        name="root"
        path="/" />
</paths>

当然,如果您在应用程序目录中管理其他文件,则应在此处使用子目录。

现在,在您想要触发共享意图的类中。

1. 在文件目录中创建一个空文件

private fun createFileInFilesDir(filename: String): File {
    val file = File(filesDir.path + "/" + filename)
    if (file.exists()) {
        if (!file.delete()) {
            throw IOException()
        }
    }
    if (!file.createNewFile()) {
        throw IOException()
    }
    return file
}

2. 将资产的内容复制到文件中

private fun copyAssetToFile(assetName: String, file: File) {
    val buffer = ByteArray(1024)
    val inputStream = assets.open(assetName)
    val outputStream: OutputStream = FileOutputStream(file)
    while (inputStream.read(buffer) > 0) {
        outputStream.write(buffer)
    }
}

3. 为文件创建共享意图

private fun createIntentForFile(file: File, intentAction: String): Intent {
    val uri = FileProvider.getUriForFile(this, applicationContext.packageName + ".provider", file)
    val intent = Intent(intentAction)
    intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
    intent.setDataAndType(uri, "application/pdf")
    return intent
}

4. 执行1-3并触发意图

private fun sharePdfAsset(assetName: String, intentAction: String) {
    try {
        val file = createFileInFilesDir(assetName)
        copyAssetToFile(assetName, file)
        val intent = createIntentForFile(file, intentAction)
        startActivity(Intent.createChooser(intent, null))
    } catch (e: IOException) {
        e.printStackTrace()
        AlertDialog.Builder(this)
            .setTitle(R.string.error)
            .setMessage(R.string.share_error)
            .show()
    }
}

5. 调用函数

sharePdfAsset("your_pdf_asset.pdf", Intent.ACTION_SEND)

如果你想分享文件后删除它,你可以使用 startActivityForResult() 并在之后删除它。通过更改 intentAction,您还可以使用 Intent.ACTION_VIEW 对其进行“打开方式”操作。 对于 assetsfilesDir,... 当然需要在一个 Activity 中或具有 Context

4

许多应用程序需要您提供图像的名称和大小。因此,这里是一个改进的代码(以谷歌的FileProvider代码为例):

public class AssetsProvider extends ContentProvider {

    private final static String LOG_TAG = AssetsProvider.class.getName();

    private static final String[] COLUMNS = {
            OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE };

    @Override
    public boolean onCreate() {
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        /**
         * Source: {@link FileProvider#query(Uri, String[], String, String[], String)} .
         */
        if (projection == null) {
            projection = COLUMNS;
        }

        final AssetManager am = getContext().getAssets();
        final String path = getRelativePath(uri);
        long fileSize = 0;
        try {
            final AssetFileDescriptor afd = am.openFd(path);
            fileSize = afd.getLength();
            afd.close();
        } catch(IOException e) {
            Log.e(LOG_TAG, "Can't open asset file", e);
        }

        final String[] cols = new String[projection.length];
        final 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++] = uri.getLastPathSegment();
            } else if (OpenableColumns.SIZE.equals(col)) {
                cols[i] = OpenableColumns.SIZE;
                values[i++] = fileSize;
            }
        }

        final MatrixCursor cursor = new MatrixCursor(cols, 1);
        cursor.addRow(values);
        return cursor;
    }

    @Override
    public String getType(Uri uri) {
        /**
         * Source: {@link FileProvider#getType(Uri)} .
         */
        final String file_name = uri.getLastPathSegment();
        final int lastDot = file_name.lastIndexOf('.');
        if (lastDot >= 0) {
            final String extension = file_name.substring(lastDot + 1);
            final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
            if (mime != null) {
                return mime;
            }
        }

        return "application/octet-stream";
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
        final AssetManager am = getContext().getAssets();
        final String path = getRelativePath(uri);
        if(path == null) {
            throw new FileNotFoundException();
        }
        AssetFileDescriptor afd = null;
        try {
            afd = am.openFd(path);
        } catch(IOException e) {
            Log.e(LOG_TAG, "Can't open asset file", e);
        }
        return afd;
    }

    private String getRelativePath(Uri uri) {
        String path = uri.getPath();
        if (path.charAt(0) == '/') {
            path = path.substring(1);
        }
        return path;
    }
}

2
据我所知,无法分享来自“assets”文件夹的图像。但是可以分享来自“res”文件夹的资源。

你能给我一个例子,如何从资源的Drawable文件夹共享图片? - Buda Gavril
3
格式为:android.resource://[包名]/[类型]/[id]。因此,一个URI示例是:"android.resource://com.your.app/drawable/" + Integer.toStrng(R.drawable.some_resource) - Michael
我尝试使用share.putExtra(Intent.EXTRA_STREAM, Uri.parse("android.resource://com.packake.myapp/drawable/" + Integer.toString(R.drawable.myimage)));它可以在Gmail中分享,但是当我尝试在Yahoo邮件中分享时,我收到了“读取访问被拒绝。无法读取资源”的消息。你有任何想法为什么会这样吗? - Buda Gavril
我认为这取决于应用程序如何处理此类URI。 - Michael
1
@smith324 可能吧。但对我来说这不是一个实际问题。然而这并不意味着你的解决方案不好。+1 - Michael
显示剩余3条评论

1

如果要从资产文件夹分享,我只能推荐cwac-provider库(StreamProvider)。

除了避免开发自己的内容提供程序外,它还为古怪的旧应用程序添加了一些支持(请检查USE_LEGACY_CURSOR_WRAPPER)。


已停用 :( - John Gorenfeld

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接