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