• StrictMode卡顿与泄漏检测-StrictMode原理(2)


    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; // Disabled by default.
    
        @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 /* callsite */);
        }
    
    		//关闭之前,
        @UnsupportedAppUsage
        @SystemApi(client = MODULE_LIBRARIES)
        @libcore.api.IntraCoreApi
        public void close() {
            Tracker tracker = currentTracker;
            if (tracker != null && closerNameOrAllocationInfo instanceof Throwable) {
                // Invoke tracker on close only if we invoked it on open. Tracker may have changed.
                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 + ". ");
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    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()
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    埋炸弹 -首先来看看构造函数:

    private final CloseGuard guard = CloseGuard.get();
    public FileOutputStream(File file, boolean append)
            throws FileNotFoundException
        {
            String name = (file != null ? file.getPath() : null);
    	   		.......
            // Android-added: CloseGuard support. Android添加的
            guard.open("close");
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    拆炸弹 - 关闭输出流

        public void close() throws IOException {
            synchronized (closeLock) {
                if (closed) {
                    return;
                }
                closed = true;
            }
    
            // Android-added: CloseGuard support.
            guard.close();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    爆炸 - 没有调用close便被回收

        protected void finalize() throws IOException {
            // Android-added: CloseGuard support.
            if (guard != null) {
                guard.warnIfOpen();
            }
    				...
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    同理推断其他的流也会有其他的机制来检测泄漏。

    2.2 IO卡顿检测

    看卡顿前,我们需要先准备点预备知识,IO读取与写出的预备知识:

    FileInputStream/FileOutputStream -> IOBridge -> ForwardingOs -> BlockGuardOS -> LibCore.OS
    
    • 1

    我们需要关注的主要在BlockGuardOS这一层,我们先看看源代码:

    public class BlockGuardOs extends ForwardingOs {
        @Override public FileDescriptor open(String path, int flags, int mode) throws ErrnoException {
            BlockGuard.getThreadPolicy().onReadFromDisk();//检测IO读
            BlockGuard.getVmPolicy().onPathAccess(path);//检测VM策略路径访问
            if ((flags & O_ACCMODE) != O_RDONLY) {
                BlockGuard.getThreadPolicy().onWriteToDisk(); //检测IO写
            }
    				....
        }
      
          @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);
        }
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    这里比较清晰的看到,BlockGuardOs内部代理了BlockGuard,每次调用函数前,都会检测一下是否有卡顿问题。最后再回到StrictMode.

    public void onReadFromDisk() {
        if ((mThreadPolicyMask & DETECT_THREAD_DISK_READ) == 0) {
          return;
        }
        if (tooManyViolationsThisLoop()) {
          return;
        }
        //这个函数里面会进行打印
        startHandlingViolationException(new DiskReadViolation());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.3 SqliteCursor泄漏检测

    location:frameworks/base/core/java/android/database/sqlite/SQLiteCursor.java

      
    protected void finalize() {
            try {
                // if the cursor hasn't been closed yet, close it first
                if (mWindow != null) {
                    // Report original sql statement
                    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();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    很明显就可以看出,当mWindow不为空时,会进行泄漏的消息上报。这里的window指的是cursor的查询窗口,执行过move函数会赋值

  • 相关阅读:
    【ArcGIS Pro微课1000例】0031:las点云提取(根据范围裁剪点云)
    手机微博保存的图片无法在电脑端查看 - 解决方案
    GrapeCity Documents for Excel:GcExcel 5.1.0
    读《vue3设计与实现》2- vue的diff算法核心
    腾讯云GPU云服务器使用Docker 安装 TensorFlow 并设置 GPU/CPU 支持!
    Config
    JAVA 去除空格
    使用node+vite还有axios发出get请求出现此类问题
    IDEA常用插件
    Day23力扣打卡
  • 原文地址:https://blog.csdn.net/aa375809600/article/details/125420052