Die Lösung(ACHTUNG NUR
SDK API >=8):
Problem ist wie im Edit oben beschrieben. Ich habe es so gelösst, das ich mir eine Methode gebaut habe, welche es mir ermöglicht, die Microthumbnails zu refreshen.
Dazu musste ich mich von etwas Code aus dem ANDROID
SDK 2.3.5 bedienen. sollte aber bis zur API 8 abwärtskompatibel sein.
1. MiniThumbFile
Diese Datei konnte ich im normalen
SDK nicht einbinden also habe ich sie mir aus einem Git repository gezogen und per Hand eingebunden und um eine Methode erweitert. "createNewMicroThumbnail"
PHP-Code:
package de.unicate.android.thumbnailtest;
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.Hashtable;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
/**
* This class handles the mini-thumb file. A mini-thumb file consists
* of blocks, indexed by id. Each block has BYTES_PER_MINTHUMB bytes in the
* following format:
*
* 1 byte status (0 = empty, 1 = mini-thumb available)
* 8 bytes magic (a magic number to match what's in the database)
* 4 bytes data length (LEN)
* LEN bytes jpeg data
* (the remaining bytes are unused)
*
* @hide This file is shared between MediaStore and MediaProvider and should remained internal use
* only.
*/
public class MiniThumbFile {
private static final String TAG = "MiniThumbFile";
private static final int MINI_THUMB_DATA_FILE_VERSION = 3;
public static final int BYTES_PER_MINTHUMB = 10000;
private static final int HEADER_SIZE = 1 + 8 + 4;
private Uri mUri;
private RandomAccessFile mMiniThumbFile;
private FileChannel mChannel;
private ByteBuffer mBuffer;
private static Hashtable<String, MiniThumbFile> sThumbFiles =
new Hashtable<String, MiniThumbFile>();
/**
* We store different types of thumbnails in different files. To remain backward compatibility,
* we should hashcode of content://media/external/images/media remains the same.
*/
public static synchronized void reset() {
for (MiniThumbFile file : sThumbFiles.values()) {
file.deactivate();
}
sThumbFiles.clear();
}
public static synchronized MiniThumbFile instance(Uri uri) {
String type = uri.getPathSegments().get(1);
MiniThumbFile file = sThumbFiles.get(type);
// Log.v(TAG, "get minithumbfile for type: "+type);
if (file == null) {
file = new MiniThumbFile(
Uri.parse("content://media/external/" + type + "/media"));
sThumbFiles.put(type, file);
}
return file;
}
private String randomAccessFilePath(int version) {
String directoryName =
Environment.getExternalStorageDirectory().toString()
+ "/DCIM/.thumbnails";
return directoryName + "/.thumbdata" + version + "-" + mUri.hashCode();
}
private void removeOldFile() {
String oldPath = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION - 1);
File oldFile = new File(oldPath);
if (oldFile.exists()) {
try {
oldFile.delete();
} catch (SecurityException ex) {
// ignore
}
}
}
private RandomAccessFile miniThumbDataFile() {
if (mMiniThumbFile == null) {
removeOldFile();
String path = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION);
File directory = new File(path).getParentFile();
if (!directory.isDirectory()) {
if (!directory.mkdirs()) {
Log.e(TAG, "Unable to create .thumbnails directory "
+ directory.toString());
}
}
File f = new File(path);
try {
mMiniThumbFile = new RandomAccessFile(f, "rw");
} catch (IOException ex) {
// Open as read-only so we can at least read the existing
// thumbnails.
ex.printStackTrace();
try {
mMiniThumbFile = new RandomAccessFile(f, "r");
} catch (IOException ex2) {
// ignore exception
}
}
if (mMiniThumbFile != null) {
mChannel = mMiniThumbFile.getChannel();
}
}
return mMiniThumbFile;
}
public MiniThumbFile(Uri uri) {
mUri = uri;
mBuffer = ByteBuffer.allocateDirect(BYTES_PER_MINTHUMB);
}
public synchronized void deactivate() {
if (mMiniThumbFile != null) {
try {
mMiniThumbFile.close();
mMiniThumbFile = null;
} catch (IOException ex) {
// ignore exception
}
}
}
// Get the magic number for the specified id in the mini-thumb file.
// Returns 0 if the magic is not available.
public synchronized long getMagic(long id) {
// check the mini thumb file for the right data. Right is
// defined as having the right magic number at the offset
// reserved for this "id".
RandomAccessFile r = miniThumbDataFile();
if (r != null) {
long pos = id * BYTES_PER_MINTHUMB;
FileLock lock = null;
try {
mBuffer.clear();
mBuffer.limit(1 + 8);
lock = mChannel.lock(pos, 1 + 8, true);
// check that we can read the following 9 bytes
// (1 for the "status" and 8 for the long)
if (mChannel.read(mBuffer, pos) == 9) {
mBuffer.position(0);
if (mBuffer.get() == 1) {
return mBuffer.getLong();
}
}
} catch (IOException ex) {
Log.v(TAG, "Got exception checking file magic: ", ex);
} catch (RuntimeException ex) {
// Other NIO related exception like disk full, read only channel..etc
Log.e(TAG, "Got exception when reading magic, id = " + id +
", disk full or mount read-only? " + ex.getClass());
} finally {
try {
if (lock != null) lock.release();
}
catch (IOException ex) {
// ignore it.
}
}
}
return 0;
}
public synchronized void saveMiniThumbToFile(byte[] data, long id, long magic)
throws IOException {
RandomAccessFile r = miniThumbDataFile();
if (r == null) return;
long pos = id * BYTES_PER_MINTHUMB;
FileLock lock = null;
try {
if (data != null) {
if (data.length > BYTES_PER_MINTHUMB - HEADER_SIZE) {
// not enough space to store it.
return;
}
mBuffer.clear();
mBuffer.put((byte) 1);
mBuffer.putLong(magic);
mBuffer.putInt(data.length);
mBuffer.put(data);
mBuffer.flip();
lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, false);
mChannel.write(mBuffer, pos);
}
} catch (IOException ex) {
Log.e(TAG, "couldn't save mini thumbnail data for "
+ id + "; ", ex);
throw ex;
} catch (RuntimeException ex) {
// Other NIO related exception like disk full, read only channel..etc
Log.e(TAG, "couldn't save mini thumbnail data for "
+ id + "; disk full or mount read-only? " + ex.getClass());
} finally {
try {
if (lock != null) lock.release();
}
catch (IOException ex) {
// ignore it.
}
}
}
/**
* Gallery app can use this method to retrieve mini-thumbnail. Full size
* images share the same IDs with their corresponding thumbnails.
*
* @param id the ID of the image (same of full size image).
* @param data the buffer to store mini-thumbnail.
*/
public synchronized byte [] getMiniThumbFromFile(long id, byte [] data) {
RandomAccessFile r = miniThumbDataFile();
if (r == null) return null;
long pos = id * BYTES_PER_MINTHUMB;
FileLock lock = null;
try {
mBuffer.clear();
lock = mChannel.lock(pos, BYTES_PER_MINTHUMB, true);
int size = mChannel.read(mBuffer, pos);
if (size > 1 + 8 + 4) { // flag, magic, length
mBuffer.position(0);
byte flag = mBuffer.get();
long magic = mBuffer.getLong();
int length = mBuffer.getInt();
if (size >= 1 + 8 + 4 + length && data.length >= length) {
mBuffer.get(data, 0, length);
return data;
}
}
} catch (IOException ex) {
Log.w(TAG, "got exception when reading thumbnail id=" + id + ", exception: " + ex);
} catch (RuntimeException ex) {
// Other NIO related exception like disk full, read only channel..etc
Log.e(TAG, "Got exception when reading thumbnail, id = " + id +
", disk full or mount read-only? " + ex.getClass());
} finally {
try {
if (lock != null) lock.release();
}
catch (IOException ex) {
// ignore it.
}
}
return null;
}
public void createNewMicroThumbnail(Bitmap bitmap, long origId) throws IOException {
long magic = getMagic(origId);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); //bm is the bitmap object
byte[] b = baos.toByteArray();
saveMiniThumbToFile(b, origId, magic);
}
}
Diese Methode erstellt mir aus einer Bitmap eine neue Microthumbnail zur passenden Image.
Man beachte hierbei, das es beim aufruf dieser Methode ein Thumbnailfile geben sollte, sonst wird getMagic(origId) eine Exception werfen.
2. ThumbnailUtils erweitern
Dann habe ich die Klasse ThumbnailUtils erweitert, da (aus welchen Gründen auch immer, wenn das jemand weiß, dann immer raus damit) die Methode "createImageThumbnail" nicht im
SDK verfügbar ist.
PHP-Code:
package de.unicate.android.thumbnailtest;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.provider.MediaStore.Images;
import android.util.Log;
public class ThumbnailUtils extends android.media.ThumbnailUtils {
private static final String TAG = "ThumbnailUtils";
/* Maximum pixels size for created bitmap. */
private static final int MAX_NUM_PIXELS_THUMBNAIL = 512 * 384;
private static final int MAX_NUM_PIXELS_MICRO_THUMBNAIL = 128 * 128;
private static final int UNCONSTRAINED = -1;
/**
* Constant used to indicate we should recycle the input in
* {@link #extractThumbnail(Bitmap, int, int, int)} unless the output is the input.
*/
public static final int OPTIONS_RECYCLE_INPUT = 0x2;
/**
* Constant used to indicate the dimension of mini thumbnail.
* @hide Only used by media framework and media provider internally.
*/
public static final int TARGET_SIZE_MINI_THUMBNAIL = 320;
/**
* Constant used to indicate the dimension of micro thumbnail.
* @hide Only used by media framework and media provider internally.
*/
public static final int TARGET_SIZE_MICRO_THUMBNAIL = 96;
/**
* This method first examines if the thumbnail embedded in EXIF is bigger than our target
* size. If not, then it'll create a thumbnail from original image. Due to efficiency
* consideration, we want to let MediaThumbRequest avoid calling this method twice for
* both kinds, so it only requests for MICRO_KIND and set saveImage to true.
*
* This method always returns a "square thumbnail" for MICRO_KIND thumbnail.
*
* @param filePath the path of image file
* @param kind could be MINI_KIND or MICRO_KIND
* @return Bitmap
*
* @hide This method is only used by media framework and media provider internally.
*/
public static Bitmap createImageThumbnail(String filePath, int kind) {
boolean wantMini = (kind == Images.Thumbnails.MINI_KIND);
int targetSize = wantMini
? TARGET_SIZE_MINI_THUMBNAIL
: TARGET_SIZE_MICRO_THUMBNAIL;
int maxPixels = wantMini
? MAX_NUM_PIXELS_THUMBNAIL
: MAX_NUM_PIXELS_MICRO_THUMBNAIL;
Bitmap bitmap = null;
if (bitmap == null) {
try {
FileDescriptor fd = new FileInputStream(filePath).getFD();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fd, null, options);
if (options.mCancel || options.outWidth == -1
|| options.outHeight == -1) {
return null;
}
options.inSampleSize = computeSampleSize(
options, targetSize, maxPixels);
options.inJustDecodeBounds = false;
options.inDither = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);
} catch (IOException ex) {
Log.e(TAG, "", ex);
}
}
if (kind == Images.Thumbnails.MICRO_KIND) {
// now we make it a "square thumbnail" for MICRO_KIND thumbnail
bitmap = extractThumbnail(bitmap,
TARGET_SIZE_MICRO_THUMBNAIL,
TARGET_SIZE_MICRO_THUMBNAIL, OPTIONS_RECYCLE_INPUT);
}
return bitmap;
}
/*
* Compute the sample size as a function of minSideLength
* and maxNumOfPixels.
* minSideLength is used to specify that minimal width or height of a
* bitmap.
* maxNumOfPixels is used to specify the maximal size in pixels that is
* tolerable in terms of memory usage.
*
* The function returns a sample size based on the constraints.
* Both size and minSideLength can be passed in as IImage.UNCONSTRAINED,
* which indicates no care of the corresponding constraint.
* The functions prefers returning a sample size that
* generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED.
*
* Also, the function rounds up the sample size to a power of 2 or multiple
* of 8 because BitmapFactory only honors sample size this way.
* For example, BitmapFactory downsamples an image by 2 even though the
* request is 3. So we round up the sample size to avoid OOM.
*/
private static int computeSampleSize(BitmapFactory.Options options,
int minSideLength, int maxNumOfPixels) {
int initialSize = computeInitialSampleSize(options, minSideLength,
maxNumOfPixels);
int roundedSize;
if (initialSize <= 8 ) {
roundedSize = 1;
while (roundedSize < initialSize) {
roundedSize <<= 1;
}
} else {
roundedSize = (initialSize + 7) / 8 * 8;
}
return roundedSize;
}
private static int computeInitialSampleSize(BitmapFactory.Options options,
int minSideLength, int maxNumOfPixels) {
double w = options.outWidth;
double h = options.outHeight;
int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
(int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
int upperBound = (minSideLength == UNCONSTRAINED) ? 128 :
(int) Math.min(Math.floor(w / minSideLength),
Math.floor(h / minSideLength));
if (upperBound < lowerBound) {
// return the larger one when there is no overlapping zone.
return lowerBound;
}
if ((maxNumOfPixels == UNCONSTRAINED) &&
(minSideLength == UNCONSTRAINED)) {
return 1;
} else if (minSideLength == UNCONSTRAINED) {
return lowerBound;
} else {
return upperBound;
}
}
}
Zu guter Letzt muss noch eine Methode her die mir die Thumbnails refreshed.
PHP-Code:
package de.unicate.android.thumbnailtest;
import android.content.ContentResolver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;
public class BitmapUtilities {
private static final String TAG = BitmapUtilities.class.getSimpleName();
public static void refreshMicroThumbnails(ContentResolver cr, long[] thumbnails2Refresh) {
final String[] PROJECTION = new String[] {MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA};
MiniThumbFile thumbFile = MiniThumbFile.instance(MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI);
for(long origId : thumbnails2Refresh) {
Cursor c = null;
try {
Uri uri = Uri.parse(
MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI.buildUpon().appendPath(String.valueOf(origId))
.toString().replaceFirst("thumbnails", "media"));
c = cr.query(uri, PROJECTION, null, null, null);
if (c == null || !c.moveToFirst()) {
Log.e(TAG, "cannot find thumbnailpath!");
return;
}
String filePath = c.getString(1);
Bitmap bitmap = ThumbnailUtils.createImageThumbnail(filePath, MediaStore.Images.Thumbnails.MICRO_KIND);
thumbFile.createNewMicroThumbnail(bitmap, origId);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(c!=null)c.close();
}
}
}
}
In einer Activity z.B. kann das ganze dann folgendermaßen aufgerufen werden:
PHP-Code:
private Bitmap getThumbnail(long id, int kind, boolean forceRefresh) {
if(forceRefresh)
BitmapUtilities.refreshMicroThumbnails(getContentResolver(), new long[]{id});
return MediaStore.Images.Thumbnails.getThumbnail(this.getContentResolver(), id, kind, null);
}
Ich hoffe ich kann damit noch jemandem helfen, da dieses Problem mich zum Wahnsinn getrieben hat.