I'm using sharedElementTransition in my app and i've used a custom class for sharedElementCallback to update the views at run time. It was causing OutOfMemory errors at sometimes, so i searched about it and from this solution i used code of LeakFreeSupportSharedElementCallback to avoid crashes but i still get following crash logs very often.
Fatal Exception: java.lang.OutOfMemoryError: Failed to allocate a 8357052 byte allocation with 953048 free bytes and 930KB until OOM
at dalvik.system.VMRuntime.newNonMovableArray(VMRuntime.java)
at android.graphics.Bitmap.nativeCopy(Bitmap.java)
at android.graphics.Bitmap.copy(Bitmap.java:684)
at com.fayvo.ui.main.home.post.PostDetailSharedElementCallback.onCreateSnapshotView(PostDetailSharedElementCallback.java:279)
at android.support.v4.app.ActivityCompat$SharedElementCallback21Impl.onCreateSnapshotView(ActivityCompat.java:609)
at android.app.ActivityTransitionCoordinator.createSnapshots(ActivityTransitionCoordinator.java:666)
at android.app.EnterTransitionCoordinator.startSharedElementTransition(EnterTransitionCoordinator.java:400)
at android.app.EnterTransitionCoordinator.-wrap4(EnterTransitionCoordinator.java)
at android.app.EnterTransitionCoordinator$5$1$1.run(EnterTransitionCoordinator.java:475)
at android.app.ActivityTransitionCoordinator.startTransition(ActivityTransitionCoordinator.java:836)
at android.app.EnterTransitionCoordinator$5$1.onPreDraw(EnterTransitionCoordinator.java:472)
at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:1013)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2513)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1522)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7098)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:927)
at android.view.Choreographer.doCallbacks(Choreographer.java:702)
at android.view.Choreographer.doFrame(Choreographer.java:638)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:913)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6682)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)
CustomCallback class:
public class CustomSharedElementCallback extends SharedElementCallback {
static final String BUNDLE_SNAPSHOT_BITMAP = "BUNDLE_SNAPSHOT_BITMAP";
static final String BUNDLE_SNAPSHOT_IMAGE_SCALETYPE = "BUNDLE_SNAPSHOT_IMAGE_SCALETYPE";
static final String BUNDLE_SNAPSHOT_IMAGE_MATRIX = "BUNDLE_SNAPSHOT_IMAGE_MATRIX";
static final String BUNDLE_SNAPSHOT_TYPE = "BUNDLE_SNAPSHOT_TYPE";
static final String BUNDLE_SNAPSHOT_TYPE_IMAGE_VIEW = "BUNDLE_SNAPSHOT_TYPE";
private static int MAX_IMAGE_SIZE = (1024 * 1024);
private List<View> mSharedElements;
private int currentPosition = 0;
private boolean enableChangeImageTransform;
private SparseArray<Matrix> tempMatrixes;
public PostDetailSharedElementCallback() {
mSharedElements = new ArrayList<>();
tempMatrixes = new SparseArray<>();
}
#Override
public void onSharedElementStart(List<String> sharedElementNames,
List<View> sharedElements,
List<View> sharedElementSnapshots) {
/*AppLogger.d("usm_shared_callback_0.1", "onSharedElementStart: " + sharedElementNames.get(0)
+ " ,enableChangeImageTransform= " + enableChangeImageTransform
);*/
boolean allowTransform = enableChangeImageTransform && sharedElements != null && sharedElements.size() > 0;
if (allowTransform && sharedElements.get(0) instanceof CustomPhotoView) {
((CustomPhotoView) sharedElements.get(0)).setScaleType(ImageView.ScaleType.CENTER_CROP);
} else if (allowTransform && sharedElements.get(0) instanceof ImageView) {
((ImageView) sharedElements.get(0)).setScaleType(ImageView.ScaleType.FIT_CENTER);
}
}
#Override
public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
/*AppLogger.d("usm_shared_callback_0.2", "onSharedElementEnd: " + sharedElementNames.get(0)
+ " ,enableChangeImageTransform= " + enableChangeImageTransform
);*/
boolean allowTransform = enableChangeImageTransform && sharedElements != null && sharedElements.size() > 0;
if (allowTransform && sharedElements.get(0) instanceof CustomPhotoView) {
((CustomPhotoView) sharedElements.get(0)).setScaleType(ImageView.ScaleType.FIT_CENTER);
// updateCloudChipBoxTagsView(sharedElementNames, sharedElements);
updateBoxIconView(sharedElementNames, sharedElements);
updateUserNameView(sharedElementNames, sharedElements);
} else if (allowTransform && sharedElements.get(0) instanceof ImageView) {
((ImageView) sharedElements.get(0)).setScaleType(ImageView.ScaleType.CENTER_CROP);
}
}
private void updateCloudChipBoxTagsView(List<String> sharedElementNames, List<View> sharedElements) {
int i = 0;
for (String sharedName : sharedElementNames) {
if (sharedName.contains(PostTags.TRANSITION_NAME_TAGS)) {
if (sharedElements.get(i) instanceof ChipCloud) {
ChipCloud chipCloud = ((ChipCloud) sharedElements.get(i));
int textColor = ContextCompat.getColor(chipCloud.getContext(), R.color.fayvo_color);
// adding modifyChips method to make animation transition experience a bit better
chipCloud.modifyChips(chipCloud.getContext().getResources().getDimension(R.dimen.hint_text_size), textColor);
break;
}
}
i++;
}
}
private void updateBoxIconView(List<String> sharedElementNames, List<View> sharedElements) {
int i = 0;
for (String sharedName : sharedElementNames) {
if (sharedName.contains(PostTags.TRANSITION_NAME_BOX_ICON)) {
if (sharedElements.get(i) instanceof ImageView) {
ImageView ivBoxIcon = (ImageView) sharedElements.get(i);
int color = ContextCompat.getColor(ivBoxIcon.getContext(), R.color.fayvo_color);
ivBoxIcon.setColorFilter(color);
break;
}
}
i++;
}
}
private void updateUserNameView(List<String> sharedElementNames, List<View> sharedElements) {
int i = 0;
for (String sharedName : sharedElementNames) {
if (sharedName.contains(PostTags.TRANSITION_NAME_USERNAME)) {
if (sharedElements.get(i) instanceof TextView) {
TextView textView = (TextView) sharedElements.get(i);
int color = ContextCompat.getColor(textView.getContext(), R.color.black);
float textSizePx = textView.getContext().getResources().getDimension(R.dimen.et_text_size);
textView.setTextColor(color);
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx);
break;
}
}
i++;
}
}
#Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
removeObsoleteElements(names, sharedElements, mapObsoleteElements(names));
// update transition names
setTransitionNames();
for (int i = 0; i < mSharedElements.size(); i++)
mapSharedElement(names, sharedElements, mSharedElements.get(i));
}
public int getCurrentPosition() {
return currentPosition;
}
public void setCurrentPosition(int position) {
// AppLogger.d("usm_shared_callback_1", "currentPosition= " + position);
this.currentPosition = position;
}
public void setEnableChangeImageTransform(boolean enableTransform) {
this.enableChangeImageTransform = enableTransform;
}
/**
* This method is used to set Transition name of shared views.
* Note: Keep the sequence exactly same in which the views are
* passed for transition.
*/
public void setTransitionNames() {
// AppLogger.d("usm_shared_callback_2", "Setting transition names: " + currentPosition);
if (mSharedElements.size() > 0)
ViewCompat.setTransitionName(mSharedElements.get(0), PostTags.TRANSITION_NAME_POST_BODY + currentPosition);
if (mSharedElements.size() > 1)
ViewCompat.setTransitionName(mSharedElements.get(1), PostTags.TRANSITION_NAME_USER_PIC + currentPosition);
if (mSharedElements.size() > 2)
ViewCompat.setTransitionName(mSharedElements.get(2), PostTags.TRANSITION_NAME_USERNAME + currentPosition);
if (mSharedElements.size() > 3)
ViewCompat.setTransitionName(mSharedElements.get(3), PostTags.TRANSITION_NAME_TAGS + currentPosition);
if (mSharedElements.size() > 4)
ViewCompat.setTransitionName(mSharedElements.get(4), PostTags.TRANSITION_NAME_BOX_ICON + currentPosition);
}
public List<View> getSharedViews() {
// AppLogger.d("usm_shared_callback_4", "setSharedViews is called");
if (mSharedElements != null)
return mSharedElements;
return null;
}
public void setSharedViews(#NonNull View... sharedViews) {
// AppLogger.d("usm_shared_callback_4", "setSharedViews is called");
clearSharedViews();
for (View view : sharedViews) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mSharedElements.add(view);
}
}
}
public void clearSharedViews() {
mSharedElements.clear();
tempMatrixes.clear();
}
/**
* Maps all views that don't start with "android" namespace.
*
* #param names All shared element names.
* #return The obsolete shared element names.
*/
#NonNull
private List<String> mapObsoleteElements(List<String> names) {
List<String> elementsToRemove = new ArrayList<>(names.size());
for (String name : names) {
if (name.startsWith("android")) continue;
elementsToRemove.add(name);
}
return elementsToRemove;
}
/**
* Removes obsolete elements from names and shared elements.
*
* #param names Shared element names.
* #param sharedElements Shared elements.
* #param elementsToRemove The elements that should be removed.
*/
private void removeObsoleteElements(List<String> names,
Map<String, View> sharedElements,
List<String> elementsToRemove) {
if (elementsToRemove.size() > 0) {
names.removeAll(elementsToRemove);
for (String elementToRemove : elementsToRemove) {
sharedElements.remove(elementToRemove);
}
}
}
/**
* Puts a shared element to transitions and names.
*
* #param names The names for this transition.
* #param sharedElements The elements for this transition.
* #param view The view to add.
*/
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void mapSharedElement(List<String> names, Map<String, View> sharedElements, View view) {
String transitionName = view.getTransitionName();
names.add(transitionName);
sharedElements.put(transitionName, view);
}
/**
* This method is overridden to avoid memory leaks
* #param context
* #param snapshot
* #return
*/
#Override
public View onCreateSnapshotView(Context context, Parcelable snapshot) {
// AppLogger.d("usm_shared_element_call_back", "onCreateSnapshotView: mSharedElements= " + mSharedElements.size());
View view = null;
if (snapshot instanceof Bundle) {
Bundle bundle = (Bundle) snapshot;
Bitmap bitmap = bundle.getParcelable(BUNDLE_SNAPSHOT_BITMAP);
if (bitmap == null) {
bundle.clear();
return null;
}
// Curiously, this is required to have the bitmap be GCed almost immediately after transition ends
// otherwise, garbage-collectable mem will still build up quickly
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false);
if (bitmap == null) {
return null;
}
if (BUNDLE_SNAPSHOT_TYPE_IMAGE_VIEW.equals(((Bundle) snapshot).getString(BUNDLE_SNAPSHOT_TYPE))) {
ImageView imageView = new ImageView(context);
view = imageView;
imageView.setImageBitmap(bitmap);
imageView.setScaleType(
ImageView.ScaleType.valueOf(bundle.getString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE)));
if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) {
float[] values = bundle.getFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX);
Matrix matrix = new Matrix();
matrix.setValues(values);
imageView.setImageMatrix(matrix);
}
} else {
view = new View(context);
Resources resources = context.getResources();
view.setBackground(new BitmapDrawable(resources, bitmap));
}
bundle.clear();
}
return view;
// return super.onCreateSnapshotView(context, snapshot);
}
/**
* This method is overridden to avoid memory leaks
* #param sharedElement
* #param viewToGlobalMatrix
* #param screenBounds
* #return
*/
#Override
public Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix, RectF screenBounds) {
//AppLogger.d("usm_shared_element_call_back", "onCaptureSharedElementSnapshot: mSharedElements= " + mSharedElements.size()
// + " ,sharedElement name= " + sharedElement.getTransitionName() + " ,id= " + sharedElement.getId());
if (sharedElement instanceof ImageView) {
ImageView imageView = ((ImageView) sharedElement);
Drawable d = imageView.getDrawable();
Drawable bg = imageView.getBackground();
if (d != null && (bg == null || bg.getAlpha() == 0)) {
Bitmap bitmap = TransitionUtils.createDrawableBitmap(d);
if (bitmap != null) {
Bundle bundle = new Bundle();
bundle.putParcelable(BUNDLE_SNAPSHOT_BITMAP, bitmap);
bundle.putString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE,
imageView.getScaleType().toString());
if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) {
Matrix matrix = imageView.getImageMatrix();
float[] values = new float[9];
matrix.getValues(values);
bundle.putFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX, values);
}
bundle.putString(BUNDLE_SNAPSHOT_TYPE, BUNDLE_SNAPSHOT_TYPE_IMAGE_VIEW);
return bundle;
}
}
}
if (tempMatrixes.get(sharedElement.getId(), null) == null) {
tempMatrixes.put(sharedElement.getId(), new Matrix(viewToGlobalMatrix));
} else {
tempMatrixes.get(sharedElement.getId()).set(viewToGlobalMatrix);
}
Bundle bundle = new Bundle();
Bitmap bitmap = TransitionUtils.createViewBitmap(sharedElement, tempMatrixes.get(sharedElement.getId()), screenBounds);
bundle.putParcelable(BUNDLE_SNAPSHOT_BITMAP, bitmap);
return bundle;
// return super.onCaptureSharedElementSnapshot(sharedElement, viewToGlobalMatrix, screenBounds);
}
}
In your manifest file under application tag add the following line
<application
android:largeHeap="true"
>
That is not a very good approach but this will resolve your issue or you have to find a way to manage your memory allocation. But these days devices are coming with large memories. So this will not affect noticeable performance. You are good to go with this.
For further information regarding memory allocation do visit this link:
performance and memory
Related
I'd like to use ExoPlayer2 with playlists having possibility to dinamically change the tracks (add or remove them from playlist) and change the loop settings.
Since ConcatenatingMediaSource has static arrays (and not lists), I'm implementing a DynamicMediaSource, like Concatenating one but with lists instead of arrays and one mode method addSource to add one more media source to the list.
public void addSource(MediaSource mediaSource) {
this.mediaSources.add(mediaSource);
duplicateFlags = buildDuplicateFlags(this.mediaSources);
if(!mediaSources.isEmpty())
prepareSource(mediaSources.size() -1);
else
prepareSource(0);
}
When I invoke addSource
MediaSource ms = buildMediaSource(mynewuri, null);
mediaSource.addSource(ms);
the track is added to the arrays but it seems something is missing because I always obtain ArrayOutOfBoundsException in createPeriod method.
In createPeriod the method
mediaSources.get(sourceIndex)...
is trying to access the index = mediaSources.size().
Can you help me?
I eventually managed it.
It was my fault during the conversion from arrays to lists.
I had to use SparseArrays for timelines and manifests and everything began to work.
In the DynamicMediaSource simply set the following types:
private final List<MediaSource> mediaSources;
private final SparseArray<Timeline> timelines;
private final SparseArray<Object> manifests;
private final Map<MediaPeriod, Integer> sourceIndexByMediaPeriod;
private SparseArray<Boolean> duplicateFlags;
you have to use sparse arrays to set the proper values into the timelines and manifests in the method
private void handleSourceInfoRefreshed(int sourceFirstIndex, Timeline sourceTimeline,
Object sourceManifest) {
// Set the timeline and manifest.
timelines.put(sourceFirstIndex, sourceTimeline);
manifests.put(sourceFirstIndex, sourceManifest);
// Also set the timeline and manifest for any duplicate entries of the same source.
for (int i = sourceFirstIndex + 1; i < mediaSources.size(); i++) {
if (mediaSources.get(i).equals(mediaSources.get(sourceFirstIndex))) {
timelines.put(i, sourceTimeline);
manifests.put(i, sourceManifest);
}
}
for(int i= 0; i<mediaSources.size(); i++){
if(timelines.get(i) == null){
// Don't invoke the listener until all sources have timelines.
return;
}
}
timeline = new DynamicTimeline(new ArrayList(asList(timelines)));
listener.onSourceInfoRefreshed(timeline, new ArrayList(asList(manifests)));
}
Here is the complete code of DynamicMediaSource class:
public final class DynamicMediaSource implements MediaSource {
private static final String TAG = "DynamicSource";
private final List<MediaSource> mediaSources;
private final List<Timeline> timelines;
private final List<Object> manifests;
private final Map<MediaPeriod, Integer> sourceIndexByMediaPeriod;
private SparseArray<Boolean> duplicateFlags;
private Listener listener;
private DynamicTimeline timeline;
/**
* #param mediaSources The {#link MediaSource}s to concatenate. It is valid for the same
* {#link MediaSource} instance to be present more than once in the array.
*/
public DynamicMediaSource(MediaSource... mediaSources) {
this.mediaSources = new ArrayList<MediaSource>(Arrays.asList(mediaSources));
timelines = new ArrayList<Timeline>();
manifests = new ArrayList<Object>();
sourceIndexByMediaPeriod = new HashMap<>();
duplicateFlags = buildDuplicateFlags(this.mediaSources);
}
public void addSource(MediaSource mediaSource) {
this.mediaSources.add(mediaSource);
duplicateFlags = buildDuplicateFlags(this.mediaSources);
/*if(!mediaSources.isEmpty())
prepareSource(mediaSources.size() -1);
else
prepareSource(0);*/
}
#Override
public void prepareSource(Listener listener) {
this.listener = listener;
for (int i = 0; i < mediaSources.size(); i++) {
prepareSource(i);
/*if (duplicateFlags.get(i) == null || !duplicateFlags.get(i)) {
final int index = i;
mediaSources.get(i).prepareSource(new Listener() {
#Override
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
handleSourceInfoRefreshed(index, timeline, manifest);
}
});
}*/
}
}
private void prepareSource(int sourceindex) {
if (duplicateFlags.get(sourceindex) == null || !duplicateFlags.get(sourceindex)) {
final int index = sourceindex;
mediaSources.get(sourceindex).prepareSource(new Listener() {
#Override
public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
handleSourceInfoRefreshed(index, timeline, manifest);
}
});
}
}
#Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
for (int i = 0; i < mediaSources.size(); i++) {
if (duplicateFlags.get(i) == null || !duplicateFlags.get(i)) {
mediaSources.get(i).maybeThrowSourceInfoRefreshError();
}
}
}
#Override
public MediaPeriod createPeriod(int index, Callback callback, Allocator allocator,
long positionUs) {
int sourceIndex = timeline.getSourceIndexForPeriod(index);
int periodIndexInSource = index - timeline.getFirstPeriodIndexInSource(sourceIndex);
MediaPeriod mediaPeriod = mediaSources.get(sourceIndex).createPeriod(periodIndexInSource, callback,
allocator, positionUs);
sourceIndexByMediaPeriod.put(mediaPeriod, sourceIndex);
return mediaPeriod;
}
#Override
public void releasePeriod(MediaPeriod mediaPeriod) {
int sourceIndex = sourceIndexByMediaPeriod.get(mediaPeriod);
sourceIndexByMediaPeriod.remove(mediaPeriod);
mediaSources.get(sourceIndex).releasePeriod(mediaPeriod);
}
#Override
public void releaseSource() {
for (int i = 0; i < mediaSources.size(); i++) {
if (duplicateFlags.get(i) == null || !duplicateFlags.get(i)) {
mediaSources.get(i).releaseSource();
}
}
}
private void handleSourceInfoRefreshed(int sourceFirstIndex, Timeline sourceTimeline,
Object sourceManifest) {
// Set the timeline and manifest.
timelines.add(sourceFirstIndex, sourceTimeline);
manifests.add(sourceFirstIndex, sourceManifest);
// Also set the timeline and manifest for any duplicate entries of the same source.
for (int i = sourceFirstIndex + 1; i < mediaSources.size(); i++) {
if (mediaSources.get(i).equals(mediaSources.get(sourceFirstIndex))) {
timelines.add(i, sourceTimeline);
manifests.add(i, sourceManifest);
}
}
for (Timeline timeline : timelines) {
if (timeline == null) {
// Don't invoke the listener until all sources have timelines.
return;
}
}
timeline = new DynamicTimeline(new ArrayList(timelines));
listener.onSourceInfoRefreshed(timeline, new ArrayList(manifests));
}
private static SparseArray<Boolean> buildDuplicateFlags(List<MediaSource> mediaSources) {
SparseArray<Boolean> duplicateFlags = new SparseArray<Boolean>();
IdentityHashMap<MediaSource, Void> sources = new IdentityHashMap<>(mediaSources.size());
for (int i = 0; i < mediaSources.size(); i++) {
MediaSource mediaSource = mediaSources.get(i);
if (!sources.containsKey(mediaSource)) {
sources.put(mediaSource, null);
} else {
duplicateFlags.setValueAt(i, true);
}
}
return duplicateFlags;
}
/**
* A {#link Timeline} that is the concatenation of one or more {#link Timeline}s.
*/
private static final class DynamicTimeline extends Timeline {
private final List<Timeline> timelines;
private final List<Integer> sourcePeriodOffsets;
private final List<Integer> sourceWindowOffsets;
public DynamicTimeline(List<Timeline> timelines) {
List<Integer> sourcePeriodOffsets = new ArrayList<>();
List<Integer> sourceWindowOffsets = new ArrayList<>();
int periodCount = 0;
int windowCount = 0;
for (Timeline timeline : timelines) {
periodCount += timeline.getPeriodCount();
windowCount += timeline.getWindowCount();
sourcePeriodOffsets.add(periodCount);
sourceWindowOffsets.add(windowCount);
}
this.timelines = timelines;
this.sourcePeriodOffsets = sourcePeriodOffsets;
this.sourceWindowOffsets = sourceWindowOffsets;
}
#Override
public int getWindowCount() {
return sourceWindowOffsets.get(sourceWindowOffsets.size() - 1);
}
#Override
public Window getWindow(int windowIndex, Window window, boolean setIds) {
int sourceIndex = getSourceIndexForWindow(windowIndex);
int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex);
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
timelines.get(sourceIndex).getWindow(windowIndex - firstWindowIndexInSource, window, setIds);
window.firstPeriodIndex += firstPeriodIndexInSource;
window.lastPeriodIndex += firstPeriodIndexInSource;
return window;
}
#Override
public int getPeriodCount() {
return sourcePeriodOffsets.get(sourcePeriodOffsets.size() - 1);
}
#Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
int sourceIndex = getSourceIndexForPeriod(periodIndex);
int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex);
int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex);
timelines.get(sourceIndex).getPeriod(periodIndex - firstPeriodIndexInSource, period, setIds);
period.windowIndex += firstWindowIndexInSource;
if (setIds) {
period.uid = Pair.create(sourceIndex, period.uid);
}
return period;
}
#Override
public int getIndexOfPeriod(Object uid) {
if (!(uid instanceof Pair)) {
return C.INDEX_UNSET;
}
Pair<?, ?> sourceIndexAndPeriodId = (Pair<?, ?>) uid;
if (!(sourceIndexAndPeriodId.first instanceof Integer)) {
return C.INDEX_UNSET;
}
int sourceIndex = (Integer) sourceIndexAndPeriodId.first;
Object periodId = sourceIndexAndPeriodId.second;
if (sourceIndex < 0 || sourceIndex >= timelines.size()) {
return C.INDEX_UNSET;
}
int periodIndexInSource = timelines.get(sourceIndex).getIndexOfPeriod(periodId);
return periodIndexInSource == C.INDEX_UNSET ? C.INDEX_UNSET
: getFirstPeriodIndexInSource(sourceIndex) + periodIndexInSource;
}
private int getSourceIndexForPeriod(int periodIndex) {
return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex, true, false) + 1;
}
private int getFirstPeriodIndexInSource(int sourceIndex) {
return sourceIndex == 0 ? 0 : sourcePeriodOffsets.get(sourceIndex - 1);
}
private int getSourceIndexForWindow(int windowIndex) {
return Util.binarySearchFloor(sourceWindowOffsets, windowIndex, true, false) + 1;
}
private int getFirstWindowIndexInSource(int sourceIndex) {
return sourceIndex == 0 ? 0 : sourceWindowOffsets.get(sourceIndex - 1);
}
}
}
I have two following methods those load my Bitmaps. And when I've tried to load huge image 2000px x 6000px I've got UIfreeze for about 3 seconds. Can anubody help me to improve loading speed of huge images? I's important for me on screen orientation change, because these methods always have been called by that lib and my UI always freezes for about 3 second before rotate...
/**
* Async task used to get image details
*/
private static class TilesInitTask implements Runnable {
private final WeakReference<ScaleImageView> viewRef;
private final WeakReference<Context> contextRef;
private final WeakReference<DecoderFactory<? extends ImageRegionDecoder>> decoderFactoryRef;
private final Uri source;
private ImageRegionDecoder decoder;
private Exception exception;
public TilesInitTask(ScaleImageView view, Context context, DecoderFactory<? extends ImageRegionDecoder> decoderFactory, Uri source) {
this.viewRef = new WeakReference<>(view);
this.contextRef = new WeakReference<>(context);
this.decoderFactoryRef = new WeakReference<DecoderFactory<? extends ImageRegionDecoder>>(decoderFactory);
this.source = source;
}
public void run() {
ScaleImageView view = null;
try {
String sourceUri = source.toString();
Context context = contextRef.get();
DecoderFactory<? extends ImageRegionDecoder> decoderFactory = decoderFactoryRef.get();
view = viewRef.get();
if (context != null && decoderFactory != null && view != null) {
decoder = decoderFactory.make();
Point dimensions = decoder.init(context, source);
int sWidth = dimensions.x;
int sHeight = dimensions.y;
int exifOrientation = view.getExifOrientation(sourceUri);
if (view.sRegion != null) {
sWidth = view.sRegion.width();
sHeight = view.sRegion.height();
}
view.onTilesInited(decoder, sWidth, sHeight, exifOrientation);
}
} catch (Exception e) {
Log.e(TAG, "Failed to initialise bitmap decoder", e);
this.exception = e;
if (view != null && view.onImageEventListener != null) {
view.onImageEventListener.onImageLoadError(exception);
}
}
}
}
/**
* Called by worker task when decoder is ready and image size and EXIF orientation is known.
*/
private synchronized void onTilesInited(ImageRegionDecoder decoder, int sWidth, int sHeight, int sOrientation) {
// If actual dimensions don't match the declared size, reset everything.
if (this.sWidth > 0 && this.sHeight > 0 && (this.sWidth != sWidth || this.sHeight != sHeight)) {
reset(false);
if (bitmap != null) {
if (!bitmapIsCached) {
bitmap.recycle();
}
bitmap = null;
bitmapIsPreview = false;
bitmapIsCached = false;
}
}
this.decoder = decoder;
this.sWidth = sWidth;
this.sHeight = sHeight;
this.sOrientation = sOrientation;
checkReady();
checkImageLoaded();
invalidate();
requestLayout();
}
/**
* Async task used to load images
*/
private static class TileLoadTask implements Runnable {
private final WeakReference<ScaleImageView> viewRef;
private final WeakReference<ImageRegionDecoder> decoderRef;
private final WeakReference<Tile> tileRef;
private Exception exception;
public TileLoadTask(ScaleImageView view, ImageRegionDecoder decoder, Tile tile) {
this.viewRef = new WeakReference<>(view);
this.decoderRef = new WeakReference<>(decoder);
this.tileRef = new WeakReference<>(tile);
tile.loading = true;
}
public void run() {
ScaleImageView view = null;
try {
view = viewRef.get();
ImageRegionDecoder decoder = decoderRef.get();
Tile tile = tileRef.get();
if (decoder != null && tile != null && view != null && decoder.isReady()) {
synchronized (view.decoderLock) {
// Update tile's file sRect according to rotation
view.fileSRect(tile.sRect, tile.fileSRect);
if (view.sRegion != null) {
tile.fileSRect.offset(view.sRegion.left, view.sRegion.top);
}
tile.bitmap = decoder.decodeRegion(tile.fileSRect, tile.sampleSize);
tile.loading = false;
view.onTileLoaded();
}
} else if (tile != null) {
tile.loading = false;
}
} catch (Exception e) {
Log.e(TAG, "Failed to decode tile", e);
this.exception = e;
if (view != null && view.onImageEventListener != null) {
view.onImageEventListener.onTileLoadError(exception);
}
}
}
}
/**
* Called by worker task when a tile has loaded. Redraws the view.
*/
private synchronized void onTileLoaded() {
checkReady();
checkImageLoaded();
if (isBaseLayerReady() && bitmap != null) {
if (!bitmapIsCached) {
bitmap.recycle();
}
bitmap = null;
bitmapIsPreview = false;
bitmapIsCached = false;
}
invalidate();
}
Working with bitmaps and pictures is a sensitive operation. I suggest you to use a library for these kind of operations suces as
https://github.com/nostra13/Android-Universal-Image-Loader
http://square.github.io/picasso/
Edit:
Here you can how to add and init library.
Correct usage of Universal Image Loader
After you init with best configuration you can use file paths below which is suitable for your operation:
"http://example.com/image.png" // from Web
"file:///mnt/sdcard/image.png" // from SD card
"file:///mnt/sdcard/video.mp4" // from SD card (video thumbnail)
"content://media/external/images/media/13" // from content provider
"content://media/external/video/media/13" // from content provider (video thumbnail)
"assets://image.png" // from assets
"drawable://" + R.drawable.img // from drawables (non-9patch images)
And finally you can load it with:
ImageLoader imageLoader = ImageLoader.getInstance(); // Get singleton instance
imageLoader.displayImage(imageUri, imageView);
Good luck.
Recently I tried to write an app which captures frame buffer from say USB camera (that need to be displayed as preview), and image processed output that need to be overlapped on the preview. Can anybody give me some pointers how to start? Moreover I need to draw some rectangle on the preview.
I was trying to use multiple SurfaceView to draw but not succeeded. Can anybody help me out?
What you need it the bitmap and image view reference counting. Android keeps image data in native array, which is not recycling automatically when the vm GC running. Vm part is garbage collected one way and the native part is another way and much later. You app may run out of memory pretty quickly.
Here is set of classes that can help. I think I've got them from android image tutorial and modified a bit for my own convenience.
package com.example.android.streaming.ui.cache;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.util.AttributeSet;
import android.widget.ImageView;
/**
* Sub-class of ImageView which automatically notifies the drawable when it is
* being displayed.
*/
public class RecyclingImageView extends ImageView {
public RecyclingImageView(Context context) {
super(context);
}
public RecyclingImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* #see android.widget.ImageView#onDetachedFromWindow()
*/
#Override
protected void onDetachedFromWindow() {
// This has been detached from Window, so clear the drawable
setImageDrawable(null);
super.onDetachedFromWindow();
}
/**
* #see android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)
*/
#Override
public void setImageDrawable(Drawable drawable) {
// Keep hold of previous Drawable
final Drawable previousDrawable = getDrawable();
// Call super to set new Drawable
super.setImageDrawable(drawable);
// Notify new Drawable that it is being displayed
notifyDrawable(drawable, true);
// Notify old Drawable so it is no longer being displayed
notifyDrawable(previousDrawable, false);
}
#Override
public void setImageResource(int resId) {
// Keep hold of previous Drawable
final Drawable previousDrawable = getDrawable();
super.setImageResource(resId);
// Notify new Drawable that it is being displayed
final Drawable newDrawable = getDrawable();
notifyDrawable(newDrawable, true);
// Notify old Drawable so it is no longer being displayed
notifyDrawable(previousDrawable, false);
}
/**
* Notifies the drawable that it's displayed state has changed.
*
* #param drawable
* #param isDisplayed
*/
private static void notifyDrawable(Drawable drawable, final boolean isDisplayed) {
if (drawable != null) {
if (drawable instanceof RecyclingBitmapDrawable) {
// The drawable is a CountingBitmapDrawable, so notify it
((RecyclingBitmapDrawable) drawable).setIsDisplayed(isDisplayed);
} else if (drawable instanceof LayerDrawable) {
// The drawable is a LayerDrawable, so recurse on each layer
LayerDrawable layerDrawable = (LayerDrawable) drawable;
for (int i = 0, z = layerDrawable.getNumberOfLayers(); i < z; i++) {
notifyDrawable(layerDrawable.getDrawable(i), isDisplayed);
}
}
}
}
}
And here is another one, a bitmap itself.
package com.example.android.streaming.ui.cache;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;
import com.example.android.streaming.StreamingApp;
import com.vg.hangwith.BuildConfig;
/**
* A BitmapDrawable that keeps track of whether it is being displayed or cached.
* When the drawable is no longer being displayed or cached,
* {#link Bitmap#recycle() recycle()} will be called on this drawable's bitmap.
*/
public class RecyclingBitmapDrawable extends BitmapDrawable {
private int cacheRefCount = 0;
private int displayRefCount = 0;
private boolean hasBeenDisplayed;
public RecyclingBitmapDrawable(Resources res, Bitmap bitmap) {
super(res, bitmap);
}
/**
* Notify the drawable that the displayed state has changed. Internally a
* count is kept so that the drawable knows when it is no longer being
* displayed.
*
* #param isDisplayed
* - Whether the drawable is being displayed or not
*/
public void setIsDisplayed(boolean isDisplayed) {
synchronized (this) {
if (isDisplayed) {
displayRefCount++;
hasBeenDisplayed = true;
} else {
displayRefCount--;
}
}
// Check to see if recycle() can be called
checkState();
}
/**
* Notify the drawable that the cache state has changed. Internally a count
* is kept so that the drawable knows when it is no longer being cached.
*
* #param isCached
* - Whether the drawable is being cached or not
*/
public void setIsCached(boolean isCached) {
synchronized (this) {
if (isCached) {
cacheRefCount++;
} else {
cacheRefCount--;
}
}
// Check to see if recycle() can be called
checkState();
}
private synchronized void checkState() {
// If the drawable cache and display ref counts = 0, and this drawable
// has been displayed, then recycle
if (cacheRefCount <= 0 && displayRefCount <= 0 && hasBeenDisplayed && hasValidBitmap()) {
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "No longer being used or cached so recycling. " + toString());
getBitmap().recycle();
}
}
private synchronized boolean hasValidBitmap() {
Bitmap bitmap = getBitmap();
return bitmap != null && !bitmap.isRecycled();
}
}
Now, iun your activity, whatever it does, if it needs to present recyclable image, you add this in xml res:
<com.example.android.streaming.ui.cache.RecyclingImageView
android:id="#+id/ad_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:background="#drawable/bkgd_whitegradient"
android:contentDescription="#string/dummy_desc"
android:padding="20dip"/>
This is just an example, id, background, can be whatever you need.
final RecyclingImageView adImage = (RecyclingImageView) findViewById(R.id.ad_image);
adImage.setImageDrawable(new RecyclingBitmapDrawable(getResources(), getBitmap(this)));
adImage.setVisibility(View.VISIBLE);
Note the getBitmap(), this is an example. It is you who should implement it in a way you need. It returns Bitmap instance. In your case, this Bitmap will be created out of array of bytes you've received from your camera. Let's try to do it here too.
Next, I have a class for managing avatars in my app (long list of users is a good example). It has number of useful static methods, so you may not need to create it.
package com.example.android.streaming.ui.cache;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.json.JSONObject;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import android.util.LruCache;
import com.example.android.streaming.StreamingApp;
import com.example.android.streaming.datamodel.Broadcast;
import com.example.android.streaming.datamodel.Channel;
import com.facebook.model.GraphUser;
import com.parse.ParseFile;
import com.parse.ParseUser;
import com.vg.hangwith.BuildConfig;
import com.vg.hangwith.R;
public class AvatarCache {
private Map<String, LoadImageTask> tasks = new HashMap<String, AvatarCache.LoadImageTask>();
private LruCache<String, RecyclingBitmapDrawable> memoryCache;
public final static int AVATAR_BOUNDS = 100;
private String cacheDir;
private Context context;
public synchronized void addTask(String tag, LoadImageTask task) {
tasks.put(tag, task);
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "Added avatar load task for tag " + tag);
}
public synchronized void removeTask(String tag) {
tasks.remove(tag);
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "Removed avatar load task for tag " + tag);
}
public synchronized void cancelTasks(int keepLastItems) {
int count = 0;
Iterator<Map.Entry<String, LoadImageTask>> iter = tasks.entrySet().iterator();
while (iter.hasNext() && tasks.size() > keepLastItems) {
Map.Entry<String, LoadImageTask> entry = iter.next();
entry.getValue().cancel(true);
iter.remove();
count++;
}
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "Canceled " + count + " avatar load tasks");
}
public void cancelTasks() {
cancelTasks(0);
}
public final static Bitmap downscaleAvatar(Bitmap bitmap) {
if (bitmap.getWidth() > AVATAR_BOUNDS && bitmap.getHeight() > AVATAR_BOUNDS) {
int height = (int) Math.floor(bitmap.getHeight() / ((1.0f * bitmap.getWidth()) / AVATAR_BOUNDS));
Bitmap scaled = Bitmap.createScaledBitmap(bitmap, AVATAR_BOUNDS, height, false);
bitmap.recycle();
bitmap = null;
return scaled;
} else {
return bitmap;
}
}
public final static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
public class LoadImageTask extends AsyncTask<Void, Void, RecyclingBitmapDrawable> {
protected RecyclingImageView image;
protected String url, tag;
protected boolean avatar;
public LoadImageTask(String url, String tag, boolean avatar, RecyclingImageView image) {
super();
this.url = url;
this.tag = tag;
this.image = image;
this.avatar = avatar;
image.setTag(R.string.tag_key, tag);
addTask(tag, this);
}
#Override
protected RecyclingBitmapDrawable doInBackground(Void... dummy) {
if (isCancelled() || !isSameImage())
return null;
RecyclingBitmapDrawable drawable = getAvatarFromMemCache(tag);
if (drawable == null) {
drawable = getAvatarFromDiskCache(tag);
if (drawable == null) {
try {
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "Loading avatar " + url);
/* First decode bounds to check the image size. */
BitmapFactory.Options options = new BitmapFactory.Options();
/* Calculate if the avatar should be down scaled. */
if (avatar) {
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new URL(url).openConnection().getInputStream(), null, options);
options.inSampleSize = calculateInSampleSize(options, AVATAR_BOUNDS, AVATAR_BOUNDS);
}
options.inJustDecodeBounds = false;
/* Download down scaled avatar. */
Bitmap bitmap = BitmapFactory.decodeStream(new URL(url).openConnection().getInputStream(), null, options);
if (bitmap != null) {
drawable = new RecyclingBitmapDrawable(context.getResources(), bitmap);
if (drawable != null) {
addAvatarToDiskCache(tag, url, drawable);
addAvatarToMemoryCache(tag, drawable);
}
}
} catch (Exception e) {
Log.w(StreamingApp.TAG, "Failed to load and save avatar image. " + e.getMessage());
}
} else {
addAvatarToMemoryCache(tag, drawable);
}
}
return drawable;
}
private synchronized boolean isSameImage() {
// In case that the same image is reused for different avatar (during scroll), this
// function will return false.
Object imageTag = image.getTag(R.string.tag_key);
return imageTag != null && imageTag.equals(tag);
}
private void finishedWithResult(RecyclingBitmapDrawable result) {
if (result != null && isSameImage())
image.setImageDrawable(result);
removeTask(tag);
}
#Override
protected void onPostExecute(RecyclingBitmapDrawable result) {
finishedWithResult(result);
super.onPostExecute(result);
}
#Override
protected void onCancelled(RecyclingBitmapDrawable result) {
finishedWithResult(result);
super.onCancelled();
}
#Override
protected void onCancelled() {
finishedWithResult(null);
super.onCancelled();
}
}
public AvatarCache(Context context) {
super();
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/10th of the available memory for this memory cache. With small avatars like
// we have this is enough to keep ~100 avatars in cache.
final int cacheSize = maxMemory / 10;
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "Init avatar cache, size: " + cacheSize + ", max mem size: " + maxMemory);
memoryCache = new LruCache<String, RecyclingBitmapDrawable>(cacheSize) {
#Override
protected int sizeOf(String key, RecyclingBitmapDrawable drawable) {
// The cache size will be measured in kilobytes rather than
// number of items.
Bitmap bitmap = drawable.getBitmap();
int bitmapSize = bitmap != null ? bitmap.getByteCount() / 1024 : 0;
return bitmapSize == 0 ? 1 : bitmapSize;
}
#Override
protected void entryRemoved(boolean evicted, String key, RecyclingBitmapDrawable oldValue,
RecyclingBitmapDrawable newValue) {
// The removed entry is a recycling drawable, so notify it.
// that it has been removed from the memory cache
oldValue.setIsCached(false);
}
};
this.cacheDir = context.getCacheDir().getAbsolutePath();
this.context = context;
}
public void flush() {
int oldSize = memoryCache.size();
memoryCache.evictAll();
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "Flush avatar cache, flushed " + (oldSize - memoryCache.size()) + " new size "
+ memoryCache.size());
cancelTasks();
}
public void addAvatarToMemoryCache(String key, RecyclingBitmapDrawable drawable) {
if (getAvatarFromMemCache(key) == null) {
drawable.setIsCached(true);
memoryCache.put(key, drawable);
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "Add to avatar cache, size: " + memoryCache.size());
}
}
public RecyclingBitmapDrawable getAvatarFromMemCache(String key) {
return memoryCache.get(key);
}
public void addAvatarToDiskCache(String name, String url, RecyclingBitmapDrawable drawable) throws IOException {
if (drawable == null)
return;
File dir = new File(cacheDir);
if (!dir.exists())
dir.mkdirs();
File file = new File(dir, name);
Bitmap bitmap = drawable.getBitmap();
if (!file.exists() && bitmap != null) {
OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
drawable.getBitmap().compress(Bitmap.CompressFormat.PNG, 85, out);
out.flush();
out.close();
}
}
/*
* Update avatar from the network if older than this.
*/
public static final int AVATAR_MAX_AGE_DAYS = 7;
public RecyclingBitmapDrawable getAvatarFromDiskCache(String name) {
File file = new File(cacheDir, name);
/* Check if cached bitmap is old. */
if ((System.currentTimeMillis() - file.lastModified()) > AVATAR_MAX_AGE_DAYS * 24 * 60 * 60 * 1000)
return null;
try {
Bitmap bitmap = BitmapFactory.decodeFile(file.getCanonicalPath());
if (bitmap != null) {
// Log.w(App.TAG, "Loaded " + (bitmap.getByteCount() / 1024.0f) + "K bitmap " + name + " w: "
// + bitmap.getWidth() + " h: " + bitmap.getHeight());
return new RecyclingBitmapDrawable(context.getResources(), bitmap);
}
} catch (Exception e) {
Log.w(StreamingApp.TAG, "Failed to decode avatar image " + name + ". " + e.getMessage());
}
return null;
}
public static boolean isValidURL(String url) {
try {
new URL(url);
return true;
} catch (Exception e) {
}
return false;
}
public void loadUrlAvatar(String url, String name, RecyclingImageView image, int placeholder, boolean checkDiskCache) {
RecyclingBitmapDrawable drawable = getAvatarFromMemCache(name);
if (drawable == null && checkDiskCache) {
drawable = getAvatarFromDiskCache(name);
if (drawable != null)
addAvatarToMemoryCache(name, drawable);
}
if (drawable == null) {
image.setImageResource(placeholder);
if (url != null && isValidURL(url))
new LoadImageTask(url, name, true, image).execute();
} else {
image.setImageDrawable(drawable);
}
}
public static String getUserAvatarURL(ParseUser user) {
if (user == null)
return null;
if (user.get("avatar") == null || user.get("avatar") == JSONObject.NULL)
return user.getString("avatar_url");
if (user.get("avatar") instanceof JSONObject)
Log.w(StreamingApp.TAG, "JSONObject found instead of ParseFile: " + ((JSONObject) user.get("avatar")).toString());
return ((ParseFile) user.get("avatar")).getUrl();
}
public static String getUserAvatarURL(GraphUser user) {
return "http://graph.facebook.com/" + user.getId() + "/picture";
}
public static String getBroadcastAvatarURL(Broadcast broadcast) {
if (broadcast.getThumbnail() == null)
return null;
return broadcast.getThumbnail().getUrl();
}
public void loadUserAvatar(ParseUser user, RecyclingImageView image, int placeholder, boolean checkDiskCache) {
if (user != null)
loadUrlAvatar(getUserAvatarURL(user), user.getUsername(), image, placeholder, checkDiskCache);
}
public void loadUserAvatar(GraphUser user, RecyclingImageView image, int placeholder, boolean checkDiskCache) {
if (user != null)
loadUrlAvatar(getUserAvatarURL(user), user.getId(), image, placeholder, checkDiskCache);
}
public void loadBroadcastAvatar(Broadcast broadcast, RecyclingImageView image, int placeholder,
boolean checkDiskCache) {
if (broadcast != null)
loadUrlAvatar(getBroadcastAvatarURL(broadcast), broadcast.getObjectId(), image, placeholder, checkDiskCache);
}
public void clearUserAvatar(ParseUser user) {
File file = new File(cacheDir, user.getUsername());
if (file.exists())
file.delete();
memoryCache.remove(user.getUsername());
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "Remove avatar from cache, size: " + memoryCache.size());
}
public static String getChannelImageURL(Channel channel, boolean small, boolean ageRestricted) {
if (ageRestricted) {
if (small && channel.getSmallRestrictedState() != null)
return channel.getSmallRestrictedState().getUrl();
else if (!small && channel.getLargeRestrictedState() != null)
return channel.getLargeRestrictedState().getUrl();
} else {
if (small && channel.getSmallEmptyState() != null)
return channel.getSmallEmptyState().getUrl();
else if (!small && channel.getLargeEmptyState() != null)
return channel.getLargeEmptyState().getUrl();
}
return null;
}
public static final String channelImageCacheName(Channel channel, boolean small, boolean ageRestricted) {
return channel.getObjectId() + "-" + (ageRestricted ? "age" : "empty") + "-" + (small ? "small" : "large");
}
public boolean loadChannelImage(Channel channel, RecyclingImageView image, boolean checkDiskCache, boolean small,
boolean ageRestricted) {
boolean result = false;
if (channel == null)
return false;
String name = channelImageCacheName(channel, small, ageRestricted);
RecyclingBitmapDrawable drawable = getAvatarFromMemCache(name);
if (drawable == null && checkDiskCache) {
drawable = getAvatarFromDiskCache(name);
if (drawable != null)
addAvatarToMemoryCache(name, drawable);
}
if (drawable == null) {
String url = getChannelImageURL(channel, small, ageRestricted);
result = url != null && isValidURL(url);
if (result)
new LoadImageTask(url, name, false, image).execute();
} else {
image.setImageDrawable(drawable);
result = true;
}
return result;
}
public void loadUrlImage(String url, RecyclingImageView image, String name, boolean checkDiskCache) {
RecyclingBitmapDrawable drawable = getAvatarFromMemCache(name);
if (drawable == null && checkDiskCache) {
drawable = getAvatarFromDiskCache(name);
if (drawable != null)
addAvatarToMemoryCache(name, drawable);
}
if (drawable == null) {
if (url != null && isValidURL(url))
new LoadImageTask(url, name, false, image).execute();
} else {
image.setImageDrawable(drawable);
}
}
}
Note, it uses Parse framework at some places. Just ignore it.
In this example, AvatarCache is loading image by url in doInBackground() function. As you can see it gets an input stream of out url. You can modify it to feed it some different input stream that you use for loading your image. Then you also need to modify loadUrlImage(). In other words, just remove the url thing.
And this is how you can use it with Uri. Modify it for using input stream or array of bytes. Just use appropriate BitmapFactory.decodeSomething() method.
public Bitmap getBitmap(Uri uri) {
BitmapFactory.Options options = new BitmapFactory.Options();
AssetFileDescriptor fd = null;
Bitmap b = null;
try {
fd = getContentResolver().openAssetFileDescriptor(uri, "r");
if (fd != null) {
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, options);
options.inSampleSize = AvatarCache.calculateInSampleSize(options, AvatarCache.AVATAR_BOUNDS, AvatarCache.AVATAR_BOUNDS);
options.inJustDecodeBounds = false;
b = BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, options);
try {
fd.close();
} catch (IOException e) {
}
}
} catch (Exception e) {
e.printStackTrace();
}
return b;
}
As you can see, AvatarCache is only used statically in this example. In case you need to manage a lot of images, like a photo lib preview.create AvatarCache in your app instance. Also add memory mgnt methods.
#Override
public void onCreate() {
super.onCreate();
avatarCache = new AvatarCache(this);
}
public void onTrimMemory(int level) {
if (level == TRIM_MEMORY_COMPLETE || level == TRIM_MEMORY_RUNNING_CRITICAL || level == TRIM_MEMORY_RUNNING_LOW) {
if (avatarCache != null)
avatarCache.flush();
}
super.onTrimMemory(level);
}
#Override
public void onLowMemory() {
Log.w(StreamingApp.TAG, "Low memory event received. Clear avatars cache.");
if (avatarCache != null)
avatarCache.flush();
super.onLowMemory();
}
And then you can use it a way like this:
avatarCache.loadUserAvatar(...);
It will automatically load the image and place it to the cache. When the app is short of memory, cache will be flushed.
Hope this helps. It is quite a lot of stuff here but when you go through this once, you will never have issues with images in your android app. Happy coding!
PS. Ask questions if you need. Just be specific with what you need asking and give also some context of your particular use case.
I need to add a progress listener to each element of the horizontal list view, how can I do that?
For each item I upload a file.
I wanna to do something like that but holder is not final, so I have an error.
public class UploadsViewAdapter extends BaseAdapter {
private Context mContext;
private int mLayoutResourceId;
List<Upload> listFileToUpload;
public UploadsViewAdapter(Context context, int layoutResourceId, List<Upload> data) {
this.mLayoutResourceId = layoutResourceId;
this.mContext = context;
this.listFileToUpload = data;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
ViewHolder holder = null;
if (row == null) {
LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
row = inflater.inflate(mLayoutResourceId, parent, false);
holder = new ViewHolder();
holder.image = (ImageView) row.findViewById(R.id.upload_item_image);
holder.progressUpdate = (ProgressBar) row.findViewById(R.id.progressUpdate);
row.setTag(holder);
} else {
holder = (ViewHolder) row.getTag();
}
final Upload item = getItem(position);
holder.image.setImageBitmap(item.getThumbnail(160, 160));
item.setProgressListener(new ProgressListener() {
#Override
public void onProgress(int progress) {
holder.progressUpdate.setProgress(item.getProgress());
}
});
holder.progressUpdate.setProgress(item.getProgress());
return row;
}
static class ViewHolder {
ImageView image;
ProgressBar progressUpdate;
}
public void updateListFileToUpdate(List<Upload> listFileToUpload) {
this.listFileToUpload = listFileToUpload;
}
#Override
public int getCount() {
return listFileToUpload.size();
}
#Override
public Upload getItem(int location) {
return listFileToUpload.get(location);
}
#Override
public long getItemId(int position) {
return 0;
}
}
Class Update.java
public class Upload {
public String path;
public String type;
private int mProgress = 0;
private ProgressListener mListener;
public Upload(String path, String type) {
this.path = path;
this.type = type;
}
public void setProgressListener(ProgressListener listener) {
mListener = listener;
}
public void setProgress(int progress) {
mProgress = progress;
if (mListener != null) {
mListener.onProgress(progress);
}
}
public int getProgress() {
return mProgress;
}
public Bitmap getThumbnail(int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(path, options);
}
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while (
(halfHeight / inSampleSize) > reqHeight &&
(halfWidth / inSampleSize) > reqWidth
) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public interface ProgressListener {
public void onProgress(int progress);
}
}
Class which updates the file:
public class TheFormFragment extends Fragment {
private AmazonS3Client mS3Client = new AmazonS3Client(
new BasicAWSCredentials(Config.AWS_ACCESS_KEY, Config.AWS_SECRET_KEY));
public static final int RESULT_PHOTO_DISK = 10;
public static final int RESULT_PHOTO_APN = 100;
public static final int RESULT_VIDEO_APN = 1000;
public static final String INTENT_PHOTO_APN_PATH = "INTENT_PHOTO_APN_PATH";
public static final String INTENT_VIDEO_APN_PATH = "INTENT_VIDEO_APN_PATH";
private List<Medias> mTheFormPictures;
private static Activity mActivity;
private static ArrayList<Upload> mQueue;
private KeyboardEventLinearLayout mLinearLayoutBackground;
private LinearLayout mLinearLayoutPublish;
private TextView mTextViewPublish;
private ImageView mImageViewPublish; // todo image du bouton
private EditText mEditTextText;
private TextView mTextViewTitle;
private CircularImageView mCircularImageViewAvatar;
private ImageButton mImageButtonClose;
private ImageButton mImageButtonCamera;
private ImageButton mImageButtonLibrary;
private Tag[] mTags = null;
private Range mAutocompleting;
private LinearLayout mAutocompleteContainer;
private HorizontalListView mUploadsList;
private UploadsViewAdapter mUploading;
/**Contains list of images, vidoe to update*/
private List<Upload> listFileToUpload;
private KeyboardEventLinearLayout.KeyboardListener mKeyboardListener = new KeyboardEventLinearLayout.KeyboardListener() {
#Override
public void onShow() {
if (mUploadsList != null) {
mUploadsList.setVisibility(View.GONE);
}
}
#Override
public void onHide() {
if (mUploadsList != null) {
mUploadsList.setVisibility(View.VISIBLE);
}
}
};
private class Range {
public int start;
public int end;
public String value;
public Range(int start, int end, String value) {
this.start = start;
this.end = end;
this.value = value;
}
}
private TextWatcher textWatcher = new TextWatcher() {
#Override
public void onTextChanged(CharSequence text, int start, int oldCount, int newCount) {
String before = text.subSequence(0, start + newCount).toString();
Range range = findEditingTag(before);
autocompete(range);
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
#Override
public void afterTextChanged(Editable s) {}
};
private OnClickListener mAutocompleteItemClickListener = new OnClickListener() {
#Override
public void onClick(View view) {
Editable text = mEditTextText.getText();
CharSequence tag = ((TextView) view).getText();
text.replace(mAutocompleting.start, mAutocompleting.end, tag);
}
};
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mActivity = activity;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_the_form, container, false);
return rootView;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
mTheFormPictures = new ArrayList<Medias>();
mQueue = new ArrayList<Upload>();
mS3Client.setRegion(Region.getRegion(Config.AWS_REGION));
mLinearLayoutBackground = (KeyboardEventLinearLayout) mActivity.findViewById(R.id.linearLayoutBackground);
mLinearLayoutPublish = (LinearLayout) mActivity.findViewById(R.id.linearLayoutPublish);
mTextViewPublish = (TextView) mActivity.findViewById(R.id.textViewPublish);
mTextViewTitle = (TextView) mActivity.findViewById(R.id.textViewTitle);
mImageViewPublish = (ImageView) mActivity.findViewById(R.id.imageViewPublish);
mCircularImageViewAvatar = (CircularImageView) mActivity.findViewById(R.id.circularImageViewAvatar);
mEditTextText = (EditText) mActivity.findViewById(R.id.editTextText);
mImageButtonClose = (ImageButton) mActivity.findViewById(R.id.imageButtonClose);
mImageButtonCamera = (ImageButton) mActivity.findViewById(R.id.imageButtonCamera);
mImageButtonLibrary = (ImageButton) mActivity.findViewById(R.id.imageButtonLibrary);
mAutocompleteContainer = (LinearLayout) mActivity.findViewById(R.id.autocompleteLayout);
mUploadsList = (HorizontalListView) mActivity.findViewById(R.id.uploadsList);
listFileToUpload =new ArrayList<Upload>();
mUploading = new UploadsViewAdapter(mActivity, R.layout.upload_item, listFileToUpload);
mUploadsList.setAdapter(mUploading);
mLinearLayoutBackground.setKeyboardListener(mKeyboardListener);
configure();
super.onActivityCreated(savedInstanceState);
}
#SuppressLint("NewApi")
#SuppressWarnings("deprecation")
private void configure() {
AQuery aq = new AQuery(mActivity);
ImageOptions options = new ImageOptions();
//options.round = 180;
options.fileCache = false;
options.memCache = true;
//options.animation = AQuery.FADE_IN;
User user = Preferences.getUser(mActivity);
if (user != null) {
mTextViewTitle.setText(user.getFirst_name() + " " + user.getLast_name());
if (user.getAvatar() != null && user.getAvatar().length() > 0) {
aq.id(mCircularImageViewAvatar).image(user.getAvatar(), options);
}
}
StatusConfigTheForm configTheForm = Preferences.getConfigTheForm(mActivity);
if (configTheForm != null) {
Log.i("theform config success");
Log.d("avatar: " + user.getAvatar() + " " + configTheForm.getBorderWidth());
if (configTheForm.getColors().getBackground().length == 3) {
mLinearLayoutBackground.setBackgroundColor(Color.parseColor(Utils.getHexaColor(configTheForm.getColors().getBackground())));
}
if (configTheForm.getColors().getBackgrounPublishButton().length == 3) {
// mButtonPublish.setBackgroundColor(Color.parseColor(Utils.getHexaColor(configTheForm.getColors().getBackgrounPublishButton())));
// prepare
int roundRadius = 6;
// normal state
GradientDrawable background_normal = new GradientDrawable();
background_normal.setColor(Color.parseColor(Utils.getHexaColor(configTheForm.getColors().getBackgrounPublishButton())));
background_normal.setCornerRadius(roundRadius);
// pressed state
GradientDrawable bacground_pressed = new GradientDrawable();
bacground_pressed.setColor(Color.parseColor(Utils.getHexaColor(configTheForm.getColors().getBackgrounPublishButton()).replace("#", "#CC"))); // opacity
bacground_pressed.setCornerRadius(roundRadius);
// states (normal and pressed)
StateListDrawable states = new StateListDrawable();
states.addState(new int[] {android.R.attr.state_pressed}, bacground_pressed);
states.addState(new int[] {-android.R.attr.state_pressed}, background_normal);
if (Build.VERSION.SDK_INT >= 16) {
mLinearLayoutPublish.setBackground(states);
} else {
mLinearLayoutPublish.setBackgroundDrawable(states);
}
}
if (configTheForm.getColors().getBackgroundTextView().length == 3) {
mEditTextText.setBackgroundColor(Color.parseColor(Utils.getHexaColor(configTheForm.getColors().getBackgroundTextView())));
}
if (configTheForm.getColors().getBorderColorPicture().length == 3) {
mCircularImageViewAvatar.setBorderColor(Utils.getHexaColor(configTheForm.getColors().getBorderColorPicture()));
}
// add color tag here
if (configTheForm.getColors().getColorTextPublish().length == 3) {
mTextViewPublish.setTextColor(Color.parseColor(Utils.getHexaColor(configTheForm.getColors().getColorTextPublish())));
}
if (configTheForm.getColors().getColorTextAttachment().length == 3) {
mCircularImageViewAvatar.setBorderColor(Utils.getHexaColor(configTheForm.getColors().getColorTextAttachment()));
}
if (configTheForm.getColors().getColorTextUser().length == 3) {
mTextViewTitle.setTextColor(Color.parseColor(Utils.getHexaColor(configTheForm.getColors().getColorTextUser())));
}
if (configTheForm.getBorderWidth() > 0) {
mCircularImageViewAvatar.setBorderWidth(configTheForm.getBorderWidth() * Integer.valueOf(Float.valueOf(getResources().getDisplayMetrics().density).intValue()));
}
// pictures
if (configTheForm.getPictures() != null) {
String baseUrl = configTheForm.getUrlPicto() + configTheForm.getFolder() + File.separator;
String ext = Utils.setExtension(mActivity, Config.PICTURE_EXTENSION);
Pictures pics = configTheForm.getPictures();
if (configTheForm.getPictures().getPictureBack() != null) {
aq.id(mImageButtonClose).image(baseUrl + pics.getPictureBack() + ext, options);
}
if (configTheForm.getPictures().getPictureCamera() != null) {
aq.id(mImageButtonCamera).image(baseUrl + pics.getPictureCamera() + ext, options);
}
if (configTheForm.getPictures().getPictureLibrary() != null) {
aq.id(mImageButtonLibrary).image(baseUrl + pics.getPictureLibrary() + ext, options);
}
if (configTheForm.getPictures().getPicturePublish() != null) {
mImageViewPublish.setVisibility(View.VISIBLE);
aq.id(mImageViewPublish).image(baseUrl + pics.getPicturePublish() + ext, options);
} else {
mImageViewPublish.setVisibility(View.GONE);
}
}
}
mEditTextText.addTextChangedListener(textWatcher);
}
private Range findEditingTag(String text) {
Pattern pattern = Pattern.compile("#[A-z0-9_]+$");
Matcher match = pattern.matcher(text);
if (match.find()) {
String value = text.substring(match.start());
return new Range(match.start(), match.end(), value);
}
return null;
}
private void autocompete(Range range) {
mAutocompleting = range;
mAutocompleteContainer.removeAllViews();
if (range != null && mTags != null) {
String tag;
for (int i = 0; i < mTags.length; i++) {
tag = "#" + mTags[i].getName();
if (tag.startsWith(range.value) && tag.equals(range.value) == false) {
addAutocompleteItem(tag);
}
}
}
}
#SuppressWarnings("deprecation")
#SuppressLint("NewApi")
private void addAutocompleteItem(String text) {
Drawable background = mActivity.getResources().getDrawable(R.drawable.autocomplete_item);
int textColor = mActivity.getResources().getColor(R.color.autocomplete_item_text);
TextView view = new TextView(mActivity);
view.setText(text);
view.setTextColor(textColor);
view.setPadding(20, 10, 20, 10);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
params.setMargins(10, 0, 10, 0);
view.setLayoutParams(params);
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
view.setBackgroundDrawable(background);
} else {
view.setBackground(background);
}
view.setClickable(true);
view.setOnClickListener(mAutocompleteItemClickListener);
mAutocompleteContainer.addView(view);
}
private void updateProgress() {
//int progress = 0;
//for (Upload file: mUploading) {
// progress += file.getProgress();
//}
//progress /= mUploading.size();
//mProgressBarFile.setProgress(progress);
//mTextViewFileProgress.setText(String.format(getString(R.string.theform_text_file_progress), Integer.valueOf(progress).toString()));
}
private class S3PutObjectTask extends AsyncTask<Upload, Integer, S3TaskResult> {
//private Dialog progressDialog;
ObjectMetadata mMetadata = new ObjectMetadata();
private String mS3Filename;
private Upload mFile;
#Override
protected void onPreExecute() {
updateProgress();
}
#Override
protected void onProgressUpdate(Integer... values) {
int progress = Integer.valueOf( (int) ((values[0] * 100) / mMetadata.getContentLength()) );
mFile.setProgress(progress);
updateProgress();
super.onProgressUpdate(values);
}
protected S3TaskResult doInBackground(Upload... files) {
if (files == null || files.length != 1 || files[0] == null) {
return null;
} else {
mFile = files[0];
}
ContentResolver resolver = mActivity.getContentResolver();
// The file location of the image selected.
Uri selectedSource = Uri.parse(mFile.path);
if (mFile.type.equals("image")) {
String size = null;
String fileSizeColumn[] = { OpenableColumns.SIZE };
Cursor cursor = resolver.query(selectedSource, fileSizeColumn, null, null, null);
if (cursor != null && cursor.getCount() > 0) {
cursor.moveToFirst();
int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
// If the size is unknown, the value stored is null. But since an int can't be
// null in java, the behavior is implementation-specific, which is just a fancy
// term for "unpredictable". So as a rule, check if it's null before assigning
// to an int. This will happen often: The storage API allows for remote
// files, whose size might not be locally known.
if (!cursor.isNull(sizeIndex)) {
// Technically the column stores an int, but cursor.getString will do the
// conversion automatically.
size = cursor.getString(sizeIndex);
}
cursor.close();
}
mMetadata.setContentType(resolver.getType(selectedSource));
if (size != null) {
mMetadata.setContentLength(Long.parseLong(size));
}
}
if (mMetadata.getContentType() == null) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inJustDecodeBounds = true;
BitmapFactory.decodeFile(selectedSource.toString(), opt);
mMetadata.setContentType(opt.outMimeType);
}
if (mMetadata.getContentLength() <= 0) {
mMetadata.setContentLength(new File(selectedSource.toString()).length());
}
selectedSource = Uri.parse("file://" + selectedSource.toString().replace("content://", ""));
S3TaskResult result = new S3TaskResult();
// Put the image data into S3.
try {
Calendar cal = Calendar.getInstance();
if (mFile.type.equals("image")) {
mS3Filename = Long.valueOf(cal.getTime().getTime()).toString() + ".jpg";
} else {
mS3Filename = Long.valueOf(cal.getTime().getTime()).toString() + ".mp4";
}
PutObjectRequest por = new PutObjectRequest(
Config.getPictureBucket(cal.getTime().getTime()), mS3Filename,
resolver.openInputStream(selectedSource), mMetadata
).withGeneralProgressListener(new ProgressListener() {
int total = 0;
#Override
public void progressChanged(ProgressEvent pv) {
total += (int) pv.getBytesTransferred();
publishProgress(total);
}
});
mS3Client.putObject(por);
result.setName(mS3Filename);
} catch (Exception exception) {
exception.printStackTrace();
result.setName(null);
result.setErrorMessage(exception.getMessage());
}
return result;
}
protected void onPostExecute(S3TaskResult result) {
//mProgressBarFile.setProgress(0);
//mTextViewFileProgress.setText("");
// AWS Error
if (result != null && result.getErrorMessage() != null && result.getName() == null) {
FastDialog.showDialog(mActivity, FastDialog.SIMPLE_DIALOG, result.getErrorMessage());
} else {
// add picture name
mTheFormPictures.add(new Medias(result.getName()));
}
}
}
public static String getRealPathFromUri(Context context, Uri contentUri) {
Cursor cursor = null;
try {
String[] proj = { MediaStore.Images.Media.DATA };
cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
/** close activity **/
public void close(View v) {
mActivity.finish();
}
/** select picture **/
public void selectPicture(View v) {
//Intent intent;
Intent intent = new Intent(
Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
/*if (Build.VERSION.SDK_INT < 19) {
intent = new Intent();
intent.setAction(Intent.ACTION_GET_CONTENT);
} else {
intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
}
if (Build.VERSION.SDK_INT >= 18) {
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
}*/
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
intent.setType("image/* video/*");
startActivityForResult(intent, RESULT_PHOTO_DISK);
}
/** take picture **/
public void tackePicture(View v) {
ApnActivity.show(mActivity, RESULT_PHOTO_APN);
}
/** record video **/
public void recordVideo(View v) {
RecordVideoActivity.show(mActivity, RESULT_VIDEO_APN);
}
/** publish button **/
public void publish(View v) {
// object
WebViewTheFormResult theFormResult = new WebViewTheFormResult(mEditTextText.getText().toString(), mTheFormPictures);
if (theFormResult.isEmpty()) {
FastDialog.showDialog(mActivity, FastDialog.SIMPLE_DIALOG, getString(R.string.theform_publish_error));
} else {
// intent
Intent dataIntent = new Intent();
Bundle bundle = new Bundle();
bundle.putSerializable(WebViewActivity.INTENT_OBJECT, (Serializable) theFormResult);
dataIntent.putExtras(bundle);
mActivity.setResult(Activity.RESULT_OK, dataIntent);
mActivity.finish();
}
}
#SuppressLint("NewApi")
public static void actionOnActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_CANCELED) {
return;
}
if (requestCode != RESULT_PHOTO_APN && requestCode != RESULT_VIDEO_APN) {
requestCode = RESULT_PHOTO_DISK;
}
switch (requestCode) {
case RESULT_PHOTO_DISK:
if (Build.VERSION.SDK_INT >= 18 && data.getData() == null) {
ClipData clipdata = data.getClipData();
for (int i = 0, l = clipdata.getItemCount(); i < l; i++) {
onDiskResult(clipdata.getItemAt(i).getUri());
}
} else {
onDiskResult(data.getData());
}
break;
case RESULT_PHOTO_APN:
onApnPhotoResult(data);
break;
case RESULT_VIDEO_APN:
onApnVideoResult(data);
break;
}
}
private static void onDiskResult(Uri selectedImage) {
InputStream imageStream;
try {
File file = new File(
Environment.getExternalStorageDirectory() + File.separator +
"Android" + File.separator +
"data" + File.separator +
mActivity.getPackageName()
);
if (new File(file.getAbsolutePath()).exists() == false) {
file.mkdirs();
}
imageStream = mActivity.getContentResolver().openInputStream(selectedImage);
Bitmap goodPicture = BitmapFactory.decodeStream(imageStream);
String filePath = file.getAbsoluteFile().toString() + "/" + String.valueOf(Utils.uid()) + Config.PHOTO_TMP_NAME;
try {
//goodPicture = ThumbnailUtils.createVideoThumbnail(filePath, MediaStore.Images.Thumbnails.MINI_KIND);
FileOutputStream out = new FileOutputStream(filePath);
goodPicture = Bitmap.createScaledBitmap(goodPicture, 800, 600, false);
goodPicture.compress(Bitmap.CompressFormat.JPEG, 80, out);
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
queueFile(filePath, "image");
} catch (FileNotFoundException e1) {
e1.printStackTrace();
return;
}
}
private static void onApnPhotoResult(Intent data) {
String filePath = data.getExtras().getString(TheFormFragment.INTENT_PHOTO_APN_PATH);
if (filePath.equals(Integer.valueOf(RESULT_VIDEO_APN).toString())) {
RecordVideoActivity.show(mActivity, RESULT_VIDEO_APN);
} else {
queueFile(filePath, "image");
}
}
private static void onApnVideoResult(Intent data) {
String filePath = data.getExtras().getString(TheFormFragment.INTENT_VIDEO_APN_PATH);
if (filePath.equals(Integer.valueOf(RESULT_PHOTO_APN).toString())) {
ApnActivity.show(mActivity, RESULT_PHOTO_APN);
} else {
queueFile(filePath, "video");
}
}
private static void queueFile(String filePath, String fileType) {
mQueue.add(new Upload(filePath, fileType));
}
#Override
public void onResume() {
fetchTags();
while (mQueue.size() > 0) {
uploadFile(mQueue.remove(mQueue.size() - 1));
}
super.onResume();
}
private void uploadFile(Upload file) {
new S3PutObjectTask().execute(file);
listFileToUpload.add(file);
mUploading.updateListFileToUpdate(listFileToUpload);
mUploading.notifyDataSetChanged();
}
private void fetchTags() {
Api.getTags(mActivity, new Api.Callback() {
#Override
public void onSuccess(String json) {
Gson gson = new Gson();
mTags = gson.fromJson(json, Tag[].class);
}
});
}
}
How can I resolve the problem?
Something like this should be enough:
...
holder.image.setImageBitmap(item.getThumbnail(160, 160));
final ViewHolder finalHolder = holder;
item.setProgressListener(new ProgressListener() {
#Override
public void onProgress(int progress) {
finalHolder.progressUpdate.setProgress(item.getProgress());
}
});
finalHolder.progressUpdate.setProgress(item.getProgress());
Think that you can remove setProgressListener() from Upload class.
Instead add a ProgressBar variable and a method setProgressBar() to Upload.
In getView():
Upload upload = getItem(position )
upload.setProgressBar(holder.progressUpdate);
In Upload: in setProgress() you can now directly address the ProgressBar variable
this is the setProgress() that in your AsyncTask is called with mFile.setProgress(progress);
Instead of removing better out comment the function and the call.
Untested. Please test. This will not be much work.
Don't make holder final. It will not help you. You also made item final in order to use it in onProgress. But that will not do either. You have to determine the right holder with getTag() and if you put position in the holder (as int variable) then you can use holder.position to get the right item again with a getItem(holder.position)
Thanks for your responses all.
I resolved the problem like this:
public class UploadsViewAdapter extends BaseAdapter {
private Context mContext;
List<Upload> listFileToUpload;
UploadsViewAdapter instanceUploadsViewAdapter;
public UploadsViewAdapter(Context context,
List<Upload> data) {
this.mContext = context;
this.listFileToUpload = data;
this.instanceUploadsViewAdapter = this;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View row = convertView;
ViewHolder holder = null;
if (row == null) {
LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
row = inflater.inflate(R.layout.upload_item, parent, false);
holder = new ViewHolder();
holder.image = (ImageView) row.findViewById(R.id.upload_item_image);
holder.play = (ImageView) row.findViewById(R.id.play);
holder.progressUpdate = (ProgressBar) row
.findViewById(R.id.progressUpdate);
holder.deleteFileUploaded = (ImageView) row.findViewById(R.id.delete_file_uploaded);
row.setTag(holder);
} else {
holder = (ViewHolder) row.getTag();
}
final Upload item = getItem(position);
holder.image.setImageBitmap(item.getThumbnail(160, 160));
final ViewHolder finalHolder = holder;
item.setProgressListener(new ProgressListener() {
#Override
public void onProgress(int progress) {
//item.setProgress(progress);
finalHolder.progressUpdate.setProgress(progress);
if(progress==100){
finalHolder.image.setAlpha(1.0f);
finalHolder.progressUpdate.setVisibility(View.GONE);
}
else{
finalHolder.image.setAlpha(0.5f);
finalHolder.progressUpdate.setVisibility(View.VISIBLE);
}
}
});
if(item.getProgress()==100){
finalHolder.image.setAlpha(1.0f);
finalHolder.progressUpdate.setVisibility(View.GONE);
}
else{
finalHolder.image.setAlpha(0.5f);
finalHolder.progressUpdate.setVisibility(View.VISIBLE);
}
holder.deleteFileUploaded.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
listFileToUpload.remove(position);
instanceUploadsViewAdapter.notifyDataSetChanged();
}
});
if(item.getType().equals(EnumTypeFile.IMAGE.getTypeFile())){
holder.play.setVisibility(View.GONE);
}
else{
holder.play.setVisibility(View.VISIBLE);
}
finalHolder.progressUpdate.setProgress(item.getProgress());
return row;
}
static class ViewHolder {
ImageView image;
ProgressBar progressUpdate;
ImageView deleteFileUploaded;
ImageView play;
}
public void updateListFileToUpdate(List<Upload> listFileToUpload) {
this.listFileToUpload = listFileToUpload;
}
#Override
public int getCount() {
return listFileToUpload.size();
}
#Override
public Upload getItem(int location) {
return listFileToUpload.get(location);
}
#Override
public long getItemId(int position) {
return 0;
}
}
I am trying to make a File browser for which I am taking help of this library.
Now according their code I am trying to implement, it's opening the desired location in the cell, however I am only able to see directories not files. As instructed I have chosen the option to choose Files only still I am not able to see Files there. Below is the code I am using
Intent fileExploreIntent = new Intent(FileBrowserActivity.INTENT_ACTION_SELECT_DIR,
null, getActivity(), FileBrowserActivity.class
);
startActivityForResult(fileExploreIntent,1);
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// TODO Auto-generated method stub
if (requestCode == 1) {
if(resultCode == getActivity().RESULT_OK) {
String newDir = data.getStringExtra(FileBrowserActivity.returnDirectoryParameter);
Toast.makeText( getActivity(), "Received path from file browser:"+newDir, Toast.LENGTH_LONG).show();
} else {//if(resultCode == this.RESULT_OK) {
Toast.makeText( getActivity(),"Received NO result from file browser",Toast.LENGTH_LONG).show();
}//END } else {//if(resultCode == this.RESULT_OK) {
}//if (requestCode == REQUEST_CODE_PICK_FILE_TO_SAVE_INTERNAL) {
super.onActivityResult(requestCode, resultCode, data);
}
Code for FileBrowserActivity is
public class FileBrowserActivity extends Activity {
// Intent Action Constants
public static final String INTENT_ACTION_SELECT_DIR = "com.afixi.xmppclientandroid.SELECT_DIRECTORY_ACTION";
public static final String INTENT_ACTION_SELECT_FILE = "com.afixi.xmppclientandroid.SELECT_FILE_ACTION";
// Intent parameters names constants
public static final String startDirectoryParameter = "com.afixi.xmppclientandroid.directoryPath";
public static final String returnDirectoryParameter = "com.afixi.xmppclientandroid.directoryPathRet";
public static final String returnFileParameter = "com.afixi.xmppclientandroid.filePathRet";
public static final String showCannotReadParameter = "com.afixi.xmppclientandroid.showCannotRead";
public static final String filterExtension = "com.afixi.xmppclientandroid.filterExtension";
// Stores names of traversed directories
ArrayList<String> pathDirsList = new ArrayList<String>();
// Check if the first level of the directory structure is the one showing
// private Boolean firstLvl = true;
private static final String LOGTAG = "F_PATH";
private List<Item> fileList = new ArrayList<Item>();
private File path = null;
private String chosenFile;
// private static final int DIALOG_LOAD_FILE = 1000;
ArrayAdapter<Item> adapter;
private boolean showHiddenFilesAndDirs = true;
private boolean directoryShownIsEmpty = false;
private String filterFileExtension = null;
// Action constants
private static int currentAction = -1;
private static final int SELECT_DIRECTORY = 1;
private static final int SELECT_FILE = 2;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// In case of
// ua.com.vassiliev.androidfilebrowser.SELECT_DIRECTORY_ACTION
// Expects com.mburman.fileexplore.directoryPath parameter to
// point to the start folder.
// If empty or null, will start from SDcard root.
setContentView(R.layout.ua_com_vassiliev_filebrowser_layout);
// Set action for this activity
Intent thisInt = this.getIntent();
currentAction = SELECT_DIRECTORY;// This would be a default action in
// case not set by intent
if (thisInt.getAction().equalsIgnoreCase(INTENT_ACTION_SELECT_FILE)) {
Log.d(LOGTAG, "SELECT ACTION - SELECT FILE");
currentAction = SELECT_FILE;
}
showHiddenFilesAndDirs = thisInt.getBooleanExtra(
showCannotReadParameter, true);
filterFileExtension = thisInt.getStringExtra(filterExtension);
setInitialDirectory();
parseDirectoryPath();
loadFileList();
this.createFileListAdapter();
this.initializeButtons();
this.initializeFileListView();
updateCurrentDirectoryTextView();
Log.d(LOGTAG, path.getAbsolutePath());
}
private void setInitialDirectory() {
Intent thisInt = this.getIntent();
String requestedStartDir = thisInt
.getStringExtra(startDirectoryParameter);
if (requestedStartDir != null && requestedStartDir.length() > 0) {// if(requestedStartDir!=null
File tempFile = new File(requestedStartDir);
if (tempFile.isDirectory())
this.path = tempFile;
}// if(requestedStartDir!=null
if (this.path == null) {// No or invalid directory supplied in intent
// parameter
if (Environment.getExternalStorageDirectory().isDirectory()
&& Environment.getExternalStorageDirectory().canRead())
path = Environment.getExternalStorageDirectory();
else
path = new File("/");
}// if(this.path==null) {//No or invalid directory supplied in intent
// parameter
}// private void setInitialDirectory() {
private void parseDirectoryPath() {
pathDirsList.clear();
String pathString = path.getAbsolutePath();
String[] parts = pathString.split("/");
int i = 0;
while (i < parts.length) {
pathDirsList.add(parts[i]);
i++;
}
}
private void initializeButtons() {
Button upDirButton = (Button) this.findViewById(R.id.upDirectoryButton);
upDirButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Log.d(LOGTAG, "onclick for upDirButton");
loadDirectoryUp();
loadFileList();
adapter.notifyDataSetChanged();
updateCurrentDirectoryTextView();
}
});// upDirButton.setOnClickListener(
Button selectFolderButton = (Button) this
.findViewById(R.id.selectCurrentDirectoryButton);
if (currentAction == SELECT_DIRECTORY) {
selectFolderButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Log.d(LOGTAG, "onclick for selectFolderButton");
returnDirectoryFinishActivity();
}
});
} else {// if(currentAction == this.SELECT_DIRECTORY) {
selectFolderButton.setVisibility(View.GONE);
}// } else {//if(currentAction == this.SELECT_DIRECTORY) {
}// private void initializeButtons() {
private void loadDirectoryUp() {
// present directory removed from list
String s = pathDirsList.remove(pathDirsList.size() - 1);
// path modified to exclude present directory
path = new File(path.toString().substring(0,
path.toString().lastIndexOf(s)));
fileList.clear();
}
private void updateCurrentDirectoryTextView() {
int i = 0;
String curDirString = "";
while (i < pathDirsList.size()) {
curDirString += pathDirsList.get(i) + "/";
i++;
}
if (pathDirsList.size() == 0) {
((Button) this.findViewById(R.id.upDirectoryButton))
.setEnabled(false);
curDirString = "/";
} else
((Button) this.findViewById(R.id.upDirectoryButton))
.setEnabled(true);
long freeSpace = getFreeSpace(curDirString);
String formattedSpaceString = formatBytes(freeSpace);
if (freeSpace == 0) {
Log.d(LOGTAG, "NO FREE SPACE");
File currentDir = new File(curDirString);
if(!currentDir.canWrite())
formattedSpaceString = "NON Writable";
}
((Button) this.findViewById(R.id.selectCurrentDirectoryButton))
.setText("Select\n[" + formattedSpaceString
+ "]");
((TextView) this.findViewById(R.id.currentDirectoryTextView))
.setText("Current directory: " + curDirString);
}// END private void updateCurrentDirectoryTextView() {
private void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
private void initializeFileListView() {
ListView lView = (ListView) this.findViewById(R.id.fileListView);
lView.setBackgroundColor(Color.LTGRAY);
LinearLayout.LayoutParams lParam = new LinearLayout.LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
lParam.setMargins(15, 5, 15, 5);
lView.setAdapter(this.adapter);
lView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
chosenFile = fileList.get(position).file;
File sel = new File(path + "/" + chosenFile);
Log.d(LOGTAG, "Clicked:" + chosenFile);
if (sel.isDirectory()) {
if (sel.canRead()) {
// Adds chosen directory to list
pathDirsList.add(chosenFile);
path = new File(sel + "");
Log.d(LOGTAG, "Just reloading the list");
loadFileList();
adapter.notifyDataSetChanged();
updateCurrentDirectoryTextView();
Log.d(LOGTAG, path.getAbsolutePath());
} else {// if(sel.canRead()) {
showToast("Path does not exist or cannot be read");
}// } else {//if(sel.canRead()) {
}// if (sel.isDirectory()) {
// File picked or an empty directory message clicked
else {// if (sel.isDirectory()) {
Log.d(LOGTAG, "item clicked");
if (!directoryShownIsEmpty) {
Log.d(LOGTAG, "File selected:" + chosenFile);
returnFileFinishActivity(sel.getAbsolutePath());
}
}// else {//if (sel.isDirectory()) {
}// public void onClick(DialogInterface dialog, int which) {
});// lView.setOnClickListener(
}// private void initializeFileListView() {
private void returnDirectoryFinishActivity() {
Intent retIntent = new Intent();
retIntent.putExtra(returnDirectoryParameter, path.getAbsolutePath());
this.setResult(RESULT_OK, retIntent);
this.finish();
}// END private void returnDirectoryFinishActivity() {
private void returnFileFinishActivity(String filePath) {
Intent retIntent = new Intent();
retIntent.putExtra(returnFileParameter, filePath);
this.setResult(RESULT_OK, retIntent);
this.finish();
}// END private void returnDirectoryFinishActivity() {
private void loadFileList() {
try {
path.mkdirs();
} catch (SecurityException e) {
Log.e(LOGTAG, "unable to write on the sd card ");
}
fileList.clear();
if (path.exists() && path.canRead()) {
FilenameFilter filter = new FilenameFilter() {
public boolean accept(File dir, String filename) {
File sel = new File(dir, filename);
boolean showReadableFile = showHiddenFilesAndDirs
|| sel.canRead();
// Filters based on whether the file is hidden or not
if (currentAction == SELECT_DIRECTORY) {
return (sel.isDirectory() && showReadableFile);
}
if (currentAction == SELECT_FILE) {
// If it is a file check the extension if provided
if (sel.isFile() && filterFileExtension != null) {
return (showReadableFile && sel.getName().endsWith(
filterFileExtension));
}
return (showReadableFile);
}
return true;
}// public boolean accept(File dir, String filename) {
};// FilenameFilter filter = new FilenameFilter() {
String[] fList = path.list(filter);
this.directoryShownIsEmpty = false;
for (int i = 0; i < fList.length; i++) {
// Convert into file path
File sel = new File(path, fList[i]);
Log.d(LOGTAG,
"File:" + fList[i] + " readable:"
+ (Boolean.valueOf(sel.canRead())).toString());
int drawableID = R.drawable.file_icon;
boolean canRead = sel.canRead();
// Set drawables
if (sel.isDirectory()) {
if (canRead) {
drawableID = R.drawable.folder_icon;
} else {
drawableID = R.drawable.folder_icon_light;
}
}
fileList.add(i, new Item(fList[i], drawableID, canRead));
}// for (int i = 0; i < fList.length; i++) {
if (fileList.size() == 0) {
// Log.d(LOGTAG, "This directory is empty");
this.directoryShownIsEmpty = true;
fileList.add(0, new Item("Directory is empty", -1, true));
} else {// sort non empty list
Collections.sort(fileList, new ItemFileNameComparator());
}
} else {
Log.e(LOGTAG, "path does not exist or cannot be read");
}
// Log.d(TAG, "loadFileList finished");
}// private void loadFileList() {
private void createFileListAdapter() {
adapter = new ArrayAdapter<Item>(this,
android.R.layout.select_dialog_item, android.R.id.text1,
fileList) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// creates view
View view = super.getView(position, convertView, parent);
TextView textView = (TextView) view
.findViewById(android.R.id.text1);
// put the image on the text view
int drawableID = 0;
if (fileList.get(position).icon != -1) {
// If icon == -1, then directory is empty
drawableID = fileList.get(position).icon;
}
textView.setCompoundDrawablesWithIntrinsicBounds(drawableID, 0,
0, 0);
textView.setEllipsize(null);
// add margin between image and text (support various screen
// densities)
// int dp5 = (int) (5 *
// getResources().getDisplayMetrics().density + 0.5f);
int dp3 = (int) (3 * getResources().getDisplayMetrics().density + 0.5f);
// TODO: change next line for empty directory, so text will be
// centered
textView.setCompoundDrawablePadding(dp3);
textView.setBackgroundColor(Color.LTGRAY);
return view;
}// public View getView(int position, View convertView, ViewGroup
};// adapter = new ArrayAdapter<Item>(this,
}// private createFileListAdapter(){
private class Item {
public String file;
public int icon;
public boolean canRead;
public Item(String file, Integer icon, boolean canRead) {
this.file = file;
this.icon = icon;
}
#Override
public String toString() {
return file;
}
}// END private class Item {
private class ItemFileNameComparator implements Comparator<Item> {
public int compare(Item lhs, Item rhs) {
return lhs.file.toLowerCase().compareTo(rhs.file.toLowerCase());
}
}
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Log.d(LOGTAG, "ORIENTATION_LANDSCAPE");
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
Log.d(LOGTAG, "ORIENTATION_PORTRAIT");
}
// Layout apparently changes itself, only have to provide good onMeasure
// in custom components
// TODO: check with keyboard
// if(newConfig.keyboard == Configuration.KEYBOARDHIDDEN_YES)
}// END public void onConfigurationChanged(Configuration newConfig) {
public static long getFreeSpace(String path) {
StatFs stat = new StatFs(path);
long availSize = (long) stat.getAvailableBlocks()
* (long) stat.getBlockSize();
return availSize;
}// END public static long getFreeSpace(String path) {
public static String formatBytes(long bytes) {
// TODO: add flag to which part is needed (e.g. GB, MB, KB or bytes)
String retStr = "";
// One binary gigabyte equals 1,073,741,824 bytes.
if (bytes > 1073741824) {// Add GB
long gbs = bytes / 1073741824;
retStr += (new Long(gbs)).toString() + "GB ";
bytes = bytes - (gbs * 1073741824);
}
// One MB - 1048576 bytes
if (bytes > 1048576) {// Add GB
long mbs = bytes / 1048576;
retStr += (new Long(mbs)).toString() + "MB ";
bytes = bytes - (mbs * 1048576);
}
if (bytes > 1024) {
long kbs = bytes / 1024;
retStr += (new Long(kbs)).toString() + "KB";
bytes = bytes - (kbs * 1024);
} else
retStr += (new Long(bytes)).toString() + " bytes";
return retStr;
}// public static String formatBytes(long bytes){
}// END public class FileBrowserActivity extends Activity {