performance #1 memory
TRANSCRIPT
![Page 1: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/1.jpg)
A good memory is one trained to forget the trivial
Where is my RAM, dude?
+
![Page 2: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/2.jpg)
First,
![Page 3: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/3.jpg)
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
![Page 4: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/4.jpg)
Logistics
![Page 5: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/5.jpg)
https://www.facebook.com/groups/android.academy.ils/
![Page 6: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/6.jpg)
![Page 7: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/7.jpg)
#PerfMatters
![Page 8: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/8.jpg)
What’s next?
30/5 - Felix
- How to draw right? ( Overdraw, Cliprect, Bitmaps)
13/6 - Britt
- View, Animations
![Page 9: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/9.jpg)
What’s next?
4/7 - Yonatan
- Networking, JSON, Batching, Location
10/8 - Felix
- Battery & CPU
![Page 10: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/10.jpg)
What’s next?
14/9 - Britt
- Threadinggg...
And…
![Page 11: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/11.jpg)
31.10New course coming
![Page 12: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/12.jpg)
Memory mmm…
![Page 13: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/13.jpg)
![Page 14: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/14.jpg)
![Page 15: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/15.jpg)
OutOfMemory is just an iceberg of problems with a memory
![Page 16: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/16.jpg)
![Page 17: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/17.jpg)
Wow! What should I do?
![Page 18: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/18.jpg)
1.Be Aware2.Learn & Understand3.Apply - Profile your code
![Page 19: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/19.jpg)
I’mCollector,
Garbage Collector
![Page 20: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/20.jpg)
GC
![Page 21: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/21.jpg)
Goal
1.Find objects that can’t be accessed
2.Reclaim the resource from them
![Page 22: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/22.jpg)
Mark & Sweeping
![Page 23: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/23.jpg)
So what happens when we don’t have enough free space
for our “new Object();”?
![Page 24: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/24.jpg)
Larger heap
![Page 25: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/25.jpg)
![Page 26: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/26.jpg)
Pitfall
![Page 27: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/27.jpg)
More Garbage = Larger GC Pauses
![Page 28: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/28.jpg)
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
![Page 29: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/29.jpg)
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).
![Page 30: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/30.jpg)
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
![Page 31: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/31.jpg)
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
![Page 32: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/32.jpg)
Why i care about memory
![Page 33: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/33.jpg)
60FPS!!!
![Page 34: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/34.jpg)
But How Does It Work?
![Page 35: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/35.jpg)
But How Does It Work?
SmoothMotion
60
No Difference
60+
Flip Book
12
Movies
Frames Per Second
Fluid Motion
24+effects
![Page 36: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/36.jpg)
We Have A Winner!
SmoothMotion
60
No Difference
60+
Flip Book
12
Movies
Frames Per Second
Fluid Motion
24+effects
SmoothMotion
60
![Page 37: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/37.jpg)
60 FPS
60 Frames / Second = Frame / 16.666 Millisecond
![Page 38: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/38.jpg)
![Page 39: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/39.jpg)
Okey, OkeyI got the idea.What’s next?
![Page 40: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/40.jpg)
Reasons
1.A lot of allocations
2.Memory leaks
3.Not designing for performance
![Page 41: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/41.jpg)
Memory churnpublic void startAllocations(View view) {
AndroidAcademy object;
for (int i = 0; i < 1000; i++) {
object = new AndroidAcademy();
}
}
![Page 42: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/42.jpg)
![Page 43: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/43.jpg)
A Volume hit the GC
![Page 44: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/44.jpg)
When usually avoid it happen
1.onDraw()
2.String concatenation on buffer reading from stream
3.Inner loops allocation
![Page 45: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/45.jpg)
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?
![Page 46: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/46.jpg)
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); }
}
![Page 47: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/47.jpg)
![Page 48: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/48.jpg)
Detecting Memory Issues
![Page 49: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/49.jpg)
![Page 50: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/50.jpg)
GC now!Dump Java
Heap
Start Allocation Tracker
![Page 51: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/51.jpg)
![Page 52: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/52.jpg)
![Page 53: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/53.jpg)
LeakCanary
![Page 54: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/54.jpg)
![Page 55: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/55.jpg)
How to?public class GetTaxiDriverBoxApp extends Application { protected RefWatcher mRefWatcher;
public void onCreate() { super.onCreate(); mRefWatcher = installLeakCanary(); }
protected RefWatcher installLeakCanary() { return RefWatcher.DISABLED;}
}
![Page 56: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/56.jpg)
In Debug Onlypublic class DebugApplication extends GetTaxiDriverBoxApp {
@Override protected RefWatcher installLeakCanary() { mRefWatcher = LeakCanary.install(this, LeakSlackUploadService.class, AndroidExcludedRefs.createAppDefaults().build()); return mRefWatcher; }}
![Page 57: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/57.jpg)
When should be killed - watched it@Overridepublic void onDetach() { super.onDetach();
GetTaxiDriverBoxApp.getRefWatcher(getContext()).watch(this);
}
![Page 58: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/58.jpg)
Let’s dive into some examples
![Page 59: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/59.jpg)
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
![Page 60: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/60.jpg)
Memory Leak Life Example
![Page 61: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/61.jpg)
Some of them are really hard to spot
![Page 62: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/62.jpg)
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
![Page 63: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/63.jpg)
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; }}
![Page 64: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/64.jpg)
Simple Dialognew AlertDialog.Builder(this) .setPositiveButton("Baguette", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { MyActivity.this.makeBread(); } }).show();
![Page 65: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/65.jpg)
.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();
![Page 66: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/66.jpg)
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; //...
![Page 67: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/67.jpg)
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(); }};
![Page 68: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/68.jpg)
Fix?
![Page 69: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/69.jpg)
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
![Page 70: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/70.jpg)
Good Practices
![Page 71: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/71.jpg)
Do not keep long-lived references to a context-activitypublic static Context mContext;
public NoLifeCycleClass(Activity myActivity) {
mContext = (Context) myActivity;}
![Page 72: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/72.jpg)
Try using the context-application instead of a context-activity
StringUtilsUI.doSomeLongRunningTask(getApplicationContext());
![Page 73: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/73.jpg)
Avoid non-static inner classespublic class DialogCountdown extends BaseDialogFragment { private class CountDownHandler extends Handler { //do some work }}
![Page 74: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/74.jpg)
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(); } } }
![Page 75: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/75.jpg)
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; }}
![Page 76: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/76.jpg)
Last world...
![Page 77: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/77.jpg)
![Page 78: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/78.jpg)
Today
![Page 79: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/79.jpg)
With Proguard
![Page 80: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/80.jpg)
Jack
![Page 81: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/81.jpg)
Jill
![Page 82: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/82.jpg)
http://trickyandroid.com/the-dark-world-of-jack-and-jill/
![Page 83: Performance #1 memory](https://reader035.vdocuments.site/reader035/viewer/2022062821/589cae591a28abbe4a8b55d3/html5/thumbnails/83.jpg)