StrictMode原理剖析

Quibbler 2021-6-8 1243

StrictMode原理剖析


        在上一篇StrictMode:严格模式的使用中了解了StrictMode的使用,接下来深入源码,探索StrictMode的实现。主要涉及的源码目录如下:

        /frameworks/base/core/java/android/os/strictmode

        /libcore/dalvik/src/main/java/dalvik/system

        /libcore/luni/src/main/java/libcore

        ... ...

        


1、VmPolicy检测实现

        框架在内部做了很多“插桩”完成监控,举两个例子就清楚了:一个是Activity泄露检测;另一个是Closeable对象未关闭检测。在框架中插入的StrictMode代码,大都会通过下面具体的StrictMode回调,最终回调onVmPolicyViolation(...)

    onSqliteObjectLeaked
    onWebViewMethodCalledOnWrongThread
    onIntentReceiverLeaked
    onServiceConnectionLeaked
    onFileUriExposed
    onContentUriWithoutPermission
    onCleartextNetworkDetected
    onUntaggedSocket
    onImplicitDirectBoot
    onIncorrectContextUsed
    onCredentialProtectedPathAccess


1.1、StrictMode对Activity插桩检测

        在AOSP中框架的源码中找到Activity源码,会发现有一个未使用的内部成员mInstanceTracker

    @SuppressWarnings("unused")
    private final Object mInstanceTracker = StrictMode.trackActivity(this);

        通过StrictMode创建InstanceTracker实例并返回:

    public static Object trackActivity(Object instance) {
        return new InstanceTracker(instance);
    }

        InstanceTracker内部通过一个静态集合sInstanceCounts记录创建的Activity对象,在finalize()方法中,每当InstanceTracker对象回收,都会更新记录。

    private static final class InstanceTracker {
        private static final HashMap<Class<?>, Integer> sInstanceCounts =
                new HashMap<Class<?>, Integer>();
        private final Class<?> mKlass;
        public InstanceTracker(Object instance) {
            mKlass = instance.getClass();
            synchronized (sInstanceCounts) {
                final Integer value = sInstanceCounts.get(mKlass);
                final int newValue = value != null ? value + 1 : 1;
                sInstanceCounts.put(mKlass, newValue);
            }
        }
        @Override
        protected void finalize() throws Throwable {
            try {
                synchronized (sInstanceCounts) {
                    final Integer value = sInstanceCounts.get(mKlass);
                    if (value != null) {
                        final int newValue = value - 1;
                        if (newValue > 0) {
                            sInstanceCounts.put(mKlass, newValue);
                        } else {
                            sInstanceCounts.remove(mKlass);
                        }
                    }
                }
            } finally {
                super.finalize();
            }
        }
        public static int getInstanceCount(Class<?> klass) {
            synchronized (sInstanceCounts) {
                final Integer value = sInstanceCounts.get(klass);
                return value != null ? value : 0;
            }
        }
    }

        那么如何利用这个检测呢?一定是在Activity启动前启动后的方法中进行插桩,回忆一下Activity启动流程全新版本(基于API 30)一文。找到performLaunchActivity(...)方法,果然可以找到调用了StrictModeincrementExpectedActivityCount(Class klass)方法:

    /**  Core implementation of activity launch. */
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;
        ...
            StrictMode.incrementExpectedActivityCount(activity.getClass());
        ...
        return activity;
    }

        在回调ActivityonDestroy()方法,也就是performDestroyActivity(...)方法中:

    /** Core implementation of activity destroy call. */
    ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        ...
        StrictMode.decrementExpectedActivityCount(activityClass);
        return r;
    }

        同样可以看到在这里调用了对释放的Activity就行回调计数的方法decrementExpectedActivityCount(),判断Activity释放已经释放还是被持有。如果有问题就会回调onVmPolicyViolation(...)方法。

    /** @hide */
    public static void decrementExpectedActivityCount(Class klass) {
        ...
        System.gc();
        System.runFinalization();
        System.gc();
        long instances = VMDebug.countInstancesOfClass(klass, false);
        if (instances > limit) {
            onVmPolicyViolation(new InstanceCountViolation(klass, instances, limit));
        }
    }


