Android动态实例化view,Android旁门左道之动态替换系统View类

2024-02-24 11:30

本文主要是介绍Android动态实例化view,Android旁门左道之动态替换系统View类,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

66b52468c121889b900d4956032f1009.png

8种机械键盘轴体对比

本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?

导语 本文讲述如何通过替换系统View类的方法,定位一个特殊机型问题

作者: yarkeyzhang  2017.6.29

一,ImageView抛来一个异常

应用程序Crash是Android

App开发习以为常的问题,大部分Crash我们通过日志找到调用栈可以很快定位到出错的代码。然而有一些Crash却显得没那么直接,比如下面这个由Android系统抛(throw)出来的异常。

17-06-06 11:36:20|1496720179572[20047]1|E|StatisticCollector|

getCrashExtraMessage...

isNativeCrashed: false

crashType=java.lang.RuntimeException

crashAddress=android.graphics.Canvas.throwIfCannotDraw(Canvas.java:1270)

crashStack=android.graphics.Canvas.throwIfCannotDraw(Canvas.java:1270)

android.graphics.Canvas.drawBitmap(Canvas.java:1404)

android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:544)

android.widget.ImageView.onDraw(ImageView.java:1246)

android.view.View.draw(View.java:16245)

android.view.View.updateDisplayListIfDirty(View.java:15242)

android.view.View.draw(View.java:16015)

android.view.ViewGroup.drawChild(ViewGroup.java:3740)

android.view.ViewGroup.dispatchDraw(ViewGroup.java:3530)

android.view.View.draw(View.java:16248)

android.view.View.updateDisplayListIfDirty(View.java:15242)

android.view.View.draw(View.java:16015)

android.view.ViewGroup.drawChild(ViewGroup.java:3740)

android.view.ViewGroup.dispatchDraw(ViewGroup.java:3530)

android.view.View.updateDisplayListIfDirty(View.java:15237)

android.view.View.draw(View.java:16015)

android.view.ViewGroup.drawChild(ViewGroup.java:3740)

android.view.ViewGroup.dispatchDraw(ViewGroup.java:3530)

android.view.View.updateDisplayListIfDirty(View.java:15237)

android.view.View.draw(View.java:16015)

android.view.ViewGroup.drawChild(ViewGroup.java:3740)

android.view.ViewGroup.dispatchDraw(ViewGroup.java:3530)

android.view.View.draw(View.java:16248)

com.android.internal.policy.PhoneWindow$DecorView.draw(PhoneWindow.java:2822)

android.view.View.updateDisplayListIfDirty(View.java:15242)

android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:282)

android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:288)

android.view.ThreadedRenderer.draw(ThreadedRenderer.java:323)

android.view.ViewRootImpl.draw(ViewRootImpl.java:2649)

android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2468)

android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2072)

android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1108)

android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6146)

android.view.Choreographer$CallbackRecord.run(Choreographer.java:892)

android.view.Choreographer.doCallbacks(Choreographer.java:704)

android.view.Choreographer.doFrame(Choreographer.java:640)

android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:878)

android.os.Handler.handleCallback(Handler.java:739)

android.os.Handler.dispatchMessage(Handler.java:95)

android.os.Looper.loop(Looper.java:148)

android.app.ActivityThread.main(ActivityThread.java:5648)

我们看看日志,ViewRootImpl.doTraversal()是遍历Window所有View刷新界面的过程,这个过程由系统触发,我们在调用栈中找不到任何的App客户代码。过程中,ImageView在执行onDraw()的时候出现了异常。这是某部手机在开启多窗口模式时必现Crash。怎么办?

二、寻求解决思路

这个问题出现在一个编辑图片的页面,页面中含有很多的ImageView(大约20个)实例,单单靠调用栈我们无法定位具体哪个ImageView出现了问题。

不过相信大家已经有很多解决思路:

1. 通过日志文件寻找出错前后是否有更多帮助信息,配合源码定位问题

2. 借到问题手机,连接电脑配合源码打断点(ImageView,BitmapDrawable,Canvas)

思路1无法快速解决问题;思路2恕我实在借不到那个型号的手机,另外我们IDE中的Android源码与手机中行数不一定匹配,给ImageView,BitmapDrawable等等这些系统类打断点,代码行数对不上的话也就很难搞。

这里我想到了一个思路:能不能重写ImageView.onDraw()方法,在出现异常时打印出所有我们需要的日志信息(比如view id)

三、往LayoutInflater下手

重写ImageView.onDraw()方法实际上等于我们需要替换ImageView类,把所有的xml布局文件中的ImageView换成我们新定义的CatchExceptionImageView?这个显然不太好办。最后我在LayoutInflater类中找到了方法。

ad947170e91ba0b5e64f376019da94b3552102cf14d1ac876cbced9cbb5c6873

每个Activity拥有一个LayoutInflater 对象,它负责解析Android xml 布局文件然后实例化View或者View子类对象。核心函数是

LayoutInflater.createViewFromTag(View parent, String name, AttributeSet

attrs),它通过xml标签指定的类名字,实例化出View对象。在这里做手脚,我们可以将xml中所有的标签实例化成

CatchExceptionImageView。

950108b9e6717511666023bd26ec644fc640244f5123b63164ee9dbe12059260

查看createViewFromTag()源码我们可以发现, LayoutInflater其实支持外部提供工厂类来自定义View的创建机制,对应的方法是

