Cette situation très irritante est due au fait que, lorsque le .apk
est construit, certains actifs sont compressés avant d'être stockés, tandis que d'autres sont traités comme étant déjà compressés (par exemple, les images, les vidéos) et sont laissés tels quels. Ce dernier groupe peut être ouvert à l'aide de openAssetFd
Le premier groupe ne le peut pas - si vous essayez, vous obtenez l'erreur "This file can not be opened as a file descriptor ; it is probably compressed" (Ce fichier ne peut pas être ouvert en tant que descripteur de fichier ; il est probablement compressé).
Une option est de tromper le système de construction pour qu'il ne compresse pas les actifs (voir le lien dans la réponse de @nicstrong), mais c'est délicat. Il vaut mieux essayer de contourner le problème de manière plus prévisible.
La solution que j'ai trouvée utilise le fait que, bien que vous ne puissiez pas ouvrir un AssetFileDescriptor
pour l'actif, vous pouvez toujours ouvrir un InputStream
. Vous pouvez l'utiliser pour copier la ressource dans le cache de fichiers de l'application, puis renvoyer un descripteur à cet effet :
@Override
public AssetFileDescriptor openAssetFile(final Uri uri, final String mode) throws FileNotFoundException
{
final String assetPath = uri.getLastPathSegment(); // or whatever
try
{
final boolean canBeReadDirectlyFromAssets = ... // if your asset going to be compressed?
if (canBeReadDirectlyFromAssets)
{
return getContext().getAssets().openFd(assetPath);
}
else
{
final File cacheFile = new File(getContext().getCacheDir(), assetPath);
cacheFile.getParentFile().mkdirs();
copyToCacheFile(assetPath, cacheFile);
return new AssetFileDescriptor(ParcelFileDescriptor.open(cacheFile, MODE_READ_ONLY), 0, -1);
}
}
catch (FileNotFoundException ex)
{
throw ex;
}
catch (IOException ex)
{
throw new FileNotFoundException(ex.getMessage());
}
}
private void copyToCacheFile(final String assetPath, final File cacheFile) throws IOException
{
final InputStream inputStream = getContext().getAssets().open(assetPath, ACCESS_BUFFER);
try
{
final FileOutputStream fileOutputStream = new FileOutputStream(cacheFile, false);
try
{
//using Guava IO lib to copy the streams, but could also do it manually
ByteStreams.copy(inputStream, fileOutputStream);
}
finally
{
fileOutputStream.close();
}
}
finally
{
inputStream.close();
}
}
Cela signifie que votre application laissera traîner des fichiers de cache, mais ce n'est pas grave. Elle n'essaie pas non plus de réutiliser les fichiers de cache existants, ce qui n'est pas forcément important pour vous.