2. StrictMode关闭检测原理
StrictMode的关闭检测其实是比较简单的,基本上就是在开启的时候埋桩,释放的时候检测桩子是否有关闭,没有关闭则检测为泄漏。
2.1 CloseGuard
这里有一个预备知识,那就是CloseGuard,它是一个StrictMode专门为了处理此类事情的类,主要包括三个函数:
- open 开始埋桩
- close 拆桩
- warnIfOpen 爆炸
SystemApi(client = MODULE_LIBRARIES)
@libcore.api.IntraCoreApi
public final class CloseGuard {
private static volatile boolean stackAndTrackingEnabled = true;
private static volatile Reporter reporter = new DefaultReporter();
private static volatile Tracker currentTracker = null;
@UnsupportedAppUsage
@SystemApi(client = MODULE_LIBRARIES)
public static void setEnabled(boolean enabled) {
CloseGuard.stackAndTrackingEnabled = enabled;
}
public static boolean isEnabled() {
return stackAndTrackingEnabled;
}
@UnsupportedAppUsage(trackingBug=111170242)
@SystemApi(client = MODULE_LIBRARIES)
@libcore.api.IntraCoreApi
public void open(String closer) {
openWithCallSite(closer, null );
}
@UnsupportedAppUsage
@SystemApi(client = MODULE_LIBRARIES)
@libcore.api.IntraCoreApi
public void close() {
Tracker tracker = currentTracker;
if (tracker != null && closerNameOrAllocationInfo instanceof Throwable) {
tracker.close((Throwable) closerNameOrAllocationInfo);
}
closerNameOrAllocationInfo = null;
}
public void warnIfOpen() {
if (closerNameOrAllocationInfo != null) {
if (closerNameOrAllocationInfo instanceof Throwable) {
reporter.report(MESSAGE, (Throwable) closerNameOrAllocationInfo);
} else if (stackAndTrackingEnabled) {
reporter.report(MESSAGE + " Callsite: " + closerNameOrAllocationInfo);
} else {
System.logW("A resource failed to call "
+ (String) closerNameOrAllocationInfo + ". ");
}
}
}
}
2.1 IO关闭检测
回顾一下之前的例子
findViewById<Button>(R.id.io_not_close_btn).setOnClickListener {
var outputStream = FileOutputStream(File(getExternalFilesDir("")?.path + "hello.json"))
outputStream.write("hello world".toByteArray())
outputStream = FileOutputStream(File(getExternalFilesDir("")?.path + "hello.json"))
Runtime.getRuntime().gc()
Runtime.getRuntime().gc()
}
埋炸弹 -首先来看看构造函数:
private final CloseGuard guard = CloseGuard.get();
public FileOutputStream(File file, boolean append)
throws FileNotFoundException
{
String name = (file != null ? file.getPath() : null);
.......
guard.open("close");
}
拆炸弹 - 关闭输出流
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
guard.close();
}
爆炸 - 没有调用close便被回收
protected void finalize() throws IOException {
if (guard != null) {
guard.warnIfOpen();
}
...
}
同理推断其他的流也会有其他的机制来检测泄漏。
2.2 IO卡顿检测
看卡顿前,我们需要先准备点预备知识,IO读取与写出的预备知识:
FileInputStream/FileOutputStream -> IOBridge -> ForwardingOs -> BlockGuardOS -> LibCore.OS
我们需要关注的主要在BlockGuardOS这一层,我们先看看源代码:
public class BlockGuardOs extends ForwardingOs {
@Override public FileDescriptor open(String path, int flags, int mode) throws ErrnoException {
BlockGuard.getThreadPolicy().onReadFromDisk();
BlockGuard.getVmPolicy().onPathAccess(path);
if ((flags & O_ACCMODE) != O_RDONLY) {
BlockGuard.getThreadPolicy().onWriteToDisk();
}
....
}
@UnsupportedAppUsage
@Override public void close(FileDescriptor fd) throws ErrnoException {
try {
if (fd.isSocket$()) {
if (isLingerSocket(fd)) {
BlockGuard.getThreadPolicy().onNetwork();
}
}
} catch (ErrnoException ignored) {
}
super.close(fd);
}
}
这里比较清晰的看到,BlockGuardOs内部代理了BlockGuard,每次调用函数前,都会检测一下是否有卡顿问题。最后再回到StrictMode.
public void onReadFromDisk() {
if ((mThreadPolicyMask & DETECT_THREAD_DISK_READ) == 0) {
return;
}
if (tooManyViolationsThisLoop()) {
return;
}
startHandlingViolationException(new DiskReadViolation());
}
2.3 SqliteCursor泄漏检测
location:frameworks/base/core/java/android/database/sqlite/SQLiteCursor.java
protected void finalize() {
try {
if (mWindow != null) {
if (StrictMode.vmSqliteObjectLeaksEnabled()) {
String sql = mQuery.getSql();
int len = sql.length();
StrictMode.onSqliteObjectLeaked(
"Finalizing a Cursor that has not been deactivated or closed. "
+ "database = " + mQuery.getDatabase().getLabel()
+ ", table = " + mEditTable
+ ", query = " + sql.substring(0, (len > 1000) ? 1000 : len),
null);
}
}
} finally {
super.finalize();
}
}
很明显就可以看出,当mWindow不为空时,会进行泄漏的消息上报。这里的window指的是cursor的查询窗口,执行过move函数会赋值
|