2016年10月12日星期三

使用LeakCanary检测Android内存泄漏


LeakCanary是Square开源的Java与Android内存泄漏检测工具:https://github.com/square/leakcanary,当前最新版本:1.6.2。

一、在Android项目中集成与使用

在Android项目中集成与使用LeakCanary相当简单:

1. 添加项目依赖


在构建脚本build.gradle中添加对LeakCanary库的依赖:

dependencies {
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
    testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
}
可以看到,release及test使用的是no-op的版本,no-op版本中接口都是空实现,因此不影响发布版本的业务逻辑,也不会影响效率。

2. 初始化LeakCanary


在Application中对LeakCanary工具进行初始化:

public class ExampleApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // LeakCanary会创建单独的进程用于内存堆分析,这里不应该调用我们的初始化代码,直接返回即可
            return;
        }
        LeakCanary.install(this);
        // 程序的初始化代码
    }
}
LeakCanary初始化时会注册系统Activity生命周期监听器,监听所有Activity的onDestroy方法调用,以此分析Activity的内存泄漏:

@TargetApi(14)
public final class ActivityRefWatcher {
        private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacks() {
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        }

        public void onActivityStarted(Activity activity) {
        }

        public void onActivityResumed(Activity activity) {
        }

        public void onActivityPaused(Activity activity) {
        }

        public void onActivityStopped(Activity activity) {
        }

        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
        }

        public void onActivityDestroyed(Activity activity) {
            ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
    };
    private final Application application;
    private final RefWatcher refWatcher;

    public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
        if(VERSION.SDK_INT >= 14) {
            ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
            activityRefWatcher.watchActivities();
        }
    }

    public ActivityRefWatcher(Application application, RefWatcher refWatcher) {
        this.application = (Application)Preconditions.checkNotNull(application, "application");
        this.refWatcher = (RefWatcher)Preconditions.checkNotNull(refWatcher, "refWatcher");
    }

    void onActivityDestroyed(Activity activity) {
        this.refWatcher.watch(activity);
    }

    public void watchActivities() {
        this.stopWatchingActivities();
        this.application.registerActivityLifecycleCallbacks(this.lifecycleCallbacks);
    }

    public void stopWatchingActivities() {
        this.application.unregisterActivityLifecycleCallbacks(this.lifecycleCallbacks);
    }
}

3. 监听其他对象


Android中最常见的内存泄漏就是Context的内存泄漏,包括Activity、BroadcastReceiver、Service等,还有常见的Fragment等的泄漏。LeakCanary初始化时已经添加了所有Activity的监听,不需要我们再额外处理。下面看看其他对象的监听

分析Fragment内存泄漏分析,首先需要获取一个RefWatcher实例的引用:

public class ExampleApplication extends Application {
    private static RefWatcher sRefWatcher;

    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // LeakCanary会创建单独的进程用于内存堆分析,这里不应该调用我们的初始化代码,直接返回即可
            return;
        }
        sRefWatcher = LeakCanary.install(this);
        // 程序的初始化代码
    }

    public static void watch(final Object watchReference) {
        if(null != sRefWatcher) {
            sRefWatcher.watch(watchReference);
        }
    }
}
在Fragment的onDestroy中监听:

public class BaseFragment extends Fragment {

    @Override
    public void onDestroy() {
        super.onDestroy();
        ExampleApplication.watch(this);
    }
}
其他对象的监听类似,在将要释放对象时,增加对象的监听:

ExampleApplication.watch(obj);

4. 收集内存泄漏及分析:

项目中集成LeakCanary后,运行debug程序,一段时间后退出程序。桌面上LeakCanary额外创建了一个名为"Leaks"的快捷方式,打开快捷方式可以看到一条条的内存泄漏日志,如下图示例:

点击内存泄漏日志,可以查看内存泄漏详情,以此修复程序内存泄漏。

二、工作原理

  1. LeakCanary中调用RefWatcher.watch()方法会创建一个KeyedWeakReference的弱引用来监控对象
  2. 一段时间后,背景线程会检测这个引用对象是否被回收,如果没有被回收,则主动触发一次GC进行垃圾回收
  3. 如果这个引用对象仍然没有被回收,则dump堆内存到一个.hprof文件中存储到文件系统
  4. LeakCanary会在一个单独的进程中启动 HeapAnalyzerService,这个Service会调用HeapAnalyzer分析内存dump,而HeapAnalyzer使用得是另外一个Square开源工具HAHA来分析Android堆内存的dump文件
  5. HeapAnalyzer根据唯一key来找到堆内存中KeyedWeakReference对象并定位内存泄漏对象的引用
  6. HeapAnalyzer

三. 更多

https://github.com/square/leakcanary
https://github.com/square/leakcanary/wiki/FAQ
https://github.com/square/leakcanary/wiki/Customizing-LeakCanary
http://droidyue.com/blog/2016/03/28/android-leakcanary/index.html

2016年10月8日星期六

安装Groovy环境

Groovy的安装可以参看官网 。可以自己手动下载Groovy压缩包自己配置环境变量,也可以使用sdkman等工具来管理安装,这里记录下安装方法:

一、使用 SDKMAN


SDKMAN的使用可以参看:使用SDKMAN软件管理开发工具SDK

$ sdk install groovy
$ groovy -version
Groovy Version: 2.4.7 JVM: 1.8.0_101 Vendor: Oracle Corporation OS: Mac OS X

二、手动安装


使用SDKMAN安装的Groovy只有二进制程序,不包含源码和文档,这里介绍下手动安装的方法:

1. 首先下载Groovy压缩包,这里我选择了二进制+源码+文档的集成包,当前最新版本2.4.7:https://bintray.com/artifact/download/groovy/maven/apache-groovy-sdk-2.4.7.zip

2. 解压到本地:$HOME/bin/groovy-2.4.7

3. 设置环境变量GROOVY_HOME,ubuntu中~/.bashrc或mac中~/.bash_profile文件中添加:

export GROOVY_HOME=$HOME/bin/groovy-2.4.7
export PATH=$GROOVY_HOME/bin:$PATH

4. 重新打开命令行窗口,查看安装是否正确:

$ groovy -version
Groovy Version: 2.4.7 JVM: 1.8.0_101 Vendor: Oracle Corporation OS: Mac OS X

Android logcat

Log等级 Android log 等级在 android/log.h 中定义如下: typedef   enum   android_LogPriority {    /** For internal use only. */ ANDROID_LOG_UNKNOWN =...