Android View 生成唯一 Id
Android View 生成唯一 Id
可以使用 Hook LayoutInflater 的方法替换 SystemService 原有的 LayoutInflater,在自定义的 LayoutInflater 遍历每一个 view,为它们生成 md5 作为 view 的唯一 id。
Hook LayoutInflater
Hook LayoutInflater 的核心在于使用反射调用 registerService 方法,注册自定义的 LayoutInflater。
public class LayoutInflaterHook {private static final String TAG = "LayoutInflaterHook";public static void hookLayoutInflater() throws Exception {Class<?> serviceFetcher = Class.forName("android.app.SystemServiceRegistry$ServiceFetcher");// 获取 ServiceFetcher 的实例 serviceFetcherImplObject serviceFetcherImpl = Proxy.newProxyInstance(LayoutInflaterHook.class.getClassLoader(),new Class<?>[]{serviceFetcher},new ServiceFetcherHandler());// 获取 SystemServiceRegistry 的 registerService 方法Class<?> systemServiceRegistry = Class.forName("android.app.SystemServiceRegistry");// 无法反射调用 registerService,registerService 在 Android 10 版本以上都是 blacklist 级别的 api,// 反射调用会被系统拒绝,抛出 NoSuchMethodException。Method registerService = systemServiceRegistry.getDeclaredMethod("registerService",String.class,CustomLayoutInflater.class.getClass(),serviceFetcher);registerService.setAccessible(true);// 调用 registerService 方法,将自定义的 CustomLayoutInflater 设置到 SystemServiceRegistryregisterService.invoke(systemServiceRegistry,Context.LAYOUT_INFLATER_SERVICE, CustomLayoutInflater.class, serviceFetcherImpl);// 测试Field systemServiceFetchers = systemServiceRegistry.getDeclaredField("SYSTEM_SERVICE_FETCHERS");systemServiceFetchers.setAccessible(true);Map systemServiceFetchersField = (Map) systemServiceFetchers.get(null);Set set = systemServiceFetchersField.keySet();Object service = systemServiceFetchersField.get(Context.LAYOUT_INFLATER_SERVICE);Log.w(TAG, "find layout inflater:" + service);for (Object next : set) {Object value = systemServiceFetchersField.get(next);Log.d(TAG, "key:" + next);Log.d(TAG,"value:" + value);if (Context.LAYOUT_INFLATER_SERVICE.equals(next)) {Log.e(TAG, "find Service for layout inflater:" + value);}}}}
动态代理 ServiceFetcher
因为 ServiceFetcher 是 SystemServiceRegistry 的内部借口,因此需要使用动态代理的方式实现它的 invoke 方法。
public class ServiceFetcherHandler implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 当调用 ServiceFetcherImpl 的 getService 的时候,会返回自定义的 LayoutInflaterif ("toString".equals(method.getName())) {return "ServiceFetcherHandler";}return new CustomLayoutInflater((Context) args[0]);}
}
自定义 LayoutInflater
当调用 getService 时,会返回自定义的 CustomLayoutInflater。
public class CustomLayoutInflater extends LayoutInflater {private static final String[] sClassPrefixList = {"android.widget.","android.webkit."};private static int VIEW_TAG = 0x10000000;public CustomLayoutInflater(Context context) {super(context);}public CustomLayoutInflater(LayoutInflater original, Context newContext) {super(original, newContext);}@Overrideprotected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {for (String prefix : sClassPrefixList) {try {View view = createView(name, prefix, attrs);if (view != null) {return view;}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (InflateException e) {e.printStackTrace();}}return super.onCreateView(name, attrs);}@Overridepublic LayoutInflater cloneInContext(Context newContext) {return new CustomLayoutInflater(this, newContext);}@Overridepublic View inflate(int resource, @Nullable ViewGroup root, boolean attachToRoot) {View viewGroup = super.inflate(resource, root, attachToRoot);View rootView = viewGroup;View tempView = viewGroup;// 向上遍历得到根 Viewwhile (tempView != null) {rootView = viewGroup;tempView = ((ViewGroup) tempView.getParent());}traversalViewGroup(rootView);return viewGroup;}private void traversalViewGroup(View rootView) {if (rootView == null || !(rootView instanceof ViewGroup)) {return;}// 如果 rootView 没有 tag,设置它的 view 值为 VIEW_TAG 计数值if (rootView.getTag() == null) {rootView.setTag(getViewTag());}ViewGroup viewGroup = (ViewGroup) rootView;int childCount = ((ViewGroup) rootView).getChildCount();for (int i = 0; i < childCount; ++i) {View childView = viewGroup.getChildAt(i);if (childView.getTag() == null) {childView.setTag(combineTag(getViewTag(), rootView.getTag().toString()));}Log.e("Hooker", "childView name=" + childView.getClass().getName()+ ", id = " + childView.getTag().toString());if (childView instanceof ViewGroup) {// 深度优先遍历traversalViewGroup(childView);}}}private static String combineTag(String tag1, String tag2) {return getMd5(getMd5(tag1) + getMd5(tag2));}private static String getViewTag() {return String.valueOf(VIEW_TAG++);}public static String getMd5(String str) {try {MessageDigest md5 = MessageDigest.getInstance("MD5");md5.update(str.getBytes());return new BigInteger(1, md5.digest()).toString(16);} catch (NoSuchAlgorithmException e) {e.printStackTrace();}return "null";}
}
通过组合父 view 和自身 view 的 md5 得到唯一的 view id。
Android 黑名单的限制
从 Android 9 开始,Android 系统限制了部分 System api 的调用。Hook LayoutInflater 用到的 registerService 方法就被列入了黑名单,反射调用直接抛出 NoSuchMethodException。因此 Hook LayoutInflater 的方法实际不可用,但是可以通过修改系统设置本地调试。
adb shell settings put global hidden_api_policy 1
关于在 Android 10 中授予对非 SDK 接口的访问权限,可以查看 Android 10 中有关限制非 SDK 接口的更新
源码
https://github.com/caoshen/AndroidEfficientAdvanced
Android View 生成唯一 Id相关推荐
- 生成唯一字符串算法_面试官问:在分布式场景,生成唯一ID,你有几种方案?...
来源:http://t.cn/RG0AW0a 说明:本文代码采用C#,重要的是理解解决方案,代码实现都是次要的. 系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结.生成I ...
- php给留言分配id_如何使用php生成唯一ID的4种方法
php生成唯一ID的应用场景非常普遍,如临时缓存文件名称,临时变量,临时安全码等,uniqid()函数基于以微秒计的当前时间,生成一个唯一的 ID.由于生成唯一ID与微秒时间关联,因此ID的唯一性非常 ...
- 根据twitter的snowflake算法生成唯一ID
C#版本 /// <summary>/// 根据twitter的snowflake算法生成唯一ID/// snowflake算法 64 位/// 0---0000000000 000000 ...
- php生成不重复时间戳,PHP获取时间戳和微秒数以及生成唯一ID
microtime函数 描述:返回当前Unix时间戳和微秒数 语法:mixed microtime( [ bool $get_as_float ] ) //直接输出 echo microtime(); ...
- php生成游客id_PHP生成唯一ID 公认较为安全的写法 上传随机文件名
PHP生成随机文件名有多种方式,本次介绍一个在公认比较新.比较安全的随机ID函数.较老的方式一般单一使用rand函数生成随机数字,或者用md5,或者进一步使用uniqid()函数,但不论以上哪种方式, ...
- 游戏后台生成唯一ID
游戏后台生成唯一ID MMO游戏后台通常需要由大量服务器来共同承载海量玩家,虽然玩家可能分布在不同的游戏大区,但是他们可能会通过跨服等等方式进行各种交互.游戏中的角色,装备,物品等需要生成一个全局 ...
- JS生成唯一id方式介绍(UUID和NanoID)
记录下JS生成唯一id的方法. 1.生成uuid的方法 方法一: function guid() {return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.repl ...
- java唯一id_生成唯一ID的四种办法 程序员必备
我们在实际编程过程中会经常遇到需要用唯一ID的场合,这些唯一ID还会存到数据库中以便于我们将来进行查询. 例如用户编号.订单编号.客户编号等等,几乎凡是需要用来严格划分用户数据归属性的地方就需要用到唯 ...
- JavaScript 生成唯一ID的几种方式
这篇文章主要介绍了JavaScript 生成唯一ID的几种方式,帮助大家更好的理解和使用JavaScript,感兴趣的朋友可以了解下. 编程的世界里,在很多的时候,我们都需要一个唯一的ID来代表一些数 ...
最新文章
- memcache nginx
- shell中的函数、shell中的数组、 告警系统需求分析
- 西安市2008驾照理论考试题
- Springboot细节挖掘(对web的支持之数据校验)
- 一个软件工程师的成长之路(二):回味经典组合 DOS + 五笔 + WPS + FOXBASE
- 重启Usb蓝牙设备(PD虚拟机模拟插拔)
- 今日新闻快讯摘要十条
- Python-OpenCV 的 remap函数
- cocos2d编写android插件,[分享]Xposed插件dump Cocos2d-x应用的lua脚本
- 【JZOJ 4598】准备食物
- 我是如何拿到:百度 腾讯 头条 美团 度小满等互联网offer的?
- Firefox(火狐)好用的插件
- 牛客网刷题-java
- 云服务器一般用什么系统,云服务器用什么系统好
- 什么是认知?什么是认知科学?
- 满头黑发开始_python
- 【案例分享】高效率利器 - SC 频谱分析仪
- 工信部:2015年电信业务收入完成11251.4亿元
- 维修记录,移动升级千兆宽带过程中的坑
- Microsoft Office
热门文章
- 小计Tomcat的调优思路
- 2016第七届ACM山东省赛
- (10.2.3.3)静电的设计教室:APP设计利器Sketch教程(03)-让插件助你一臂之力(原创
- ukf(无迹卡尔曼滤波)算法的matlab程序.
- const violate
- Python: 使用xlrd读取Excel文件
- 如何在sql语句里添加注释
- C++ 调用打印机 打印一段文字
- 图形学 ---- 二维几何变换(二维图形矩阵平移,旋转,缩放)
- 手机数字雨_cmd命令如何实现数字雨的效果