位运算在Android中的应用

摘要

本文主要讲解了位运算在Android中的简单使用,配合Android源码的分析,达到深入理解并熟练运用。

什么是位运算?

  • 与: 2(0010) & 1(0001) => 0(0000)
  • 或: 2(0010) | 1(0001) => 3(0011)
  • 非: ~2(0010) => 13(1101)
  • 异或: 2(0010) ^ 1(0001) => 3(0011)

0 和另一个数相与得到01 与另一个数相与则另一个数维持不变,这点很重要!说明0可以清空一个数,1可以保留一个数。异或是去同存异,只有两个bit位不一样结果才会为1。

负数的表示,通常都是用补码表示的,比如-2,原码为0010,反码为1101,反码+1得到补码为1110。当然这样算太麻烦了,我大学老师教过一个简单的方法,原码0010从右往左看,以第一次出现的1为界,可以分成两份(左边00和右边0),左边全部取反,右边保持不变,即1110,嗯哼…是不是跟之前的补码一样。。。。

在MD5中的应用

public static String md5(String content) {
	try {
		MessageDigest digest = MessageDigest.getInstance("MD5");
		digest.reset();
		digest.update(content.getBytes(Charset.forName("UTF-8")));
		byte[] hash = digest.digest();
		StringBuilder hexString = new StringBuilder();
		for (byte b : hash) {
			int number = b & 0xff;
			if (number < 0x10)
			     hexString.append('0');
			hexString.append(Integer.toHexString(number));
		}
		return hexString.toString();
	}
	catch (NoSuchAlgorithmException e) {
		return "000000000000000000000000000000000000";
	}
}

这是一段典型的MD5加密代码。这里有两个地方需要注意,一个是

if (number < 0x10)
     hexString.append('0');

这里要手动判断加0,否则最后得出的结果可能不足32位,如果用来加密密码什么的,可能会导致和其他平台计算md5得出的结果不一致。

另一个要注意的的是

int number = b & 0xff;

有人可能会问了,& 0xff干嘛用的?o(*≧▽≦)ツ这里要长篇大论了。。。

byte8位,int32位,如果要转换的byte是个负数,比如-2,二进制补码表示为1111 1110
直接转换为int后,高24位进行补1(窄的整型转换成较宽的整型时符号扩展规则:如果最初的数值类型是有符号的,那么就执行符号扩展(即如果符号位为1,则扩展为1,如果为零,则扩展为0)),得到**11111111 11111111 11111111** 1111 1110。此时二进制转化为十进制后的值是正确的(还是-2),但是二进制数据已经错了,因为它比原始的二进制数据多了241

执行& 0xff操作以后,高24位全部会变成0,即**00000000 00000000 00000000** 1111 1110,起到了只保留低8位的作用。这时候二进制数据是跟原来一样的,转化成十进制是错的(不是-2了)。

当然十六进制也是一样的,-2(0xfe) 直接转 int 得到 0xfffffffe,-2 & 0xff => 0x000000fe。

所以个人理解,执行& 0xff操作是为了在非十进制计算的场景下保持byte数据转化前后的二进制值是一致的(当然这样也保持了十六进制数据的一致性)。

上面代码中的这个&操作正是出于保持byte值转换前后数据一致的原因。

在Android源码中的应用

举个栗子,我要在对象里面保存一个flag变量,这个变量的表示该对象是否支持某一种或多种属性,来看下如何定义这些属性,如何给flag变量添加某种属性,如何判断flag变量是否支持某种属性。

  1. 首先,我们可以使用16进制的方式来定义几个属性。比如可点击和长按属性。
public static final int CLICKABLE = 0x01;
public static final int LONG_CLICKABLE = 0x02;

private int mFlags;

为什么是0x01和0x02?先往下看。

  1. 设置flag
public void setFlag(int flag){
    mFlags |= flag;
}

假设现在mFlags为0,调用setFlag(CLICKABLE)

mFlags | CLICKABLE => 0000 | 0001 => 0001

现在mFlags的值是0001,继续调用setFlag(LONG_CLICKABLE)

mFlags | LONG_CLICKABLE => 0001 | 0010 => 0011

现在mFlags的值是0011。

  1. 获取flag
/**
*
* @param flag {@link #CLICKABLE#LONG_CLICKABLE}
*/
public boolean isSupport(int flag){
    return mFlags & flag;
}

调用isSupport(CLICKABLE)判断是否支持点击:

mFlags & CLICKABLE => 0x11 & 0x01 => 0x01,即1 => 返回true

通过或操作来设置属性,通过与操作来获取属性,这两个操作的关键点是只操作对应的标志位,比如点击只操作第一位,长按只操作第二位。这里我们知道了&操作可以取出标志位,|操作可以添加标志位。

下面来看下Android的源码是怎么用的,我们经常设置ViewVisibility属性,它其实就是按上述方式设置的,只不过复杂点。这个属性保存在ViewmViewFlags变量中。

/**
* The view flags hold various views states.
* {@hide}
*/
@ViewDebug.ExportedProperty(formatToHexString = true)
int mViewFlags;

这个变量保存了很多View的状态属性。

/** @hide */
@IntDef({VISIBLE, INVISIBLE, GONE})
@Retention(RetentionPolicy.SOURCE)
public @interface Visibility {}

/**
* This view is visible.
* Use with {@link #setVisibility} and <a href="#attr_android:visibility">{@code
* android:visibility}.
*/
public static final int VISIBLE = 0x00000000;

/**
* This view is invisible, but it still takes up space for layout purposes.
* Use with {@link #setVisibility} and <a href="#attr_android:visibility">{@code
* android:visibility}.
*/
public static final int INVISIBLE = 0x00000004;

/**
* This view is invisible, and it doesn't take any space for layout
* purposes. Use with {@link #setVisibility} and <a href="#attr_android:visibility">
* {@code android:visibility}.
*/
public static final int GONE = 0x00000008;

/**
* Mask for use with setFlags indicating bits used for visibility.
* {@hide}
*/
static final int VISIBILITY_MASK = 0x0000000C;
private static final int[] VISIBILITY_FLAGS = {VISIBLE, INVISIBLE, GONE};

首先定义了一个Visibility注解,这个注解加在变量上可以约束变量的值为@IntDef中声明的其中一个,如果传入该变量的值在@IntDef中没有声明,则会有红色警告,但是这是源码警告(@Retention(RetentionPolicy.SOURCE)),不会影响编译。

接下来看下属性常量,可以发现都是16进制,而且貌似结尾还有一定的规律,0,4,8,这样看看不出什么,转成二进制,我们只观察低8位:

VISIBLE(0): 0000 0000
INVISIBLE(4): 0000 0100
GONE(8): 0000 1000

还是跟刚才的小例子一样,每个属性单独占一位!
还剩下一个VISIBILITY_MASK,这个常量叫做位掩码,功能是屏蔽掉不相关的位。

VISIBILITY_MASK©: 0000 1100

这个掩码的意思是只保留第三位和第四位,通过掩码我们可以确定,二进制第三位和第四位是用来表示Visibility属性的。

接着来看如何给mViewFlags赋值。

/**
* Set the visibility state of this view.
*
* @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
* @attr ref android.R.styleable#View_visibility
*/
@RemotableViewMethod
public void setVisibility(@Visibility int visibility) {
	setFlags(visibility, VISIBILITY_MASK);
}

这个setFlags(int flags, int mask)方法很长,我们假设当前的mViewFlags1101 0100,传入的参数为VISIBLEVISIBILITY_MASK,挑重点一点一点来分析。

int old = mViewFlags;
mViewFlags = (mViewFlags & ~mask) | (flags & mask);

首先把当前的mViewFlags保存一份,然后再赋值。分解这个赋值的过程如下:

~mask => ~VISIBILITY_MASK => ~0000 1100 => 1111 0011

位掩码取反,发现第三四位为0,可以预测它是想要清除第三、四位,其他位为1是要保留。

mViewFlags & ~mask => 1101 0100 & 1111 0011 => 1101 0000

第三、四位清空了,其他标志位没变,此时我们可以得出结论:&~操作可以清除标志位。

flags & mask => VISIBLE & VISIBILITY_MASK => 0000 0000 & 0000 1100 => 0000 0000

位掩码清掉了flags的其他标志位(虽然其他标志位本来就是0),只保留第三、四位(虽然第三、四位本来就是0 (≧▽≦)/)。

mViewFlags = (mViewFlags & ~mask) | (flags & mask) => 1101 0000 | 0000 0000 => 1101 0000

原来的mViewFlags的值是1101 0100,传进来的flags0000 0000,最后得出的值是1101 0000,说明属性设置好了。

继续往下看:

int changed = mViewFlags ^ old;
if (changed == 0) {
    return;
}

这里有一个异或操作,检查变量有没有发生变化,没变化就返回。

final int newVisibility = flags & VISIBILITY_MASK;
if (newVisibility == VISIBLE) {
    if ((changed & VISIBILITY_MASK) != 0) {
        /*
         * If this view is becoming visible, invalidate it in case it changed while
         * it was not visible. Marking it drawn ensures that the invalidation will
         * go through.
         */
        mPrivateFlags |= PFLAG_DRAWN;
        invalidate(true);

        needGlobalAttributesUpdate(true);

        // a view becoming visible is worth notifying the parent
        // about in case nothing has focus.  even if this specific view
        // isn't focusable, it may contain something that is, so let
        // the root view try to give this focus if nothing else does.
        if ((mParent != null) && (mBottom > mTop) && (mRight > mLeft)) {
            mParent.focusableViewAvailable(this);
        }
    }
}

首先flags & VISIBILITY_MASK只保留Visibility的标志位,然后判断标志位是否是VISIBLE,如果是,再判断改变的二进制位是否是Visibility的标识位,如果是,然后就执行刷新操作等等。

/* Check if the GONE bit has changed */
if ((changed & GONE) != 0) {
    needGlobalAttributesUpdate(false);
    requestLayout();

    if (((mViewFlags & VISIBILITY_MASK) == GONE)) {
        if (hasFocus()) clearFocus();
        clearAccessibilityFocus();
        destroyDrawingCache();
        if (mParent instanceof View) {
            // GONE views noop invalidation, so invalidate the parent
            ((View) mParent).invalidate(true);
        }
        // Mark the view drawn to ensure that it gets invalidated properly the next
        // time it is visible and gets invalidated
        mPrivateFlags |= PFLAG_DRAWN;
    }
    if (mAttachInfo != null) {
        mAttachInfo.mViewVisibilityChanged = true;
    }
}

判断代表GONE(0x00000008: 0000 1000)的第四位标志位是不是改变了,然后执行刷新操作,接着判断GONE属性是设置了还是清除了,并执行相应的操作。

看到这里,相信其余代码的位运算你都能理解了,有兴趣的可以自己看下面贴出的setFlags()方法的完整代码,只要一步一步分解,其实Android源码没你想的那么难( ̄▽ ̄)",那本文就到此结束啦O(∩_∩)O

/**
 * Set flags controlling behavior of this view.
 *
 * @param flags Constant indicating the value which should be set
 * @param mask Constant indicating the bit range that should be changed
 */