1.2、CloseGuard检测对象关闭

        相信开发者能够猜到StrictMode是如何实现对Closeable对象实现检测:首先在创建或者开启的地方记录一下,再在close的地方进行一次记录。两次抵消,如果对象回收的时候发现标志位还在,说明Closeable对象没能关闭,从而检测出来。

        Android对Java中的很多标准代码进行了修改,往往能够看到修改的注释痕迹:Android-added或者Android-changed


        以FileInputStream为例(或者SQLiteCursor),在这里,添加了一个新的成员CloseGuard,用来监控对象的关闭。

    public class FileInputStream extends InputStream{
        ...
        // Android-added: CloseGuard support.
        @ReachabilitySensitive
        private final CloseGuard guard = CloseGuard.get();
        ...
    }

        在Closeable对象创建的地方,调用open()方法将标志置位“打开”状态。

    public FileInputStream(File file) throws FileNotFoundException {
        ...
        // Android-added: CloseGuard support.
        guard.open("close");
    }

        CloseGuardopen(String closer)方法如下:

    public void open(String closer) {
        // always perform the check for valid API usage...
        if (closer == null) {
            throw new NullPointerException("closer == null");
        }
        // ...but avoid allocating an allocation stack if "disabled"
        if (!stackAndTrackingEnabled) {
            closerNameOrAllocationInfo = closer;
            return;
        }
        String message = "Explicit termination method '" + closer + "' not called";
        Throwable stack = new Throwable(message);
        closerNameOrAllocationInfo = stack;
        Tracker tracker = currentTracker;
        if (tracker != null) {
            tracker.open(stack);
        }
    }


        另外,还需要在Closeable对象的close()方法中调用CloseGuard的关闭方法

    public void close() throws IOException {
        synchronized (closeLock) {
            if (closed) {
                return;
            }
            closed = true;
        }
        // Android-added: CloseGuard support.
        guard.close();
        ...
    }

        CloseGuard中的close()方法将此前设置的追踪标记关闭。

    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;
    }


        当对象被回收的时候,虚拟机会调用finalize()方法(不一定保证执行完),在该方法中对guard进行判断,看看是否未关闭对象:

    protected void finalize() throws IOException {
        // Android-added: CloseGuard support.
        if (guard != null) {
            guard.warnIfOpen();
        }
        ...
    }

        warnIfOpen()CloseGuard中的实现如下:

    public void warnIfOpen() {
        if (closerNameOrAllocationInfo != null) {
            if (closerNameOrAllocationInfo instanceof String) {
                System.logW("A resource failed to call "
                        + (String) closerNameOrAllocationInfo + ". ");
            } else {
                String message =
                        "A resource was acquired at attached stack trace but never released. ";
                message += "See java.io.Closeable for information on avoiding resource leaks.";
                Throwable stack = (Throwable) closerNameOrAllocationInfo;
                reporter.report(message, stack);
            }
        }
    }

        其中实现Reporter接口的类正是StrictMode中的AndroidCloseGuardReporter内部类:

    private static class AndroidCloseGuardReporter implements CloseGuard.Reporter {
        @Override
        public void report(String message, Throwable allocationSite) {
            onVmPolicyViolation(new LeakedClosableViolation(message, allocationSite));
        }
        @Override
        public void report(String message) {
            onVmPolicyViolation(new LeakedClosableViolation(message));
        }
    }

        当给StrictMode设置VmPolicy策略的时候,会调用setReporter(Reporter rep)方法设置默认的AndroidCloseGuardReporterCloseGuard

    // Sets up CloseGuard in Dalvik/libcore
    private static void setCloseGuardEnabled(boolean enabled) {
        if (!(CloseGuard.getReporter() instanceof AndroidCloseGuardReporter)) {
            CloseGuard.setReporter(new AndroidCloseGuardReporter());
        }
        CloseGuard.setEnabled(enabled);
    }

        后面就是回调onVmPolicyViolation(...)方法,最终完成对未关闭对象的监控。



2、ThreadPolicy如何检测?

        那么又如何实现ThreadPolicy的检测呢?这更简单直接,比如检测文件读写,网络请求。在文件读写或者开启网络流的地方加一个回调通知即可。用到的是BlockGuard类。


        同样以FileInputStream为例(或SharedPreferencesImpl),读文件的时候skip(long n)方法中插入了onReadFromDisk()回调方法:

    public long skip(long n) throws IOException {
        // Android-added: close() check before I/O.
        if (closed) {
            throw new IOException("Stream Closed");
        }
        try {
            // Android-added: BlockGuard support.
            BlockGuard.getThreadPolicy().onReadFromDisk();
            return skip0(n);
        } catch(UseManualSkipException e) {
            return super.skip(n);
        }
    }


        BlockGuard类中定义了Policy接口:

    public interface Policy {
        void onWriteToDisk();
        void onReadFromDisk();
        void onNetwork();
        void onUnbufferedIO();
        void onExplicitGc();
        int getPolicyMask();
    }

        BlockGuard内部默认使用的实现LAX_POLICY都是空方法:

    public static final Policy LAX_POLICY = new Policy() {
        @Override public String toString() { return "LAX_POLICY"; }
        @Override public void onWriteToDisk() {}
        @Override public void onReadFromDisk() {}
        @Override public void onNetwork() {}
        @Override public void onUnbufferedIO() {}
        @Override public void onExplicitGc() {}
        @Override
        public int getPolicyMask() {
            return 0;
        }
    };


        StrictMode中的内部类AndroidBlockGuardPolicy是真正的实现,当给StrictMode设置ThreadPolicy的时候会将一个AndroidBlockGuardPolicy实例同时设置给BlockGuard

    private static void setBlockGuardPolicy(@ThreadPolicyMask int threadPolicyMask) {
        if (threadPolicyMask == 0) {
            BlockGuard.setThreadPolicy(BlockGuard.LAX_POLICY);
            return;
        }
        final BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
        final AndroidBlockGuardPolicy androidPolicy;
        if (policy instanceof AndroidBlockGuardPolicy) {
            androidPolicy = (AndroidBlockGuardPolicy) policy;
        } else {
            androidPolicy = THREAD_ANDROID_POLICY.get();
            BlockGuard.setThreadPolicy(androidPolicy);
        }
        androidPolicy.setThreadPolicyMask(threadPolicyMask);
    }

        看看AndroidBlockGuardPolicy实现的其中一个“违例”回调onReadFromDisk()方法:

        // Part of BlockGuard.Policy interface:
        public void onReadFromDisk() {
            if ((mThreadPolicyMask & DETECT_THREAD_DISK_READ) == 0) {
                return;
            }
            if (tooManyViolationsThisLoop()) {
                return;
            }
            startHandlingViolationException(new DiskReadViolation());
        }

        后面还会经过多次的方法调用,最终处理,这里不往下跟了。



