Android: Declaring static bitmaps? Yay or nay? - android

Somebody recently commented on my code where I declare the following:
private Bitmap splashBackground;
private Bitmap lightDot;
private static Bitmap scaledBackground;
private static Bitmap scaledLightDot;
They advised me against declaring satic Bitmaps.
However, I've tried everything and my code doesn't work unless I declare them as static.
Also, "public static Bitmap createScaledBitmap (Bitmap src, int dstWidth, int dstHeight, boolean filter)" seems to appear on the official Android Developer site so I'm a bit confused about what I should and shouldn't be doing.
Any pointers would be appreciated - thank you
Edit: For clarification:
When I remove the static from my declaration, then by the time I get to my onDraw() method, the scaled bitmap is null. (I am creating the scaled bitmap object in an initialise() method and once it's been created, it is valid (ie, not null) - but then seems to become null at onDraw unless I declare it as static.
I am calling my initialise() method from my activity class.
Edit: More code as requested.
My OnCreate method: As you can see, I'm passing my screen height and width over so I can create my scaled bitmaps
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
displaySplashScreen= new SplashScreen(this);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
// set View
setContentView(new SplashScreen(this));
WindowManager w = getWindowManager();
Display d = w.getDefaultDisplay();
int width=d.getWidth();
int height=d.getHeight();
displaySplashScreen.initialise(width, height);
}
My initalise method:
public void initialise(int w, int h)
{
//Get width and height (passed in from Activity)
vwidth=w;
vheight=h;
//Create pre-scaled bitmaps
scaledBackground = Bitmap.createScaledBitmap(splashBackground, vwidth, vheight, true);
scaledLightDot = Bitmap.createScaledBitmap(lightDot, vwidth, vheight, true);
}
I could add also that if I use a standard variable in the same way (say int number;) and set it in initalise (number = 5;), then number is only equal to 5in my initialise method. If I log it from onDraw() it will always repeatedly return '0'!! It's baffling.
Thanks everyone so far, please let me know if more code is required......

In general, utilizing static for Bitmaps is a very bad idea. There are a lot of good reasons for this, mostly having to do with avoiding memory leaks.
Also, "public static Bitmap createScaledBitmap (Bitmap src, int dstWidth, int dstHeight, boolean filter)" seems to appear on the official Android Developer site ...
This is not a static Bitmap. This is a method call to a class method. Static does not work the same and the return type (Bitmap) is not static. What this means is that the method is static and does not require an instance to be called. It will return a Bitmap to be placed in an appropriate variable of your choice.
I am calling my initialise() method from my activity class.
This statement is quite unhelpful. From where in the class is it being called? Is it in onCreate(), onStart(), onResume(), some other custom method? Where and when you choose to do certain things can have a huge effect as to how successful they are.
... but then seems to become null at onDraw unless I declare it as static.
This could be for several possible reasons, and since we don't have any of your code there really isn't a qualified answer. Here are some things to look at.
This could be because the Activity is getting recreated.
This could also be because some method that seems unrelated is actually getting called. This would probably be somewhere that you are manually setting it to null.
This might be due to improper use of the createScaledBitmap() method.
The Bitmap might be getting recycled due to low memory (this actually happens more often than one would think)
EDIT: After reviewing your code
This looks like it may be the culprit. Above, you have...
displaySplashScreen= new SplashScreen(this);
Below, you add...
setContentView(new SplashScreen(this));
This means that you are creating two Splashscreens. One reason why you are getting a null pointer when you are not using static may be because further down the line you use...
displaySplashScreen.initialise(width, height);
But since your contentView is set to a new SplashScreen, you are not actually utilizing that View. To resolve this, make sure you are talking to the same view object. I.E.
setContentView(displaySplashScreen);
This will at least make sure you are looking at the same object. It is possible that you may have to reorganize a bit depending upon what other things are happening. For instance,
setContentView(displaySplashScreen);
... may have to appear below ...
displaySplashScreen.initialise(width, height);
This is something that you might have to toy with, but I don't see anything else that gives any other immediate indication. Be aware that resolving nullpointerexceptions will often result in revealing more errors in code, at first. Stay the course and resolve each in order.

This line is wrong:
// set View
setContentView(new SplashScreen(this)); // This line is wrong.
Should be this:
// set View
setContentView(displaySplashScreen); // displaySplashScreen is created earlier.
You are created two instances of SplashScreen. You should keep using the same instance.

