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相关推荐

  1. 生成唯一字符串算法_面试官问:在分布式场景,生成唯一ID,你有几种方案?...

    来源:http://t.cn/RG0AW0a 说明:本文代码采用C#,重要的是理解解决方案,代码实现都是次要的. 系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结.生成I ...

  2. php给留言分配id_如何使用php生成唯一ID的4种方法

    php生成唯一ID的应用场景非常普遍,如临时缓存文件名称,临时变量,临时安全码等,uniqid()函数基于以微秒计的当前时间,生成一个唯一的 ID.由于生成唯一ID与微秒时间关联,因此ID的唯一性非常 ...

  3. 根据twitter的snowflake算法生成唯一ID

    C#版本 /// <summary>/// 根据twitter的snowflake算法生成唯一ID/// snowflake算法 64 位/// 0---0000000000 000000 ...

  4. php生成不重复时间戳,PHP获取时间戳和微秒数以及生成唯一ID

    microtime函数 描述:返回当前Unix时间戳和微秒数 语法:mixed microtime( [ bool $get_as_float ] ) //直接输出 echo microtime(); ...

  5. php生成游客id_PHP生成唯一ID 公认较为安全的写法 上传随机文件名

    PHP生成随机文件名有多种方式,本次介绍一个在公认比较新.比较安全的随机ID函数.较老的方式一般单一使用rand函数生成随机数字,或者用md5,或者进一步使用uniqid()函数,但不论以上哪种方式, ...

  6. 游戏后台生成唯一ID

    游戏后台生成唯一ID   MMO游戏后台通常需要由大量服务器来共同承载海量玩家,虽然玩家可能分布在不同的游戏大区,但是他们可能会通过跨服等等方式进行各种交互.游戏中的角色,装备,物品等需要生成一个全局 ...

  7. JS生成唯一id方式介绍(UUID和NanoID)

    记录下JS生成唯一id的方法. 1.生成uuid的方法 方法一: function guid() {return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.repl ...

  8. java唯一id_生成唯一ID的四种办法 程序员必备

    我们在实际编程过程中会经常遇到需要用唯一ID的场合,这些唯一ID还会存到数据库中以便于我们将来进行查询. 例如用户编号.订单编号.客户编号等等,几乎凡是需要用来严格划分用户数据归属性的地方就需要用到唯 ...

  9. JavaScript 生成唯一ID的几种方式

    这篇文章主要介绍了JavaScript 生成唯一ID的几种方式,帮助大家更好的理解和使用JavaScript,感兴趣的朋友可以了解下. 编程的世界里,在很多的时候,我们都需要一个唯一的ID来代表一些数 ...

最新文章

  1. memcache nginx
  2. shell中的函数、shell中的数组、 告警系统需求分析
  3. 西安市2008驾照理论考试题
  4. Springboot细节挖掘(对web的支持之数据校验)
  5. 一个软件工程师的成长之路(二):回味经典组合 DOS + 五笔 + WPS + FOXBASE
  6. 重启Usb蓝牙设备(PD虚拟机模拟插拔)
  7. 今日新闻快讯摘要十条
  8. Python-OpenCV 的 remap函数
  9. cocos2d编写android插件,[分享]Xposed插件dump Cocos2d-x应用的lua脚本
  10. 【JZOJ 4598】准备食物
  11. 我是如何拿到:百度 腾讯 头条 美团 度小满等互联网offer的?
  12. Firefox(火狐)好用的插件
  13. 牛客网刷题-java
  14. 云服务器一般用什么系统,云服务器用什么系统好
  15. 什么是认知?什么是认知科学?
  16. 满头黑发开始_python
  17. 【案例分享】高效率利器 - SC 频谱分析仪
  18. 工信部:2015年电信业务收入完成11251.4亿元
  19. 维修记录,移动升级千兆宽带过程中的坑
  20. Microsoft Office

热门文章

  1. 小计Tomcat的调优思路
  2. 2016第七届ACM山东省赛
  3. (10.2.3.3)静电的设计教室:APP设计利器Sketch教程(03)-让插件助你一臂之力(原创
  4. ukf(无迹卡尔曼滤波)算法的matlab程序.
  5. const violate
  6. Python: 使用xlrd读取Excel文件
  7. 如何在sql语句里添加注释
  8. C++ 调用打印机 打印一段文字
  9. 图形学 ---- 二维几何变换(二维图形矩阵平移,旋转,缩放)
  10. 手机数字雨_cmd命令如何实现数字雨的效果