I'm trying to share an image from my assets folder. My code is:
Intent share = n开发者_运维知识库ew 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"));
but it doesn't work. Do you have any ideas?
It is possible to share files (images including) from the assets folder through a custom ContentProvider
You need to extend ContentProvider
, register it in your manifest and implement the openAssetFile
method. You can then assess the assets via Uri
s
@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;
}
Complementing what @intrepidis answered:
You will need override methods like example class above:
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;
}
}
I needed to override two times the query method. And add these lines above tag in your androidmanifest.xml:
<provider
android:name="com.android.example.AssetsProvider"
android:authorities="com.android.example"
android:grantUriPermissions="true"
android:exported="true" />
And with this, all work like a charm :D
This blog explains it all:
http://nowherenearithaca.blogspot.co.uk/2012/03/too-easy-using-contentprovider-to-send.html
Basically, this goes in the manifest:
<provider android:name="yourclass.that.extendsContentProvider" android:authorities="com.yourdomain.whatever"/>
The content provider class has this:
@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);
}
And the calling code does this:
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);
Many apps require you to provide name and size of the image. So here is an improved code (using Google's FileProvider code as an example):
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;
}
}
Since none of the other answers here worked for me (in 2019) I made a workaround by copying the asset to the app's internal file directory and then sharing this file. In my case, I needed to share a pdf file from the assets folder.
In the AndroidManifest.xml add a file provider (no need to use a custom one):
<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>
Create a filepaths.xml file in res/xml/
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="root"
path="/" />
</paths>
Of course you should use a subdirectory here if you manage other files in your app directory.
Now in the class where you want to trigger the share intent.
1. Create an empty file in the files directory
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. Copy the content of the asset to the file
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. Create a share intent for the file
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. Execute 1-3 and fire the intent
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. Call the function
sharePdfAsset("your_pdf_asset.pdf", Intent.ACTION_SEND)
If you want to delete the file after sharing it, you probably could use startActivityForResult()
and delete it afterwards. By changing the intentAction
you can also use this process for an "open with..." action by using Intent.ACTION_VIEW
.
For assets
, filesDir
, ... you need to be in an Activity
or have a Context
of course.
AFAIK, there's no way to share an image from the assets
folder. But it's possible to share resources from the res
folder.
To share from assets folder I can only recommend the cwac-provider library (StreamProvider).
Among avoiding to develop your own content provider, it adds some support for capricious legacy apps (check USE_LEGACY_CURSOR_WRAPPER
).
精彩评论