Android重学系列(六):从源码看严格模式StrictMode(上)

1,693 阅读9分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击查看活动详情

前言

惯例,我们先来看看官方对它的定义

StrictMode 是一个开发人员工具,它可以检测您可能不小心做的事情,并将它们提请您注意,以便您修复它们。 StrictMode 最常用于捕获应用程序主线程上的意外磁盘或网络访问,其中接收 UI 操作并发生动画。使磁盘和网络操作远离主线程可以使应用程序更加流畅、响应速度更快。通过保持应用程序的主线程响应,您还可以防止向用户显示 ANR 对话框

可以看到,StrictMode主要是一个检测工具,帮助我们检测主线程上做得一些IO访问等危险操作

使用

我们看下使用方法,可以在Activity、Application的onCreate中启用

 public void onCreate() {
     StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
             .detectDiskReads()
             .detectDiskWrites()
             .detectNetwork()   // or .detectAll() for all detectable problems
             .penaltyLog()
             .build());
     StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
             .detectLeakedSqlLiteObjects()
             .detectLeakedClosableObjects()
             .penaltyLog()
             .penaltyDeath()
             .build());
     super.onCreate();
 }

其实主要分两个方法,一个是当前线程的检测,一个是虚拟机进程中所有线程的检测

1、ThreadPolicy.Builder

方法名意义
detectCustomSlowCalls()慢方法检测
detectDiskReads()磁盘读检测
detectDiskWrites()磁盘写检测
detectNetwork()网络检测
detectResourceMismatches()资源不匹配检测
detectUnbufferedIo()没有buffer检测

2、VmPolicy.Builder

方法名意义
detectActivityLeaks()检测Activity泄露
detectCleartextNetwork()检测没有SSL/TLS包裹的流量
detectContentUriWithoutPermission()检测是否有权限到第三方
detectCredentialProtectedWhileLocked()检测对用户锁定文件的访问
detectFileUriExposure()检测File Uri暴露
detectIncorrectContextUse()检测为正确使用的context
detectLeakedClosableObjects()检测close对象的泄漏
detectLeakedRegistrationObjects()检测广播的泄露
detectLeakedSqlLiteObjects()检测sqllite泄露
detectNonSdkApiUsage()检测不是sdk的反射使用
detectUnsafeIntentLaunch()检测不安全的intent
detectUntaggedSockets()检测socket

源码解读

我们先从ThreadPolicy开始

1、onReadFromDisk

在主线程读取文件,我们这里主要是调用了System.loadLibrary(),我们先看下告警信息

 2022-09-05 23:33:00.738 15172-15172/com.army.crypto D/StrictMode: StrictMode policy violation; ~duration=15 ms: android.os.strictmode.DiskReadViolation
        at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1596)
        at libcore.io.BlockGuardOs.open(BlockGuardOs.java:249)
        at libcore.io.ForwardingOs.open(ForwardingOs.java:166)
        at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7542)
        at libcore.io.IoUtils.canOpenReadOnly(IoUtils.java:275)
        at dalvik.system.DexPathList$NativeLibraryElement.findNativeLibrary(DexPathList.java:884)
        at dalvik.system.DexPathList.findLibrary(DexPathList.java:595)
        at dalvik.system.BaseDexClassLoader.findLibrary(BaseDexClassLoader.java:281)
        at java.lang.Runtime.loadLibrary0(Runtime.java:1061)
        at java.lang.Runtime.loadLibrary0(Runtime.java:1008)
        at java.lang.System.loadLibrary(System.java:1664)
        at com.army.cryptolib.Crypto.<clinit>(Crypto.java:6)
        at com.army.cryptolib.Crypto.encode(Native Method)
        at com.army.crypto.ui.main.MainFragment.initView$lambda-2$lambda-1(MainFragment.kt:64)
        at com.army.crypto.ui.main.MainFragment.$r8$lambda$fpt5zWKHDPomBcQX5YIo9wNFnSg(Unknown Source:0)
        at com.army.crypto.ui.main.MainFragment$$ExternalSyntheticLambda1.onClick(Unknown Source:4)
        at android.view.View.performClick(View.java:7448)
        at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119)
        at android.view.View.performClickInternal(View.java:7425)
        at android.view.View.access$3600(View.java:810)
        at android.view.View$PerformClick.run(View.java:28305)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

