performance #1: memory
TRANSCRIPT
Yonatan Levin
Google Developer Expert & Android @ Gett
Idan Felix
Senior Android & Redhead Varonis
Jonathan Yarkoni
Android Developer & Advocate
Ironsource
Android Academy Staff
Britt Barak
Android LeadReal
Muiriel Felix
Android Design
What’s next?
30/5 - Felix
- How to draw right? ( Overdraw, Cliprect, Bitmaps)
13/6 - Britt
- View, Animations
Dalvik
07-01 15:56:16.785: I/dalvikvm-heap(30615): Grow heap (frag case) to 38.179MB for
8294416-byte allocation
07-01 15:56:17.625: I/Choreographer(30615): Skipped 35 frames! The application may be
doing too much work on its main thread.
07-01 15:56:19.035: D/dalvikvm(30615): GC_CONCURRENT freed 35838K, 43% free
51351K/89052K, paused 3ms+5ms, total 106ms
07-01 15:56:19.035: D/dalvikvm(30615): WAIT_FOR_CONCURRENT_GC blocked 96ms
GC Reason
GC_CONCURRENT - A concurrent GC that frees up memory as your heap begins to fill up.
GC_FOR_MALLOC - A GC caused because your app attempted to allocate memory when your
heap was already full, so the system had to stop your app and reclaim memory.
GC_HPROF_DUMP_HEAP - A GC that occurs when you request to create an HPROF file to
analyze your heap.
GC_EXPLICIT - An explicit GC, such as when you call gc() (which you should avoid
calling and instead trust the GC to run when needed).
GC_EXTERNAL_ALLOC - This happens only on API level 10 and lower (newer versions
allocate everything in the Dalvik heap). A GC for externally allocated memory (such
as the pixel data stored in native memory or NIO byte buffers).
ART
07-01 16:00:44.531: I/art(198): Explicit concurrent mark sweep GC freed 700(30KB)
AllocSpace objects, 0(0B) LOS objects, 792% free, 18MB/21MB, paused 186us total
12.763ms
07-01 16:00:46.517: I/art(29197): Background partial concurrent mark sweep GC freed
74626(3MB) AllocSpace objects, 39(4MB) LOS objects, 1496% free, 25MB/32MB, paused
4.422ms total 1.371747s
07-01 16:00:48.534: I/Choreographer(29197): Skipped 30 frames! The application may be
doing too much work on its main thread.
07-01 16:00:48.566: I/art(29197): Background sticky concurrent mark sweep GC freed
70319(3MB) AllocSpace objects, 59(5MB) LOS objects, 825% free, 49MB/56MB, paused
6.139ms total 52.868ms
GC Reason
Concurrent - A concurrent GC which does not suspend app threads. This GC runs in a
background thread and does not prevent allocations.
Alloc - The GC was initiated because your app attempted to allocate memory when your
heap was already full. In this case, the garbage collection occurred in the
allocating thread.
Explicit - The garbage collection was explicitly requested by an app, for instance,
by calling gc() or gc().
NativeAlloc - The collection was caused by native memory pressure from native
allocations such as Bitmaps or RenderScript allocation objects
But How Does It Work?
SmoothMotion
60
No Difference
60+
Flip Book
12
Movies
Frames Per Second
Fluid Motion
24+effects
We Have A Winner!
SmoothMotion
60
No Difference
60+
Flip Book
12
Movies
Frames Per Second
Fluid Motion
24+effects
SmoothMotion
60
Memory churn
public void startAllocations(View view) {
AndroidAcademy object;
for (int i = 0; i < 1000; i++) {
object = new AndroidAcademy();
}
}
When usually avoid it happen
1. onDraw()
2. String concatenation on buffer reading from stream
3. Inner loops allocation
Good Practice
1. Allocate outside of the loop
2. Consider reuse of the object
3. Pool of objects
4. To think does it really necessary?
Memory Leak
public class MainActivity extends AppCompatActivity {
private static Drawable sBackground;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = (TextView) findViewById(R.id.text);
textView.setText("Leak fun!");
if (sBackground == null) {
sBackground = ContextCompat.getDrawable(this,android.R.drawable.ic_menu_zoom);
}
textView.setBackgroundDrawable(sBackground);
}
}
How to?
public class GetTaxiDriverBoxApp extends Application {
protected RefWatcher mRefWatcher;
public void onCreate() {
super.onCreate();
mRefWatcher = installLeakCanary();
}
protected RefWatcher installLeakCanary() {
return RefWatcher.DISABLED;
}
}
In Debug Only
public class DebugApplication extends GetTaxiDriverBoxApp {
@Override
protected RefWatcher installLeakCanary() {
mRefWatcher = LeakCanary.install(this, LeakSlackUploadService.class,
AndroidExcludedRefs.createAppDefaults().build());
return mRefWatcher;
}
}
When should be killed - watched it
@Override
public void onDetach() {
super.onDetach();
GetTaxiDriverBoxApp.getRefWatcher(getContext()).watch(this);
}
Memory Leak Life Example Google Maps
* GC ROOT thread java.lang.Thread.<Java Local> (named 'AsyncTask #5')
* references android.os.AsyncTask$3.this$0 (anonymous class extends java.util.concurrent.
FutureTask)
* references com.google.maps.android.clustering.ClusterManager$ClusterTask.this$0
* references com.google.maps.android.clustering.ClusterManager.mRenderer
* references com.gettaxi.dbx_lib.features.heatmap.FixedBucketsResizingDrawableClusterRenderer.
mIconGenerator
* references com.google.maps.android.ui.IconGenerator.mContext
* leaks com.gettaxi.dbx.android.activities.MainActivity instance
Memory Leak - Example
* GC ROOT thread com.squareup.picasso.Dispatcher.DispatcherThread.<Java Local>
* references android.os.Message.obj
* references com.example.MyActivity$MyDialogClickListener.this$0
* leaks com.example.MyActivity.MainActivity instance
Handler.java
public final Message obtainMessage(int what, Object obj) {
return Message.obtain(this, what, obj);
}
public void handleMessage(Message msg) {
switch (msg.what) {
case WHAT_ORDER_UPDATES: {
//do something
break;
}
}
Simple Dialog
new AlertDialog.Builder(this)
.setPositiveButton("Baguette", new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialog, int which) {
MyActivity.this.makeBread();
}
}).show();
.class of OnClickListener
class MyActivity$0 implements DialogInterface.OnClickListener {
final MyActivity this$0;
MyActivity$0(MyActivity this$0) {
this.this$0 = this$0;
}
@Override public void onClick(DialogInterface dialog, int which) {
this$0.makeBread();
}
}
new AlertDialog.Builder(this)
.setPositiveButton("Baguette", new MyActivity$0(this));
.show();
AlertController.java
public void setButton(int whichButton, CharSequence text,
DialogInterface.OnClickListener listener, Message msg) {
if (msg == null && listener != null) {
msg = mHandler.obtainMessage(whichButton, listener);
}
switch (whichButton) {
case DialogInterface.BUTTON_POSITIVE:
mButtonPositiveText = text;
mButtonPositiveMessage = msg;
break;
//...
private final View.OnClickListener mButtonHandler = new View.OnClickListener() {
@Override public void onClick(View v) {
final Message m;
if (v == mButtonPositive && mButtonPositiveMessage != null) {
m = Message.obtain(mButtonPositiveMessage); }
//...some other code
if (m != null) {
m.sendToTarget();
}
// Post a message so we dismiss after the above handlers are executed.
mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
.sendToTarget();
}
};
Check the square blog
Wrapper for clickListener and clear it on detach
Or
Send idle messages to clean app the messages
https://corner.squareup.com/2015/08/a-small-leak.html
Do not keep long-lived references to a context-activitypublic static Context mContext;
public NoLifeCycleClass(Activity myActivity) {
mContext = (Context) myActivity;
}
Try using the context-application instead of a context-activity
StringUtilsUI.doSomeLongRunningTask(getApplicationContext());
Avoid non-static inner classes
public class DialogCountdown extends BaseDialogFragment {
private class CountDownHandler extends Handler {
//do some work
}
}
Avoid non-static inner classes private static class CountDownHandler extends Handler {
private final WeakReference<DialogCountdown> mDialogCountdownWeakReference;
public CountDownHandler(DialogCountdown dialogCountdown) {
super();
mDialogCountdownWeakReference = new WeakReference<>(dialogCountdown);
}
public void handleMessage(Message msg) {
if(mDialogCountdownWeakReference.get()!=null) {
mDialogCountdownWeakReference.get().onCountDown();
}
}
}
Clean/Stop all your handlers, animation listeners onDestroy();protected void onStop() {
super.onStop();
mHandler.clearAllMessages();
unregisterReceivers();
heatMapsDone();
if (mServiceBound) {
mServiceBound = false;
Services.unbindFromRidesService(this, this);
}
if (mMapStateMachine != null) {
mMapStateMachine.stop();
mMapStateMachine = null;
}
}