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