LeakCanary的使用介绍
LeakCanary是一个开源的内存泄露检测的项目,可以到github上查看。
1. 引入LeakCanary
在build.gradle中添加依赖
dependencies {
// leakcanary
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
}
2. 使用方式一:监控activity泄露(默认)
在Application中初始化LeakCanary,并且将其作为android:name
注册到 AndroidManifest.xml 的 Application 节点下。在Activity Destroy的时候,LeakCanary会自动监测是否有内存泄露(可以看源码)。
public class LeakTestApp extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {//1
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
}
}
下面这段代码中,AsyncTask是一个匿名的内部类,隐式的持有外部类(Activity)的引用,在AsncTask中用sleep 20秒模拟了一个耗时操作。当点击Activity中的按钮后立刻退出,Activity执行了onDestroy方法,但是任务还是没有结束,于是Activity得不到释放。而Activity又指向一个Window,Window又拥有整个View继承树,算下来是一大段的内存空间。
public class LeakActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e("leakcanary test", "leak activity has started.");
setContentView(R.layout.activity_leak);
TextView mview1 = (TextView)findViewById(R.id.viewtest1);
mview1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startAsyncTask();
}
});
}
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();
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e("leakcanary test","activity leak has been desdroyed");
}
}
3. 使用方式二:监控Fragment泄露
在Application中初始化时需要返回refwatcher
,在需要监测Fragment回收的地方,用refwatcher
完成监测
public class LeakTestApp extends Application {
public static RefWatcher refWatcher;
@Override
public void onCreate() {
super.onCreate();
refWatcher = LeakCanary.install(this);
}
}
public abstract class BaseFragment extends Fragment {
@Override
public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = LeakTestApp.refWatcher;
refWatcher.watch(this);
}
}
4. 测试结果
参考其他资料说可以在通知栏显示,但是在实际测试过程中,加入了内存的读写权限,也没有在通知栏弹出显示,后续再继续看一下这个地方。但是在Log中可以看到如下打印,完整显示了leak trace
LeakCanary: In com.example.zxp.myapplication:1.0:1.
LeakCanary: * android.arch.lifecycle.ReportFragment has leaked:
LeakCanary: * thread Thread.!(<Java Local>)! (named 'AsyncTask #1')
LeakCanary: * ↳ LeakActivity$1.!(this$0)! (anonymous subclass of android.os.AsyncTask)
LeakCanary: * ↳ LeakActivity.mFragments
LeakCanary: * ↳ FragmentController.mHost
LeakCanary: * ↳ Activity$HostCallbacks.mFragmentManager
LeakCanary: * ↳ FragmentManagerImpl.mAdded
LeakCanary: * ↳ ArrayList.elementData
LeakCanary: * ↳ array Object[].[0]
LeakCanary: * ↳ ReportFragment
LeakCanary:
LeakCanary: * Reference Key: 265bd213-c1bd-495a-b3aa-8ddd52fa5e3d
LeakCanary: * Device: hi VU HAT4KDTV ls
LeakCanary: * Android Version: 8.0.0 API: 26 LeakCanary: 1.6.1 26145bf
LeakCanary: * Durations: watch=5019ms, gc=145ms, heap dump=2976ms, analysis=17720ms
LeakCanary:
LeakCanary: * Details:
LeakCanary: * Instance of java.lang.Thread
LeakCanary: | static NANOS_PER_MILLI = 1000000
LeakCanary: | static uncaughtExceptionPreHandler = com.android.internal.os.RuntimeInit$LoggingHandler@316723512 (0x12e0d138)
LeakCanary: | static EMPTY_STACK_TRACE = java.lang.StackTraceElement[0]@1865076336 (0x6f2ace70)
LeakCanary: | static defaultUncaughtExceptionHandler = com.android.internal.os.RuntimeInit$KillApplicationHandler@316723504 (0x12e0d130)
LeakCanary: | static MIN_PRIORITY = 1
LeakCanary: | static MAX_PRIORITY = 10
LeakCanary: | static NORM_PRIORITY = 5
LeakCanary: | static SUBCLASS_IMPLEMENTATION_PERMISSION = java.lang.RuntimePermission@1865076352 (0x6f2ace80)
LeakCanary: | static $classOverhead = byte[232]@1865078929 (0x6f2ad891)
LeakCanary: | static threadInitNumber = 2
LeakCanary: | static threadSeqNumber = 317
LeakCanary: | blocker = null
LeakCanary: | blockerLock = java.lang.Object@317577544 (0x12edd948)
LeakCanary: | contextClassLoader = dalvik.system.PathClassLoader@316725784 (0x12e0da18)
LeakCanary: | daemon = false
LeakCanary: | eetop = 0
LeakCanary: | group = java.lang.ThreadGroup@1864352488 (0x6f1fc2e8)
LeakCanary: | inheritableThreadLocals = null
LeakCanary: | inheritedAccessControlContext = java.security.AccessControlContext@317577552 (0x12edd950)
LeakCanary: | lock = java.lang.Object@316672544 (0x12e00a20)
LeakCanary: | name = "AsyncTask #1"
LeakCanary: | nativeParkEventPointer = 0
LeakCanary: | nativePeer = 2517719040
LeakCanary: | parkBlocker = null
LeakCanary: | parkState = 1
LeakCanary: | priority = 5
LeakCanary: | single_step = false
LeakCanary: | stackSize = 0
LeakCanary: | started = true
LeakCanary: | stillborn = false
LeakCanary: | target = java.util.concurrent.ThreadPoolExecutor$Worker@316672976 (0x12e00bd0)
LeakCanary: | threadLocalRandomProbe = 0
LeakCanary: | threadLocalRandomSecondarySeed = 0
LeakCanary: | threadLocalRandomSeed = 0
LeakCanary: | threadLocals = null
LeakCanary: | threadQ = null
LeakCanary: | threadStatus = 0
LeakCanary: | tid = 316
LeakCanary: | uncaughtExceptionHandler = null
LeakCanary: | shadow$_klass_ = java.lang.Thread
LeakCanary: | shadow$_monitor_ = 0
LeakCanary: * Instance of com.example.zxp.myapplication.LeakActivity$1
LeakCanary: | static $classOverhead = byte[112]@316672553 (0x12e00a29)
LeakCanary: | this$0 = com.example.zxp.myapplication.LeakActivity@317509920 (0x12ecd120)
LeakCanary: | mCancelled = java.util.concurrent.atomic.AtomicBoolean@317509888 (0x12ecd100)
LeakCanary: | mFuture = android.os.AsyncTask$3@316672856 (0x12e00b58)
LeakCanary: | mHandler = android.os.AsyncTask$InternalHandler@317501680 (0x12ecb0f0)
LeakCanary: | mStatus = android.os.AsyncTask$Status@1864392000 (0x6f205d40)
LeakCanary: | mTaskInvoked = java.util.concurrent.atomic.AtomicBoolean@317509904 (0x12ecd110)
LeakCanary: | mWorker = android.os.AsyncTask$2@316672840 (0x12e00b48)
LeakCanary: | shadow$_klass_ = com.example.zxp.myapplication.LeakActivity$1
LeakCanary: | shadow$_monitor_ = 0
LeakCanary: * Instance of com.example.zxp.myapplication.LeakActivity
LeakCanary: | static $classOverhead = byte[2100]@316993449 (0x12e4efa9)
LeakCanary: | mDelegate = android.support.v7.app.AppCompatDelegateImpl@317510192 (0x12ecd230)
LeakCanary: | mResources = null
LeakCanary: | mThemeId = 2131492869
LeakCanary: | mCreated = true
LeakCanary: | mFragments = android.support.v4.app.FragmentController@317510328 (0x12ecd2b8)
LeakCanary: | mHandler = android.support.v4.app.FragmentActivity$1@317510344 (0x12ecd2c8)
LeakCanary: | mNextCandidateRequestIndex = 0
LeakCanary: | mPendingFragmentActivityResults = android.support.v4.util.SparseArrayCompat@317510376 (0x12ecd2e8)
LeakCanary: | mRequestedPermissionsFromFragment = false
LeakCanary: | mResumed = false
LeakCanary: | mStartedActivityFromFragment = false
LeakCanary: | mStartedIntentSenderFromFragment = false
LeakCanary: | mStopped = true
LeakCanary: | mViewModelStore = null
LeakCanary: | mExtraDataMap = android.support.v4.util.SimpleArrayMap@317510400 (0x12ecd300)
LeakCanary: | mLifecycleRegistry = android.arch.lifecycle.LifecycleRegistry@317510424 (0x12ecd318)
LeakCanary: | mActionBar = null
LeakCanary: | mActionModeTypeStarting = 0
LeakCanary: | mActivityInfo = android.content.pm.ActivityInfo@317510456 (0x12ecd338)
LeakCanary: | mActivityTransitionState = android.app.ActivityTransitionState@317510608 (0x12ecd3d0)
LeakCanary: | mApplication = com.example.zxp.myapplication.LeakTestApp@316725528 (0x12e0d918)
LeakCanary: | mAutoFillResetNeeded = false
LeakCanary: | mAutofillManager = null
LeakCanary: | mAutofillPopupWindow = null
LeakCanary: | mCalled = true
LeakCanary: | mChangeCanvasToTranslucent = false
LeakCanary: | mChangingConfigurations = false
LeakCanary: | mComponent = android.content.ComponentName@317510664 (0x12ecd408)
LeakCanary: | mConfigChangeFlags = 0
LeakCanary: | mCurrentConfig = android.content.res.Configuration@317510680 (0x12ecd418)
LeakCanary: | mDecor = null
LeakCanary: | mDefaultKeyMode = 0
LeakCanary: | mDefaultKeySsb = null
LeakCanary: | mDestroyed = true
LeakCanary: | mDoReportFullyDrawn = false
LeakCanary: | mEmbeddedID = null
LeakCanary: | mEnableDefaultActionBarUp = false
LeakCanary: | mEnterTransitionListener = android.app.SharedElementCallback$1@1864376848 (0x6f202210)
LeakCanary: | mExitTransitionListener = android.app.SharedElementCallback$1@1864376848 (0x6f202210)
LeakCanary: | mFinished = true
LeakCanary: | mFragments = android.app.FragmentController@317510792 (0x12ecd488)
LeakCanary: | mHandler = android.os.Handler@317510808 (0x12ecd498)
LeakCanary: | mHasCurrentPermissionsRequest = false
LeakCanary: | mIdent = 231889398
LeakCanary: | mInstanceTracker = android.os.StrictMode$InstanceTracker@317510840 (0x12ecd4b8)
LeakCanary: | mInstrumentation = android.app.Instrumentation@317510856 (0x12ecd4c8)
LeakCanary: | mIntent = android.content.Intent@317510928 (0x12ecd510)
LeakCanary: | mLastAutofillId = 1073741823
LeakCanary: | mLastNonConfigurationInstances = null
LeakCanary: | mMainThread = android.app.ActivityThread@316670208 (0x12e00100)
LeakCanary: | mManagedCursors = java.util.ArrayList@317510992 (0x12ecd550)
LeakCanary: | mManagedDialogs = null
LeakCanary: | mMenuInflater = null
LeakCanary: | mParent = null
LeakCanary: | mReferrer = "com.example.zxp.myapplication"
LeakCanary: | mResultCode = 0
LeakCanary: | mResultData = null
LeakCanary: | mResumed = false
LeakCanary: | mSearchEvent = null
LeakCanary: | mSearchManager = null
LeakCanary: | mStartedActivity = false
LeakCanary: | mStopped = true
LeakCanary: | mTaskDescription = android.app.ActivityManager$TaskDescription@317511072 (0x12ecd5a0)
LeakCanary: | mTemporaryPause = false
LeakCanary: | mTitle = "My Application"
LeakCanary: | mTitleColor = 0
LeakCanary: | mTitleReady = true
LeakCanary: | mToken = android.os.BinderProxy@317511112 (0x12ecd5c8)
LeakCanary: | mTranslucentCallback = null
LeakCanary: | mUiThread = java.lang.Thread@1912332288 (0x71fbe000)
LeakCanary: | mVisibleBehind = false
LeakCanary: | mVisibleFromClient = true
LeakCanary: | mVisibleFromServer = true
LeakCanary: | mVoiceInteractor = null
LeakCanary: | mWindow = com.android.internal.policy.PhoneWindow@317511144 (0x12ecd5e8)
LeakCanary: | mWindowAdded = true
LeakCanary: | mWindowManager = android.view.WindowManagerImpl@317511512 (0x12ecd758)
LeakCanary: | mInflater = com.android.internal.policy.PhoneLayoutInflater@317511536 (0x12ecd770)
LeakCanary: | mOverrideConfiguration = null
LeakCanary: | mResources = android.content.res.Resources@317511584 (0x12ecd7a0)
LeakCanary: | mTheme = android.content.res.Resources$Theme@317511624 (0x12ecd7c8)
LeakCanary: | mThemeResource = 2131492869
LeakCanary: | mBase = android.app.ContextImpl@317511640 (0x12ecd7d8)
LeakCanary: | shadow$_klass_ = com.example.zxp.myapplication.LeakActivity
LeakCanary: | shadow$_monitor_ = 1245427187
LeakCanary: * Instance of android.app.FragmentController
LeakCanary: | static $classOverhead = byte[208]@1865906065 (0x6f377791)
LeakCanary: | mHost = android.app.Activity$HostCallbacks@317573352 (0x12edc8e8)
LeakCanary: | shadow$_klass_ = android.app.FragmentController
LeakCanary: | shadow$_monitor_ = 0
LeakCanary: * Instance of android.app.Activity$HostCallbacks
LeakCanary: | static $classOverhead = byte[184]@1865460793 (0x6f30ac39)
LeakCanary: | this$0 = com.example.zxp.myapplication.LeakActivity@317509920 (0x12ecd120)
LeakCanary: | mActivity = com.example.zxp.myapplication.LeakActivity@317509920 (0x12ecd120)
LeakCanary: | mAllLoaderManagers = android.util.ArrayMap@317573400 (0x12edc918)
LeakCanary: | mCheckedForLoaderManager = true
LeakCanary: | mContext = com.example.zxp.myapplication.LeakActivity@317509920 (0x12ecd120)
LeakCanary: | mFragmentManager = android.app.FragmentManagerImpl@317573432 (0x12edc938)
LeakCanary: | mHandler = android.os.Handler@317510808 (0x12ecd498)
LeakCanary: | mLoaderManager = null
LeakCanary: | mLoadersStarted = true
LeakCanary: | mRetainLoaders = false
LeakCanary: | mWindowAnimations = 0
LeakCanary: | shadow$_klass_ = android.app.Activity$HostCallbacks
LeakCanary: | shadow$_monitor_ = 0
LeakCanary: * Instance of android.app.FragmentManagerImpl
LeakCanary: | static USER_VISIBLE_HINT_TAG = "android:user_visible_hint"
LeakCanary: | static TAG = "FragmentManager"
LeakCanary: | static TARGET_REQUEST_CODE_STATE_TAG = "android:target_req_state"
LeakCanary: | static DEBUG = false
LeakCanary: | static VIEW_STATE_TAG = "android:view_state"
LeakCanary: | static TARGET_STATE_TAG = "android:target_state"
LeakCanary: | static $classOverhead = byte[481]@1865124441 (0x6f2b8a59)
LeakCanary: | mActive = android.util.SparseArray@317573544 (0x12edc9a8)
LeakCanary: | mAdded = java.util.ArrayList@317573568 (0x12edc9c0)
LeakCanary: | mAllowOldReentrantBehavior = false
LeakCanary: | mAvailBackStackIndices = null
LeakCanary: | mBackStack = null
LeakCanary: | mBackStackChangeListeners = null
LeakCanary: | mBackStackIndices = null
LeakCanary: | mContainer = null
LeakCanary: | mCreatedMenus = null
LeakCanary: | mCurState = 0
LeakCanary: | mDestroyed = true
LeakCanary: | mExecCommit = android.app.FragmentManagerImpl$1@317573592 (0x12edc9d8)
LeakCanary: | mExecutingActions = false
LeakCanary: | mHavePendingDeferredStart = false
LeakCanary: | mHost = null
LeakCanary: | mLifecycleCallbacks = java.util.concurrent.CopyOnWriteArrayList@317573608 (0x12edc9e8)
LeakCanary: | mNeedMenuInvalidate = false
LeakCanary: | mNextFragmentIndex = 1
LeakCanary: | mNoTransactionsBecause = null
LeakCanary: | mParent = null
LeakCanary: | mPendingActions = java.util.ArrayList@317573624 (0x12edc9f8)
LeakCanary: | mPostponedTransactions = null
LeakCanary: | mPrimaryNav = null
LeakCanary: | mSavedNonConfig = null
LeakCanary: | mStateArray = null
LeakCanary: | mStateBundle = null
LeakCanary: | mStateSaved = false
LeakCanary: | mTmpAddedFragments = java.util.ArrayList@317573648 (0x12edca10)
LeakCanary: | mTmpIsPop = java.util.ArrayList@317573672 (0x12edca28)
LeakCanary: | mTmpRecords = java.util.ArrayList@317573696 (0x12edca40)
LeakCanary: | shadow$_klass_ = android.app.FragmentManagerImpl
LeakCanary: | shadow$_monitor_ = 0
LeakCanary: * Instance of java.util.ArrayList
LeakCanary: | static MAX_ARRAY_SIZE = 2147483639
LeakCanary: | static serialVersionUID = 8683452581122892189
LeakCanary: | static DEFAULTCAPACITY_EMPTY_ELEMENTDATA = java.lang.Object[0]@1864791560 (0x6f267608)
LeakCanary: | static EMPTY_ELEMENTDATA = java.lang.Object[0]@1864980896 (0x6f2959a0)
LeakCanary: | static $classOverhead = byte[208]@1865178297 (0x6f2c5cb9)
LeakCanary: | static DEFAULT_CAPACITY = 10
LeakCanary: | elementData = java.lang.Object[10]@317573984 (0x12edcb60)
LeakCanary: | size = 1
LeakCanary: | modCount = 1
LeakCanary: | shadow$_klass_ = java.util.ArrayList
LeakCanary: | shadow$_monitor_ = 0
LeakCanary: * Array of java.lang.Object[]
LeakCanary: | [0] = android.arch.lifecycle.ReportFragment@317574040 (0x12edcb98)
LeakCanary: | [1] = null
LeakCanary: | [2] = null
LeakCanary: | [3] = null
LeakCanary: | [4] = null
LeakCanary: | [5] = null
LeakCanary: | [6] = null
LeakCanary: | [7] = null
LeakCanary: | [8] = null
LeakCanary: | [9] = null
LeakCanary: * Instance of android.arch.lifecycle.ReportFragment
LeakCanary: | static REPORT_FRAGMENT_TAG = "android.arch.lifecycle.LifecycleDispatcher.report_fragment_tag"
LeakCanary: | static $classOverhead = byte[668]@316777905 (0x12e1a5b1)
LeakCanary: | mProcessListener = null
LeakCanary: | mAdded = false
LeakCanary: | mAnimationInfo = null
LeakCanary: | mArguments = null
LeakCanary: | mBackStackNesting = 0
LeakCanary: | mCalled = true
LeakCanary: | mCheckedForLoaderManager = false
LeakCanary: | mChildFragmentManager = null
LeakCanary: | mChildNonConfig = null
LeakCanary: | mContainer = null
LeakCanary: | mContainerId = 0
LeakCanary: | mDeferStart = false
LeakCanary: | mDetached = false
LeakCanary: | mFragmentId = 0
LeakCanary: | mFragmentManager = null
LeakCanary: | mFromLayout = false
LeakCanary: | mHasMenu = false
LeakCanary: | mHidden = false
LeakCanary: | mHiddenChanged = false
LeakCanary: | mHost = null
LeakCanary: | mInLayout = false
LeakCanary: | mIndex = -1
LeakCanary: | mIsNewlyAdded = false
LeakCanary: | mLayoutInflater = null
LeakCanary: | mLoaderManager = null
LeakCanary: | mLoadersStarted = false
LeakCanary: | mMenuVisible = true
LeakCanary: | mParentFragment = null
LeakCanary: | mPerformedCreateView = false
LeakCanary: | mRemoving = false
LeakCanary: | mRestored = false
LeakCanary: | mRetainInstance = false
LeakCanary: | mRetaining = false
LeakCanary: | mSavedFragmentState = null
LeakCanary: | mSavedViewState = null
LeakCanary: | mState = 0
LeakCanary: | mTag = null
LeakCanary: | mTarget = null
LeakCanary: | mTargetIndex = -1
LeakCanary: | mTargetRequestCode = 0
LeakCanary: | mUserVisibleHint = true
LeakCanary: | mView = null
LeakCanary: | mWho = null
LeakCanary: | shadow$_klass_ = android.arch.lifecycle.ReportFragment
LeakCanary: | shadow$_monitor_ = 0
LeakCanary: * Excluded Refs:
LeakCanary: | Field: android.os.Message.obj
LeakCanary: | Field: android.os.Message.next
LeakCanary: | Field: android.os.Message.target
LeakCanary: | Field: android.view.inputmethod.InputMethodManager.mNextServedView
LeakCanary: | Field: android.view.inputmethod.InputMethodManager.mServedView
LeakCanary: | Field: android.view.inputmethod.InputMethodManager.mServedInputConnection
LeakCanary: | Field: android.view.inputmethod.InputMethodManager.mCurRootView
LeakCanary: | Field: android.accounts.AccountManager$AmsTask$Response.this$1
LeakCanary: | Field: android.view.accessibility.AccessibilityNodeInfo.mOriginalText
LeakCanary: | Field: com.android.internal.policy.BackdropFrameRenderer.mDecorView
LeakCanary: | Field: android.view.Choreographer$FrameDisplayEventReceiver.mMessageQueue (always)
LeakCanary: | Thread:FinalizerWatchdogDaemon (always)
LeakCanary: | Thread:main (always)
LeakCanary: | Thread:LeakCanary-Heap-Dump (always)
LeakCanary: | Class:java.lang.ref.WeakReference (always)
LeakCanary: | Class:java.lang.ref.SoftReference (always)
LeakCanary: | Class:java.lang.ref.PhantomReference (always)
LeakCanary: | Class:java.lang.ref.Finalizer (always)
LeakCanary: | Class:java.lang.ref.FinalizerReference (always)