3、默认的StrictMode

        开发者很多时候并没有在应用中设置任何StrictMode,那么这些已存在在框架和系统的StrictMode相关代码该如何执行?


3.1、应用默认的StrictMode

        APP进程启动流程一文了解了APP的启动流程,应用启动时通过handleBindApplication(AppBindData data)方法启用默认的StrictMode策略:

    @UnsupportedAppUsage
    private void handleBindApplication(AppBindData data) {
        // Register the UI Thread as a sensitive thread to the runtime.
        VMRuntime.registerSensitiveThread();
        ...
        StrictMode.initThreadDefaults(data.appInfo);
        StrictMode.initVmDefaults(data.appInfo);
        ...
    }


3.2、系统默认的StrictMode

        系统服务SystemServer启动时,也会对StrictMode进行设置。不过只设置虚拟机策略,并没有设置线程检测策略。在SystemServer系统服务启动一文中了解过,通过run()方法启动系统服务,其中就有设置StrictMode策略:

    private void run() {
        ...
        // Start services.
        t.traceBegin("StartServices");
        startBootstrapServices(t);
        startCoreServices(t);
        startOtherServices(t);
        ...
        StrictMode.initVmDefaults(null);
        ...
        // Loop forever.
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }


         默认设置的VmPolicy只在debug、eng版本才开启检测,正式release或user版本不会启用任何检测。

    public static void initVmDefaults(ApplicationInfo ai) {
        final VmPolicy.Builder builder = new VmPolicy.Builder();
        final int targetSdkVersion =
                (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;
        // Starting in N, we don't allow file:// Uri exposure
        if (targetSdkVersion >= Build.VERSION_CODES.N) {
            builder.detectFileUriExposure();
            builder.penaltyDeathOnFileUriExposure();
        }
        if (Build.IS_USER || DISABLE || SystemProperties.getBoolean(DISABLE_PROPERTY, false)) {
            // Detect nothing extra
        } else if (Build.IS_USERDEBUG) {
            // Detect everything in bundled apps (except activity leaks, which
            // are expensive to track)
            ...
        } else if (Build.IS_ENG) {
            // Detect everything in bundled apps
            ...
        }
        setVmPolicy(builder.build());
    }

        默认的ThreadPolicy策略也是如此:

    /**
     * Initialize default {@link ThreadPolicy} for the current thread.
     *
     * @hide
     */
    public static void initThreadDefaults(ApplicationInfo ai) {
        final ThreadPolicy.Builder builder = new ThreadPolicy.Builder();
        final int targetSdkVersion =
                (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;
        // Starting in HC, we don't allow network usage on the main thread
        if (targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
            builder.detectNetwork();
            builder.penaltyDeathOnNetwork();
        }
        if (Build.IS_USER || DISABLE || SystemProperties.getBoolean(DISABLE_PROPERTY, false)) {
            // Detect nothing extra
        } else if (Build.IS_USERDEBUG) {
            // Detect everything in bundled apps
            if (isBundledSystemApp(ai)) {
                builder.detectAll();
                builder.penaltyDropBox();
                if (SystemProperties.getBoolean(VISUAL_PROPERTY, false)) {
                    builder.penaltyFlashScreen();
                }
            }
、        } else if (Build.IS_ENG) {
            // Detect everything in bundled apps
            if (isBundledSystemApp(ai)) {
                builder.detectAll();
                builder.penaltyDropBox();
                builder.penaltyLog();
                builder.penaltyFlashScreen();
            }
        }
        setThreadPolicy(builder.build());
    }


        别小瞧StrictMode,可以说框架中很多地方都能找到它的身影。了解了StrictMode的实现原理,开发者也应该能利用插桩实现自己的“StrictMode”监控,这里面的设计思想值得学习实践。


不忘初心的阿甘
最新回复 (0)
    • 安卓笔记本
      2
        登录 注册 QQ
返回
仅供学习交流,切勿用于商业用途。如有错误欢迎指出:fluent0418@gmail.com