I vote for nay,
this one is a static variable
private Bitmap splashBackground;
private Bitmap lightDot;
private static Bitmap scaledBackground;
private static Bitmap scaledLightDot;
and this one is a static method
public static Bitmap createScaledBitmap (Bitmap src,
int dstWidth, int dstHeight, boolean filter)
static variable is usually declared for a constant and the variable belongs to class not object as an example if you have a class
public class car {
private static colorOfCar;
private numberOfDoor;
}
let's say you have a class car and have 2 variable colorOfCar and numberOfDoor when you create an object porsche and ferrari from car class if you change the number of door it's OK the numberOfDoor in your porsche object and ferrari object will be different, but if you change the colorOfCar both porsche object and ferrari object colorOfCar will be changed because the colorOfCar is a static object that belong to the class not the object.
I hope you understand my explanation. If you find my answer helping you please vote and accept my answer and if you have any other question feel free to ask in the comment, thank you :)

Without your code, it seems you are using your view is being created more than once. Maybe in two instances of the view, or maybe the same view being recreated (Activity restarting). In the first time around, initialize() is being called before onDraw(), making your static Drawable initialized and valid. The second time around, onDraw() is running before initialize() (which works when Drawable is static). This is most likely due to you inflating the view and calling initialize() afterwards (this means the view is already in the layout). i.e.
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.mylayout); //the view is added to layout, onDraw() may be called
MyView view = (MyView)findViewById(R.id.myview);
view.initialize(); //initializing the drawable from null
//no guarentee that initialize was called before onDraw()
}
This runs ok when your Drawable is static because when the second view is drawn, it is using the same Drawable which was initialized by the first view. When you remove the static, you need to make sure initialize is always called before onDraw().
Instead of calling initialize() from your activity, why not invoke it from the View's constructor? I often use the following pattern:
public class MyView extends View {
private Bitmap splashBackground;
private Bitmap lightDot;
private Bitmap scaledBackground;
private Bitmap scaledLightDot;
public MyView(Context context) {
super(context);
init();
}
public MyView(AttributeSet attr, Context context) {
super(attr, context);
//parse attr for xml attributes
init();
}
private void init() {
splashBackground = getResources().getDrawable(R.drawable.splash_background);
lightDot = getResources().getDrawable(R.drawable.light_dot);
scaledLightDot = Bitmap.createScaledBitmap(lightDot, getDPI(32), getDPI(32), false);
}
public void onSizeChanged(int width, int height) {
scaledBackground = Bitmap.createScaledBitmap (splashBackground, width, height, false);
}
/**
* Convert pixel value to device independent pixels (DPI)
* #params pixels Value for pixel size for MDPI screens
*/
private int getDPI(int pixels) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, pixels, getResources().getDisplayMetrics());
}
public void onDraw(Canvas canvas) {
//non-static drawables are guaranteed to be not-null
canvas.draw(scaledBackground, 0, 0, null);
canvas.draw(scaledLightDot, 10, 10, null);
}
}
You'll be all set

Related

Getting Image w/ BitmapFactory

