Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to monitor click events globally in android

2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)05/31 Report--

Today, the editor will share with you the relevant knowledge points about how to monitor click events globally in android. The content is detailed and the logic is clear. I believe most people still know too much about this knowledge, so share this article for your reference. I hope you can get something after reading this article. Let's take a look at it.

First, adapt the listening interface, reserve the global processing interface and use it as the base class for all listeners.

Abstract the listening object of the common base class, and reserve the interception mechanism and universal click processing. The brief code is as follows:

Public abstract class CustClickListener implements View.OnClickListener {@ Override public void onClick (View view) {if (! interceptViewClick (view)) {onViewClick (view);}} protected boolean interceptViewClick (View view) {/ / TODO: here you can do a general process such as dotting, blocking, etc. Return false;} protected abstract void onViewClick (View view);}

One way to use anonymous objects as public listeners

CustClickListener mClickListener = new CustClickListener () {@ Overrideprotected void onViewClick (View view) {Toast.makeText (CustActvity.this, view.toString (), Toast.LENGTH_SHORT). Show ();}}; @ Overrideprotected void onCreate (@ Nullable Bundle savedInstanceState) {super.onCreate (savedInstanceState); setContentView (R.layout.activity_login); findViewById (R.id.button) .setOnClickListener (mClickListener);}

This approach is relatively simple and has no compatibility problems, but you need to use a listener object based on the base class from beginning to end, which is more restrictive to the developer. This convention is applicable to new projects at the beginning of the project. For the old code refactoring workload is relatively large, and if you access the third-party ink cartridge module, there is nothing you can do.

The second way is to reflect the agent, and the developers don't know it at the right time and do the general processing in the adaptation wrapper.

The following is the proxy interface and the built-in listening adapter. The global listening interface needs to implement IProxyClickListener and set it to the built-in adapter WrapClickListener

