静态代理在性能方面明显优于动态代理,静态代理在编译器织入代码,或者修改代码,对运行时的程序不会带来太大的性能影响,而动态代理在程序运行阶段发生,可能需要反射,对程序性能有一定的影响。
一般思路有两种:
我们不希望在原有代码上做修改,我们就需要做功能增强,功能增强除了可以通过代理设计模式,还可以通过装饰器模式。我们的任务:
除了在 OnResume 生命周期来到的时候进行上述遍历替换行为,在 OnResume 后动态 addView 的时候也要进行上述替换行为。对于后来动态添加View的情况,我们可以采用 ViewTreeObserer.OnGlobalLayoutListener 来监听View树的动态变化回调,从而重新进行上述遍历替换行为。
缺点:
除了反射性能问题、遍历耗时问题,还有兼容性问题
Application.ActivityLifecycleCallbacks 要求 API 14+
也可以通过hook Instrumentation来做生命周期监听
View.hasOnClickListeners() 要求 API 15+
removeOnGlobalLayoutListener 要求 API 16+
无法采集其他Window的点击事件,例如 Dialog、PopupWindow等
我们可以通过代理 Window.Callback 增强其 dispatchTouchEvent 方法,或者通过增加一个顶级透明ViewGroup,增强其 dispatchTouchEvent 方法,目的都是为了获取到用户点击的位置,然后通过遍历View,找到点击的View是哪个。
View的点击事件 performClick() 内部会调用 sendAccessibilityEvent(AccessbilityEvent.TYPE_VIEW_CLICKED),里面调用了 mAccessbilityDelegate 对象的 sendAccessibilityEvent 方法,并传入了 View 对象。我们可以通过装饰器模式来增强 AccessibilityDelegate 对象。这里就不需要反射了,View本身就提供了 getter setter 该对象的方法。
缺点:
优点:
Activity结构如下,Activity的布局为LinearLayout为父布局,里面只放了一个Button按钮:
public class MainActivity extends AppCompatActivity {Button btn;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn = findViewById(R.id.btn_click);btn.setOnClickListener(v->{Toast.makeText(this, "hello", Toast.LENGTH_SHORT).show();Log.e(TAG,"btn clicked");});}
}
Application 基础结构:
public class MyApplication extends Application {private static final String TAG = "MyApplicationTAG";@Overridepublic void onCreate() {super.onCreate();registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {//...@Overridepublic void onActivityResumed(@NonNull Activity activity) {//发起点击事件监听(埋点)listenToClick(activity);}});}private void listenToClick(@NonNull Activity activity) {//1. 拿到windowWindow window = activity.getWindow();//PhoneWindow//2.拿到根ViewView decor = window.getDecorView();//根View(除了ViewRootImpl)//3.层序遍历 空间复杂度为最大宽度Queue queue = new LinkedList<>();queue.add(decor);while(!queue.isEmpty()){int n = queue.size();for(int i = 0; i< n;i++){View poll = queue.poll();//埋点if (poll instanceof ViewGroup) {for (int j = 0; j < ((ViewGroup) poll).getChildCount(); j++) {View child = ((ViewGroup) poll).getChildAt(j);queue.add(child);}}}}}
}
搭建上述结构的时候,可能会有一些问题:
为什么不用反射获取 ViewGroup 中的 mChildren?
答:Android对反射Field增加了限制,一般反射只能反射到 public 对象。但对方法的反射则暂时没用这样的限制。
基础结构中的 【埋点】 部分,实现为 insertWrapperOnClickListener(View view)
//...
View poll = queue.poll();
//代理 OnCLickLisenter
insertWrapperOnClickListener(poll);
if(poll instanceof ViewGroup){...}
//...
首先需要实现一个装饰类WrapperOnClickListener
来做功能增强:
//Wrapper包装类,功能增强
private static class WrapperOnClickListener implements View.OnClickListener {//装饰对象View.OnClickListener upStream;public WrapperOnClickListener(View.OnClickListener upStream) {this.upStream = upStream;}//功能增强@Overridepublic void onClick(View v) {//preLog.e(TAG, "before click");//打印view信息(可以用于存储埋点)Log.e(TAG,"view info :"+createViewInfo(v));//perform clickupStream.onClick(v);//afterLog.e(TAG, "after click");}//打印控件的唯一标识信息//parent1[index]#id/parent2[index]#id/view[index]#idprivate String createViewInfo(View v) {Deque path = new LinkedList<>();View cur = v;StringBuilder info;while ((cur)!=null){info = new StringBuilder();info.append(cur.getClass().getSimpleName());info.append("[");if (cur.getParent()!=null && cur.getParent() instanceof ViewGroup){info.append(((ViewGroup) cur.getParent()).indexOfChild((View)cur));}else{info.append(0);}info.append("]");if (((View) cur).getId()!=-1) {//如果有idinfo.append("#");info.append(((View) cur).getId());}path.add(info.toString());if (!(cur.getParent() instanceof View))break;cur = (View)cur.getParent();}//拼接StringBuilder result = new StringBuilder();result.append("path: ");while (!path.isEmpty()){result.append(path.pollLast());if (!path.isEmpty()){result.append("/");}}return result.toString();}
}
有了功能增强的装饰类,接下来就是注入了,我们通过反射拿到 View的ListenerInfo的mOnClickListener对象,将其替换为我们的装饰类:
private void insertWrapperOnClickListener(View view) {if (view.hasOnClickListeners()) {//如果有主动设置点击监听器//反射拿到原来的mOnClickListenerClass> v = View.class;try {//注意这里要用 getDeclaredMethod 而不是 getMethodMethod getListenerInfo = v.getDeclaredMethod("getListenerInfo");getListenerInfo.setAccessible(true);Object li = getListenerInfo.invoke(view);//下列注释代码运行失败,虽然反射实例性能更快,但是android有反射限制,只能通过反射方法来获取 ListenerInfo 对象// Field mListenerInfo = v.getDeclaredField("mListenerInfo");//mListenerInfo.setAccessible(true);// Object li = mListenerInfo.get(v);if (li != null) {//拿到li中的mOnClickListenerClass> liClass = li.getClass();//这是个public的域,是允许直接反射获取的Field clickListenerField = liClass.getDeclaredField("mOnClickListener");clickListenerField.setAccessible(true);View.OnClickListener o = (View.OnClickListener) clickListenerField.get(li);//之前已经判空过了//注入装饰类Log.e(TAG, "get one click listener : " + o);clickListenerField.set(li, new WrapperOnClickListener(o));o = (View.OnClickListener) clickListenerField.get(li);Log.e(TAG, "get one click listener : " + o);}} catch (IllegalAccessException | NoSuchFieldException | NoSuchMethodException | InvocationTargetException e) {e.printStackTrace();Log.e(TAG, "error : " + e);}}
}
运行结果:
2023-03-18 16:39:59.611 21646-21646/? E/MyApplicationTAG: get one click listener : MainActivity$$ExternalSyntheticLambda0@fea8936
2023-03-18 16:39:59.611 21646-21646/? E/MyApplicationTAG: get one click listener : MyApplication$WrapperOnClickListener@26f1037
//点击按钮之后:
2023-03-18 16:40:05.041 21646-21646/? E/MyApplicationTAG: before click
2023-03-18 16:40:05.041 21646-21646/? E/MyApplicationTAG: view info :path: DecorView[0]/LinearLayout[0]/FrameLayout[1]/ActionBarOverlayLayout[0]#2131230845/ContentFrameLayout[0]#16908290/LinearLayout[0]/MaterialButton[0]#2131230808
2023-03-18 16:40:05.055 21646-21646/? E/MyApplicationTAG: btn clicked
2023-03-18 16:40:05.055 21646-21646/? E/MyApplicationTAG: after click
可以看到代理成功。在替换 OnClickListener 前,点击监听器为匿名内部类,替换完成后,点击监听器变为了我们的装饰器类。点击按钮之后,除了执行按钮本身监听器内容之外,还执行了我们增强的功能。也就是埋点的功能。
基础结构中的 【埋点】 部分,实现为替换 AccessibilityDelegate:
private void insertWrapperAccessibilityDelegate(View view){//插入装饰类if (view.getAccessibilityDelegate()==null){view.setAccessibilityDelegate(new WrapperAccessibilityDelegate(null));}else{view.setAccessibilityDelegate(new WrapperAccessibilityDelegate(view.getAccessibilityDelegate()));}
}//包装类
private static class WrapperAccessibilityDelegate extends View.AccessibilityDelegate{//包装对象View.AccessibilityDelegate upstream;public WrapperAccessibilityDelegate(View.AccessibilityDelegate upstrea) {this.upstream = upstrea;}//功能增强@Overridepublic void sendAccessibilityEvent(View host, int eventType) {//pre 监听事件Log.e(TAG,"begin sendAccessibilityEvent");Log.e(TAG,"view info :"+createViewInfo(host));//调用装饰对象的方法if (upstream!=null)upstream.sendAccessibilityEvent(host,eventType);else super.sendAccessibilityEvent(host,eventType);//post 监听事件Log.e(TAG,"end sendAccessibilityEvent");}//打印控件的唯一标识信息//parent1[index]#id/parent2[index]#id/view[index]#idprivate String createViewInfo(View v) {Deque path = new LinkedList<>();View cur = v;StringBuilder info;while ((cur)!=null){info = new StringBuilder();info.append(cur.getClass().getSimpleName());info.append("[");if (cur.getParent()!=null && cur.getParent() instanceof ViewGroup){info.append(((ViewGroup) cur.getParent()).indexOfChild((View)cur));}else{info.append(0);}info.append("]");if (((View) cur).getId()!=-1) {//如果有idinfo.append("#");info.append(((View) cur).getId());}path.add(info.toString());if (!(cur.getParent() instanceof View))break;cur = (View)cur.getParent();}//拼接StringBuilder result = new StringBuilder();result.append("path: ");while (!path.isEmpty()){result.append(path.pollLast());if (!path.isEmpty()){result.append("/");}}return result.toString();}
}
运行效果:
2023-03-18 16:42:32.510 21856-21856/? E/MyApplicationTAG: btn clicked
2023-03-18 16:42:32.510 21856-21856/? E/MyApplicationTAG: begin sendAccessibilityEvent
2023-03-18 16:42:32.511 21856-21856/? E/MyApplicationTAG: view info :path: DecorView[0]/LinearLayout[0]/FrameLayout[1]/ActionBarOverlayLayout[0]#2131230845/ContentFrameLayout[0]#16908290/LinearLayout[0]/MaterialButton[0]#2131230808
2023-03-18 16:42:32.511 21856-21856/? E/MyApplicationTAG: end sendAccessibilityEvent
可以看到代理成功。按钮执行完点击事件后,回调到了 mAccessibilityDeletage的sendAccessibilityEvent方法。
在View.java中,这个变量定义如下,这是一个 private 修饰的变量
//View.java
ListenerInfo mListenerInfo;
而 Android 的 Class.java 的反射方法被修改了,只能获取 public 修饰的变量:
//Class.java in Android
// Android-changed: Removed SecurityException.
public native Field getDeclaredField(String name) throws NoSuchFieldException;
但是可以通过反射 getListenerInfo() 方法来获得 mListenerInfo 变量
public Method getDeclaredMethod(String name, Class>... parameterTypes)throws NoSuchMethodException, SecurityException {return getMethod(name, parameterTypes, false);
}
可以直接通过反射 Field 来获取 mOnClickListener,因为它的访问权限是 public
//View.java
static class ListenerInfo {//未来可能会变成 privatepublic OnClickListener mOnClickListener;
}
View如果消费了点击事件,最后回来到 performClick() 方法:
public boolean performClick() {notifyAutofillManagerOnClick();//如果点击监听器有设置的话,回调监听器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);notifyEnterOrExitForAutoFillIfNeeded(true);return result;}//如果有 mAccessibilityDelegate的话,会回调其 sendAccessibilityEvent 方法
public void sendAccessibilityEvent(int eventType) {if (mAccessibilityDelegate != null) {mAccessibilityDelegate.sendAccessibilityEvent(this, eventType);} else {sendAccessibilityEventInternal(eventType);}
}
上一篇:【数据结构】KMP算法(详解)