阅读代码的技巧

header.jpeg

前言

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

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

平日在阅读代码的时候,经常被跳来跳去的函数(或者是方法)调用栈绕晕,尤其是遇到多态和接口的时候,方法的实际执行的类和实现跟方法定义的位置很难通过 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容