阅读代码的技巧

1,123 阅读8分钟

前言

记录一些阅读代码的一些技巧和知识点

这个方法到底是哪里调用的?

平日在阅读代码的时候,经常被跳来跳去的函数(或者是方法)调用栈绕晕,尤其是遇到多态和接口的时候,方法的实际执行的类和实现跟方法定义的位置很难通过 IDE 的跳转关系理清。一不小心就会把自己绕进去,好不容易理清了吧,时间久了再次看的时候又得理一遍,而且有些调用链特别长,那么有没有什么办法可以快速的知道方法调用栈呢?

其实我们可以借助 Exception (准确来说是 Throwable) 的 printStackTrace() 方法打印调用栈。我们知道在发生异常的时候,一般会调用 e.printStackTrace() 打印错误信息,帮助我们定位到发生异常的位置。其实,我们也可以主动创建 Exception 对象去打印方法调用栈。

比如在 ActivityonCreate() 方法中,我们想知道 Activity 到底是如何创建的。

方法调用栈工具类

我们首先创建一个工具类,方便复用

object SystemTools {

    fun printMethodTrace(tag: String) {
        val trace = Exception(tag)
        trace.printStackTrace()
    }
}

Activity 生命周期调用链

然后在 Activity 的生命周期中调用工具类

class WrapContentActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_wrap_content)
        SystemTools.printMethodTrace("onCreate")
    }

    override fun onResume() {
        super.onResume()
        SystemTools.printMethodTrace("onResume")
    }
}

看一下输出结果:


com.engineer.android.mini W: java.lang.Exception: onCreate
com.engineer.android.mini W:     at com.engineer.android.mini.util.SystemTools.printMethodTrace(SystemTools.kt:17)
com.engineer.android.mini W:     at com.engineer.android.mini.ui.pure.WrapContentActivity.onCreate(CustomViewPlayGround.kt:357)
com.engineer.android.mini W:     at android.app.Activity.performCreate(Activity.java:8000)
com.engineer.android.mini W:     at android.app.Activity.performCreate(Activity.java:7984)
com.engineer.android.mini W:     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
com.engineer.android.mini W:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
com.engineer.android.mini W:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
com.engineer.android.mini W:     at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
com.engineer.android.mini W:     at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
com.engineer.android.mini W:     at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
com.engineer.android.mini W:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
com.engineer.android.mini W:     at android.os.Handler.dispatchMessage(Handler.java:106)
com.engineer.android.mini W:     at android.os.Looper.loop(Looper.java:223)
com.engineer.android.mini W:     at android.app.ActivityThread.main(ActivityThread.java:7656)
com.engineer.android.mini W:     at java.lang.reflect.Method.invoke(Native Method)
com.engineer.android.mini W:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
com.engineer.android.mini W:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

可以看到 ActivityThread.java(2066)行,内部类 H 接收到 Message 消息后,便开始了方法调用链,包括 LaunchActivityItem.execute,handleLaunchActivity,performLaunchActivity,Instrumentation.callActivityOnCreate,performCreate 等我们在 AMS 流程中经常看到这些方法。

com.engineer.android.mini W: java.lang.Exception: onResume
com.engineer.android.mini W:     at com.engineer.android.mini.util.SystemTools.printMethodTrace(SystemTools.kt:17)
com.engineer.android.mini W:     at com.engineer.android.mini.ui.pure.WrapContentActivity.onResume(CustomViewPlayGround.kt:351)
com.engineer.android.mini W:     at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1456)
com.engineer.android.mini W:     at android.app.Activity.performResume(Activity.java:8135)
com.engineer.android.mini W:     at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4434)
com.engineer.android.mini W:     at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4476)
com.engineer.android.mini W:     at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:52)
com.engineer.android.mini W:     at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
com.engineer.android.mini W:     at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
com.engineer.android.mini W:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
com.engineer.android.mini W:     at android.os.Handler.dispatchMessage(Handler.java:106)
com.engineer.android.mini W:     at android.os.Looper.loop(Looper.java:223)
com.engineer.android.mini W:     at android.app.ActivityThread.main(ActivityThread.java:7656)
com.engineer.android.mini W:     at java.lang.reflect.Method.invoke(Native Method)
com.engineer.android.mini W:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
com.engineer.android.mini W:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

onResume 的调用也是类似 onCreate 都是由 ActivityThread 处理 Message 消息开始。

View measure

可以再来看一个大家比较熟悉的 View measure 流程的代码。可以先思考一下,一个继承自View.java 的自定义 View 。其 OnMeasure 至少会执行多少次?

