The tree graph nodes each represent a memory allocation which optionally holds pointers to other memory allocations. The tree graph may be more of a visual representation of how Java tracks reachable and unreachable memory and not the actually internal representation used.
Memory - all allocations reachable "actice"
Memory Table
Address Size Active 1 n bytes active 2 n bytes active 3 n bytes active 4 n bytes active 5 n bytes active 6 n bytes active 7 n bytes active 8 n bytes active 9 (root) n bytes active Memory Tree
Example of inactive memory cluster which GC will release.
Memory - few inactive allocations GC will release
Memory Table
Address Size Active 1 n bytes active 2 n bytes active 3 n bytes active 4 n bytes active 5 n bytes inactive 6 n bytes active 7 n bytes active 8 n bytes inactive 9 (root) n bytes active Memory Tree
Example of circular reference which has become unreachable and will be released by GC.
Memory - with inactive circular references cluster
Memory Table
Address Size Active 1 n bytes inactive 2 n bytes inactive 3 n bytes inactive 4 n bytes inactive 5 n bytes inactive 6 n bytes active 7 n bytes active 8 n bytes active 9 (root) n bytes active > Memory Tree
Since Java VM will automatically release any unreachable memory it is hard to make a memory leak. In other languages like C/C++ you can allocate memory using malloc() and forget to release the memory by not calling free() on the pointer. In Java there is no free method and the memory cleans up after itself. In Java it is not possible to have a true leak like you can in C/C++. Instead Java leaks are just poor object managment. Meaning you can consume too much memory if you hold on to objects too long. In Android a common leak occurs when creating listener and adding it to a list and failing to remove it from the list.
The following code fragment intentionally creates a leak by saving the anonymous listener of the class it is making. Since the anonymous class holds a pointer to the class (see disucssion below), holding a copy of the listener causes class memory to stay active.
public class Leak1 {
long[] mBigMemory = new long[512]; // allocate some memory.
interface Listener {
void myListener(int value);
}
Listener getListener() {
return new Listener() {
@Override
public void myListener(int value) {
System.out.println(" Got value=" + value);
}
};
}
public void doWork() {
// do something.
}
}
public class LeakTester {
List<Leak1.Listener> mListeners = new ArrayList<>();
public void Tester(int itemCnt) {
for (int idx = 0; idx < itemCnt; idx++) {
Leak1 leak1 = new Leak1();
// Create leak by holding Listener (anonymous/inner class)
// which has pointer to parent class.
mListeners.add(leak1.getListener());
}
}
}
By removing the listener from the list, we allow the class memory to be released. Example code fragment which avoids leak.
public class LeakTester {
List mListeners = new ArrayList<>();
public void Tester(int itemCnt) {
for (int idx = 0; idx < itemCnt; idx++) {
Leak1 leak1 = new Leak1();
// Create leak by holding Listener (anonymous/inner class)
// which has pointer to parent class.
mListeners.add(leak1.getListener());
// use Leak1 class
leak1.doWork();
// Clear listener to release class memory and avoid leak.
mListeners.remove(leak1.getListener());
}
}
}
Background on Reference Counting:
Examples of circular references:
Self -> Self // Reference to self
Item1 -> Item2 -> Item3 -> Item1 // Circular chain
// Java example of circular reference to self.
class Self {
Object mTag;
};
Self self = new Self();
self.mTag = self;
// Java example of circular reference chain.
class Base {
int mIdx;
Base(int idx) {
mIdx = idx;
}
}
class Item extends Base {
Base mBase;
Item(Base base, int idx) {
super(idx);
mBase = base;
}
void set(Base base) {
mBase = base;
}
}
void makeCircularChain() {
Item item;
Item last = item = new Item(null, 0);
for (int idx = 1; idx < 3; idx++) {
last = new Item(last, idx);
}
item.set(last); // 3 instances of item form circular reference chain.
}
The above are examples of common Android circular references which don't produce memory leaks
because Java ignores reference counting.
They are leaks only if the circular reference cluster is not reachable from the root allocation
nodes.
Circular references are still important to understand because they can cause extra classes to stay active and not get released. See circular memory released and leak code fragment .
Some common ways Android uses anonymous classes are:
// Example of anonymous class, creating a reference to parent class.
mSpecialButton = (Button)findViewById(R.id.at_button);
mSpecialButton.setOnClickListener(new View.OnClickListener() {
// The moment this anonymous class is created it has a pointer to the parent.
@Override public void onClick(View v) {
// do something special
}
});
// Example of anonymous class using Runnable creating a circular reference to parent.
// NOTE - Once Runnable executes and removed from mHandle queue,
// circular reference disappears.
public void updateSeeMore() {
mHandle.post(new Runnable() {
@Override
public void run() {
// do something.
}
});
}
Anonymous class can be a problem because they have an automatic pointer to their parent.
If you store an anonymous class instance in another class you link the anonymous outer class
and the holder class. If you fail to clear the anonymous class in the holder and break
the linkage, the anonymous outer may leak. See solution in Avoid Leaks.
// Example of non-static inner class memory leak.
class MyActivity extends Activity {
// Storing non-static inner class in static data member creates
// a permenant reference. MyActivity will never get garbage collected
// until the static value is cleared.
static ClickHandler clickHandler = new ClickHandler();
public void myMethod() {
Button[] buttons = getButtonArray();
for (Button button : buttons) {
button.setOnClickListener(clickHandler);
}
}
// Non-static inner class has pointer to parent class.
class ClickHandler implements View.OnClickListener {
// Inner class has automatic pointer to parent as if it had
// a data member like this:
//
// MyActivity mPointer2Parent = MyActivity.this;
public void onClick(View v) {
showToast(((Button) v).getText());
}
}
}
Note - By refactoring the non-static inner class into a static inner class you can avoid the parent reference.
The other option is to clear any callbacks (ex: setOnClickListener(null)) to a null value when your are done with the class.
In the code above, you would set clickHandler to null..
If your class provides public methods for external users to post delayed messages on your Handler you need to explicitly remove ALL messages during destruction to avoid leaks.
// Example sending message to Handler
class GalleryFragment .... {
final int MSG_SHOW_IMAGE = 100;
final Handler mHandler = new Handler();
updateGallery(InputStream is) {
final Bitmap bitmap = BitmapFactory.decodeStream(is);
mHandler.obtainMessage(MSG_SHOW_IMAGE, bitmap).sendToTarget();
}
}
While the message is pending the Handler will have a circular reference.
The pending message is typically a short term problem because it is quickly processed and the
circular reference disappears. The problem can be extended by using a delayed message.
final Message msg = handler.obtainMessage();
mHandler.sendMessageDelayed(MSG_SHOW_IMAGE, bitmap, TimeUnit.MINUTES.toMillis(1000));
The delayed send will obviously increase the lenght the circular reference exists.
The same is true for post and postDelayed message APIs on Handlers and View objects.
The leak occurs when you have pending messages which are not processed and your class needs to be destroyed.
The following is a graph from Google I/O presentation on ViewHolder showing only a small improvement
in performance from 50 frames/second to 55 frames/second when scrolling
In the following simple ViewHolder, the benefit of making
and populating the ViewHolder is only justified if every ViewHolder is reused multiple times.
If your device and List layout presents a list which never scrolls, then the ViewHolder is costing you
performance, memory and adding code complexity.
If your list scrolls one full page (meaning every ViewHolder has been reused once), you may be close to improving performance.
If your list scrolls multiple pages you are starting to see
a benefit. Looking at the graph provided by Google I/O you can see there is a very small gain even with 10,000
items. The truth is
Recommendation - ONLY use a ViewHolder if you have:
// Example - simple ViewHolder
// ViewHolder must be static class to avoid leaking.
public static class ViewHolder {
TextView text;
ImageView icon;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item_icon_text, parent, false);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(holder); // <--- Risk of leak if ViewHolder has extra objects.
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.text.setText(DATA[position]);
holder.icon.setImageBitmap(position & 1) == 1 ? mIcon1 : mIcon2);
return convertView;
}
// Example - Leaking ViewHolder
// ViewHolder must be static class to avoid leaking.
public static class ViewHolderLeak {
TextView text;
ImageView icon;
View parent; // < -- hold parent forms circular reference.
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolderLeak holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item_icon_text, parent, false);
holder = new ViewHolderLeak();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.image);
holder.parent = convertView;
convertView.setTag(holder); // < -- Circular reference, may prevent memory from releasing.
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.text.setText(DATA[position]);
holder.icon.setImageBitmap(position & 1) == 1 ? mIcon1 : mIcon2);
return convertView;
}
To avoid leaking memory you need to break all references back to the root allocation. The process is to reverse the actions you performed during class creation. If during the creation, you:
Object | Life Cycle State Method | Diagram | |||
---|---|---|---|---|---|
Activity | onStop() or onDestroy() | Activity Life Cycle Diagram | |||
Fragment | onDestroyView(), onDestroy() or onDetach() | Fragment Life Cycle Diagram | |||
View | onDetachedFromWindow() | View Life Cycle Diagram
RecyclerView | onDetachedFromRecyclerView() or onViewRecycled()
| Recycler Life Cycle Diagram
| |
The following is a custom layout which has several circular references. By breaking these in the destroy LIFE state onDetachFromWindow() method the object memory leaks are reduced.
// Example custom layout which implements onDetachFromWindow()
// to avoid oject (memory) leaks.
//
// Example has Listener (callbacks) and Containers (ArrayList) which
// hold Anonymous classes causing circular references.
//
// Class also has a Handler which may have pending messages.
//
public class LeakLayout extends FrameLayout {
OnClickListener mClickListener;
private final List<SwipeListener> mSwipeListeners = new ArrayList<>();
private final Map<View, Rect> mViewBoundCache = new HashMap<>();
private final Handler mHandler = new Handler();
public final int MSG_SWIPE_LEFT = 1;
public final int MSG_SWIPE_RIGHT = 2;
public interface SwipeListener {
void onOpen(LeakLayout layout);
void onClose(LeakLayout layout);
}
public LeakLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void setOnClickListener(OnClickListener listener) {
// Store listener in parent and in our private container.
// Note - clear both in destroy LIFE state to avoid leaks.
super.setOnClickListener(listener);
mClickListener = listener;
}
public void addSwipeListener(SwipeListener listener) {
// Note - clear container in destroy LIFE state to avoid leaks.
mSwipeListeners.add(listener);
}
public void postMessage(int what, int when) {
// Note - any pending messages will need to be removed
// in destroy LIFE state to avoid leaks.
mHandler.obtainMessage(what, when).sendToTarget();
}
/**
* Get children's bounds.
*/
private void captureChildrenBound() {
for (int idx = 0; idx < getChildCount(); idx++) {
View child = getChildAt(idx);
mViewBoundCache.put(child, child.getClipBounds());
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// Create anonymous classes - creating circular references to parent.
if (mClickListener == null) {
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// performAdapterViewItemClick();
}
});
}
}
@Override
protected void onDetachedFromWindow() {
// Destroy LIFE state - avoid memory leaks by:
// 1. Remove all pending messages or runnables (always do first)
// 2. Clear callbacks: unregister or set callbacks to null
// ** Depending on case, this may be redundant with step#3
// 3. Set all objects with extra memory references to null.
// Setting to null may be enough to break multiple refs, making step#2 optional.
// 4. Callup to super
mHandler.removeMessages(MSG_SWIPE_LEFT);
mHandler.removeMessages(MSG_SWIPE_RIGHT);
setOnClickListener(null);
setOnLongClickListener(null);
mSwipeListeners.clear();
mViewBoundCache.clear();
super.onDetachedFromWindow();
}
}
If an object has an array of listener callbacks and the object is held by multiple users, you may have to remove ALL of the listener callbacks. But if the object is only held by one object, only that object has a reference, so you either need to remove its associated listener callback OR set the object to null inside the object.
// Owner creates circular reference when addChild is called.
class Owner {
Child mChild;
public void addChild(Child child) {
mChild = child;
mChild.addOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
// do something special
}
});
}
}
// Worker creates a one-to-many circular reference.
class Worker {
Child mChild = new Child();
List<Owner> owners = new ArrayList();
public void addOwners(Owner owner) {
owners.add(owner);
owner.addChild(mChild);
}
}
In the above case every Owner instance has a pointer to a Child object which in turn has a clickListener which has a pointer to the Owner (anonymous class has pointer to outer class). Assuming addOnClickListener is storing the listeners in a list we have a single Child instance which is causing every Owner instantance to leak until the circular references are broken.
To free the memory either remove listener from every Owner or clear the Child in every Owner. Assuming the Child view object is also in a layout, clearing the Child from the Owner will not release the memory until the Child is released from its parent layout. The first solution will allow immediate release of the Owner objects once Worker clears its list or goes out of scope.
// Updated - to provide ways to cleanup and avoid leaks.
// Owner creates circular reference when addChild is called.
class Owner {
Child mChild;
View.OnClickListener mListener; // Save listener to use in remove
public void addChild(Child child) {
mChild = child;
mListener = new View.OnClickListener() {
@Override public void onClick(View v) {
// do something special
}
});
mChild.addOnClickListener(mListener);
}
// Required for cleanup option #2
public void removeChild() {
mChild = null;
}
// Required for cleanup option #3, use previously saved listener
public void removeListener() {
mChild.removeOnClickListener(mListener);
}
}
class Worker {
Child mChild = new Child();
List<Owner> owners = new ArrayList();
public void addOwners(Owner owner) {
owners.add(owner);
owner.addChild(mChild);
}
public void onDestroy() {
// or option #1 (if a remove All is available)
mChild.removeAllListeners();
// or option #2, remove child from Owners
// Memory not freed until Child view also released from parent layout.
for (Owner owner : owners) {
owner.rmoveChild();
}
// or option #3
for (Owner owner : owners) {
owner.removeListener();
}
}
}
The above code could also be re-written in several ways to avoid any cleanup code:
// Owner does not hold a copy of Child to avoid circular reference.
class Owner {
public void addChild(Child child) {
child.addOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
// do something special
}
});
}
}
For example, if you customize a Layout and you have some member View objects which are also stored in the Layout. You can set your member view objects to null but don't call onDetachFromWindow() on them because the Android LIFE cycle will perform the same task. Double calling these methods can create side effects.
@Override
protected void onDetachedFromWindow() {
// Chaining LIFE cycle into children is NOT recommended.
mCustomView.onDetachedFromWindow();
// Clearing Listener (callbacks) is recommend.
mCustomView.setOnClickListenern(null);
// Setting complex objects to null is recommend.
mCustomView = null;
// Callup up to super is mandatory.
super.onDetachedFromWindow();
}
On a Handler you have to remove the messages by object instance or by what value. Handler removeMessage(int what). You can use remove CallbacksAndMessages(null) to remove pending anonymous Runnables.
void update() {
// Example of delayed messange and post
mUIHandler.sendEmptyMessageDelayed(MSG_ON_ITEM_CHANGED, ON_ITEM_CHANGED_DELAY);
mRecyclerView.postDelayed(new Runnable() {
@Override
public void run() {
mRecyclerView.smoothScrollToPosition(position);
}
}, 200);
}
@Override
protected void onDetachedFromWindow() {
// Example of cleanup
mUIHandler.removeMessages(MSG_ON_ITEM_CHANGED);
// Remove pending anonymous Runnables.
mUIHandler.removeCallbacksAndMessages(null);
mRecyclerView.cancelPendingInputEvents();
mRecyclerView = null;
}
See also:
void init() {
// Creation -
// 1. Setup click on button
// 2. Add Listener to media player.
mButton1 = findViewById(R.id.at_button1);
mButton1.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
// do something special
}
});
mMediaPlayer = new ExoMediaPlayer(RendererBuilderFactory.createRendererBuilder(getContext(), new Media(mUri), true), mUri);
mMediaPlayer.addListener(playerListener);
}
@Override
protected void onDetachedFromWindow() {
// Example of cleanup, reverse construction
// 1. Clear anonymous click listener
// 2. Remove Media Player listener
mButton1.setOnClickListener(null);
mMediaPlayer.removeListener(playerListener);
mMediaPlayer.release();
mMediaPlayer = null;
}
// Example ViewTreeObserver leaking listener
HorizontalScrollView mScrollView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mFragmentView = inflater.inflate(R.layout.fragment, container, false);
mScrollView = (HorizontalScrollView)mFragmentView.findViewById(R.id.console_card_list_horz);
mScrollView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// Do something...
}
});
Because the listener is anonymous we have a parent reference.
Lets try and reduce the memory references in the cleaup code onDestroyView().
// Example trying to avoid memory leak by removing listener (still leaks due to flaw in ViewTreeObserver)
HorizontalScrollView mScrollView;
ViewTreeObserver.OnGlobalLayoutListener mGlobalListener;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mFragmentView = inflater.inflate(R.layout.fragment, container, false);
mScrollView = (HorizontalScrollView)mFragmentView.findViewById(R.id.console_card_list_horz);
mGlobalListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// Do something...
}
};
mScrollView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalListener);
@Override
public void onDestroyView() {
// Still leaks because of flaw in Android with latency on releasing listener.
mScrollView.getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalListener);
}
The above leaks due to the internal flaw in ViewTreeObserver and the only fix is to call the appropriate dispatch method for your listener to cause the internal lists to update.
Listener | Dispatch |
---|---|
addOnDrawListener
removeOnDrawListener | dispatchOnDraw |
addOnGlobalFocusChangeListener
removeOnGlobalFocusChangeListener | [package] dispatchOnGlobalFocusChange |
addOnGlobalLayoutListener
removeOnGlobalLayoutListener | dispatchOnGlobalLayout |
addOnPreDrawListener
removeOnPreDrawListener | dispatchOnPreDraw |
addOnScrollChangedListener
removeOnScrollChangedListener | [package] dispatchOnScrollChanged |
addOnTouchModeChangeListener
removeOnTouchModeChangeListener | [package] dispatchOnTouchModeChanged |
addOnWindowAttachListener
removeOnWindowAttachListener | [package] dispatchOnWindowAttachedChange |
addOnWindowFocusChangeListener
removeOnWindowFocusChangeListener | [package] dispatchOnWindowFocusChange |
addOnWindowShownListener
removeOnWindowShownListener | dispatchOnWindowShown |
// Example which does not leak ViewTreeObserver listener.
@Override
public void onDestroyView() {
// Leaks because of flaw in Android with latency on releasing listener.
mScrollView.getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalListener);
// Force internal custom concurrent list to switch to new list and free listener.
// Listener is now gone, breaking circular reference, avoiding memory leak.
mScrollView.getViewTreeObserver().dispatchOnGlobalLayout();
}
Here is code fragments from Android API 25 which shows the problem:
//
// Code extracted from ViewTreeObserver API 25
// ** Custom concurrent CopyOnWriteArray with two lists **
//
/**
* Copy on write array. This array is not thread safe, and only one loop can
* iterate over this array at any given time. This class avoids allocations
* until a concurrent modification happens.
*
* Usage:
*
* CopyOnWriteArray.Access<MyData> access = array.start();
* try {
* for (int i = 0; i < access.size(); i++) {
* MyData d = access.get(i);
* }
* } finally {
* access.end();
* }
*/
static class CopyOnWriteArray<T> {
private ArrayList<T> mData = new ArrayList<T>();
private ArrayList<T> mDataCopy; // <--- Latency due to this 2nd list
private final Access<T> mAccess = new Access<T>();
private boolean mStart;
static class Access<T> {
private ArrayList<T> mData;
private int mSize;
T get(int index) {
return mData.get(index);
}
int size() {
return mSize;
}
}
CopyOnWriteArray() {
}
private ArrayList<T> getArray() {
if (mStart) {
if (mDataCopy == null) mDataCopy = new ArrayList<T>(mData);
return mDataCopy;
}
return mData;
}
// Replace active list with the temporary list.
Access<T> start() {
if (mStart) throw new IllegalStateException("Iteration already started");
mStart = true;
mDataCopy = null;
mAccess.mData = mData;
mAccess.mSize = mData.size();
return mAccess;
}
void end() {
if (!mStart) throw new IllegalStateException("Iteration not started");
mStart = false;
if (mDataCopy != null) {
mData = mDataCopy;
mAccess.mData.clear();
mAccess.mSize = 0;
}
mDataCopy = null;
}
int size() {
return getArray().size();
}
// Create temporary list from active list, perform add on temporary list.
void add(T item) {
getArray().add(item);
}
// Create temporary list from active list, perform add on temporary list.
void addAll(CopyOnWriteArray<T> array) {
getArray().addAll(array.mData);
}
// Create temporary list from active list, perform remove on temporary list.
void remove(T item) {
getArray().remove(item);
}
void clear() {
getArray().clear();
}
}
// -----------------------------------------------------------------------------------------
// Code fragment of ViewTreeObserver
class ViewTreeObserver {
// Non-recursive listeners use CopyOnWriteArray
// Any listener invoked from ViewRootImpl.performTraversals() should not be recursive
private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
private boolean mAlive = true;
/**
* Register a callback to be invoked when the global layout state or the visibility of views
* within the view tree changes
*
* @param listener The callback to add
*
* @throws IllegalStateException If {@link #isAlive()} returns false
*/
public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {
// checkIsAlive();
if (mOnGlobalLayoutListeners == null) {
mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>();
}
mOnGlobalLayoutListeners.add(listener); // <--- listener added to temporary internal list.
}
/**
* Remove a previously installed global layout callback
*
* @param victim The callback to remove
*
* @throws IllegalStateException If {@link #isAlive()} returns false
*
* @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
*/
public void removeOnGlobalLayoutListener(OnGlobalLayoutListener victim) {
// checkIsAlive();
if (mOnGlobalLayoutListeners == null) {
return;
}
mOnGlobalLayoutListeners.remove(victim); // <--- listener removed from temporary internal list only.
}
/**
* Notifies registered listeners that a global layout happened. This can be called
* manually if you are forcing a layout on a View or a hierarchy of Views that are
* not attached to a Window or in the GONE state.
*/
public final void dispatchOnGlobalLayout() {
// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
if (listeners != null && listeners.size() > 0) {
CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start(); // <--- use temporary list.
try {
int count = access.size();
for (int i = 0; i < count; i++) {
access.get(i).onGlobalLayout();
}
} finally {
listeners.end();
}
}
}
}
// Example of possible leaking thread if it never completes
// or runs longer then you expect.
ImageView imageView = (ImageView)parent.findViewById(R.id.image1);
String image_url = "http//www.gotimages.com/nice.jpg"
new Thread(new Runnable()
{
@Override
public void run() {
try {
final Bitmap bitmap = BitmapFactory.decodeStream((InputStream) new URL(image_url).getContent());
imageView.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
} catch (Exception ex) {
// TODO: handle exception
}
}
}).start();
Better to store Thread instance in a variable so you can stop it.
// AVOID leaking thread by keeping hold of thread in a variable,
// so you can stop execution and cleanup.
void update() {
ImageView imageView = (ImageView)parent.findViewById(R.id.image1);
String image_url = "http//www.gotimages.com/nice.jpg"
mWorkerThread = new Thread(new Runnable()
{
@Override
public void run() {
try {
final Bitmap bitmap = BitmapFactory.decodeStream((InputStream) new URL(image_url).getContent());
imageView.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
} catch (Exception ex) {
// TODO: handle exception
}
}
});
mWorkerThread.start();
}
@Override
protected void onDetachedFromWindow() {
mImageView.cancelPendingInputEvents();
mWorkerThread.interrupt();
mWorkerThread = null;
mImageView = null;
}
I used the built-in memory monitor in Android Studio to visualize the active allocate memory heap and the Heap Profiler view to see the outstanding active object counts and size. Once I found and fixed a leak I used dumpsys meminfo <package name> to capture a memory report before (with the leak) and after (leak removed) to verify the changes.
Dumpsys MemInfo is a nice way to get a summary of the current state of memory resources in your package.
Syntax:
adb shell dumpsys meminfo <package-name> > save-results.txt
Example:
adb shell dumpsys meminfo com.wmur.android.weather > wmur.txt
On Unix/Linix/OSX you can continuously monitor using the 'watch' command:
watch "adb shell dumpsys meminfo com.wmur.android.weather"
A DOS/Windows version of watch can be found here:
llwatch
Sample reported generated from
dumpsys meminfo.
The key spots I monitor are the Native Heap Alloc, Java Heap summary and Views count.
Applications Memory Usage (kB):
Uptime: 99778973 Realtime: 780584992
** MEMINFO in pid 24644 [com.wmur.android.weather] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 28618 28572 0 0 46336 40903 5432
Dalvik Heap 22969 22896 0 0 41688 27812 13876
Dalvik Other 2752 2752 0 0
Stack 1440 1440 0 0
Ashmem 1060 1016 0 0
Gfx dev 22688 20776 0 0
Other dev 12 0 12 0
.so mmap 19402 284 12196 0
.apk mmap 2177 0 1716 0
.ttf mmap 251 0 216 0
.dex mmap 21717 32 18296 0
.oat mmap 2948 0 1224 0
.art mmap 1895 1344 292 0
Other mmap 878 16 444 0
EGL mtrack 45520 45520 0 0
Unknown 10940 10940 0 0
TOTAL 185267 135588 34396 0 88024 68715 19308
App Summary
Pss(KB)
------
Java Heap: 24532
Native Heap: 28572
Code: 33964
Stack: 1440
Graphics: 66296
Private Other: 15180
System: 15283
TOTAL: 185267 TOTAL SWAP (KB): 0
Objects
Views: 316 ViewRootImpl: 1
AppContexts: 6 Activities: 1
Assets: 7 AssetManagers: 5
Local Binders: 64 Proxy Binders: 37
Parcel memory: 42 Parcel count: 172
Death Recipients: 5 OpenSSL Sockets: 7
Using egrep (enhance grep, or grep with -E) you can limit the output of meminfo to the key parts.
adb shell dumpsys meminfo com.wmur.android.weather | egrep '(Heap|Views)'
Pss Private Private Swapped Heap Heap Heap
Native Heap 28594 28548 0 0 47872 40205 7666
Dalvik Heap 29137 29064 0 0 43954 38224 5730
Java Heap: 30700
Native Heap: 28548
Views: 316 ViewRootImpl: 1
List of Android Memory Tools: