美文网首页Utils高级Android 开源项目
[Android组件解读] LeakCanary详解

[Android组件解读] LeakCanary详解

作者: 乱码桑 | 来源:发表于2017-03-20 18:22 被阅读964次

    前言

    LeakCanary是Square公司提供的用于Android检测内存的小工具,他能帮助我们快速定位代码隐藏的BUG,减少OOM的机会。

    此处为git地址链接:https://github.com/square/leakcanary

    题外话:Square真的是家良心公司,提供了很多有名的组件。后续会整理目前市面上有名的组件。比如Facebook的开源组件... 现在先介绍下Square有哪些开源组件

    OKHttp 一个开源稳定的Http的通信依赖库,感觉比HttpUrlConnection好用,
        okhttp现在已经得到Google官方的认可了。
        
    okie  OKHttp依赖这个库
    
    dagger 快速依赖注入框架。现在已经由google公司维护了,
    现在应该是dagger2.官网地址:https://google.github.io/dagger/
    
    picasso 一个图片缓存库,可以实现图片的下载和缓存功能
    
    retrofit 是一个RESTFUL(自行百度)的Http网络请求框架的封装,基于OKHttp,retrofit在于对接口的封装,实质是使用OKHttp进行网络请求
    
    leakcanary 一个检测内存的小工具,本文说的就是这个
    
    otto Android事件总线,降低代码的耦合性,可以跟EventBus做比较
    
    ...
    

    回到正文,现在开始讲解LeakCanary的使用

    使用LeakCanary

    其实可以参考leakcanary的Sample介绍

    1. 首先在build.gradle中引用
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
            exclude group: 'com.android.support', module: 'support-annotations'
        })
        compile 'com.android.support:appcompat-v7:25.2.0'
        compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha9'
        testCompile 'junit:junit:4.12'
    
        // LeakCanary
        debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
        releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
        testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
    }
    
    1. 在Application的onCreate添加方法
        public class ExampleApp extends Application{
        @Override
        public void onCreate() {
            super.onCreate();
            // LeakCanary初始化
            LeakCanary.install(this);
        }
    }
    
    1. 在App中添加内存泄漏代码,本文就参照sample中的的例子,写了一个SystemClock.sleep(20000);
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            setContentView(R.layout.activity_main);
    
            Button asynTaskBtn = (Button) this.findViewById(R.id.async_task);
            asynTaskBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    startAsyncTask();
                }
            });
        }
    
        private void startAsyncTask() {
            // This async task is an anonymous class and therefore has a hidden reference to the outer
            // class MainActivity. If the activity gets destroyed before the task finishes (e.g. rotation),
            // the activity instance will leak.
            new AsyncTask<Void, Void, Void>() {
                @Override protected Void doInBackground(Void... params) {
                    // Do some slow work in background
                    SystemClock.sleep(20000);
                    return null;
    
                }
            }.execute();
        }
    }
    
    1. 运行,会发现出现一个LeakCanary的图标。后续会介绍这个图标是如何出现的,当出现内存泄漏的时候,会在通知栏显示一条内存泄漏通知,点击通知会进入内存泄漏的具体问题。


      LeakCanary图标
    通知栏显示内存泄漏 内存泄漏详情

    根据图标显示我们能够看出内存泄漏在AsyncTask中,根据AsyncTask中进行内存修改

    讲解完如何使用,现在开始讲解LeakCanary。

    LeakCanary详解

    代码目录结构

    .
    ├── AbstractAnalysisResultService.java 
    ├── ActivityRefWatcher.java -- Activity监控者,监控其生命周期
    ├── AndroidDebuggerControl.java --Android Debug控制开关,就是判断Debug.isDebuggerConnected()
    ├── AndroidExcludedRefs.java -- 内存泄漏基类
    ├── AndroidHeapDumper.java --生成.hrpof的类
    ├── AndroidWatchExecutor.java -- Android监控线程,延迟5s执行
    ├── DisplayLeakService.java -- 显示通知栏的内存泄漏,实现了AbstractAnalysisResultService.java
    ├── LeakCanary.java --对外类,提供install(this)方法
    ├── ServiceHeapDumpListener.java 
    └── internal --这个文件夹用于显示内存泄漏的情况(界面相关)
        ├── DisplayLeakActivity.java --内存泄漏展示的Activity
        ├── DisplayLeakAdapter.java 
        ├── DisplayLeakConnectorView.java 
        ├── FutureResult.java
        ├── HeapAnalyzerService.java 在另一个进程启动的Service,用于接收数据并发送数据到界面
        ├── LeakCanaryInternals.java
        ├── LeakCanaryUi.java
        └── MoreDetailsView.java
    

    对外方法LeakCanary.install(this)

    实际上LeakCanary对外提供的方法只有

    LeakCanary.install(this);
    

    从这里开始切入,对应源码

    /**
     * Creates a {@link RefWatcher} that works out of the box, and starts watching activity
     * references (on ICS+).
     */
    public static RefWatcher install(Application application) {
        return install(application, DisplayLeakService.class,
                AndroidExcludedRefs.createAppDefaults().build());
    }
    
    /**
     * Creates a {@link RefWatcher} that reports results to the provided service, and starts watching
     * activity references (on ICS+).
     */
    public static RefWatcher install(Application application,
                                     Class<? extends AbstractAnalysisResultService> listenerServiceClass,
                                     ExcludedRefs excludedRefs) {
        // 判断是否在Analyzer进程
        if (isInAnalyzerProcess(application)) {
            return RefWatcher.DISABLED;
        }
        // 允许显示内存泄漏情况Activity
        enableDisplayLeakActivity(application);
    
        HeapDump.Listener heapDumpListener =
                new ServiceHeapDumpListener(application, listenerServiceClass);
    
        RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);
        ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
        return refWatcher;
    }
    

    为什么LeakCanary要求在4.0以上

    <mark>通过注释能看出这个LeakCanary是用于4.0以上的方法

    references (on ICS+).
    

    为什么要使用在4.0以上呢?

    ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
    

    这句方法告诉我们这个是用在Ics+(即4.0版本以上),那这个类ActivityRefWatcher具体使用来干什么的呢

    @TargetApi(ICE_CREAM_SANDWICH) public final class ActivityRefWatcher {
    
      public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
        if (SDK_INT < ICE_CREAM_SANDWICH) {
          // If you need to support Android < ICS, override onDestroy() in your base activity.
          return;
        }
        ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
        activityRefWatcher.watchActivities();
      }
    
      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);
            }
          };
    
      private final Application application;
      private final RefWatcher refWatcher;
    
      /**
       * Constructs an {@link ActivityRefWatcher} that will make sure the activities are not leaking
       * after they have been destroyed.
       */
      public ActivityRefWatcher(Application application, final RefWatcher refWatcher) {
        this.application = checkNotNull(application, "application");
        this.refWatcher = checkNotNull(refWatcher, "refWatcher");
      }
    
      void onActivityDestroyed(Activity activity) {
        refWatcher.watch(activity);
      }
    
      public void watchActivities() {
        // Make sure you don't get installed twice.
        stopWatchingActivities();
        application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
      }
    
      public void stopWatchingActivities() {
        application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
      }
    }
    

    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);这个方法使用在Android4.0上的,用于观察Activity的生命周期。从上面代码看出,LeakCanary监听Activity的销毁操作

    ActivityRefWatcher.this.onActivityDestroyed(activity);
    

    LeakCanary如何出现LeakCanry的图标

    public static void setEnabled(Context context, final Class<?> componentClass,
                                      final boolean enabled) {
            final Context appContext = context.getApplicationContext();
            // 耗时操作
            executeOnFileIoThread(new Runnable() {
                @Override
                public void run() {
                    ComponentName component = new ComponentName(appContext, componentClass);
                    PackageManager packageManager = appContext.getPackageManager();
                    int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
                    // Blocks on IPC.
                    packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
                }
            });
        }
    

    在install的方法执行的时候调用了

     // 允许显示内存泄漏情况Activity
    enableDisplayLeakActivity(application);
    

    这个方法执行了上面显示的方法setEnable.最核心的方法是packageManager.setComponentEnabledSetting。
    这个方法可以用来隐藏/显示应用图标
    具体可以参照android 禁用或开启四大组件setComponentEnabledSetting

    [重点]LeakCanary如何捕获内存泄漏

    通过Debug.dumpHprofData()方法生成.hprof文件,然后利用开源库HAHA(开源地址:https://github.com/square/haha)解析.hprof文件,并发送给DisplayLeakActivity进行展示

    public final class AndroidHeapDumper implements HeapDumper {
    
      private static final String TAG = "AndroidHeapDumper";
    
      private final Context context;
      private final Handler mainHandler;
    
      public AndroidHeapDumper(Context context) {
        this.context = context.getApplicationContext();
        mainHandler = new Handler(Looper.getMainLooper());
      }
    
      @Override public File dumpHeap() {
        if (!isExternalStorageWritable()) {
          Log.d(TAG, "Could not dump heap, external storage not mounted.");
        }
        File heapDumpFile = getHeapDumpFile();
        if (heapDumpFile.exists()) {
          Log.d(TAG, "Could not dump heap, previous analysis still is in progress.");
          // Heap analysis in progress, let's not put too much pressure on the device.
          return NO_DUMP;
        }
    
        FutureResult<Toast> waitingForToast = new FutureResult<>();
        showToast(waitingForToast);
    
        if (!waitingForToast.wait(5, SECONDS)) {
          Log.d(TAG, "Did not dump heap, too much time waiting for Toast.");
          return NO_DUMP;
        }
    
        Toast toast = waitingForToast.get();
        try {
          Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
          cancelToast(toast);
          return heapDumpFile;
        } catch (IOException e) {
          cleanup();
          Log.e(TAG, "Could not perform heap dump", e);
          // Abort heap dump
          return NO_DUMP;
        }
      }
    
      /**
       * Call this on app startup to clean up all heap dump files that had not been handled yet when
       * the app process was killed.
       */
      public void cleanup() {
        LeakCanaryInternals.executeOnFileIoThread(new Runnable() {
          @Override public void run() {
            if (isExternalStorageWritable()) {
              Log.d(TAG, "Could not attempt cleanup, external storage not mounted.");
            }
            File heapDumpFile = getHeapDumpFile();
            if (heapDumpFile.exists()) {
              Log.d(TAG, "Previous analysis did not complete correctly, cleaning: " + heapDumpFile);
              heapDumpFile.delete();
            }
          }
        });
      }
    
      private File getHeapDumpFile() {
        return new File(storageDirectory(), "suspected_leak_heapdump.hprof");
      }
    
      private void showToast(final FutureResult<Toast> waitingForToast) {
        mainHandler.post(new Runnable() {
          @Override public void run() {
            final Toast toast = new Toast(context);
            toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
            toast.setDuration(Toast.LENGTH_LONG);
            LayoutInflater inflater = LayoutInflater.from(context);
            toast.setView(inflater.inflate(R.layout.leak_canary_heap_dump_toast, null));
            toast.show();
            // Waiting for Idle to make sure Toast gets rendered.
            Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
              @Override public boolean queueIdle() {
                waitingForToast.set(toast);
                return false;
              }
            });
          }
        });
      }
    
      private void cancelToast(final Toast toast) {
        mainHandler.post(new Runnable() {
          @Override public void run() {
            toast.cancel();
          }
        });
      }
    }
    

    检测时机

    在Activity销毁的时候会执行RefWatch.watch方法,然后就去执行内存检测

    这里又看到一个比较少的用法,IdleHandler,IdleHandler的原理就是在messageQueue因为空闲等待消息时给使用者一个hook。那AndroidWatchExecutor会在主线程空闲的时候,派发一个后台任务,这个后台任务会在DELAY_MILLIS时间之后执行。LeakCanary设置的是5秒。

    public void watch(Object watchedReference, String referenceName) {
        checkNotNull(watchedReference, "watchedReference");
        checkNotNull(referenceName, "referenceName");
        if (debuggerControl.isDebuggerAttached()) {
          return;
        }
        final long watchStartNanoTime = System.nanoTime();
        String key = UUID.randomUUID().toString();
        retainedKeys.add(key);
        final KeyedWeakReference reference =
            new KeyedWeakReference(watchedReference, key, referenceName, queue);
    
        watchExecutor.execute(new Runnable() {
          @Override public void run() {
            ensureGone(reference, watchStartNanoTime);
          }
        });
      }
    
    public final class AndroidWatchExecutor implements Executor {
    
      static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";
      private static final int DELAY_MILLIS = 5000;
    
      private final Handler mainHandler;
      private final Handler backgroundHandler;
    
      public AndroidWatchExecutor() {
        mainHandler = new Handler(Looper.getMainLooper());
        HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
        handlerThread.start();
        backgroundHandler = new Handler(handlerThread.getLooper());
      }
    
      @Override public void execute(final Runnable command) {
        if (isOnMainThread()) {
          executeDelayedAfterIdleUnsafe(command);
        } else {
          mainHandler.post(new Runnable() {
            @Override public void run() {
              executeDelayedAfterIdleUnsafe(command);
            }
          });
        }
      }
    
      private boolean isOnMainThread() {
        return Looper.getMainLooper().getThread() == Thread.currentThread();
      }
    
      private void executeDelayedAfterIdleUnsafe(final Runnable runnable) {
        // This needs to be called from the main thread.
        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
          @Override public boolean queueIdle() {
            backgroundHandler.postDelayed(runnable, DELAY_MILLIS);
            return false;
          }
        });
      }
    }
    

    Fragment如何使用LeakCanary

    如果我们想检测Fragment的内存的话,可以在Application中将返回的RefWatcher存下来,可以在Fragment的onDestroy中watch它。

    public abstract class BaseFragment extends Fragment {
     
      @Override public void onDestroy() {
        super.onDestroy();
        RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
        refWatcher.watch(this);
      }
    }
    

    参考LeakCanary开源项目

    其他参考资料

    LeakCanary 内存泄露监测原理研究

    Android 内存泄漏检查工具LeakCanary源碼浅析

    相关文章

      网友评论

        本文标题: [Android组件解读] LeakCanary详解

        本文链接:https://www.haomeiwen.com/subject/gwsinttx.html