从堆栈调用顺序来看是,下面列出主要的几个节点

System.loadLibrary->IoUtils.canOpenReadOnly->BlockGuardOs.open

我们来看下BlockGuardOs.open(),这里主要是打开文件的作用,发现里面间接调用了3个api。2个是 ThreadPolicy中的onReadFromDisk(),onWriteToDisk(),还有一个则是直接回传给了VmPolicy

 @Override public FileDescriptor open(String path, int flags, int mode) throws ErrnoException {
        BlockGuard.getThreadPolicy().onReadFromDisk();
        BlockGuard.getVmPolicy().onPathAccess(path);
        if ((flags & O_ACCMODE) != O_RDONLY) {
            BlockGuard.getThreadPolicy().onWriteToDisk();
        }
        return super.open(path, flags, mode);
    }

这里的VmPolicy我们先忽略

那我们先看看ThreadPolicy是什么

public interface Policy {

        void onWriteToDisk();

        void onReadFromDisk();

        void onNetwork();

        void onUnbufferedIO();

        void onExplicitGc();

        int getPolicyMask();
    }

发现是一个接口,这里值得注意的是,我们前面介绍ThreadPolicy.Builder(),刚好和这里对应上,都是6个api,那这里的实现是什么呢?得先从StrictMode.setThreadPolicy()说起

 public static void setThreadPolicy(final ThreadPolicy policy) {
        setThreadPolicyMask(policy.mask);
        sThreadViolationListener.set(policy.mListener);
        sThreadViolationExecutor.set(policy.mCallbackExecutor);
    }

这里可以看到,我们之前通过ThreadPolicy.Builder设置的一大堆,其实最后都会归纳到一个状态上,这个状态类型是int,这个实现原理是巧妙运用掩码和位运算,可以发现Android源码里随处可见这种设计,大家可以学起来,尤其是针对那种一个场景可能映射多个状态时,特别有用

public static final int PENALTY_LOG = 1 << 30;
public static final int PENALTY_DIALOG = 1 << 29;
public static final int PENALTY_DEATH = 1 << 28;
public static final int PENALTY_FLASH = 1 << 27;
public static final int PENALTY_DROPBOX = 1 << 26;
public static final int PENALTY_DEATH_ON_NETWORK = 1 << 25;
public static final int PENALTY_DEATH_ON_CLEARTEXT_NETWORK = 1 << 24;
public static final int PENALTY_DEATH_ON_FILE_URI_EXPOSURE = 1 << 23;

我们继续看setThreadPolicyMask()

    public static void setThreadPolicyMask(@ThreadPolicyMask int threadPolicyMask) {
        setBlockGuardPolicy(threadPolicyMask);
        Binder.setThreadStrictModePolicy(threadPolicyMask);
    }

2、setBlockGuardPolicy

继续setBlockGuardPolicy,这里还将我们的设置同步给了Binder

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

这里通过BlockGuard.getThreadPolicy()获取了一个BlockGuard.Policy

看下BlockGuard.getThreadPolicy()的实现

   public static @NonNull Policy getThreadPolicy() {
        return threadPolicy.get();
    }

然后继续看threadPolicy的实现

   private static ThreadLocal<Policy> threadPolicy = new ThreadLocal<Policy>() {
        @Override protected Policy initialValue() {
            return LAX_POLICY;
        }
    };

然后看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;
        }
    };

空实现?扯淡吧?其实我们前面忽略了一个小点。就是下面代码

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

