0%

阅读代码的技巧

前言

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

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

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

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

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

方法调用栈工具类

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

1
2
3
4
5
6
7
object SystemTools {

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

Activity 生命周期调用链

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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")
}
}

看一下输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

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 流程中经常看到这些方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 至少会执行多少次?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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")
}
}

完整代码
输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

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 之前的调用链,因为后面一定是相同的

第一次是

1
2
3
4
5
6
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)

第二次是

1
2
3
4
5
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 方法中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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。 同时在结算的时候会根据输入金额的限制去除掉某一项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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

1
2
3
4
5
6
7
8
9
10
11
// 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)
}

输出

1
2
3
4
5
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 常常对应的就是某个功能是否开启了,下次阅读源码的时候,按照上面的方式理解就好了。

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


maybe continued

加个鸡腿呗.