Android那些“没用“知识(一)

    技术2022-07-13  74

    目录

    1. addOnPreDrawListener2. postFrameCallback3. setOnHierarchyChangeListener4. setOnApplyWindowInsetsListener5. SynchronizedPool6. setWillNotDraw

    不定期更新一些看源码时候常用的但平时基本用不上的东西

    1. addOnPreDrawListener

    addOnPreDrawListener方法是在View绘制前回调的方法,在CoordinatorLayout的behavior的onDependentViewChanged的调用场景之一会在这里

    boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible; if (!cancelDraw) { ... performDraw(); } else { if (isViewVisible) { scheduleTraversals(); } ... } public final boolean dispatchOnPreDraw() { boolean cancelDraw = false; final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners; if (listeners != null && listeners.size() > 0) { CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start(); try { int count = access.size(); for (int i = 0; i < count; i++) { cancelDraw |= !(access.get(i).onPreDraw()); } } finally { listeners.end(); } } return cancelDraw; }

    使用方式是

    View view; view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { return false; } });

    注意因为是在performDraw前触发的,前面的performMeasure和performLayout已经完成,这里也是可以获取视图尺寸大小的

    2. postFrameCallback

    使用方式

    Choreographer choreographer = Choreographer.getInstance(); choreographer.postFrameCallback(new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { } });

    这个是渲染帧的回调,下一次渲染帧到达时会回调这个方法,只会触发一次。 这里所谓的渲染帧指的是屏幕刷新的时间点,因为有同步机制,屏幕会在每16ms左右进行一次刷新,涉及到CPU和GPU绘制,以及SurfeFlinger的双缓冲技术,有兴趣可以查一下。这里的话就是一下次渲染到达时的回调处理。

    3. setOnHierarchyChangeListener

    ViewGroup的子View添加和删除的监听方法,比如会在addView和removeView方法中回调这个监听

    ViewGroup view; view.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() { @Override public void onChildViewAdded(View parent, View child) { } @Override public void onChildViewRemoved(View parent, View child) { } })

    4. setOnApplyWindowInsetsListener

    ImageView imageView; imageView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { @Override public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { return insets.consumeSystemWindowInsets(); } });

    setOnApplyWindowInsetsListener 方法回传的是window的内容区域限制,只有当设置了fitsSystemWindows才会生效。

    定义了fitSystemWindows后,会从ViewRootImp出发,调用dispatchApplyInsets方法依次遍历DecorView子View,给设置过这个属性的子View设置padding,比如这里测试机返回的系统限制getSystemWindowInsets()的四个区域是(0,50,0,0),而这里的50也正是状态栏高度的大小,如果有导航栏则bottom的数值也会有变动。

    一般多个View都设置的话会以第一个为准生效,这里的insets.consumeSystemWindowInsets()表示消耗掉这个WindowInsets,这个也是默认方法,可以选择不消耗,那么这个WindowInsets就会交付给下一个查找到的有fitSystemWindows的属性的View处理

    注意这个返回的getSystemWindowInsets的各个属性都是final类型的,不能修改

    可以使用ViewCompat.requestApplyInsets(View)触发一次检测,这个其实最终就会调用到ViewRootImp的scheduleTraversals方法,重新触发测量布局绘制三大流程,只是这里把mApplyInsetsRequested设置成了true,可以触发dispatchApplyInsets方法

    5. SynchronizedPool

    享元对象池,对于需要频繁创建对象的可以用一下,acquire去获取池中的对象,release去存一个新的对象。

    Pools.Pool<TestJava> sPool = new Pools.SynchronizedPool<>(3); String TAG = "1234"; TestJava t1 = new TestJava("1"); TestJava t2 = new TestJava("2"); TestJava t3 = new TestJava("3"); sPool.release(t1); sPool.release(t2); sPool.release(t3); TestJava tr1 = sPool.acquire(); Log.e(TAG, "tr1 ->> " + (tr1 != null ? tr1.id : -1)); TestJava tr2 = sPool.acquire(); Log.e(TAG, "tr2 ->> " + (tr2 != null ? tr2.id : -1)); TestJava tr3 = sPool.acquire(); Log.e(TAG, "tr3 ->> " + (tr3 != null ? tr3.id : -1)); TestJava tr4 = sPool.acquire(); Log.e(TAG, "tr4 ->> " + (tr4 != null ? tr4.id : -1));

    这里的acquire方法会把对象从池中移除,当用完需要把这个对象release再放进去 打印结果是

    E/1234: tr1 ->> 3 E/1234: tr2 ->> 2 E/1234: tr3 ->> 1 E/1234: tr4 ->> -1

    SimplePool和这个是一样的,只是SynchronizedPool的方法使用了同步加锁

    6. setWillNotDraw

    当我们定义ViewGroup的子类时,默认是不会走onDraw绘制方法的,因为在ViewGroup中有这个标记

    private void initViewGroup() { if (!debugDraw()) { setFlags(WILL_NOT_DRAW, DRAW_MASK); } ...... }

    而且在View中

    void setFlags(int flags, int mask) { ...... if ((changed & DRAW_MASK) != 0) { if ((mViewFlags & WILL_NOT_DRAW) != 0) { if (mBackground != null || mDefaultFocusHighlight != null || (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) { mPrivateFlags &= ~PFLAG_SKIP_DRAW; } else { mPrivateFlags |= PFLAG_SKIP_DRAW; } } else { mPrivateFlags &= ~PFLAG_SKIP_DRAW; } } //这个代码块有好几处调用 if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw(canvas); ...... } else { draw(canvas); } public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); }

    在draw方法中会触发onDraw,也会触发dispatchDraw方法;而dispatchDraw是完全给子类重写的,这应该是性能上的考虑,避免不必要的绘制

    这么一来,因为ViewGroup默认设置了WILL_NOT_DRAW标记,导致大部分情况下是不会走onDraw方法的

    而setWillNotDraw(false)可以清除这个标记,回调onDraw方法

    Processed: 0.017, SQL: 10