第一次取,取得是空实现,那自然就不满足instanceof AndroidBlockGuardPolicy,然后从THREAD_ANDROID_POLICY里去取,再将取到的值更新到了BlockGuard.threadPolicy中, 我们看看THREAD_ANDROID_POLICY是什么

 private static final ThreadLocal<AndroidBlockGuardPolicy> THREAD_ANDROID_POLICY =
            new ThreadLocal<AndroidBlockGuardPolicy>() {
                @Override
                protected AndroidBlockGuardPolicy initialValue() {
                    return new AndroidBlockGuardPolicy(0);
                }
            };

真像水落石出,在这里直接new AndroidBlockGuardPolicy(),其实前面堆栈日志就打印了这里,不过还是不得不佩服这个代码写的是真6,学到了。

那既然我们已经知道了ThreadPolicy的实现是AndroidBlockGuardPolicy,那回到最开始的地方,我们看看AndroidBlockGuardPolicyonReadFromDisk()实现

public void onReadFromDisk() {
            if ((mThreadPolicyMask & DETECT_THREAD_DISK_READ) == 0) {
                return;
            }
            if (tooManyViolationsThisLoop()) {
                return;
            }
            startHandlingViolationException(new DiskReadViolation());
        }

这里首先判断了我们配置里有没有把检测磁盘读取打开,然后去检测是否有太多的检测在执行,默认最大是10个,然后则调用了startHandlingViolationException()

 void startHandlingViolationException(Violation e) {
            final int penaltyMask = (mThreadPolicyMask & PENALTY_ALL);
            final ViolationInfo info = new ViolationInfo(e, penaltyMask);
            info.violationUptimeMillis = SystemClock.uptimeMillis();
            handleViolationWithTimingAttempt(info);
        }

3、handleViolationWithTimingAttempt

接着来看 handleViolationWithTimingAttempt,我们先把这个方法分成三段来讲解

先来第一段

void handleViolationWithTimingAttempt(final ViolationInfo info) {
            Looper looper = Looper.myLooper();
            if (looper == null || (info.mPenaltyMask == PENALTY_DEATH)) {
                info.durationMillis = -1; // unknown (redundant, already set)
                onThreadPolicyViolation(info);
                return;
            }
            ...
        }

这里判断当前线程是否有创建Looper,或者是否开启了PENALTY_DEATH策略(即触发严格模式,则让将整个App进程杀掉),

为什么有这个逻辑呢?是因为主线程肯定Looper!=null,那么只有子线程或者开启了PENALTY_DEATH的才会走到这里,联系后面的逻辑会在窗口显示一个红色的框,也就是说在非主线程做的高危动作是不会亮红框的,同时开启了PENALTY_DEATH策略,触发了高危应用进程会被马上kill掉,也就没必要显示红框。

值得注意的是,如果我们自己在Thread里创建了Looper,那其实还是会去显示红框

同时触发了if的条件,就不再执行后面的逻辑,就直接调用了onThreadPolicyViolation(),这是一个打印日志的方法,就是我们前面触发严格模式时,在控制台看到的log日志,这里官方有一大段的注释,大概意思是用户比较关心的是这个危险操作具体消耗了多少时间,但是这里应该是拿不到的,只有线程持有Looper才能统计得到

接着看第二段

部分代码我用省略号代替

void handleViolationWithTimingAttempt(final ViolationInfo info) {
            ...
            final ArrayList<ViolationInfo> records = violationsBeingTimed.get();
            if (records.size() >= MAX_OFFENSES_PER_LOOP) {
                // Not worth measuring.  Too many offenses in one loop.
                return;
            }
            records.add(info);
            if (records.size() > 1) {
                return;
            }

            final IWindowManager windowManager =
                    info.penaltyEnabled(PENALTY_FLASH) ? sWindowManager.get() : null;
            if (windowManager != null) {
                try {
                    windowManager.showStrictModeViolation(true);
                } catch (RemoteException unused) {
                }
            }
            ...
        }

