Upload
vitali-pekelis
View
61
Download
2
Embed Size (px)
Citation preview
A good memory is one trained to forget the trivial
Where is my RAM, dude?
+
First,
Yonatan LevinGoogle Developer
Expert & Android @ Gett
Idan FelixSenior Android &
Redhead Varonis
Jonathan Yarkoni
Android Developer & Advocate Ironsource
Android Academy Staff
Britt Barak
Android LeadReal
Muiriel Felix
Android Design
Logistics
https://www.facebook.com/groups/android.academy.ils/
#PerfMatters
What’s next?
30/5 - Felix
- How to draw right? ( Overdraw, Cliprect, Bitmaps)
13/6 - Britt
- View, Animations
What’s next?
4/7 - Yonatan
- Networking, JSON, Batching, Location
10/8 - Felix
- Battery & CPU
What’s next?
14/9 - Britt
- Threadinggg...
And…
31.10New course coming
Memory mmm…
OutOfMemory is just an iceberg of problems with a memory
Wow! What should I do?
1.Be Aware2.Learn & Understand3.Apply - Profile your code
I’mCollector,
Garbage Collector
GC
Goal
1.Find objects that can’t be accessed
2.Reclaim the resource from them
Mark & Sweeping
So what happens when we don’t have enough free space
for our “new Object();”?
Larger heap
Pitfall
More Garbage = Larger GC Pauses
Dalvik07-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 ReasonGC_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).
ART07-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 ReasonConcurrent - 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
Why i care about memory
60FPS!!!
But How Does It Work?
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
60 FPS
60 Frames / Second = Frame / 16.666 Millisecond
Okey, OkeyI got the idea.What’s next?
Reasons
1.A lot of allocations
2.Memory leaks
3.Not designing for performance
Memory churnpublic void startAllocations(View view) {
AndroidAcademy object;
for (int i = 0; i < 1000; i++) {
object = new AndroidAcademy();
}
}
A Volume hit the GC
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 Leakpublic 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); }
}
Detecting Memory Issues
GC now!Dump Java
Heap
Start Allocation Tracker
LeakCanary
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 Onlypublic 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@Overridepublic void onDetach() { super.onDetach();
GetTaxiDriverBoxApp.getRefWatcher(getContext()).watch(this);
}
Let’s dive into some examples
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 Life Example
Some of them are really hard to spot
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.javapublic 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 Dialognew AlertDialog.Builder(this) .setPositiveButton("Baguette", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { MyActivity.this.makeBread(); } }).show();
.class of OnClickListenerclass 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.javapublic 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(); }};
Fix?
Check the square blogWrapper 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
Good Practices
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 classespublic 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; }}
Last world...
Today
With Proguard
Jack
Jill
http://trickyandroid.com/the-dark-world-of-jack-and-jill/