I'm not sure why this is not getting my image. This is my first time working with android apps with java so idk what to do.
public class Player {
private GameView view;
private Bitmap bmp;
public int x, y;
public Player(GameView view) {
this.view = view;
}
public void tick() {
//This is my error here//
bmp = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
}
public void onDraw(Canvas c) {
c.drawBitmap(bmp, x, y, null);
}
}
I'm getting an error when trying to set bmp equal to what I want it to be in my tick method for getResources(). I don't know why getResources() is wrong.
Thanks
getResources() is a method on Context. Your call to getResources() is not in a Java class that inherits from Context. You probably copied this code from some class that does inherit from Context, like Activity.
Your onDraw() method suggests that you are trying to create some custom View. If so, Player would need to inherit from View, and then you could use getContext() in order to retrieve a Context to use for getResources() (e.g., getContext().getResources().

Creating bitmap from xml layouts, off the ui thread

I'm trying to create multiple bitmaps, one for each map marker.
Since it happens on the ui thread, the ui freezes for a moment..
Is there a way to create a bitmap using a layout xml on a background/worker thread?
I know that It's not recommended, but I'm not sure how to tackle this issue.
If there's a way to create a designed bitmap not using my current method,
i'll be glad to hear..
Thanks
#Override
public View onCreateView(LayoutInflater inflater,
#Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
...
mMarkerContainer = (ViewGroup) LayoutInflater.from(
getActivity()).inflate(R.layout.map_text_marker, null);
mMarkerNameTv = (TextView) mMarkerContainer
.findViewById(R.id.map_marker_name);
...
return super.onCreateView(inflater, container, savedInstanceState);
}
public Bitmap createMarkerBitmap(int markerRes, String markerName) {
mMarkerNameTv.setText(markerName);
mMarkerContainer.setBackground(mBitmapDrawables.get(markerRes));
int measureSpec = View.MeasureSpec.makeMeasureSpec(0,
View.MeasureSpec.UNSPECIFIED);
mMarkerContainer.measure(measureSpec, measureSpec);
int measuredWidth = mMarkerContainer.getMeasuredWidth();
int measuredHeight = mMarkerContainer.getMeasuredHeight();
mMarkerContainer.layout(0, 0, measuredWidth, measuredHeight);
Bitmap resultBitmap = Bitmap.createBitmap(measuredWidth,
measuredHeight, Bitmap.Config.ARGB_8888);
resultBitmap.eraseColor(Color.TRANSPARENT);
Canvas canvas = new Canvas(resultBitmap);
mMarkerContainer.draw(canvas);
return resultBitmap;
}
Is there a way to create a bitmap using a layout xml on a background/worker thread?
AFAIK, working with widgets on a background thread is not a problem, so long as they are not connected to any window (e.g., they are not part of an activity or dialog). Inflating a layout might be a problem -- I seem to recall running into that with instrumentation tests, which do not run on the main application thread. But you're welcome to try putting your inflate() call and all of the createMarkerBitmap() logic into a background thread.
However:
Creating and populating widgets normally does not take much time. You may be better served using Traceview to determine why yours is taking so long.
If the issue isn't that an individual bitmap is slow, but that you are creating two tons of bitmaps, that will be a problem regardless of how you do it. Just because the work is done on a background thread does not make it "free" from a CPU standpoint, plus there are memory pressures to consider.
If there's a way to create a designed bitmap not using my current method, i'll be glad to hear.
You could draw directly to the Canvas using the methods available on Canvas.
Generally, you can use an extension of AsyncTask, like this:
private class MyAsyncTask extends AsyncTask<Integer, Void, Bitmap> {
protected Long doInBackground(Integer... bitmapID) {
return loadYourBitmap(bitmapID[0]); //<-happens in background
}
protected void onPostExecute(Bitmap yourBitmap) {
setSomethingTo(yourBitmap); //<- happens in foreground when doInBackground is done
}
}
One possible solution to this problem is to cache the bitmaps. When you want to display multiple markers as bitmaps. Simple [LruCache][1] - based in-memory cache would work just fine. The only thing that is needed to be taken care of is how much memory you are using for caching. Have a look at this official Google docs to know more about caching in bitmaps.
You can cache BitmapDescriptor for every unique bitmap you have. This way you can get some extra performance by avoiding making calls to BitmapDescriptorFactory every time you need to create a marker.
Here is the sample code:
LruCache<String, BitmapDescriptor> cache;
private void initCache()
{
//Use 1/8 of available memory
cache = new LruCache<String, BitmapDescriptor>((int)(Runtime.getRuntime().maxMemory() / 1024 / 8));
}
private void addMarker(LatLng position, String assetPath)
{
MarkerOptions opts = new MarkerOptions();
opts.icon(getBitmapDescriptor(assetPath));
opts.position(position);
mMap.addMarker(opts);
}
private BitmapDescriptor getBitmapDescriptor(String path) {
BitmapDescriptor result = cache.get(path);
if (result == null) {
result = BitmapDescriptorFactory.fromAsset(path);
cache.put(path, result);
}
return result;
}

aChartEngine; determining when a GraphicalView is fully rendered/drawn

I've got a fragment, which I construct and insert a GraphicalView, and I need to know when the GraphicalView (chart) is done being drawn. the api isChartDrawn always returns false, including within onResume of the containing fragment.
public void onResume()
{
Log.d(TAG, "onResume, the mChart isDrawn: " + mChart.isChartDrawn());
super.onResume();
mListener.didNotificyChartDrawn();
}
Is there a notification I'm not seeing, or strategy for knowing when the chart is done being rendered? I'm asking because I need to access the series points from within one of the series of the XYChart used to construct the graphical view, like this:
mChart = new GraphicalView(getActivity(), mXYChart);
where mXYChart is an instance of the LineChart.
The graph renders fine, and I'm able to access the points I need later on via touch handling, just need to get to them a little earlier now and am hitting this issue. Any work arounds, etc, appreciated.
you're getting that because during onResume still was not draw yet. That's whole Android, not just aChartEngine. I'm not sure it's the best design decision, but that's how it is.
But good news is: there's a nice trick.
getView().getViewTreeObserver().addOnDrawListener(new OnDrawListener(){
void onDraw(){
getView().getViewTreeObserver().removeOnDrawListener(this);
// do your stuff here
}
});
this trick is used A LOT for animation, so you can measure stuff on screen and do the proper animations.
If you look at the onDraw method in GraphicalView, it sets the boolean mDrawn to true at the very end. So what must be happening is you are calling
public boolean isChartDrawn() {
return mDrawn;
}
Before it has completed the onDraw method. I would either create a interval handler to keep checking if mDrawn has been changed to true, or modify the library file GraphicalView so that it has an optional listener that you can attach to be fired off when the thing is drawn:
DrawnInterface mCallback;
public interface DrawnInterface{
public void onDrawn();
}
.....
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.getClipBounds(mRect);
......
canvas.drawBitmap(fitZoomImage, left + width - zoomSize * 0.75f, buttonY, null);
}
mDrawn = true;
mCallback.onDrawn();
}
Then make your calling activity implement the DrawnInterface you defined, and initialize the interface inside the constructor of GraphicalView