这里通过violationsBeingTimed,拿到当前线程的一个List数组,里面存储的是ViolationInfo,然后下面直接判断了数组的容量,不能超过最大值MAX_OFFENSES_PER_LOOP=10条,这里为什么这样处理,是因为到这里是默认线程里都有Looper的,过多的任务会对当前线程造成压力。这里如果容量超过的时候是直接丢弃的

然后下面又判断了records.size() > 1,这表示我们之前post到looper里的任务还没来得及处理,就直接往数组add了,直接等待handler任务被调用,数组里的信息也会被输出。

然后接下来就是重点了,根据PENALTY_FLASH是否开启,直接去调windowManager.showStrictModeViolation(true),那这个是什么呢?其实就是显示一个红框,提醒我们在主线程做了一些危险操作(理论上来说是主线程,不排除有人创建了一个线程,然后也创建了looper的情况)

接着来看第三段

void handleViolationWithTimingAttempt(final ViolationInfo info) {
            ...
            THREAD_HANDLER
                    .get()
                    .postAtFrontOfQueue(
                            () -> {
                                long loopFinishTime = SystemClock.uptimeMillis();
                                if (windowManager != null) {
                                    try {
                                        windowManager.showStrictModeViolation(false);
                                    } catch (RemoteException unused) {
                                    }
                                }

                                for (int n = 0; n < records.size(); ++n) {
                                    ViolationInfo v = records.get(n);
                                    v.violationNumThisLoop = n + 1;
                                    v.durationMillis =
                                            (int) (loopFinishTime - v.violationUptimeMillis);
                                    onThreadPolicyViolation(v);
                                }
                                records.clear();
                            });
        }

这里通过当前线程拿到Handler然后调用postAtFrontOfQueue将任务放到最前面,这样下一次消息迭代就会被取出然后执行,然后我们看看回调Runnable里做了什么逻辑

先调用了windowManager.showStrictModeViolation(false),关闭了窗口上的红框显示,这个之前是开启的。 然后去除records里的记录,循环调用onThreadPolicyViolation()去打印日志,

值得注意的是这里v.durationMillis的赋值,记录了这个危险执行大概消耗了多少时间,这也就解释了之前为啥去判断Looper的值情况,因为那样是不支持统计耗时的

4、showStrictModeViolation

为什么说这是一个显示红框的逻辑,我们到源码里看看

 final IWindowManager windowManager =
                    info.penaltyEnabled(PENALTY_FLASH) ? sWindowManager.get() : null;
            if (windowManager != null) {
                try {
                    windowManager.showStrictModeViolation(true);
                } catch (RemoteException unused) {
                }
            }

继续看WindowManagerService的实现

 public void showStrictModeViolation(boolean on) {
        final int pid = Binder.getCallingPid();
        if (on) {
            // Show the visualization, and enqueue a second message to tear it
            // down if we don't hear back from the app.
            mH.sendMessage(mH.obtainMessage(H.SHOW_STRICT_MODE_VIOLATION, 1, pid));
            mH.sendMessageDelayed(mH.obtainMessage(H.SHOW_STRICT_MODE_VIOLATION, 0, pid),
                    DateUtils.SECOND_IN_MILLIS);
        } else {
            mH.sendMessage(mH.obtainMessage(H.SHOW_STRICT_MODE_VIOLATION, 0, pid));
        }
    }

发现没有,在调用打开on=true的情况下,会接连发送一个关闭的操作,这里延时是1s,也就意味着一次告警会显示1次红框1s,我们再来看看这个红框是怎么实现的

最终会调用到showStrictModeViolation


private void showStrictModeViolation(int arg, int pid) {
        final boolean on = arg != 0;
        synchronized (mGlobalLock) {
            if (on && !mRoot.canShowStrictModeViolation(pid)) {
                return;
            }

            if (SHOW_VERBOSE_TRANSACTIONS) Slog.i(TAG_WM, ">>> showStrictModeViolation");
          
            if (mStrictModeFlash == null) {
                mStrictModeFlash = new StrictModeFlash(getDefaultDisplayContentLocked(),
                        mTransaction);
            }
            mStrictModeFlash.setVisibility(on, mTransaction);
            mTransaction.apply();
        }
    }