void setFlags(int flags, int mask) {
    final boolean accessibilityEnabled =
            AccessibilityManager.getInstance(mContext).isEnabled();
    final boolean oldIncludeForAccessibility = accessibilityEnabled && includeForAccessibility();

    int old = mViewFlags;
    mViewFlags = (mViewFlags & ~mask) | (flags & mask);

    int changed = mViewFlags ^ old;
    if (changed == 0) {
        return;
    }
    int privateFlags = mPrivateFlags;

    /* Check if the FOCUSABLE bit has changed */
    if (((changed & FOCUSABLE_MASK) != 0) &&
            ((privateFlags & PFLAG_HAS_BOUNDS) !=0)) {
        if (((old & FOCUSABLE_MASK) == FOCUSABLE)
                && ((privateFlags & PFLAG_FOCUSED) != 0)) {
            /* Give up focus if we are no longer focusable */
            clearFocus();
        } else if (((old & FOCUSABLE_MASK) == NOT_FOCUSABLE)
                && ((privateFlags & PFLAG_FOCUSED) == 0)) {
            /*
             * Tell the view system that we are now available to take focus
             * if no one else already has it.
             */
            if (mParent != null) mParent.focusableViewAvailable(this);
        }
    }

    final int newVisibility = flags & VISIBILITY_MASK;
    if (newVisibility == VISIBLE) {
        if ((changed & VISIBILITY_MASK) != 0) {
            /*
             * If this view is becoming visible, invalidate it in case it changed while
             * it was not visible. Marking it drawn ensures that the invalidation will
             * go through.
             */
            mPrivateFlags |= PFLAG_DRAWN;
            invalidate(true);

            needGlobalAttributesUpdate(true);

            // a view becoming visible is worth notifying the parent
            // about in case nothing has focus.  even if this specific view
            // isn't focusable, it may contain something that is, so let
            // the root view try to give this focus if nothing else does.
            if ((mParent != null) && (mBottom > mTop) && (mRight > mLeft)) {
                mParent.focusableViewAvailable(this);
            }
        }
    }

    /* Check if the GONE bit has changed */
    if ((changed & GONE) != 0) {
        needGlobalAttributesUpdate(false);
        requestLayout();

        if (((mViewFlags & VISIBILITY_MASK) == GONE)) {
            if (hasFocus()) clearFocus();
            clearAccessibilityFocus();
            destroyDrawingCache();
            if (mParent instanceof View) {
                // GONE views noop invalidation, so invalidate the parent
                ((View) mParent).invalidate(true);
            }
            // Mark the view drawn to ensure that it gets invalidated properly the next
            // time it is visible and gets invalidated
            mPrivateFlags |= PFLAG_DRAWN;
        }
        if (mAttachInfo != null) {
            mAttachInfo.mViewVisibilityChanged = true;
        }
    }

    /* Check if the VISIBLE bit has changed */
    if ((changed & INVISIBLE) != 0) {
        needGlobalAttributesUpdate(false);
        /*
         * If this view is becoming invisible, set the DRAWN flag so that
         * the next invalidate() will not be skipped.
         */
        mPrivateFlags |= PFLAG_DRAWN;

        if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE)) {
            // root view becoming invisible shouldn't clear focus and accessibility focus
            if (getRootView() != this) {
                if (hasFocus()) clearFocus();
                clearAccessibilityFocus();
            }
        }
        if (mAttachInfo != null) {
            mAttachInfo.mViewVisibilityChanged = true;
        }
    }

    if ((changed & VISIBILITY_MASK) != 0) {
        // If the view is invisible, cleanup its display list to free up resources
        if (newVisibility != VISIBLE && mAttachInfo != null) {
            cleanupDraw();
        }

        if (mParent instanceof ViewGroup) {
            ((ViewGroup) mParent).onChildVisibilityChanged(this,
                    (changed & VISIBILITY_MASK), newVisibility);
            ((View) mParent).invalidate(true);
        } else if (mParent != null) {
            mParent.invalidateChild(this, null);
        }

        if (mAttachInfo != null) {
            dispatchVisibilityChanged(this, newVisibility);

            // Aggregated visibility changes are dispatched to attached views
            // in visible windows where the parent is currently shown/drawn
            // or the parent is not a ViewGroup (and therefore assumed to be a ViewRoot),
            // discounting clipping or overlapping. This makes it a good place
            // to change animation states.
            if (mParent != null && getWindowVisibility() == VISIBLE &&
                    ((!(mParent instanceof ViewGroup)) || ((ViewGroup) mParent).isShown())) {
                dispatchVisibilityAggregated(newVisibility == VISIBLE);
            }
            notifySubtreeAccessibilityStateChangedIfNeeded();
        }
    }

    if ((changed & WILL_NOT_CACHE_DRAWING) != 0) {
        destroyDrawingCache();
    }

    if ((changed & DRAWING_CACHE_ENABLED) != 0) {
        destroyDrawingCache();
        mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        invalidateParentCaches();
    }

    if ((changed & DRAWING_CACHE_QUALITY_MASK) != 0) {
        destroyDrawingCache();
        mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
    }

    if ((changed & DRAW_MASK) != 0) {
        if ((mViewFlags & WILL_NOT_DRAW) != 0) {
            if (mBackground != null
                    || (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
            } else {
                mPrivateFlags |= PFLAG_SKIP_DRAW;
            }
        } else {
            mPrivateFlags &= ~PFLAG_SKIP_DRAW;
        }
        requestLayout();
        invalidate(true);
    }

    if ((changed & KEEP_SCREEN_ON) != 0) {
        if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
            mParent.recomputeViewAttributes(this);
        }
    }

    if (accessibilityEnabled) {
        if ((changed & FOCUSABLE_MASK) != 0 || (changed & VISIBILITY_MASK) != 0
                || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0
                || (changed & CONTEXT_CLICKABLE) != 0) {
            if (oldIncludeForAccessibility != includeForAccessibility()) {
                notifySubtreeAccessibilityStateChangedIfNeeded();
            } else {
                notifyViewAccessibilityStateChangedIfNeeded(
                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
            }
        } else if ((changed & ENABLED_MASK) != 0) {
            notifyViewAccessibilityStateChangedIfNeeded(
                    AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
        }
    }
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值