class SimpleViewOne @JvmOverloads
constructor(
    context: Context, attributeSet:
    AttributeSet? = null, style: Int = 0
) : View(context, attributeSet, style) {

    // else logic 

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        // ... measure logic 
        SystemTools.printMethodTrace("SimpleViewOne")
    }
}

完整代码 输出


2021-08-09 21:46:33.516 com.engineer.android.mini W: java.lang.Exception: SimpleViewOne
2021-08-09 21:46:33.516 com.engineer.android.mini W:     at com.engineer.android.mini.util.SystemTools.printMethodTrace(SystemTools.kt:17)
2021-08-09 21:46:33.516 com.engineer.android.mini W:     at com.engineer.android.mini.ui.pure.SimpleViewOne.onMeasure(CustomViewPlayGround.kt:86)
2021-08-09 21:46:33.516 com.engineer.android.mini W:     at android.view.View.measure(View.java:25466)
2021-08-09 21:46:33.516 com.engineer.android.mini W:     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
2021-08-09 21:46:33.516 com.engineer.android.mini W:     at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
2021-08-09 21:46:33.516 com.engineer.android.mini W:     at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
2021-08-09 21:46:33.516 com.engineer.android.mini W:     at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
2021-08-09 21:46:33.516 com.engineer.android.mini W:     at android.view.View.measure(View.java:25466)
2021-08-09 21:46:33.516 com.engineer.android.mini W:     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
2021-08-09 21:46:33.517 com.engineer.android.mini W:     at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
2021-08-09 21:46:33.517 com.engineer.android.mini W:     at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:145)
2021-08-09 21:46:33.517 com.engineer.android.mini W:     at android.view.View.measure(View.java:25466)
2021-08-09 21:46:33.517 com.engineer.android.mini W:     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
2021-08-09 21:46:33.517 com.engineer.android.mini W:     at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
2021-08-09 21:46:33.518 com.engineer.android.mini W:     at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
2021-08-09 21:46:33.518 com.engineer.android.mini W:     at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.view.View.measure(View.java:25466)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.view.View.measure(View.java:25466)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.view.View.measure(View.java:25466)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at com.android.internal.policy.DecorView.onMeasure(DecorView.java:747)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.view.View.measure(View.java:25466)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3397)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:2228)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2486)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1952)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8171)
2021-08-09 21:46:33.519 com.engineer.android.mini W:     at android.view.Choreographer$CallbackRecord.run(Choreographer.java:972)
2021-08-09 21:46:33.520 com.engineer.android.mini W:     at android.view.Choreographer.doCallbacks(Choreographer.java:796)
2021-08-09 21:46:33.520 com.engineer.android.mini W:     at android.view.Choreographer.doFrame(Choreographer.java:731)
2021-08-09 21:46:33.520 com.engineer.android.mini W:     at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:957)
2021-08-09 21:46:33.520 com.engineer.android.mini W:     at android.os.Handler.handleCallback(Handler.java:938)
2021-08-09 21:46:33.520 com.engineer.android.mini W:     at android.os.Handler.dispatchMessage(Handler.java:99)
2021-08-09 21:46:33.520 com.engineer.android.mini W:     at android.os.Looper.loop(Looper.java:223)
2021-08-09 21:46:33.520 com.engineer.android.mini W:     at android.app.ActivityThread.main(ActivityThread.java:7656)
2021-08-09 21:46:33.520 com.engineer.android.mini W:     at java.lang.reflect.Method.invoke(Native Method)
2021-08-09 21:46:33.520 com.engineer.android.mini W:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
2021-08-09 21:46:33.520 com.engineer.android.mini W:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)