判断了一下当前是否能显示红框,然后具体的实现交给了StrictModeFlash,通过传入窗口容器RootWindowContainer拿到了DisplayContent

我们看看它的setVisibility()

 public void setVisibility(boolean on, SurfaceControl.Transaction t) {
        if (mSurfaceControl == null) {
            return;
        }
        drawIfNeeded();
        if (on) {
            t.show(mSurfaceControl);
        } else {
            t.hide(mSurfaceControl);
        }
    }

继续看drawIfNeeded()

private void drawIfNeeded() {
        if (!mDrawNeeded) {
            return;
        }
        mDrawNeeded = false;
        final int dw = mLastDW;
        final int dh = mLastDH;
        mBlastBufferQueue.update(mSurfaceControl, dw, dh, PixelFormat.RGBA_8888);

        Canvas c = null;
        try {
            c = mSurface.lockCanvas(null);
        } catch (IllegalArgumentException | OutOfResourcesException e) {
        }
        if (c == null) {
            return;
        }

        // Top
        c.save();
        c.clipRect(new Rect(0, 0, dw, mThickness));
        c.drawColor(Color.RED);
        c.restore();
        // Left
        c.save();
        c.clipRect(new Rect(0, 0, mThickness, dh));
        c.drawColor(Color.RED);
        c.restore();
        // Right
        c.save();
        c.clipRect(new Rect(dw - mThickness, 0, dw, dh));
        c.drawColor(Color.RED);
        c.restore();
        // Bottom
        c.save();
        c.clipRect(new Rect(0, dh - mThickness, dw, dh));
        c.drawColor(Color.RED);
        c.restore();

        mSurface.unlockCanvasAndPost(c);
    }

mThickness=20,这不就画了一个宽度20像素的红色矩形吗,依次将上、左、右、下四条边通过canvas画了出来

5、onThreadPolicyViolation

接下来我们再来看看 onThreadPolicyViolation,我们也分成几部分来看

首先是第一部分

void onThreadPolicyViolation(final ViolationInfo info) {
            if (LOG_V) Log.d(TAG, "onThreadPolicyViolation; penalty=" + info.mPenaltyMask);

            if (info.penaltyEnabled(PENALTY_GATHER)) {
                ArrayList<ViolationInfo> violations = gatheredViolations.get();
                if (violations == null) {
                    violations = new ArrayList<>(1);
                    gatheredViolations.set(violations);
                }
                for (ViolationInfo previous : violations) {
                    if (info.getStackTrace().equals(previous.getStackTrace())) {
                        // Duplicate. Don't log.
                        return;
                    }
                }
                violations.add(info);
                return;
            }
   }

这里会根据PENALTY_GATHER的开关情况执行下面逻辑,会去收集其它进程传递过来的异常信息,即进程A跨进程通过AIDL调用B后,B中的告警信息也会回传回来,实现逻辑主要在Parcel中的readExceptionCode()

public final int readExceptionCode() {
        int code = readInt();
        if (code == EX_HAS_NOTED_APPOPS_REPLY_HEADER) {
            AppOpsManager.readAndLogNotedAppops(this);
            code = readInt();
        }

        if (code == EX_HAS_STRICTMODE_REPLY_HEADER) {
            int headerSize = readInt();
            if (headerSize == 0) {
                Log.e(TAG, "Unexpected zero-sized Parcel reply header.");
            } else {
               
                StrictMode.readAndHandleBinderCallViolations(this);
            }
            return 0;
        }
        return code;
    }

