热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

对于Android中TextView组件一些理解

每次我用TextView组件,我都会抱怨不停,Android的TextView的设计师一定没有ListView设计师牛逼,在我的认知里,ListView是Android中一个伟大的组件,

每次我用TextView组件,我都会抱怨不停,Android的TextView的设计师一定没有ListView设计师牛逼,在我的认知里,ListView是Android中一个伟大的组件,伟大到无与伦比,而TextView则是糟糕透顶的组件,糟糕到恶心的境界.

当然,我没有资格对TextView与ListView进行评头论足,但是,无知的我,对于这两个组件只能认知到如此地步.

当对大文本进行编辑,或者你的文本大小只有短短的几百K,TextView内存都会飙升的溢出,终于莫一天我受够了,我开始思考怎样去改进TextView中的这个重大缺陷,

我们先来分析Android中TextView的代码劣势:

1.没有使用缓存技术,(如果硬要把Spans中的分页效果说成缓存技术,那就太做作了),

2.陷得太深,类与类之间联系太深

3.测量类的缺陷性,

    我们首先看一段StaticLayout中关于测量文本的generate方法代码

         ......      
int paraEnd;
for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
if (paraEnd <0)
paraEnd = bufEnd;
else
paraEnd++;
....
measured.setPara(source, paraStart, paraEnd, textDir, b);
......
for (int spanStart = paraStart, spanEnd; spanStart {
...
if (spanned == null) {
spanEnd = paraEnd;
int spanLen = spanEnd - spanStart;
measured.addStyleRun(paint, spanLen, fm);
} else {
spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
MetricAffectingSpan.class);
int spanLen = spanEnd - spanStart;
MetricAffectingSpan[] spans =
spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
measured.addStyleRun(paint, spans, spanLen, fm);
}
....

看到每次对Text进行格式化时,都会首先寻找下一行,我们再也看一下TextUtils中的indexOf代码

    public static int indexOf(CharSequence s, char ch, int start, int end) {
Class c = s.getClass();

if (s instanceof GetChars || c == StringBuffer.class ||
c == StringBuilder.class || c == String.class) {
final int INDEX_INCREMENT = 500;
char[] temp = obtain(INDEX_INCREMENT);

while (start int segend = start + INDEX_INCREMENT;
if (segend > end)
segend = end;

getChars(s, start, segend, temp, 0);

int count = segend - start;
for (int i = 0; i if (temp[i] == ch) {
recycle(temp);
return i + start;
}
}

start = segend;
}

recycle(temp);
return -1;
}

for (int i = start; i if (s.charAt(i) == ch)
return i;

return -1;
}

其中obtain()函数,对缓存给予的Text,进行数组缓存,也就是说,它是以500大小的char数组进行循环利用,也就是说当你的字符数为10000时,他需要进行循环20次以此类推.

乍一看的确没什么问题,代码很Android

再来看一下MeasureText中的setPara方法,

    /**
* Analyzes text for bidirectional runs. Allocates working buffers.
*/
void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir,
StaticLayout.Builder builder) {
mBuilder = builder;
mText = text;
mTextStart = start;

int len = end - start;
mLen = len;
mPos = 0;

if (mWidths == null || mWidths.length
}
if (mChars == null || mChars.length mChars = ArrayUtils.newUnpaddedCharArray(len);
}
TextUtils.getChars(text, start, end, mChars, 0);

if (text instanceof Spanned) {
Spanned spanned = (Spanned) text;
ReplacementSpan[] spans = spanned.getSpans(start, end,
ReplacementSpan.class);

for (int i = 0; i int startInPara = spanned.getSpanStart(spans[i]) - start;
int endInPara = spanned.getSpanEnd(spans[i]) - start;
// The span interval may be larger and must be restricted to [start, end[
if (startInPara <0) startInPara = 0;
if (endInPara > len) endInPara = len;
for (int j = startInPara; j mChars[j] = '\uFFFC'; // object replacement character
}
}
}
}
看到了吧,又一次进行了缓存数组,并且又同时缓存了字符宽度的数组,

通常当上述两个方法单独看待,没有出现缓存过剩,情况,但是,两个方法同时出现,再加上measured.addStyleRun方法会去获取字符宽度(越多越慢),就会出现很显著的原因,卡顿严重,GC不断,而当我们的文本并没有那么多的换行符,第二个方法又会导致内存飙升,从而最终导致了OOM,这就是为什么几百K,内存也会很大的原因所在.

既然知道了原因,那么怎么去优化呢?

有两种方案:

第一种>


在MeasureText中限制获取字符缓存宽度数组的长度,这样可以暂时性的解决掉测量类过量缓存的问题,但是相应的速度会有所下降,

将开始的TextUtils.indexof换行符的方法最大的检测长度(end-start),设置为你限定的量,

然后重写StaticLayout中的generate方法,删除最外部的for循环,修正第二层循环.

假设你设定的MeasureText中缓存宽度为100,(如果Text实现了Span接口,那么首先你去获取Span的结束点,检测一下起点与结束点的位置是否大于100),每次100字符长度,进行一次循环索引换行符与测量,

说的有些笼统,因为这里面有些东西没办法描述,因为TextView里面的设定很复杂,

这一种方法只是简单的解决了OOM的问题,并没有解决加载缓慢的问题,也就是说加载更慢(但是至少它不会出现几百K内存溢出的情况)

第二种>

使用ListView的缓存技术.

缓存当前屏幕的行数,依次不断.这一中是我着重实现的.

他同时包含了上述的优点之外,在速度上简直是秒速的存在,内存节约也是独树一帜.(我的手机2GRAM,2M的文本都不会溢出)

但是,它有了一个致命的缺点,下引速度缓慢,也就是说,如果未加载完全部的起点,你索引到最后字符,会很慢.

优点:
1>降低内耗,多行布局内存可降低1/1 - 1/3 之间,随文本大  - 小决定
2>单行布局,内存可降低 1/1 - 2/3
3>秒速增删改,秒速加载,与LinkedList优点重合
4>支持原始EMOJI(当然需要字体支持),如果存在外部EMOJI请重写TextLinear中drawBitmap方法
5>Cursor/LetterSpace/LineFormat/Ellipsis/Measure等实现高度自定义
缺点:
1.由于此TextAreaView为格式安全的,当未加载完Text全部起点时,下引速度缓慢,在1MB以内速度延迟<=2秒(换行符将明显加速下引)

Github地址:github.com/Maizer/TextAreaView


Note:拿来研究一下还是可以的,因为在某些方面会有BUG



推荐阅读
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • 本文详细介绍了MySQL表分区的创建、增加和删除方法,包括查看分区数据量和全库数据量的方法。欢迎大家阅读并给予点评。 ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 在开发app时,使用了butterknife后,在androidStudio打包apk时可能会遇到报错。为了解决这个问题,可以通过打开proguard-rules.pro文件进行代码混淆来解决。本文介绍了具体的混淆代码和方法。 ... [详细]
  • Inno Setup区段之Components篇相关知识详解
    本文详细介绍了Inno Setup区段之Components篇相关的知识,包括Components和Types的使用方式以及各个参数的说明,希望对读者有一定的参考价值。内容涵盖了ComponentsName、Description、Types、ExtraDiskSpaceRequired、ExtraDiskSpaceRequiredFlags等多个关键词,帮助读者更好地理解和应用Inno Setup区段之Components篇的知识。 ... [详细]
  • IOS开发之短信发送与拨打电话的方法详解
    本文详细介绍了在IOS开发中实现短信发送和拨打电话的两种方式,一种是使用系统底层发送,虽然无法自定义短信内容和返回原应用,但是简单方便;另一种是使用第三方框架发送,需要导入MessageUI头文件,并遵守MFMessageComposeViewControllerDelegate协议,可以实现自定义短信内容和返回原应用的功能。 ... [详细]
  • 修复安装win10失败并提示“磁盘布局不受UEFI固件支持”的方法
    本文介绍了修复安装win10失败并提示“磁盘布局不受UEFI固件支持”的方法。首先解释了UEFI的概念和作用,然后提供了两种解决方法。第一种方法是在bios界面中将Boot Mode设置为Legacy Support,Boot Priority设置为Legacy First,并关闭UEFI。第二种方法是使用U盘启动盘进入PE系统,运行磁盘分区工具DiskGenius,将硬盘的分区表设置为gpt格式,并留出288MB的内存。最后,通过运行界面输入命令cmd来完成设置。 ... [详细]
  • 本文讨论了如何使用Web.Config进行自定义配置节的配置转换。作者提到,他将msbuild设置为详细模式,但转换却忽略了带有替换转换的自定义部分的存在。 ... [详细]
author-avatar
醇情咖啡_799
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有