2021-08-09 21:46:33.564 com.engineer.android.mini W: java.lang.Exception: SimpleViewOne
2021-08-09 21:46:33.565 com.engineer.android.mini W:     at com.engineer.android.mini.util.SystemTools.printMethodTrace(SystemTools.kt:17)
2021-08-09 21:46:33.565 com.engineer.android.mini W:     at com.engineer.android.mini.ui.pure.SimpleViewOne.onMeasure(CustomViewPlayGround.kt:86)
2021-08-09 21:46:33.565 com.engineer.android.mini W:     at android.view.View.measure(View.java:25466)
2021-08-09 21:46:33.565 com.engineer.android.mini W:     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
2021-08-09 21:46:33.565 com.engineer.android.mini W:     at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
2021-08-09 21:46:33.565 com.engineer.android.mini W:     at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
2021-08-09 21:46:33.565 com.engineer.android.mini W:     at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
2021-08-09 21:46:33.565 com.engineer.android.mini W:     at android.view.View.measure(View.java:25466)
2021-08-09 21:46:33.565 com.engineer.android.mini W:     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
2021-08-09 21:46:33.565 com.engineer.android.mini W:     at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
2021-08-09 21:46:33.565 com.engineer.android.mini W:     at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:145)
2021-08-09 21:46:33.565 com.engineer.android.mini W:     at android.view.View.measure(View.java:25466)
2021-08-09 21:46:33.565 com.engineer.android.mini W:     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
2021-08-09 21:46:33.565 com.engineer.android.mini W:     at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
2021-08-09 21:46:33.566 com.engineer.android.mini W:     at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
2021-08-09 21:46:33.566 com.engineer.android.mini W:     at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
2021-08-09 21:46:33.566 com.engineer.android.mini W:     at android.view.View.measure(View.java:25466)
2021-08-09 21:46:33.566 com.engineer.android.mini W:     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
2021-08-09 21:46:33.566 com.engineer.android.mini W:     at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
2021-08-09 21:46:33.566 com.engineer.android.mini W:     at android.view.View.measure(View.java:25466)
2021-08-09 21:46:33.566 com.engineer.android.mini W:     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
2021-08-09 21:46:33.566 com.engineer.android.mini W:     at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
2021-08-09 21:46:33.566 com.engineer.android.mini W:     at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
2021-08-09 21:46:33.566 com.engineer.android.mini W:     at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
2021-08-09 21:46:33.566 com.engineer.android.mini W:     at android.view.View.measure(View.java:25466)
2021-08-09 21:46:33.566 com.engineer.android.mini W:     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6957)
2021-08-09 21:46:33.566 com.engineer.android.mini W:     at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
2021-08-09 21:46:33.566 com.engineer.android.mini W:     at com.android.internal.policy.DecorView.onMeasure(DecorView.java:747)
2021-08-09 21:46:33.567 com.engineer.android.mini W:     at android.view.View.measure(View.java:25466)
2021-08-09 21:46:33.567 com.engineer.android.mini W:     at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3397)
2021-08-09 21:46:33.567 com.engineer.android.mini W:     at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2880)
2021-08-09 21:46:33.567 com.engineer.android.mini W:     at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1952)
2021-08-09 21:46:33.567 com.engineer.android.mini W:     at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8171)
2021-08-09 21:46:33.567 com.engineer.android.mini W:     at android.view.Choreographer$CallbackRecord.run(Choreographer.java:972)
2021-08-09 21:46:33.567 com.engineer.android.mini W:     at android.view.Choreographer.doCallbacks(Choreographer.java:796)
2021-08-09 21:46:33.567 com.engineer.android.mini W:     at android.view.Choreographer.doFrame(Choreographer.java:731)
2021-08-09 21:46:33.567 com.engineer.android.mini W:     at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:957)
2021-08-09 21:46:33.567 com.engineer.android.mini W:     at android.os.Handler.handleCallback(Handler.java:938)
2021-08-09 21:46:33.567 com.engineer.android.mini W:     at android.os.Handler.dispatchMessage(Handler.java:99)
2021-08-09 21:46:33.567 com.engineer.android.mini W:     at android.os.Looper.loop(Looper.java:223)
2021-08-09 21:46:33.567 com.engineer.android.mini W:     at android.app.ActivityThread.main(ActivityThread.java:7656)
2021-08-09 21:46:33.567 com.engineer.android.mini W:     at java.lang.reflect.Method.invoke(Native Method)
2021-08-09 21:46:33.567 com.engineer.android.mini W:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
2021-08-09 21:46:33.567 com.engineer.android.mini W:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

可以看到 onMeasure 是会执行两次的。并不是日志重复了,仔细看的话可以发现两次的调用链是有差异的

  • 调用栈是按方法压栈的顺序打印的,所以需要倒着看。
  • 我们只关注到 DecorView 的 onMeasure 之前的调用链,因为后面一定是相同的

第一次是

com.android.internal.policy.DecorView.onMeasure(DecorView.java:747)
android.view.View.measure(View.java:25466)
android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3397)
android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:2228)
android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2486)
android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1952)

第二次是

com.android.internal.policy.DecorView.onMeasure(DecorView.java:747)
android.view.View.measure(View.java:25466)
android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3397)
android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2880)
android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1952)

可以看到两次 performMeaure 是通过不同的方式发起的。

可以看到,利用 Throwable 的 printMethodTrace() 方法。我们可以非常方便的获得复杂的方法调用链。这样的技巧不仅可以用在定位错误,阅读复杂源码的场景。也可以用在我们日常开发中,尤其是在方法调用链比较长,且包含接口、抽象类的时候,使用这个方法可以非常方便的让我们确定方法调用关键结点,甚至是行号。使用这个在日常开发中做很多事情,这里就不一一举例了,有兴趣的话可以自己尝试一下。