Public interface IProxyClickListener {boolean onProxyClick (WrapClickListener wrap, View v); class WrapClickListener implements View.OnClickListener {IProxyClickListener mProxyListener; View.OnClickListener mBaseListener; public WrapClickListener (View.OnClickListener l, IProxyClickListener proxyListener) {mBaseListener = l; mProxyListener = proxyListener;} @ Override public void onClick (View v) {boolean handled = mProxyListener = = null? False: mProxyListener.onProxyClick (WrapClickListener.this, v); if (! handled & & mBaseListener! = null) {mBaseListener.onClick (v);}

We need to choose a time to be the hook of the listening agent for all View with listeners. At this time, you can add a view change monitor to the root View of Activity (of course, you can also choose the timing of the distribution of DOWN events in Activity):

RootView.getViewTreeObserver () .addOnGlobalLayoutListener (new ViewTreeObserver.OnGlobalLayoutListener () {@ Override public void onGlobalLayout () {hookViews (rootView, 0)}})

Note: in order to facilitate anonymous registration of monitoring, the actual use should be unregistered when Activity exits.

The Method and Field objects related to View listeners should be obtained by reflection before proxying:

Public void init () {if (sHookMethod = = null) {try {Class viewClass = Class.forName ("android.view.View"); if (viewClass! = null) {sHookMethod = viewClass.getDeclaredMethod ("getListenerInfo"); if (sHookMethod! = null) {sHookMethod.setAccessible (true);}} catch (Exception e) {reportError (e, "init") } if (sHookField = = null) {try {Class listenerInfoClass = Class.forName ("android.view.View$ListenerInfo"); if (listenerInfoClass! = null) {sHookField = listenerInfoClass.getDeclaredField ("mOnClickListener"); if (sHookField! = null) {sHookField.setAccessible (true);}} catch (Exception e) {reportError (e, "init");}

Only by ensuring the successful acquisition of sHookMethod and sHookField can you move on to the next step of recursively setting up a listening agent to steal the job. The following is the process of setting proxy listening recursively. Where mInnerClickProxy is the incoming proxy interface for handling click events globally.

Private void hookViews (View view, int recycledContainerDeep) {if (view.getVisibility () = = View.VISIBLE) {boolean forceHook = recycledContainerDeep = = 1; if (view instanceof ViewGroup) {boolean existAncestorRecycle = recycledContainerDeep > 0; ViewGroup p = (ViewGroup) view; if (! (p instanceof AbsListView | | p instanceof RecyclerView) | existAncestorRecycle) {hookClickListener (view, recycledContainerDeep, forceHook); if (existAncestorRecycle) {recycledContainerDeep++ }} else {recycledContainerDeep = 1;} int childCount = p.getChildCount (); for (int I = 0; I

< childCount; i++) { View child = p.getChildAt(i); hookViews(child, recycledContainerDeep); } } else { hookClickListener(view, recycledContainerDeep, forceHook); } }}private void hookClickListener(View view, int recycledContainerDeep, boolean forceHook) { boolean needHook = forceHook; if (!needHook) { needHook = view.isClickable(); if (needHook && recycledContainerDeep == 0) { needHook = view.getTag(mPrivateTagKey) == null; } } if (needHook) { try { Object getListenerInfo = sHookMethod.invoke(view); View.OnClickListener baseClickListener = getListenerInfo == null ? null : (View.OnClickListener) sHookField.get(getListenerInfo);//获取已设置过的监听器 if ((baseClickListener != null && !(baseClickListener instanceof IProxyClickListener.WrapClickListener))) { sHookField.set(getListenerInfo, new IProxyClickListener.WrapClickListener(baseClickListener, mInnerClickProxy)); view.setTag(mPrivateTagKey, recycledContainerDeep); } } catch (Exception e) { reportError(e,"hook"); } }} 以上深度优先从 Activity 的根 View 进行递归设置监听。只会对原来的 View 本身有点击的事件监听器的进行设置,成功设置后还会对操作的 View 设置一个 tag 标志表明已经设置了代理,避免每次变化重复设置。这个 tag 具有一定的含意,记录该 View 相对可能存在的可回收容器的层级数。因为对于像AbsListView或RecyclerView的直接子 View 是需要强制重新绑定代理的,因为它们的复用机制可能被重新设置了监听。 此方式实现实现稍微复杂,但是实现效果比较好,对开发者无感知进行监听器的hook代理。反射效率上也可以接受速度比较快无影响。对任何设置了监听器的 View都有效。 然而AbsListView的Item点击无效,因为它的点击事件不是通过 onClick 实现的,除非不是用 setItemOnClick 而是自己绑定 click 事件。 方式三,通过AccessibilityDelegate捕获点击事件。 分析View的源码在处理点击事件的回调时调用了 View.performClick 方法,内部调用了sendAccessibilityEvent而此方法有个托管接口mAccessibilityDelegate可以由外部处理所有的 AccessibilityEvent. 正好此托管接口的设置也是开放的setAccessibilityDelegate,如以下 View 源码关键片段。 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; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result;}public void sendAccessibilityEvent(int eventType) { if (mAccessibilityDelegate != null) { mAccessibilityDelegate.sendAccessibilityEvent(this, eventType); } else { sendAccessibilityEventInternal(eventType); }}public void setAccessibilityDelegate(@Nullable AccessibilityDelegate delegate) { mAccessibilityDelegate = delegate;} 基于此原理我们可在某个时机给所有的 View 注册我们自己的AccessibilityDelegate去监听系统行为事件,简要实现代码如下。 public class ViewClickTracker extends View.AccessibilityDelegate { boolean mInstalled = false; WeakReference mRootView = null; ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener = null; public ViewClickTracker(View rootView) { if (rootView != null && rootView.getViewTreeObserver() != null) { mRootView = new WeakReference(rootView); mOnGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { View root = mRootView == null ? null : mRootView.get(); boolean install = ; if (root != null && root.getViewTreeObserver() != null && root.getViewTreeObserver().isAlive()) { try { installAccessibilityDelegate(root); if (!mInstalled) { mInstalled = true; } } catch (Exception e) { e.printStackTrace(); } } else { destroyInner(false); } } }; rootView.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener); } } private void installAccessibilityDelegate(View view) { if (view != null) { view.setAccessibilityDelegate(ViewClickTracker.this); if (view instanceof ViewGroup) { ViewGroup parent = (ViewGroup) view; int count = parent.getChildCount(); for (int i = 0; i < count; i++) { View child = parent.getChildAt(i); if (child.getVisibility() != View.GONE) { installAccessibilityDelegate(child); } } } } } @Override public void sendAccessibilityEvent(View host, int eventType) { super.sendAccessibilityEvent(host, eventType); if (AccessibilityEvent.TYPE_VIEW_CLICKED == eventType && host != null) { //TODO 这里处理通用的点击事件,host 即为相应被点击的 View. } }} 以上实现比较巧妙,在监测到window上全局视图树发生变化后递归的给所有的View安装AccessibilityDelegate。经测试大多数厂商的机型和版本都是可以的,然而部分机型无法成功捕获监控到点击事件,所以不推荐使用。 方式四,通过分析 Activity 的 dispatchTouchEvent 事件并查找事件接受的目标 View。 这个方式初看有点匪夷所思,但是一系列触屏事件发生后总归要有一个组件消耗了它,查看ViewGroup关键源码如下: // First touch target in the linked list of touch targets.private TouchTarget mFirstTouchTarget;public boolean dispatchTouchEvent(MotionEvent ev) { ...... if (newTouchTarget == null && childrenCount != 0) { for (int i = childrenCount - 1; i >

= 0; iMel -) {if (dispatchTransformedTouchEvent (ev, false, child, idBitsToAssign)) {newTouchTarget = addTouchTarget (child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break;}. / / Dispatch to touch targets. If (mFirstTouchTarget = = null) {/ / No touch targets so treat this as an ordinary view. Handled = dispatchTransformedTouchEvent (ev, canceled, null, TouchTarget.ALL_POINTER_IDS);} else {/ / Dispatch to touch targets, excluding the new touch target if we already / / dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target! = null) {final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget & & target = = newTouchTarget) {handled = true;} else {final boolean cancelChild = resetCancelNextUpFlag (target.child) | | intercepted;. If (cancelChild) {if (predecessor = = null) {mFirstTouchTarget = next;} else {predecessor.next = next;} target.recycle (); target = next; continue;}} predecessor = target; target = next;}

It is found that the direct child View that is willing to accept the touch event will be added to the chained object mFirstTouchTarget, and the next is almost always null after chain adjustment. This gives us a breakthrough. You can get the immediate child View of the currently accepted event from mFirstTouchTarget.child, and then look for it recursively until mFirstTouchTarget.child is null. Even if we find the recipient of the final touch event. The best time to find this is in ACTION_UP or ACTION_CANCEL.

Through the above principles, we can obtain a series of target View of Touch events that are finally processed, and then according to our recorded press position, release position and offset, we can judge whether it is a possible click action. In order to strengthen the judgment of whether it is a real click event, you can further analyze whether the target View has a click listener installed (for the principle, please refer to method 2 above. The following acquisition and analysis of event timing is done in the dispatchTouchEvent method of Activity.

After recording the down and up events, the following is the click judgment to determine whether it is possible or not

/ / whether it could be a click actionpublic boolean isClickPossible (float slop) {if (mCancel | | mDownId = =-1 | | mUpId = =-1 | | mDownTime = = 0 | mUpTime = = 0) {return false;} else {return Math.abs (mDownX-mUpX) < slop & & Math.abs (mDownY-mUpY) < slop;}}

Immediately after the up event, look for the target View. First of all, make sure that the preparation work related to reflection mFirstTouchTarge is done.

Private boolean ensureTargetField () {if (sTouchTargetField = = null) {try {Class viewClass = Class.forName ("android.view.ViewGroup"); if (viewClass! = null) {sTouchTargetField = viewClass.getDeclaredField ("mFirstTouchTarget"); sTouchTargetField.setAccessible (true);} catch (Exception e) {e.printStackTrace () } try {if (sTouchTargetField! = null) {sTouchTargetChildField = sTouchTargetField.getType () .getDeclaredField ("child"); sTouchTargetChildField.setAccessible (true);}} catch (Exception e) {e.printStackTrace ();}} return sTouchTargetField! = null & & sTouchTargetChildField! = null;}

Then recursively look up the target View from the DecorView of Activity.

/ / find the target view who is interest in the touch event. Null if not findprivate View findTargetView () {View nextTarget, target = null; if (ensureTargetField () & & mRootView! = null) {nextTarget = findTargetView (mRootView); do {target = nextTarget; nextTarget = null; if (target instanceof ViewGroup) {nextTarget = findTargetView ((ViewGroup) target);}} while (nextTarget! = null);} return target } / / reflect to find the TouchTarget child view,null if not found .private View findTargetView (ViewGroup parent) {try {Object target = sTouchTargetField.get (parent); if (target! = null) {Object view = sTouchTargetChildField.get (target); if (view instanceof View) {return (View) view;}} catch (Exception e) {e.printStackTrace ();} return null } these are all the contents of the article "how to globally monitor click events in android". Thank you for reading! I believe you will gain a lot after reading this article. The editor will update different knowledge for you every day. If you want to learn more knowledge, please pay attention to the industry information channel.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report