publicvoidwatchActivities(){// Make sure you don't get installed twice.stopWatchingActivities();application.registerActivityLifecycleCallbacks(lifecycleCallbacks);}
Weak reference objects, which do not prevent their referents from being made finalizable, finalized, and then reclaimed. Weak references are most often used to implement canonicalizing mappings.
Suppose that the garbage collector determines at a certain point in time that an object is weakly reachable. At that time it will atomically clear all weak references to that object and all weak references to any other weakly-reachable objects from which that object is reachable through a chain of strong and soft references. At the same time it will declare all of the formerly weakly-reachable objects to be finalizable. At the same time or at some later time it will enqueue those newly-cleared weak references that are registered with reference queues.
Retryable.ResultensureGone(finalKeyedWeakReferencereference,finallongwatchStartNanoTime){longgcStartNanoTime=System.nanoTime();longwatchDurationMs=NANOSECONDS.toMillis(gcStartNanoTime-watchStartNanoTime);removeWeaklyReachableReferences();if(debuggerControl.isDebuggerAttached()){// The debugger can create false leaks.returnRETRY;}if(gone(reference)){returnDONE;}gcTrigger.runGc();removeWeaklyReachableReferences();if(!gone(reference)){longstartDumpHeap=System.nanoTime();longgcDurationMs=NANOSECONDS.toMillis(startDumpHeap-gcStartNanoTime);FileheapDumpFile=heapDumper.dumpHeap();if(heapDumpFile==RETRY_LATER){// Could not dump the heap.returnRETRY;}longheapDumpDurationMs=NANOSECONDS.toMillis(System.nanoTime()-startDumpHeap);heapdumpListener.analyze(newHeapDump(heapDumpFile,reference.key,reference.name,excludedRefs,watchDurationMs,gcDurationMs,heapDumpDurationMs));}returnDONE;}
privatevoidremoveWeaklyReachableReferences(){// 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.KeyedWeakReferenceref;while((ref=(KeyedWeakReference)queue.poll())!=null){retainedKeys.remove(ref.key);}}
publicinterfaceGcTrigger{GcTriggerDEFAULT=newGcTrigger(){@OverridepublicvoidrunGc(){// Code taken from AOSP FinalizationTest:// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/// java/lang/ref/FinalizationTester.java// System.gc() does not garbage collect every time. Runtime.gc() is// more likely to perfom a gc.Runtime.getRuntime().gc();enqueueReferences();System.runFinalization();}privatevoidenqueueReferences(){// Hack. We don't have a programmatic way to wait for the reference queue daemon to move// references to the appropriate queues.try{Thread.sleep(100);}catch(InterruptedExceptione){thrownewAssertionError();}}};voidrunGc();}
@OverridepublicFiledumpHeap(){FileheapDumpFile=leakDirectoryProvider.newHeapDumpFile();if(heapDumpFile==RETRY_LATER){returnRETRY_LATER;}FutureResult<Toast>waitingForToast=newFutureResult<>();showToast(waitingForToast);if(!waitingForToast.wait(5,SECONDS)){CanaryLog.d("Did not dump heap, too much time waiting for Toast.");returnRETRY_LATER;}Toasttoast=waitingForToast.get();try{Debug.dumpHprofData(heapDumpFile.getAbsolutePath());cancelToast(toast);returnheapDumpFile;}catch(Exceptione){CanaryLog.d(e,"Could not dump heap");// Abort heap dumpreturnRETRY_LATER;}}
@OverrideprotectedvoidonHandleIntent(Intentintent){if(intent==null){CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");return;}StringlistenerClassName=intent.getStringExtra(LISTENER_CLASS_EXTRA);HeapDumpheapDump=(HeapDump)intent.getSerializableExtra(HEAPDUMP_EXTRA);HeapAnalyzerheapAnalyzer=newHeapAnalyzer(heapDump.excludedRefs);AnalysisResultresult=heapAnalyzer.checkForLeak(heapDump.heapDumpFile,heapDump.referenceKey);AbstractAnalysisResultService.sendResultToListener(this,listenerClassName,heapDump,result);}
/*** Searches the heap dump for a {@link KeyedWeakReference} instance with the corresponding key,* and then computes the shortest strong reference path from that instance to the GC roots.*/publicAnalysisResultcheckForLeak(FileheapDumpFile,StringreferenceKey){longanalysisStartNanoTime=System.nanoTime();if(!heapDumpFile.exists()){Exceptionexception=newIllegalArgumentException("File does not exist: "+heapDumpFile);returnfailure(exception,since(analysisStartNanoTime));}try{HprofBufferbuffer=newMemoryMappedFileBuffer(heapDumpFile);HprofParserparser=newHprofParser(buffer);Snapshotsnapshot=parser.parse();deduplicateGcRoots(snapshot);InstanceleakingRef=findLeakingReference(referenceKey,snapshot);// False alarm, weak reference was cleared in between key check and heap dump.if(leakingRef==null){returnnoLeak(since(analysisStartNanoTime));}returnfindLeakTrace(analysisStartNanoTime,snapshot,leakingRef);}catch(Throwablee){returnfailure(e,since(analysisStartNanoTime));}}
privateAnalysisResultfindLeakTrace(longanalysisStartNanoTime,Snapshotsnapshot,InstanceleakingRef){ShortestPathFinderpathFinder=newShortestPathFinder(excludedRefs);ShortestPathFinder.Resultresult=pathFinder.findPath(snapshot,leakingRef);// False alarm, no strong reference path to GC Roots.if(result.leakingNode==null){returnnoLeak(since(analysisStartNanoTime));}LeakTraceleakTrace=buildLeakTrace(result.leakingNode);StringclassName=leakingRef.getClassObj().getClassName();// Side effect: computes retained size.snapshot.computeDominators();InstanceleakingInstance=result.leakingNode.instance;longretainedSize=leakingInstance.getTotalRetainedSize();// TODO: check O sources and see what happened to android.graphics.Bitmap.mBufferif(SDK_INT<=N_MR1){retainedSize+=computeIgnoredBitmapRetainedSize(snapshot,leakingInstance);}returnleakDetected(result.excludingKnownLeaks,className,leakTrace,retainedSize,since(analysisStartNanoTime));}
@OverrideprotectedfinalvoidonHeapAnalyzed(HeapDumpheapDump,AnalysisResultresult){StringleakInfo=leakInfo(this,heapDump,result,true);CanaryLog.d("%s",leakInfo);booleanresultSaved=false;booleanshouldSaveResult=result.leakFound||result.failure!=null;if(shouldSaveResult){heapDump=renameHeapdump(heapDump);resultSaved=saveResult(heapDump,result);}PendingIntentpendingIntent;StringcontentTitle;StringcontentText;if(!shouldSaveResult){contentTitle=getString(R.string.leak_canary_no_leak_title);contentText=getString(R.string.leak_canary_no_leak_text);pendingIntent=null;}elseif(resultSaved){pendingIntent=DisplayLeakActivity.createPendingIntent(this,heapDump.referenceKey);if(result.failure==null){Stringsize=formatShortFileSize(this,result.retainedHeapSize);StringclassName=classSimpleName(result.className);if(result.excludedLeak){contentTitle=getString(R.string.leak_canary_leak_excluded,className,size);}else{contentTitle=getString(R.string.leak_canary_class_has_leaked,className,size);}}else{contentTitle=getString(R.string.leak_canary_analysis_failed);}contentText=getString(R.string.leak_canary_notification_message);}else{contentTitle=getString(R.string.leak_canary_could_not_save_title);contentText=getString(R.string.leak_canary_could_not_save_text);pendingIntent=null;}// New notification id every second.intnotificationId=(int)(SystemClock.uptimeMillis()/1000);showNotification(this,contentTitle,contentText,pendingIntent,notificationId);afterDefaultHandling(heapDump,result,leakInfo);}
@Overridepublicvoidrun(){finalList<Leak>leaks=newArrayList<>();List<File>files=leakDirectoryProvider.listFiles(newFilenameFilter(){@Overridepublicbooleanaccept(Filedir,Stringfilename){returnfilename.endsWith(".result");}});for(FileresultFile:files){FileInputStreamfis=null;try{fis=newFileInputStream(resultFile);ObjectInputStreamois=newObjectInputStream(fis);HeapDumpheapDump=(HeapDump)ois.readObject();AnalysisResultresult=(AnalysisResult)ois.readObject();leaks.add(newLeak(heapDump,result,resultFile));}catch(IOException|ClassNotFoundExceptione){// Likely a change in the serializable result class.// Let's remove the files, we can't read them anymore.booleandeleted=resultFile.delete();if(deleted){CanaryLog.d(e,"Could not read result file %s, deleted it.",resultFile);}else{CanaryLog.d(e,"Could not read result file %s, could not delete it either.",resultFile);}}finally{if(fis!=null){try{fis.close();}catch(IOExceptionignored){}}}}Collections.sort(leaks,newComparator<Leak>(){@Overridepublicintcompare(Leaklhs,Leakrhs){returnLong.valueOf(rhs.resultFile.lastModified()).compareTo(lhs.resultFile.lastModified());}});mainHandler.post(newRunnable(){@Overridepublicvoidrun(){inFlight.remove(LoadLeaks.this);if(activityOrNull!=null){activityOrNull.leaks=leaks;activityOrNull.updateUi();}}});}