setFactory() 和 setFactory2()。

如果大家有用过android.support.v7.app.AppCompatActivity,那么你会发现,xml布局中的Button标签实际上创建了android.support.v7.widget.AppCompatButton对象,TextView标签实际上创建了android.support.v7.widget.AppCompatTextView对象,这是通过LayoutInflater.Factory来影响View的创建实现的。

所以,我们调用 setFactory()或者setFactory2()方法有可能会遇到失败:“A factory has already been set

on this LayoutInflater”。最后,我通过反射把我定义的Factory对象安全地注入到了LayoutInflater对象中。

四、调试代码协助定位问题

为了捕捉到抛出异常的ImageView,我大概写了下面这样的代码:

public static class CatchExceptionImageView extends ImageView {

public CatchExceptionImageView(Context context) {

super(context);

}

public CatchExceptionImageView(Context context, AttributeSet attrs) {

super(context, attrs);

}

@Override

protected void onDraw(Canvas canvas) {

Drawable drawable = getDrawable();

if (drawable instanceof BitmapDrawable) {

BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;

if (bitmapDrawable.getBitmap().isRecycled()) {

Log.e(TAG, "we'll crash !! " + this);

}

}

super.onDraw(canvas);

}

}

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

HashMap hookViewMap = new HashMap();

hookViewMap.put("ImageView", CatchExceptionImageView.class.getName());

new LayoutDebugHelper().onActivityCreate(this, hookViewMap);

setContentView(R.layout.activity_main);

}

我构建了新的包发送给远方的优测测试人员,帮我复现了问题并抓了日志,最后找到了Crash的ImageView信息,通过view id便可以找到了出错的点。

够折腾吧,哈哈,机型问题虐我千百遍!

全文完,感谢阅读!

这篇关于Android动态实例化view,Android旁门左道之动态替换系统View类的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/741957

相关文章

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

Linux系统中查询JDK安装目录的几种常用方法

《Linux系统中查询JDK安装目录的几种常用方法》:本文主要介绍Linux系统中查询JDK安装目录的几种常用方法,方法分别是通过update-alternatives、Java命令、环境变量及目... 目录方法 1:通过update-alternatives查询(推荐)方法 2:检查所有已安装的 JDK方

Linux系统之lvcreate命令使用解读

《Linux系统之lvcreate命令使用解读》lvcreate是LVM中创建逻辑卷的核心命令,支持线性、条带化、RAID、镜像、快照、瘦池和缓存池等多种类型,实现灵活存储资源管理,需注意空间分配、R... 目录lvcreate命令详解一、命令概述二、语法格式三、核心功能四、选项详解五、使用示例1. 创建逻

go动态限制并发数量的实现示例

《go动态限制并发数量的实现示例》本文主要介绍了Go并发控制方法,通过带缓冲通道和第三方库实现并发数量限制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录带有缓冲大小的通道使用第三方库其他控制并发的方法因为go从语言层面支持并发,所以面试百分百会问到

MySQL多实例管理如何在一台主机上运行多个mysql

《MySQL多实例管理如何在一台主机上运行多个mysql》文章详解了在Linux主机上通过二进制方式安装MySQL多实例的步骤,涵盖端口配置、数据目录准备、初始化与启动流程,以及排错方法,适用于构建读... 目录一、什么是mysql多实例二、二进制方式安装MySQL1.获取二进制代码包2.安装基础依赖3.清

SpringBoot 异常处理/自定义格式校验的问题实例详解

《SpringBoot异常处理/自定义格式校验的问题实例详解》文章探讨SpringBoot中自定义注解校验问题,区分参数级与类级约束触发的异常类型,建议通过@RestControllerAdvice... 目录1. 问题简要描述2. 异常触发1) 参数级别约束2) 类级别约束3. 异常处理1) 字段级别约束

Apache Ignite缓存基本操作实例详解

《ApacheIgnite缓存基本操作实例详解》文章介绍了ApacheIgnite中IgniteCache的基本操作,涵盖缓存获取、动态创建、销毁、原子及条件更新、异步执行,强调线程池注意事项,避免... 目录一、获取缓存实例(Getting an Instance of a Cache)示例代码:二、动态

linux批量替换文件内容的实现方式

《linux批量替换文件内容的实现方式》本文总结了Linux中批量替换文件内容的几种方法,包括使用sed替换文件夹内所有文件、单个文件内容及逐行字符串,强调使用反引号和绝对路径,并分享个人经验供参考... 目录一、linux批量替换文件内容 二、替换文件内所有匹配的字符串 三、替换每一行中全部str1为st

使用Python构建一个高效的日志处理系统

《使用Python构建一个高效的日志处理系统》这篇文章主要为大家详细讲解了如何使用Python开发一个专业的日志分析工具,能够自动化处理、分析和可视化各类日志文件,大幅提升运维效率,需要的可以了解下... 目录环境准备工具功能概述完整代码实现代码深度解析1. 类设计与初始化2. 日志解析核心逻辑3. 文件处

JSONArray在Java中的应用操作实例

《JSONArray在Java中的应用操作实例》JSONArray是org.json库用于处理JSON数组的类,可将Java对象(Map/List)转换为JSON格式,提供增删改查等操作,适用于前后端... 目录1. jsONArray定义与功能1.1 JSONArray概念阐释1.1.1 什么是JSONA