Window.Callback是window类的一个内部接口。该接口包含了一系列类似于dispatchXXX和onXXX的接口。当window接收到外界状态改变的通知时,就会回调其中的相应方法。比如,当用户点击某个控件时,就会回调Window.Callback中的dispatchTouchEvent方法。

  • window.Callback的定义如下:
 public interface Callback {
        boolean dispatchKeyEvent(KeyEvent var1);

        boolean dispatchKeyShortcutEvent(KeyEvent var1);

        boolean dispatchTouchEvent(MotionEvent var1);

        boolean dispatchTrackballEvent(MotionEvent var1);

        boolean dispatchGenericMotionEvent(MotionEvent var1);

        boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent var1);

        @Nullable
        View onCreatePanelView(int var1);

        boolean onCreatePanelMenu(int var1, @NonNull Menu var2);

        boolean onPreparePanel(int var1, @Nullable View var2, @NonNull Menu var3);

        boolean onMenuOpened(int var1, @NonNull Menu var2);

        boolean onMenuItemSelected(int var1, @NonNull MenuItem var2);

        void onWindowAttributesChanged(LayoutParams var1);

        void onContentChanged();

        void onWindowFocusChanged(boolean var1);

        void onAttachedToWindow();

        void onDetachedFromWindow();

        void onPanelClosed(int var1, @NonNull Menu var2);

        boolean onSearchRequested();

        boolean onSearchRequested(SearchEvent var1);

        @Nullable
        ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback var1);

        @Nullable
        ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback var1, int var2);

        void onActionModeStarted(ActionMode var1);

        void onActionModeFinished(ActionMode var1);

        default void onProvideKeyboardShortcuts(List<KeyboardShortcutGroup> data, @Nullable Menu menu, int deviceId) {
            throw new RuntimeException("Stub!");
        }

        default void onPointerCaptureChanged(boolean hasCapture) {
            throw new RuntimeException("Stub!");
        }
    }
  • 这个方法可以用来实现全埋点,通过activity.getWindow方法拿到这个activity对应的window对象,再通过window.getCallback方法就可以拿到当前对应的Callback对象,然后我们在代理这个callback对象就可以找到被点击的view对象,并插入埋点代码。

  • 案例如下:

package com.mvp.myapplication

import android.annotation.TargetApi
import android.app.Activity
import android.graphics.Rect
import android.os.Bundle
import android.util.Log
import android.view.*
import android.view.Window.Callback
import android.view.accessibility.AccessibilityEvent
import android.widget.AdapterView
import android.widget.TextView
import androidx.annotation.Nullable
import androidx.appcompat.app.AppCompatActivity


class MainActivity : AppCompatActivity() {
    private lateinit var tv:TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        this.window.callback = WrapperWindowCallback(this, window.callback)

        tv = findViewById(R.id.tv)

        tv.setOnClickListener {
            Log.e("MainActivity","原始数据")
        }

    }
}

class WrapperWindowCallback : Window.Callback {
    private var callback: Window.Callback? = null
    private var activity: Activity? = null

    constructor() {}

    public constructor(activity: Activity, callback: Callback) : this() {
        this.callback = callback
        this.activity = activity
    }

    override fun dispatchKeyEvent(p0: KeyEvent?): Boolean {
        return this.callback!!.dispatchKeyEvent(p0)
    }

    override fun dispatchKeyShortcutEvent(p0: KeyEvent?): Boolean {
        return this.callback!!.dispatchKeyShortcutEvent(p0)
    }

    override fun dispatchTouchEvent(p0: MotionEvent?): Boolean {
        dispatchTouchEvent(activity!!, p0!!)
        return this.callback!!.dispatchTouchEvent(p0)
    }

    override fun dispatchTrackballEvent(p0: MotionEvent?): Boolean {
        return this.callback!!.dispatchTrackballEvent(p0)
    }

    override fun dispatchGenericMotionEvent(p0: MotionEvent?): Boolean {
        return this.callback!!.dispatchGenericMotionEvent(p0)
    }

    override fun dispatchPopulateAccessibilityEvent(p0: AccessibilityEvent?): Boolean {
        return this.callback!!.dispatchPopulateAccessibilityEvent(p0)
    }