位运算的逻辑其实很简

我们经常在源码中看到使用位运算表达的逻辑。比如在 View 的 measure 方法中

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

        // Suppress sign extension for the low bytes
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        // 省略部分代码

        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }

可以看到这么几行代码,大量使用 逻辑运算符进行了各种逻辑判断和处理,看的时候总是有种似懂非懂的感觉,这里就来简单总结一下。

在之前 二进制 一文中,其实就二进制相关的运算的一些基础做过介绍,这里就结合一个具体的例子加深一下印象。

鸡蛋灌饼你要加点啥?

在 FantasyCake 中可以添加土豆丝、海带丝、生菜形式不同 style 的 cake。 同时在结算的时候会根据输入金额的限制去除掉某一项。

public class FantasyCake {
    public static final int FLAG_ADD_POTATO_SHREDS  = 0x00000001; // 加土豆丝
    public static final int FLAG_ADD_SEAWEED_STRIPS = 0x00000002; // 加海带丝
    public static final int FLAG_ADD_LETTUCE        = 0x00000004; // 加生菜

    private int style = 0; // 默认

    @Override
    public String toString() {
        if ((this.style & FLAG_ADD_SEAWEED_STRIPS) != FLAG_ADD_SEAWEED_STRIPS) {
            // 没有加海带丝时警告
            System.err.println("without add seaweed_strips");
        }

        return "FantasyCake{" +
                "style=" + Integer.toBinaryString(this.style) +
                '}';
    }

    public void addLettuce() {
        this.style |= FLAG_ADD_LETTUCE;
    }

    public void addPotatoShreds() {
        this.style |= FLAG_ADD_POTATO_SHREDS;
    }

    public void addSeaweedStrips() {
        this.style |= FLAG_ADD_SEAWEED_STRIPS;
    }

    public int checkout(int money) {
        int base = 5; // 基础价格 5

        if ((style & FLAG_ADD_LETTUCE) == FLAG_ADD_LETTUCE) {
            base = base + 1;  // 如果加生菜了,加 1
        }

        if ((style & FLAG_ADD_SEAWEED_STRIPS) == FLAG_ADD_SEAWEED_STRIPS) {
            base = base + 2; // 如果加海带丝了 加 2
        }

        if ((style & FLAG_ADD_POTATO_SHREDS) == FLAG_ADD_POTATO_SHREDS) {
            base = base + 3; // 如果加 土豆丝了 加 3
        }

        if (base > money) {
            // 如果钱超了,把土豆丝去掉,重新算 (这里的逻辑其实可以写的复杂一点,按照金额去掉最小值,只是实例位运算用法,就不跑偏了)
            style &= ~FLAG_ADD_POTATO_SHREDS;
            return checkout(money);
        }

        return base;
    }
}

我们可以测试一下 ,完整代码参见 FantasyCake.java

// java 写的 main 方法 AS 编译不过了,只能用 kotlin 救急了
fun main() {
    val cake = FantasyCake()
    println(cake)
    cake.addLettuce()
    cake.addPotatoShreds()
    cake.addSeaweedStrips()
    println(cake)
    println("cost = ${cake.checkout(9)}")
    println(cake)
}

输出

without add seaweed_strips
FantasyCake{style=0}
FantasyCake{style=111}
cost = 8
FantasyCake{style=110}
  • 可以看到当 style 缺失 FLAG_ADD_SEAWEED_STRIPS 会输出警告。
  • 默认的 style 为 0
  • 添加 3 种 FLAG 后,style = 111 (符合预期)
  • 结算时,由于总额大于 9 ,去除了 FLAG_ADD_POTATO_SHREDS, 并重新结算结果为 8 。

至此可以简单总结规律:

  • A |= FLAG_ANY 是对 A 进行写 FLAG_ANY 对应位的 1
  • A &= FLAG_ANY 是对 A 进行写 FLAG_ANY 对应位的 0
  • (A & FLAG_ANY) == FLAG_ANY) 确保 A 有 FLAG_ANY 对应位的 1
  • (A & FLAG_ANY) != FLAG_ANY) 确保 A 没有 FLAG_ANY 对应位的 1,即为 0。

还有常见的一种

(A & FLAG_ANY) != 0 则表明在 A 当中,至少 FLAG_ANY 对应位的值是 1。这一标志位是开启的。

在 Android 源码中,FLAG_XXX 常常对应的就是某个功能是否开启了,下次阅读源码的时候,按照上面的方式理解就好了。

可以看到使用位运算有一个 32 字节的 Int 类型就相当于可以包含 32 个标志位,相比直接使用 boolean 值节省了不少内存。同时一旦习惯了这种用法会觉得写起来更方便。


maybe continued