I am trying to do something in an if-statement, this works in every version of android (16 or higher because of the getDrawable) except Android L (tested on latest). The code is the following:
if (item.getIcon().getConstantState().equals(getResources().getDrawable(R.drawable.add_to_fav_normal).getConstantState())
Any help/hints or explanation would be appreciated!
Use item.getContext().getDrawable(int) or the equivalent ContextCompat method.
Starting in API 21, all framework widgets that load drawables use Context.getDrawable() which applies the context's current theme during inflation. This basically just calls getResources().getDrawable(..., getTheme()) internally, so you could also use context.getResources().getDrawable(..., context.getTheme()).
if (item.getIcon().getConstantState().equals(item.getContext()
.getDrawable(R.drawable.add_to_fav_normal).getConstantState())
In general, though, you shouldn't rely on this check. There are no API guarantees around what constant state you'll receive from a particular drawable.
This solution is convenient for tests only:
public static void assertEqualDrawables(Drawable drawableA, Drawable drawableB) {
Bitmap bitmap1 = ((BitmapDrawable) drawableA).getBitmap();
Bitmap bitmap2 = ((BitmapDrawable) drawableB).getBitmap();
ByteBuffer buffer1 = ByteBuffer.allocate(bitmap1.getHeight() * bitmap1.getRowBytes());
bitmap1.copyPixelsToBuffer(buffer1);
ByteBuffer buffer2 = ByteBuffer.allocate(bitmap2.getHeight() * bitmap2.getRowBytes());
bitmap2.copyPixelsToBuffer(buffer2);
Assert.assertTrue(Arrays.equals(buffer1.array(), buffer2.array()));
}
Based on #alanv's answer, below is what I did and was successful:
if (imgClicked.getDrawable().getConstantState()
.equals(ContextCompat.getDrawable(this,
R.drawable.add_profile).getConstantState())) {
//Both images are same
}else{
//Both images are NOT same
}
Thank's #alanv :)
Related
I am creating an Android Tile that is meant to display custom and dynamically created graphics, i.e. a chart.
However, due to several limitations I have yet to find a way to do so. Tiles seem to work fundamentally different than Activities do and the Tiles' API only allows for several, predefined UI elements to be created. The only usable one for me seems to be the Image LayoutElement.
The Image can be created by either passing a resource or a ByteArray. Former is not possible when dealing with dynamically created graphs.
Thus, my only hope (I think) is to create an Image in the form of a ByteArray myself.
How can I do this? Is there any Java framework to draw graphics directly?
I have considered the following:
Using the provided UI elements: wouldn't work since the placement is way to imprecise and the exact position of an element cannot be controlled. Also, these elements are not meant for drawing.
Using AWT: doesn't work on Android. Thus, almost any drawing and/or charting library is out of the game.
JavaFX: would probably work but there seems to be now way to draw directly on ByteArrays/BufferedImages as the application needs to be rendered first. Rendering JavaFX doesn't seem possible for Tiles.
Using Android's Canvas: again, an Activity is needed.
Turns out I was wrong: you can very well use the Canvas within a Tile. Converting it to a resource is, however, a little tricky, so here's some code:
final Bitmap bitmap = Bitmap.createBitmap(chart.getWidth(), chart.getHeight(),
Bitmap.Config.RGB_565);
final Canvas canvas = new Canvas(bitmap);
// Sets the background color
final Color background = Color.valueOf(chart.getBackgroundColor());
canvas.drawRGB(
Math.round(background.red() * 255),
Math.round(background.green() * 255),
Math.round(background.blue() * 255)
);
// YOUR DRAWING OPERATIONS: e.g. canvas.drawRect
final ByteBuffer byteBuffer = ByteBuffer.allocate(bitmap.getByteCount());
bitmap.copyPixelsToBuffer(byteBuffer);
final byte[] bytes = byteBuffer.array();
return new ResourceBuilders.ImageResource.Builder()
.setInlineResource(
new ResourceBuilders.InlineImageResource.Builder()
.setData(bytes)
.setWidthPx(chart.getWidth())
.setHeightPx(chart.getHeight())
.setFormat(ResourceBuilders.IMAGE_FORMAT_RGB_565)
.build()
)
.build();
This example shows using Compose Canvas to render charts for Tiles.
https://github.com/google/horologist/pull/249
Also you can encode to PDF
Remove
setFormat(ResourceBuilders.IMAGE_FORMAT_RGB_565)
and use
val bytes = ByteArrayOutputStream().apply {
compress(Bitmap.CompressFormat.PNG, 100, this)
}.toByteArray()
I am using
Drawable drawable = res.getDrawable(id);
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
Canvas canvas = new Canvas(bitmap);
bitmap.eraseColor(0);
drawable.setBounds(0,0, width, height);
drawable.draw(canvas);
return load(bitmap, linear);
to load a drawable from a resource id into OpenGL with a given width, and height. (Using
android.opengl.GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
)
The load function does the GL-calls, and calls also bitmap.recycle().
I specify width and height myself, because Android would match the resolution to the screen size, which I don't want.
Now my problem (this part is all working fine):
if I start my app for the first time, from Android Studio, everything works; HOWEVER if I want to restart it, it crashes because of OutOfMemoryError. I am doing the exactly same calls in both cases.
I located the issue to be in the resource management of Android, as you can see in the heap analysis:
my most expensive allocations
My images are way smaller than 9 MB each in raw (512x512, RGBA, so 1 MB).
How can I prevent Android from storing these large byte arrays, which probably are meant as some kind of cache; which however doesn't run on first start after app installation?
I am testing on Android 6.0.1, API Version 23, Galaxy S5.
Implementation of texImage2D looks like this:
public static void texImage2D(int target, int level, int internalformat,
Bitmap bitmap, int border) {
if (bitmap == null) {
throw new NullPointerException("texImage2D can't be used with a null Bitmap");
}
if (bitmap.isRecycled()) {
throw new IllegalArgumentException("bitmap is recycled");
}
if (native_texImage2D(target, level, internalformat, bitmap, -1, border)!=0) {
throw new IllegalArgumentException("invalid Bitmap format");
}
}
It doesn't look like it's recycling anything. Are you sure you are not loading a huge bitmap into memory? Two calls of those are more than enough to guarantee a huge explosion in your app, if not just one (I've seen it happen many times in my app). Remember, restarting your activity does not mean restarting your proccess.
Run the Android Profiler before the first load and check how much memory it takes.
Also, you can cache and reuse bitmaps yourself.
I solved it (myself) by putting the files into the raw folder of the resource directory, and loading them using
fun loadBitmap(res: Resources, rawId: Int): Bitmap {
val inputStream = BufferedInputStream(res.openRawResource(rawId))
return BitmapFactory.decodeStream(inputStream)
}
and then calling
load(bitmap, linear);
and
bitmap.recycle()
like before.
Luckily those all were png/jpeg files, so I didn't need the additional features of the drawables folder. Using this, they'll automatically use their right resolution.
My Java RAM allocation is now back on 25 MB to 35 MB instead of the 110 MB when using the old way :).
I am using the Android BitmapFun sample code to manage bitmaps in my application. I have been experiencing garbled or duplicated images in a ViewPager. I have tracked this down to the following code in ImageCache.java:
/**
* Notify the removed entry that is no longer being cached
*/
#Override
protected void entryRemoved(boolean evicted, String key,
BitmapDrawable oldValue, BitmapDrawable newValue) {
if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
// The removed entry is a recycling drawable, so notify it
// that it has been removed from the memory cache
((RecyclingBitmapDrawable) oldValue).setIsCached(false);
} else {
// The removed entry is a standard BitmapDrawable
if (Utils.hasHoneycomb()) {
// We're running on Honeycomb or later, so add the bitmap
// to a SoftRefrence set for possible use with inBitmap later
mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));
}
}
}
The bitmap is added to the reusable bitmap list when it is removed from the cache. In this case the bitmap is still in use by a ViewPager view. When a later view is created the bitmap (still in use) is reused and the bitmap appears in two positions in the ViewPager.
A bitmap that is removed from the LruCache isn't necessarily available for reuse. I have disabled the reuse of bitmaps in this code and am no longer having an issue. This problem doesn't occur with lower resolution images because the bitmaps aren't removed from the cache while in the range of the ViewPager's offscreen limit. I don't have an issue with 60 DPI images but see this issue frequently at 160 DPI. I think this would show up in the original BitmapFun sample with higher resolution images.
Anyone else experienced this problem or I am not understanding the issue properly?
Kevin
What I think the problem with the code is in the line
mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));
That line adds a bitmap that was removed from LRU cache to a reusable bitmap set to be used for inBitmap re-use. It doesn't check whether it is still being used by an ImageView or not. If you try to re-use a bitmap that is still being used by an ImageView, the underlying bitmap will be replaced with another bitmap making it not valid anymore. My suggestion is to track whether a bitmap is still being used by an ImageView before adding it to the reusable bitmap set. I've created a sample github project for this issue. Tell me what you think with my solution.
I am trying to create cached image system for Android but the memory consumption just grows and grows. I looked through Android website for some ideas, but the issue just doesn't want to disappear.
Below is my code of getting the image from SD card, setting it and later destroying.
What am I doing wrong?
WeakReference<Bitmap> newImageRef;
public void setImageFromFile(File source){
if(source.exists()){
Bitmap newImage = BitmapFactory.decodeFile(source.getAbsolutePath());
newImageRef = new WeakReference<Bitmap>(newImage);
if(newImage != null){
this.setImageBitmap(newImage);
}
}
}
#Override
protected void onDetachedFromWindow() {
Bitmap newImage = newImageRef.get();
if (newImage != null) {
newImage.recycle();
newImage = null;
}
Drawable drawable = getDrawable();
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
Bitmap bitmap = bitmapDrawable.getBitmap();
if (bitmap != null){
bitmap.recycle();
}
}
this.setImageResource(0);
newImage = null;
newImageRef = null;
System.gc();
super.onDetachedFromWindow();
}
If you are using Android version >3.0 you dont have to call recycle()as the gc will clean up bitmaps on its own eventually as long as there are no references to it. So it is safe to remove recycle calls. They do nothing much here.
The code which you posted looks neat but are you sure there the leak is not happening somewhere else. Use Android Memory Analyzer tool to see where the leak is happening and then post the info.
Good luck.
Try to use Drawable.setCallback(null);. In Android 3.0 or newer, you don't even need to recycle because of more automatic memory management or garbage collection than in earlier versions. See also this. It has good information about bitmap memory management in Android.
As of this code it's hard to check if there is a detailed bug as this seems to cleary be a simplifyed version of the "full cache". At least the few lines you provided seem to look ok.
The main issue is the GC seems to be a little strange when handling Bitmaps. If you just remove the hard references, it will sometimes hang onto the Bitmaps for a little while longer, perhaps because of the way Bitmap objects are allocated. As said before recycling is not necessary on Android 3+. So if you are adding a big amount of Bitmaps, it might take some time until this memory is free again. Or the memory leak might be in anothe part of your code. For sophisticated problems like that its wise to check already proven solutions, before re-implementing one.
This brings me to the second issue: the use of weak refrences. This might not target the main problem, but is generally not a good pattern to use for image caches in Android 2.3+ as written by android doc:
Note: In the past, a popular memory cache implementation was a SoftReference or WeakReference bitmap cache, however this is not recommended. Starting from Android 2.3 (API Level 9) the garbage collector is more aggressive with collecting soft/weak references which makes them fairly ineffective. In addition, prior to Android 3.0 (API Level 11), the backing data of a bitmap was stored in native memory which is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash.
The way to go now is to use LRU Caches, which is explained in detail in the link provided about caching.
I want to compute a SHA1 hash of different bitmaps (SHA isn't forced).
The problem is that there are some bitmaps (captchas) wich are basicly the same, but the name changes often.
I've found this:
Compute SHA256 Hash in Android/Java and C#
But it isn't the soloution i wanted.
The Bitmap.hashCode(), generates only a Integer, and when im right
Returns an integer hash code for this object. By contract, any two objects for which equals(Object) returns true must return the same hash code value. This means that subclasses of Object usually override both methods or neither method.
I dont't want a hash code of the object, i want the hashcode of the bitmap content.
Thanx!
In Android 3.1 or later (API Level 12) there is a method on Bitmap called sameAs() which will compare the pixels and return if the two represent the same image. It does this in native code so it is relatively fast.
If you must target a lower API level, you must write a method that iterates over each pixel of the two objects and see if they match. This will be a very intensive process if done in Java code, so you may consider writing a small routine using the NDK that you can call from your application to do the comparison in native code (there are Bitmap APIs in the NDK so you can easily get at the pixel buffers).
If you opt to do so in Java, getPixels() will assist you in obtaining arrays of the pixel data that you can compare between the two images.
HTH
Here is a more native way for computing Bitmap hash, using Arrays.hashCode, and bitmap.getPixels
int hash(Bitmap bitmap){
int[] buffer = new int[bitmap.getWidth(), bitmap.getHeight()];
bitmap.getPixels(buffer, 0, 0, 0, 0, bitmap.getWidth(), bitmap.getHeight());
return Arrays.hashCode(buffer);
}
The fastest solution I have found so far in kotlin:
fun Bitmap.hash(): Int {
val buffer: ByteBuffer = ByteBuffer.allocate(this.height * this.rowBytes)
this.copyPixelsToBuffer(buffer)
return buffer.hashCode()
}
nearly 100x faster than the accepted answer
You could try to write your own function using only the Pixel from the Bitmap:
public long hashBitmap(Bitmap bmp){
long hash = 31 //or a higher prime at your choice
for(int x = 0; x < bmp.getWidth(); x++){
for (int y = 0; y < bmp.getHeight(); y++){
hash *= (bmp.getPixel(x,y) + 31);
}
}
return hash;
}
if its only about comparing two images you could optimise this routine to hash just every second or x pixel
Similar problem and this worked for me (solved problem with getting a new name for a specific bitmap, so I could check if it was already stored):
fun getUniqueBitmapFileName(bitmap: Bitmap): String {
val buffer = ByteBuffer.allocate(bitmap.getByteCount())
bitmap.copyPixelsToBuffer(buffer)
return Arrays.hashCode(buffer.array()).toString()
}