事件传递是从Activity->Window->DecorView。而Window的唯一实现是PhoneWindow。PhoneWindow又包含DecorView,而我们在setContentView设置的布局文件其实是添加到DecorView中一个id为@android:id/content的View中。AppCompatActivity和Activity setContentView加载机制有些不同,但最终结果一样,我们的布局都是添加到一个id为content的View中。
本文是基于compileSdkVersion28,AppCompatActivity分析。
1.DecorView的创建
前面我们提到setContentView()设置的布局其实是添加到DecorView中一个id为@android:id/content的View中,也就是如下结构:
DecorView
//省略中间布局
...
FrameLayout @android:id/content
setContentView设置的View
那我们看下setContentView()后如何创建DecorView并将布局添加到DecorView的一个id为@android:id/content的View中。
接下来以AppCompatActivity作为分析
setContentView()调用后会调用AppCompatDelegateImpl#setContentView(),实现如下。
public void setContentView(int resId) {
//1.创建mSubDecor
ensureSubDecor();
//2.查找mSubDecor中一个id为content的ViewGroup
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
//3.将布局添加到contentParent中
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
上面这段代码主要是第一步的调用。看看ensureSubDecor()调用逻辑。
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor();
}
...
createSubDecor()调用实现
//AppCompatDelegateImpl#createSubDecor()
private ViewGroup createSubDecor() {
<!--以下是第一步-->
//调用PhoneWindow#getDecorView()创建DecorView
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
//我们使用WindowNoTitle主题,所以省略
...
inflater.inflate(R.layout.abc_screen_simple, null);
<!--以下是第二步-->
//查找subDecor中的ContentFrameLayout
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
//查找PhoneWindow#mContentParent,第一步时已经分析过了
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
//如果PhoneWindow#mContentParent有子View,那么移除并添加到subDecor中的ContentFrameLa//yout中。
if (windowContentView != null) {
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
//清除PhoneWindow#mContentParent的id,并将android.R.id.content设置给subDecor#ContentFrameLayout
windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);
}
//把subDecor设置给PhoneWindow#mContentParent当做子View
mWindow.setContentView(subDecor);
return subDecor;
}
第一步创建DecorView
//PhoneWindow#getDecorView(),最终调用PhoneWindow#installDecor()
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//创建DecorView
mDecor = generateDecor(-1);
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//创建DecorView中一个id为@android:id/content的ViewGroup。
mContentParent = generateLayout(mDecor);
}
}
第二步是将subDecor添加到 ==PhoneWindow#DecorView#mContentParent== 中,而我们的布局是添加到==subDecor#ContentFrameLayout==中。
通过代码我们知道Android对Activity和AppCompatActivity加载布局文件做了调整, AppCompatActivity#Window视图:
Activity#Window视图:
其中ParentView是我们自己的View由此可见中间多了一层==FitWindowsLinearLayout==,而AppCompatActivity设置的布局是加载到FitWindowsLinearLayout#ContentFrameLayout中。
2.事件分发分析
前面我们了解了DecorView创建过程,并且知道DecorView是PhoneWindow中一个成员变量,是布局层级的最顶端。
在分析事件分发之前有必要先了解以下说明:
-
事件传递是一层一层传递,按照View布局从外向内;按照层级从上往下。
-
ViewGroup 在整个事件分发机制有三个方法参与分别是:
dispatchTouchEvent(),onInterceptTouchEvent(),onTouchEvent()
-
View 在整个事件分发机制有两个方法参与分别是:
dispatchTouchEvent(),onTouchEvent()
因为View没有子View,所以它不存在拦截事件一说,只有消不消费一说。
- dispatchTouchEvent()代表事件分发,是事件到这一层后被调用,只要事件能到该层View,那么dispatchTouchEvent()一定会被调用。
- onInterceptTouchEvent()代表事件是否拦截,只有ViewGroup才拥有此方法,返回true代表拦截此事件,接着该层View#onTouchEvent()被调用;false代表不拦截事件,事件继续向下层View传递。
- onTouchEvent()代表事件消费,返回true代表消费事件,false代表不消费此事件。
2.1 Activity事件分发
//Activity#dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//一个空方法,不用管它
onUserInteraction();
}
//这里调用PhoneWindow#superDispatchTouchEvent()
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
//PhoneWindow#superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
//DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
由于DecorView继承自FrameLayout,FrameLayout继承ViewGroup。所以此时事件到达ViewGoup。
由此可见当顶层ViewGroup#dispatchTouchEvent()返回false时,也就是整个View树都不消费事件,Activity#onTouchEvent()将被调用。而顶层ViewGroup#dispatchTouchEvent()的返回值由它的下层View#dispatchTouchEvent()决定,如果每一层View都不消耗事件。最终这个事件就会交给Activity#onTouchEvent()处理。
2.2 ViewGroup事件分发
ViewGroup事件分发逻辑由于繁琐,这里对ViewGroup#dispatchTouchEvent()进行拆解,。这里从主到次分析,只是为了能够更好记忆并理解事件分发过程。
ViewGroup是否拦截事件判断
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
//将mFirstTouchTarget置空,清除mGroupFlags中FLAG_DISALLOW_INTERCEPT标记位
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//是否拦截此事件
final boolean intercepted;
//一个事件序列开始于down事件,判断条件左边成立
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
//disallowIntercept 代表子View请求父View不要拦截事件。
//FLAG_DISALLOW_INTERCEPT标记位通过View#requestDisallowInterceptTouchEvent()设置
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//询问ViewGroup是否拦截事件
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
mFirstTouchTarget的赋值意味着找到消费事件的View了。
mFirstTouchTarget类型为TouchTarget,TouchTarget 类描述如下
private static final class TouchTarget {
//消耗事件的View
public View child;
//触控点集合
public int pointerIdBits;
//记录下一个TouchTarget对象
public TouchTarget next;
}
pointerIdBits怎么理解呢?
比如一个根手指按下时pointerId1=1,二进制表示为0001。第二根手指pointerId2=2,二进制表示为0010。那么要将两根手指按下操作记录到同一个变量中。那这里就是使用的位运算pointerId1&pointerId2 即 0001&0010=0011。将位运算的结果存储到poinerIdBits中。这里只是举例说明pointerIdBits值的运算,并不是代表实际pointerIdBits值。实际pointerIdBits值运算需要==idBitsToAssign==参与。我们下文中会提到。
根据上面TouchTarget类结构分析,mFirstTouchTarget是个链表。==思考:为啥mFirstTouchTarget设计位链表呢?设计成View不就更加明确吗?==
小结:
-
子View#requestDisallowInterceptTouchEvent()可以影响上层ViewGroup是否拦截此事件,除了down事件。因为down事件来临时会将mFirstTouchTarget置空和清空requestDisallowInterceptTouchEvent设置的标记位即FLAG_DISALLOW_INTERCEPT。
-
每次down事件来临都会询问ViewGroup是否拦截此事件,不管之前有没有view消费事件。
接着往下分析
//检查是否是取消事件
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//是否支持多点触控
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
//标记是否有view消费事件
boolean alreadyDispatchedToNewTouchTarget = false;
resetCancelNextUpFlag()
private static boolean resetCancelNextUpFlag(@NonNull View view) {
//判断当前View是否包含PFLAG_CANCEL_NEXT_UP_EVENT标记
if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
//包含移除后返回
view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
return true;
}
return false;
}
PFLAG_CANCEL_NEXT_UP_EVENT根据View中对该变量的注释描述是view是否暂时分离。而暂时分离听起来很抽象,不过搜索下该变量。该变量在View#onStartTemporaryDetach()被添加到mPrivateFlags变量中。
这里以AbsListView举例,在AbsListView#RecycleBin#addScrapView()最终调用了View#onStartTemporaryDetach()。那根据addScrapView()方法作用不难猜出PFLAG_CANCEL_NEXT_UP_EVENT标记的意思。在ListView中被划出屏幕的itemView会被标记上PFLAG_CANCEL_NEXT_UP_EVENT 标记。在后续dispatchTouchEvent()事件时当控件持有PFLAG_CANCEL_NEXT_UP_EVENT标记时,则清除该标记并返回是取消事件。
接着往下分析
if (!canceled && !intercepted) {
...
//如果是down事件,或者第一个手指以外的手指进入屏幕
if (actionMasked == MotionEvent.ACTION_DOWN|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)) {
//获取actionIndex是为了获取手指id即pointerId
final int actionIndex = ev.getActionIndex();
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
//移除和idBitsToAssign相同的触控点,移除后,如TouchTarget不存在其他的触控点记录,则从链表中移除。
removePointersFromTouchTargets(idBitsToAssign);
...
}
什么是pointerId?
pointerId是触控点类型唯一标识。即当屏幕上只有一根手指操作时,pointerId=0。第一根手指未抬起,第二根手指按下时,pointerId=1。也就是说可以根据pointerId来识别是哪根手指在操作。
什么是actionIndex?
actionIndex 是多点触控中记录某个点的索引。比如4个手指A,B,C,D依次按下屏幕对应的索引如下
手指 | ActionIndex |
---|---|
A | 0 |
B | 1 |
C | 2 |
D | 3 |
actionIndex是怎么获取的?
MotionEvent中和getActionIndex有关系的代码:
//MotionEvent.java
public static final int ACTION_POINTER_INDEX_SHIFT = 8;
public static final int ACTION_POINTER_INDEX_MASK = 0xff00;
public static final int ACTION_MASK = 0xff;
//取getAction()高8位
public final int getActionIndex() {
return (nativeGetAction(mNativePtr) & ACTION_POINTER_INDEX_MASK)
>> ACTION_POINTER_INDEX_SHIFT;
}
//取getAction()低8位
public final int getActionMasked() {
return nativeGetAction(mNativePtr) & ACTION_MASK;
}
public final int getAction() {
return nativeGetAction(mNativePtr);
}
由以上代码可知getActionIndex()返回的是getAction()==高8位==字节。==低8位==字节是由getActionMasked()表示。一个int来存储两个信息的做法,在Android源码中比较常见,因为pointerIndex和action的范围都很少,单独给每一个分配一个空间,比较浪费。MeasureSpec就是将Mode和Size整合在一起的例子。到这里,我们就清楚了actionIndex的来历了。
idBitsToAssign取值计算:
如果支持多点触控idBitsToAssign=1 << ev.getPointerId(actionIndex)。 其实就是当一根手指时 1<<0,两根手指时 1<<1。即0001和0010。
removePointersFromTouchTargets()
private void removePointersFromTouchTargets(int pointerIdBits) {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//包含该触控点
if ((target.pointerIdBits & pointerIdBits) != 0) {
//移除该触控点
target.pointerIdBits &= ~pointerIdBits;
//移除后如果没有任何触控点记录
if (target.pointerIdBits == 0) {
if (predecessor == null) {
//清空mFirstTouchTarget
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
ViewGroup事件分发逻辑
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
//手否自定义顺序默认为false
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//逆序遍历
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
...
//是否可以接受事件和点击位置是否在view范围内
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
//先在链表中查找,找到了说明触控点作用在同一个View上。
//如果没找到则有两种情况:
//1.没有任何触控点时,进行操作
//2.存在一个触控点时,又新增一个触控点。(多点触摸作用在同一View)
if (newTouchTarget != null) {
//添加新的触控点操作,跳出循环
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//进行分发
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)){
//如果有子View消费事件,则添加到链表头部,并赋值给newTouchTarget和mFirstTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//标记已经分发给新的目标
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
canViewReceivePointerEvents()
判断依据是根据View是否VISIBLE&&是否播放动画
isTransformedTouchPointInView()
根据触控的位置判断是否在View范围内。其依据根据该层View来计算子View的点击范围。
dispatchTransformedTouchEvent()
- 当child!=null时对child进行事件分发,调用child#dispatchTouchEvent()
- 内部在对child分发前将parent的坐标转换为相对于child坐标系的坐标。然后进行分发,分发完后对其进行坐标恢复。具体代码下面给出。
- 当child=null时,调用该层View#onTouchEvent()。
- 返回值受child#dispatchTouchEvent()和该层View#onTouchEvent()影响,返回true表示消费事件,并将消费事件的View添加到链表头部,跳出循环。返回false表示不消费事件,继续下次循环,直到循环结束。
- ViewGroup遍历完子View后未找到需要消费事件的child时,child传递的是null。关于child传递null时方法内部处理,在第3点已经说明。
对child分发前将parent的坐标转换为相对于child坐标系的坐标。这就是我们在getX(),getY()时,获取的是相对于父空间的位置。而不是屏幕的位置的原因。
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
//分发前要将parent的坐标转换为相对于child坐标系的坐标
event.offsetLocation(offsetX, offsetY);
//分发
handled = child.dispatchTouchEvent(event);
//恢复
event.offsetLocation(-offsetX, -offsetY);
根据以上代码分析我们可以回答下思考问题
为啥mFirstTouchTarget设计位链表呢?设计成单个对象不就更加明确吗?
假如mFirstTouchTarget设置成单个对象,当第一根手指按下A时,mFirstTouchTarget被赋值为A。这时第二根手指按下B, 此时mFirstTouchTarget被赋值为B。那后续的move操作只能作用到B上,而A无响应。这对于游戏开发来说肯定不行,一个手控制人物方向,一个手控制人物行为。
所以不同情况对mFirstTouchTarget链表的长度有所影响:
- 非多点触控:mFirstTouchTarget链表退化成单个TouchTarget对象。
- 多点触控,目标相同:同样为单个TouchTarget对象,只是pointerIdBits保存了多个pointerId信息。
- 多点触控,目标不同:mFirstTouchTarget成为链表。
循环结束后执行以下代码
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN){
for(){
dispatchTransformedTouchEvent()
}
//将触控类型符加给上一个消费事件的view。
//我们在下面问题回答中有解释。这里简单理解为为了解决多指下作用在不同View上的特定问题。
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
if (mFirstTouchTarget == null) {
//在介绍dispatchTransformedTouchEvent()时已经说明
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//遍历分发事件
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//已经分发过down事件了,所以这里直接标记已处理
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//是否分发cancel事件
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//分发move|up|cancel事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
上面ViewGroup事件分发源码分析可以总结出以下几点:
-
子View#requestDisallowInterceptTouchEvent()可以影响上层ViewGroup是否拦截此事件,除了down事件。因为down事件来临时会将mFirstTouchTarget置空和清空requestDisallowInterceptTouchEvent设置的标记位即FLAG_DISALLOW_INTERCEPT。
-
每次down事件来临都会询问ViewGroup是否拦截此事件,不管之前有没有view消费事件。
-
ViewGroup#dispatchTouchEvent()返回值受下层View#dispatchTouchEvent()返回值影响,如果该层是ViewGroup,那么返回值受该层的下层dispatchTouchEvent()影响。依次类推,层层传递,直到找到该事件链上最后一个View。
-
如果ViewGroup拦截事件或者没有找到需要分发事件的View,则同一事件序列中的事件都交由该ViewGroup#onTouchEvent处理
-
分发给子View时要将相对于父View的坐标转换为相对于子View坐标系的坐标,所以我们拿到的MotionEvent的getX()代表这个相对child的坐标,不然拿到的是相对于屏幕的坐标。
-
如果整个事件链上的View都不消费事件,事件会向上抛,直到Activity#onTouchEvent被调起。在 2.1 Activity事件分发 已经说明。
2.3 View事件分发
前面在分析ViewGroup事件分发说当在进行down事件分发时会遍历ViewGroup的子View,并且调用ViewGroup#dispatchTransformedTouchEvent(),其内部会调用子View的dispatchTouchEvent()。那这里就从View#dispatchTouchEvent()开始分析
View#dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//设置了setOnTouchListener并且onTouch返回true
result = true;
}
if (!result && onTouchEvent(event)) {
//否则result值受onTouchEvent()返回值的影响
result = true;
}
}
return result;
}
从View#dispatchTouchEvent()看出==View#setOnTouchListener优先级高于onTouchEvent()==。由于setOnTouchListener是外部设置的,所以这里直接分析View#onTouchEvent()
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//view是否可点击
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//view设置了setEnable(false)
if ((viewFlags & ENABLED_MASK) == DISABLED) {
...
//一个disabled状态的View仍会消费事件,就是没有效果
return clickable;
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
performClickInternal();
...
break;
}
//省略down,move,cancel事件
....
return true;
}
return false;
}
private boolean performClickInternal() {
...
return performClick();
}
public boolean performClick() {
...
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
可见在up事件中View如果有设置OnClickListener,会调用OnClickListener#onClick()
根据前面分析View#dispatchTouchEvent()如果外部设置View#setOnTouchListener并且在onTouch()返回true。那么onClick()不会触发。
2.4 事件分发流程图
2.5 问题回答
-
按下A,再按下A(多点触控),为什么释放后A的点击事件只会触发一次?
因为点击事件只是在actionUp中触发,而不是actionPointerUp中触发。 第一次释放:A收到actionPointerUp事件,第二次触发A才收到actionUp。
-
按下A,按下ViewGroup(空白区域),为什么先释放A,却无法触发A的点击事件,继续释放ViewGroup,又会触发A的点击事件?
按下A 收到actionDown事件,A消耗事件,mFirstTouchTarget头部是A 。因为没有从A上抬起接着按下ViewGroup空白区域,收到actionPointDown事件。由于按下ViewGroup空白区域,导致ViewGroup子View都不消耗事件,事件交给ViewGroup消耗。此时ViewGroup在遍历子View循环结束后执行以下代码
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
newTouchTarget=null,mFirstTouchTarget!=null。actionPointDown事件id追加给A。当A先释放时,收到的是actionPointerUp事件。 继续释放ViewGroup,收到actionUp事件。
-
按下按下ViewGroup(空白区域),为什么点击A,B无响应?
按下ViewGroup收到actionDown,此时点击A,B收到的是actionPointerDown和actionPointerUp。而actionPointerUp是不会响应点击事件的。
2.6 补充说明
2.6.1 int 的高低位获取和组合:
- 获取高字节,低字节。
//分别取出int的高字节跟低字节
int value = 515;
int big = (value & 0xFF00) >> 8;
int little = value & 0xFF;
- 根据高字节,低字节。组合为一个int
int value;
int a; int b;
value= (a<< 8) | (b & 0xFF);
2.6.2 位运算
int a = 0x001
int b = 0x010
int c = 0x100
如果要进行&、|、|=、&= ~ 等操作。确保在每一位上都是独占式。
- a,b,c组合成d。
int d = a | b | c
- d中是否包含a
if(( d & a ) != 0){
println("d中包含a")
}
- 从d中移除a
d&= ~a
- 将e添加到d,并重新赋值给d
d!=e