Android内存性能分析
工具:Android Studio、MAT(Memory Analyzer Tool)
第三方库:LeakCanary
1、Android Profiler基础用法
首先附上Android Profilder的官方使用说明地址:
https://developer.android.google.cn/studio/profile/memory-profiler.html
Android Profiler可以实时查看app的cpu、memory和network状态,这里只说memory,简单写个用GridView加载大量网络图片的例子,对于加载到的bitmap对象分别采用HashMap和LruCache进行保存,观察内存变化。
当使用HashMap时,观察Android Profiler中memory的变化
可以看出内存不停增加,最终程序oom;
接着用LruCache替换HashMap,LruCahe的配置如下:
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
// 设置图片缓存大小为程序最大可用内存的1/8
mMemoryCache = new android.util.LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
};
可以看出,当内存达到一定的值时得到了回收。
*AndroidManifest.xml文件中可以设置 android:largeHeap="true"来增加app的可使用内存,实验发现默认情况下nexus5手机android6.0系统当内存达到300多M时产生oom,如果设置该选项内存达到500多M时才会oom,但是设置该值会导致gc时间变长从而一定程度上影响性能,所以要斟酌使用。
*ActivityManager获取的内存值定义在系统rom中,编译时已经写入系统,而Runtime类可以获得运行时的内存数据,也是Android profiler显示的动态图数据。
02-02 20:39:17.158 14633-14633/com.blogxuan.oomdemo2 I/OOMDEMO: 机型:AOSP on HammerHead am.getMemoryClass()为192
02-02 20:39:17.158 14633-14633/com.blogxuan.oomdemo2 I/OOMDEMO: 机型:AOSP on HammerHead am.getLargeMemoryClass()为512
02-02 20:39:17.158 14633-14633/com.blogxuan.oomdemo2 I/OOMDEMO: 机型:AOSP on HammerHeadRuntime.getRuntime().maxMemory()为:536870912
android:largeHeap="false"时
02-02 20:44:46.550 17492-17492/com.blogxuan.oomdemo2 I/OOMDEMO: 机型:AOSP on HammerHeadRuntime.getRuntime().maxMemory()为:201326592
2、使用Android Profiler进行内存泄露分析。
一个简单的内存泄露例子
public class TestActivity extends Activity {
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
textView = (TextView) findViewById(R.id.test_text_view);
TestDataModel.getInstance().setRetainedTextView(textView);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
TestDataModel的class文件
public class TestDataModel {
private static TestDataModel sInstance;
private TextView mRetainedTextView;
public static TestDataModel getInstance() {
if (sInstance == null) {
sInstance = new TestDataModel();
}
return sInstance;
}
public void setRetainedTextView(TextView textView) {
mRetainedTextView = textView;
}
}
在例子中,当TestActivity finish后,由于静态变量sInstance引用了textView,而textView引用了TestActivity的context,最终导致TestActivity对象不能被回收产生了内存泄露。
找出本应该回收但是并没有回收的对象(也就是发生了泄露的对象):
通过分析操作前和操作后的堆中的对象分配,首先找出堆计数异常大或者本该回收但是却没得到回收的对象,然后查看具体是哪些对象引用了他们导致他们没能回收。
也可以从Android Studio中将堆转储保存为hprof文件来用其他工具(比如MAT)进行分析,比如上面的例子,TestActivity打开前保存一二hprof文件,然后打开后再finish,然后再在android studio中手动gc一下,再导出hprof文件。接着在MAT中对比这两个堆文件。结果如下图:
可见TestDataModel对象和TestActivity对象并没有被回收,这是因为静态的sInstance生命周期跟application一样,而sInstance引用了textView,textView引用了TestActivity的context,因此TestActivity也没有被回收。
3、使用LeakCanary进行内存泄露分析。
LeakCanary是square公司开源的一个检测内存泄露的工具库,使用很简单,只要在gradle中依赖一下,然后在application中注册一下就行了。
github地址:https://github.com/square/leakcanary
使用LeakCanary检测内存泄露实例:
public class TestActivity2 extends Activity {
TextView textView;
Message msg;
Handler mLeakyHandler= new Handler(){
@Override
public void handleMessage(Message msg) {
}
};
Message msg;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
msg = Message.obtain();
mLeakyHandler.sendMessageDelayed(msg, 40000);
}
}
上面的例子中TestActivity打开后马上关闭,会产生内存泄露,LeakCanary会有如下内存泄露提示
这是因为handler发送一个消息给MessageQueue后,存在一条这样的引用链,主线程→mainLooper→MessageQueue→Message→handler→TestActivity,当activity destroy后,message还存在消息队列里,因此activity对象得不得回收。
要使activity能得到回收,只要在activity关闭后在这个引用链的任意一处切断都行,有如下三种方法:
1.切断Message和messageQueue的联系,即在activity的onDestroy中调用
mLeakyHandler.removeCallbacksAndMessages(null);
这个方法可以移除消息队列中的消息从而切断Message和MessageQueue的联系,使activity得到回收。
2.切断Message和handler的联系,可以通过在activity的onDestroy中调用
msg.setTarget(null);
来实现,即把handler置空。
3.由于非静态内部类或匿名类会持有外部类的引用,即handler持有activity的引用,因此可以通过把handler声明为静态内部类来切断handler对activity实例的引用,但是静态内部类的对象是不能调用外部类的实例变量的,因此可以通过传递一个外部activity实例的WeakReference给handler使用,如下所示:
public static class MyHandler extends Handler
{
private WeakReference<Context> reference;
public MyHandler(Context activity)
{
reference = new WeakReference<Context>(activity);
}
@Override
public void handleMessage(Message msg)
{
TestActivity2 mTestActivity = (TestActivity2) reference.get();
if (reference.get() != null)
{
mTestActivity.textView.setText("hello");
}
}
}
handler泄露的危害:有的人会说通常情况不会使用handler发送延迟太久的消息,而且消息达到延迟的时间后,activity不就可以自动被回收了么,因此handler泄露也没什么打不了啊,但是考虑这样一个情景,通常我们都会在onDestory里处理一些资源回收的操作,如果handler的handleMessage里面使用到了某些资源,而这些资源被回收了,那就会出问题了,最常见的就是产生空指针导致程序崩溃。
3、LeakCanary原理简单介绍。
leakCanary内存泄露检测完整时序图。
代码中我们只要简单使用
LeakCanary.install(this);
就能完成LeakCanary的初始化。
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
主要看下buildAndInstall这个方法
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}
里面调用了ActivityRefWatcher.install方法
public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).watchActivities();
}
里面调用了watchActivities这个方法
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
lifecycleCallbacks定义如下:
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override public void onActivityStarted(Activity activity) {
}
@Override public void onActivityResumed(Activity activity) {
}
@Override public void onActivityPaused(Activity activity) {
}
@Override public void onActivityStopped(Activity activity) {
}
@Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
这是application提供的一个方法,可以用来监听我们应用中所有activity的生命周期,LeakCanay就是在这里通过监控所有activity的onDestroyed方法来检测内存泄露的。
继续进入ActivityRefWatcher.this.onActivityDestroyed方法,如下所示:
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
LeakCanay内存泄露检测的核心就是这个watch方法:
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
retainedKeys是一个Set类型的集合,用来记录被监控的所有还在内存的对象(这里是刚调用了onDestoryed方法的activity对象),KeyedWeakReference继承自WeakReference,作用是用一个弱引用来引用被监控的对象(activity),queue是一个ReferenceQueue队列,作用后面说,
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
ensureGone是LeakCanay内存泄露检测主要逻辑。
private void removeWeaklyReachableReferences() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
首先从removeWeaklyReachableReferences这个方法说起,从上面的注释我们可以看出ReferenceQueue的作用(如果一个对象变成弱引用,那么这个对象入队),也就是如果我们的activity被finish了,正常情况下这个activity是应该进入这个队列了,因此当我们通过queue.poll()把这个activity对象取出来,retainedKeys中就移除这个activity,表示这个activity被回收了,而gone(reference)这个方法就是来判断retainedKeys中是否还存在这个对象,代码如下
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
removeWeaklyReachableReferences和gone方法都执行了两次,如果第一次对象就从retainedKeys移除了,那就是表示对象已经成功被回收了,如果没有,则调用gcTrigger.runGc()执行一次gc操作,再进一步确认对象是否还存在,如果还存在则猜测发生了内存泄露,因此取出dump文件进一步分析,对dump文件的分析使用了squareup的另一个haha库,如果确实出现了泄露,则找出泄露对象的引用路径通知用户。如有需要我们也可以导出LeakCanay生成的dump文件使用其他工具比如MAT,Android Studio进行分析。
*看LeakCanary源码时发现使用到了一个net.ltgt.gradle:gradle-errorprone-plugin的库,通过这个库,如果我们的handler有内存泄露的隐患,直接在运行app时就会报错,因此我们可以在项目中引用这个库来提前发现一些问题,从而避免一些安全隐患。
推荐书籍
《Android群英传:神兵利器》
《高性能Android应用开发》