what is the use of recycle() method in TypedArray

I have created a GalleryView and ImageView which displays the Image bigger when an item is clicked in the gallery. I have used the below code to implement the ImageAdapter:
public ImageAdapter(Context c)
{
context = c;
TypedArray a = obtainStyledAttributes(R.styleable.gallery1);
itemBackground = a.getResourceId(R.styleable.gallery1_android_galleryItemBackground, 0);
a.recycle();
}
When I removed the statement a.recycle() there is no change and the app is running normally as before, but everywhere I read that it is compulsory to recycle the typedArray. When there is no change in the way my app is running what is the use of the recycle() method?
The point is similar to the idea of clearing a pointer in a C-language (if you're familiar with that). It is used to make the data associated with a ready for garbage collection so memory/data is not inefficiently bound to a when it doesn't need to be. Read more here.
It's important to note that this isn't really necessary unless you're actually reusing "a". GC should automatically clear up this data for you if the object is not used again. The reason why a TypedArray is different, however, is because a TypedArray has other internal data that must be returned (known as StyledAttributes) to the TypedArray for later reuse. Read about that here.
The recycle() causes the allocated memory to be returned to the available pool immediately and will not stay until garbage collection. This method is also available for Bitmap.
recycle basically means..free/clearing all the data associated with corresponding resource.
In Android we can find recycle for Bitmap and TypedArray.
If you check both source files then you can find a boolean variable "mRecycled" which is "false"(default value). It is assigned to "true" when recycle is called.
So, Now if you check that method(recycle method in both the classes) then you we can observe that they are clearing all the values.
For reference here are the methods.
Bitmap.java:
public void recycle() {
if (!mRecycled && mNativePtr != 0) {
if (nativeRecycle(mNativePtr)) {
// return value indicates whether native pixel object was actually recycled.
// false indicates that it is still in use at the native level and these
// objects should not be collected now. They will be collected later when the
// Bitmap itself is collected.
mBuffer = null;
mNinePatchChunk = null;
}
mRecycled = true;
}
}
TypedArray.java
public void recycle() {
if (mRecycled) {
throw new RuntimeException(toString() + " recycled twice!");
}
mRecycled = true;
// These may have been set by the client.
mXml = null;
mTheme = null;
mAssets = null;
mResources.mTypedArrayPool.release(this);
}
this line
mResources.mTypedArrayPool.release(this);
will release the typedArray from the SunchronisedPool whose default value is 5.
So you shouldn't use same typedArray again as it gets cleared.
once "mRecycled" of TypedArray is true then while getting its properties it will throw RuntimeException saying "Cannot make calls to a recycled instance!".
simliar behaviour incase of Bitmap as well.
Hope it helps.
As its use is over[after initializing our local attributes] So we recycle it back to the Resource pool
Simple

Avoid memory leaks on Android

I just read a blogpost by Romain Guy on how to avoid memory leaks in Android.
In the article he gives this example:
private static Drawable sBackground;
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
Romain said:
This example is one of the simplest cases of leaking the Context.
My question is, how do you modify it correctly?
Just like this?
TextView label = new TextView(Context.getApplicationContext());
I tested both ways and the results are the same. I can't locate the difference. And I think that this is more correct than the Application context. Because this is a reference to Activity, that is to say, the TextView belongs to that Activity.
Could someone give me an explanation for this?
The actual problem with that code isn't the context passed to create the drawable, but private static Drawable sBackground;
The static Drawable is created with the Activity as the context, so in THAT case, there's a static reference to a Drawable that references the Activity, and that's why there's a leak. As long as that reference exists, the Activity will be kept in memory, leaking all of its views.
So it's the Drawable which should be created using the application context, not the TextView. Creating the TextView with "this" is perfectly fine.
edit : Actually, that might not make a big difference, the problem is that once the drawable is binded to a view, there's a reference to the view, which references the activity. So you need to "unbind" the drawable when you exit the activity.
I'm not sure if Romain had updated his blog entry since you read it, but he's pretty clear on how to avoid the leaks, even pointing you to an example in the Android OS. Note that I fixed the broken link in Romain's blog entry via archive.org.
This example is one of the simplest cases of leaking the Context and
you can see how we worked around it in the Home screen's source
code (look for the unbindDrawables() method) by setting the stored
drawables' callbacks to null when the activity is destroyed.
Interestingly enough, there are cases where you can create a chain of
leaked contexts, and they are bad. They make you run out of memory
rather quickly.
There are two easy ways to avoid context-related memory leaks. The
most obvious one is to avoid escaping the context outside of its own
scope. The example above showed the case of a static reference but
inner classes and their implicit reference to the outer class can be
equally dangerous. The second solution is to use the Application
context. This context will live as long as your application is alive
and does not depend on the activities life cycle. If you plan on
keeping long-lived objects that need a context, remember the
application object. You can obtain it easily by calling
Context.getApplicationContext() or Activity.getApplication().
In summary, to avoid context-related memory leaks, remember the
following:
Do not keep long-lived references to a context-activity (a reference to an activity should have the same life cycle as the
activity itself)
Try using the context-application instead of a context-activity
Avoid non-static inner classes in an activity if you don't control their life cycle, use a static inner class and make a weak reference to the activity inside. The solution to this issue is to use a static inner class with a WeakReference to the outer class, as done in ViewRoot and its W inner class for instance
A garbage collector is not an insurance against memory leaks
Memory leaks at that code mostly happen when you rotate your screen (that is, changing the orientation state) so your activity was destroyed and created again for the new orientation. There's a lot of explanation about memory leaks.
You can take a look at one of the Google I/O 2011 video about Memory Management here. In the video, you can also use the memory management tools like Memory Analyzer available to download here.
I don't know if you are having any trouble with this in your app, but I have created a drop in solution that fixes all the android memory leak issues with standard android classes: http://code.google.com/p/android/issues/detail?id=8488#c51
public abstract class BetterActivity extends Activity
{
#Override
protected void onResume()
{
System.gc();
super.onResume();
}
#Override
protected void onPause()
{
super.onPause();
System.gc();
}
#Override
public void setContentView(int layoutResID)
{
ViewGroup mainView = (ViewGroup)
LayoutInflater.from(this).inflate(layoutResID, null);
setContentView(mainView);
}
#Override
public void setContentView(View view)
{
super.setContentView(view);
m_contentView = (ViewGroup)view;
}
#Override
public void setContentView(View view, LayoutParams params)
{
super.setContentView(view, params);
m_contentView = (ViewGroup)view;
}
#Override
protected void onDestroy()
{
super.onDestroy();
// Fixes android memory issue 8488 :
// http://code.google.com/p/android/issues/detail?id=8488
nullViewDrawablesRecursive(m_contentView);
m_contentView = null;
System.gc();
}
private void nullViewDrawablesRecursive(View view)
{
if(view != null)
{
try
{
ViewGroup viewGroup = (ViewGroup)view;
int childCount = viewGroup.getChildCount();
for(int index = 0; index < childCount; index++)
{
View child = viewGroup.getChildAt(index);
nullViewDrawablesRecursive(child);
}
}
catch(Exception e)
{
}
nullViewDrawable(view);
}
}
private void nullViewDrawable(View view)
{
try
{
view.setBackgroundDrawable(null);
}
catch(Exception e)
{
}
try
{
ImageView imageView = (ImageView)view;
imageView.setImageDrawable(null);
imageView.setBackgroundDrawable(null);
}
catch(Exception e)
{
}
}
// The top level content view.
private ViewGroup m_contentView = null;
}

Categories

Resources