    override fun onCreatePanelView(p0: Int): View? {
        return this.callback!!.onCreatePanelView(p0)
    }

    override fun onCreatePanelMenu(p0: Int, p1: Menu): Boolean {
        return this.callback!!.onCreatePanelMenu(p0, p1)
    }

    override fun onPreparePanel(p0: Int, p1: View?, p2: Menu): Boolean {
        return this.callback!!.onPreparePanel(p0, p1, p2)
    }

    override fun onMenuOpened(p0: Int, p1: Menu): Boolean {
        return this.callback!!.onMenuOpened(p0, p1)
    }

    override fun onMenuItemSelected(p0: Int, p1: MenuItem): Boolean {
        return this.callback!!.onMenuItemSelected(p0, p1)
    }

    override fun onWindowAttributesChanged(p0: WindowManager.LayoutParams?) {
        this.callback!!.onWindowAttributesChanged(p0)
    }

    override fun onContentChanged() {
        this.callback!!.onContentChanged()
    }

    override fun onWindowFocusChanged(p0: Boolean) {
        this.callback!!.onWindowFocusChanged(p0)
    }

    override fun onAttachedToWindow() {
        this.callback!!.onAttachedToWindow()
    }

    override fun onDetachedFromWindow() {
        this.callback!!.onDetachedFromWindow()
    }

    override fun onPanelClosed(p0: Int, p1: Menu) {
        this.callback!!.onPanelClosed(p0, p1)
    }

    override fun onSearchRequested(): Boolean {
        return this.callback!!.onSearchRequested()
    }

    @Nullable
    @TargetApi(23)
    override fun onSearchRequested(p0: SearchEvent?): Boolean {
        return this.callback!!.onSearchRequested(p0)
    }

    override fun onWindowStartingActionMode(p0: ActionMode.Callback?): ActionMode? {
        return this.callback!!.onWindowStartingActionMode(p0)
    }

    @Nullable
    @TargetApi(23)
    override fun onWindowStartingActionMode(p0: ActionMode.Callback?, p1: Int): ActionMode? {
        return this.callback!!.onWindowStartingActionMode(p0, p1)
    }

    override fun onActionModeStarted(p0: ActionMode?) {
        this.callback!!.onActionModeStarted(p0)
    }

    override fun onActionModeFinished(p0: ActionMode?) {
        this.callback!!.onActionModeFinished(p0)
    }

    private fun dispatchTouchEvent(activity: Activity, event: MotionEvent) {
        if (event.action == MotionEvent.ACTION_UP) {
            val rootView = activity.window.decorView as ViewGroup
            val targetVies = getTargetViews(rootView, event) ?: return
            for (view in targetVies) {
                if (view == null) {
                    continue
                }
                Log.e("MainActivity", "埋点数据")
            }
        }
    }

    private fun getTargetViewsInGroup(
        parent: ViewGroup,
        event: MotionEvent,
        hitViews: ArrayList<View>
    ) {
        try {
            val childCount = parent.childCount
            for (i in 0 until childCount) {
                val child = parent.getChildAt(i)
                val hitChildren: ArrayList<View> = getTargetViews(child, event)
                if (hitChildren.isNotEmpty()) {
                    hitViews.addAll(hitChildren)
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    private fun getTargetViews(parent: View, event: MotionEvent): ArrayList<View> {
        val targetViews: ArrayList<View> = ArrayList()
        try {
            if (isVisible(parent) && isContainView(parent, event)) {
                if (parent is AdapterView<*>) {
                    targetViews.add(parent)
                    getTargetViewsInGroup((parent as ViewGroup), event, targetViews)
                } else if (parent.isClickable) {
                    targetViews.add(parent)
                } else if (parent is ViewGroup) {
                    getTargetViewsInGroup(parent, event, targetViews)
                }
            }
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
        return targetViews
    }

    private fun isVisible(view: View): Boolean {
        return view.visibility == View.VISIBLE
    }

    private fun isContainView(view: View, event: MotionEvent): Boolean {
        val x = event.rawX.toDouble()
        val y = event.rawY.toDouble()
        val outRect = Rect()
        view.getGlobalVisibleRect(outRect)
        return outRect.contains(x.toInt(), y.toInt())
    }

}
  • 缺点
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