接着看第二段

 Integer crashFingerprint = info.hashCode();
            long lastViolationTime = 0;
            long now = SystemClock.uptimeMillis();
            if (sLogger == LOGCAT_LOGGER) { // Don't throttle it if there is a non-default logger
                if (mRealLastViolationTime != null) {
                    Long vtime = mRealLastViolationTime.get(crashFingerprint);
                    if (vtime != null) {
                        lastViolationTime = vtime;
                    }
                    clampViolationTimeMap(mRealLastViolationTime, Math.max(MIN_LOG_INTERVAL_MS,
                                Math.max(MIN_DIALOG_INTERVAL_MS, MIN_DROPBOX_INTERVAL_MS)));
                } else {
                    mRealLastViolationTime = new SparseLongArray(1);
                }
                mRealLastViolationTime.put(crashFingerprint, now);
            }
            long timeSinceLastViolationMillis =
                    lastViolationTime == 0 ? Long.MAX_VALUE : (now - lastViolationTime);

            if (info.penaltyEnabled(PENALTY_LOG)
                    && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
                sLogger.log(info);
            }

这里主要是打印log,然后做了防抖的设计,防止同一个类型的告警重复去打印,这里具体可以看看info.hashCode(),有自定义的逻辑

接着看第三段

if (penaltyMask != 0) {
                final boolean justDropBox = (info.mPenaltyMask == PENALTY_DROPBOX);
                if (justDropBox) {
                    // If all we're going to ask the activity manager
                    // to do is dropbox it (the common case during
                    // platform development), we can avoid doing this
                    // call synchronously which Binder data suggests
                    // isn't always super fast, despite the implementation
                    // in the ActivityManager trying to be mostly async.
                    dropboxViolationAsync(penaltyMask, info);
                } else {
                    handleApplicationStrictModeViolation(penaltyMask, info);
                }
            }

            if (info.penaltyEnabled(PENALTY_DEATH)) {
                throw new RuntimeException("StrictMode ThreadPolicy violation", violation);
            }

这里主要就是根据 PENALTY_DROPBOX标志即我们初始化调用penaltyDropBox()设置的, 去把数据同步给DropBox,一般会在/data/system/dropbox/下生成日志文件,不过这里有一个地方要注意的是同步和异步的处理

  private static void dropboxViolationAsync(
            final int penaltyMask, final ViolationInfo info) {
        ...
        BackgroundThread.getHandler().post(() -> {
            handleApplicationStrictModeViolation(penaltyMask, info);
            int outstandingInner = sDropboxCallsInFlight.decrementAndGet();
            if (LOG_V) Log.d(TAG, "Dropbox complete; in-flight=" + outstandingInner);
        });
    }

为什么要异步处理呢?我们看下handleApplicationStrictModeViolation()

 private static void handleApplicationStrictModeViolation(int penaltyMask,
            ViolationInfo info) {
        final int oldMask = getThreadPolicyMask();
        try {
            setThreadPolicyMask(0);
            IActivityManager am = ActivityManager.getService();
            if (am == null) {
                Log.w(TAG, "No activity manager; failed to Dropbox violation.");
            } else {
                am.handleApplicationStrictModeViolation(
                        RuntimeInit.getApplicationObject(), penaltyMask, info);
            }
        } catch (RemoteException e) {
            if (e instanceof DeadObjectException) {
                // System process is dead; ignore
            } else {
                Log.e(TAG, "RemoteException handling StrictMode violation", e);
            }
        } finally {
            setThreadPolicyMask(oldMask);
        }
    }

学到了没有,这种我们不需要同步调用的跨进程方法,最好把它放到子线程里面去,比如我们应用层很多的getService().xx操作,最好是封装异步回调有木有

总结

我们从onReadFromDisk出发,到窗口显示红框,最后到打印日志,跟了这一整条调用链下来

我们发现StrictMode的实现机制其实很简单,就是在系统很多api进行预埋,然后被调用到则进行处理提示的过程。

在开发测试阶段,我们接入StrictMode,并指定一系列我们自己的策略,能很有效的帮助我们将某些问题在开发环境中就得到解决,而不必要带到线上去验证,同时也能促进我们一个很好的编码规范