StrictMode原理剖析

QuibblerQuibbler 2021-06-08 约 36 分钟 2756 次阅读

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(...)方法,果然可以找到调用了StrictMode的incrementExpectedActivityCount(Class klass)方法:

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

在回调Activity的onDestroy()方法,也就是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");
    }

CloseGuard中open(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)方法设置默认的AndroidCloseGuardReporter给CloseGuard。

// 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”监控,这里面的设计思想值得学习实践。

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w