让你的EditText删除表情比微信更高效--记一次android性能分析优化实战(1)

结尾

最后,针对上面谈的内容,给大家推荐一个Android资料,应该对大家有用。

首先是一个知识清单:(对于现在的Android及移动互联网来说,我们需要掌握的技术)

泛型原理丶反射原理丶Java虚拟机原理丶线程池原理丶
注解原理丶注解原理丶序列化
Activity知识体系(Activity的生命周期丶Activity的任务栈丶Activity的启动模式丶View源码丶Fragment内核相关丶service原理等)
代码框架结构优化(数据结构丶排序算法丶设计模式)
APP性能优化(用户体验优化丶适配丶代码调优)
热修复丶热升级丶Hook技术丶IOC架构设计
NDK(c编程丶C++丶JNI丶LINUX)
如何提高开发效率?
MVC丶MVP丶MVVM
微信小程序
Hybrid
Flutter

接下来是资料清单:(敲黑板!!!


1.数据结构和算法

2.设计模式

3.全套体系化高级架构视频;七大主流技术模块,视频+源码+笔记

4.面试专题资料包(怎么能少了一份全面的面试题总结呢~)

不论遇到什么困难,都不应该成为我们放弃的理由!共勉~

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

}
return false;
}

  • 之前是直接删除
  • 新的方案是先取出文本内容,复制给新的SpannableStringBuilder,在设置到输入框之前删除表情,因为此时新的SpannableStringBuilder中并不包含ChangeWatcher所以不会多次调用ChangeWatcher.onSpanChanged()
  • 删除表情后再将SpannableStringBuilder设置给EditText
  • 最后设置光标位置

完成这一系列操作之后demo一跑,删除果然变流畅了,当时心里那个高兴啊,竟然做个功能可以比微信实现的还好那么一点

输入法问题

然而总是帅不过三秒。没过一会就发现了新的问题。

  • 百度输入法只能一个个删除表情,而不能长按一溜删下来(搜狗是可以的。。。)

刚战完微信又来个百度输入法,写个表情输入功能咋跟打游戏里的boss一样呢。本来自信满满要找出百度输入法的bug,但是从来没接触过输入法相关的开发工作,跑了跑google的输入法的sample还发现官方的输入法一样有问题,又挣扎了几下翻了翻源码,最终还是无功而返

虽然没解决输入法的问题,不过也不是完全没有收获

case DO_SEND_KEY_EVENT: {
InputConnection ic = getInputConnection();
if (ic == null || !isActive()) {
Log.w(TAG, “sendKeyEvent on inactive InputConnection”);
return;
}
ic.sendKeyEvent((KeyEvent)msg.obj);
onUserAction();
return;
}

  • W/IInputConnectionWrapper: sendKeyEvent on inactive InputConnection连续删除时会出现这样的log,搜狗输入法也会出现,估计是百度输入法在出现这样的情况时就把删除按钮的触摸事件给中断了
  • 出现上面log的原因是因为InputConnection在setText()时需要被重新创建,而第二次删除时InputConnection可能还没创建好或者IInputConnectionWrapper没处于激活状态

完全版的解决方案

跟输入法死磕几天未果正愁着呢,突然想到谷歌在android 8.0发布的时候推出了一个Emoji表情库,Emoji出现在TextView中逃不出也用的是ImageSpan,想看看谷歌会不会也有从中间开始删除表情卡顿的feature,就去找了下这个库的demo,一跑发现demo中不管从末尾还是从中间删都不会卡。顿时燃起了解决这个问题的希望,看完代码才发现解决方案如此简单

之前定位到问题在于ChangeWatcher,但它是一个内部类,自己想的法子都是在外部怎么避免ChangeWatcher.onSpanChanged()被调用,谷歌直接简单粗暴的用反射获取了ChangeWatcher的Class对象,在setSpan()的时候发现如果是ChangeWatcher就把它包装在新的WatcherWrapper中,所有的操作都通过WatcherWrapper中转,就可以随心所欲控制onSpanChanged了

自定义一个Editable.Factory

  • 用反射获取了DynamicLayout.ChangeWatcher的Class对象
  • 将Class对象作为新的SpannableStringBuilder的构造参数传入

final class ImageEditableFactory extends Factory {

private static final Object sInstanceLock = new Object();
@GuardedBy(“sInstanceLock”)
private static volatile Factory sInstance;
@Nullable
private static Class<?> sWatcherClass;

@SuppressLint({“PrivateApi”})
private ImageEditableFactory() {
try {
String className = “android.text.DynamicLayout$ChangeWatcher”;
sWatcherClass = this.getClass().getClassLoader().loadClass(className);
} catch (Throwable var2) {
;
}

}

public static Factory getInstance() {
if (sInstance == null) {
Object var0 = sInstanceLock;
synchronized (sInstanceLock) {
if (sInstance == null) {
sInstance = new ImageEditableFactory();
}
}
}

return sInstance;
}

public Editable newEditable(@NonNull CharSequence source) {
return (Editable) (sWatcherClass != null ? SpannableBuilder.create(sWatcherClass, source)
: super.newEditable(source));
}
}

自定义一个SpannableStringBuilder

  • 定义一个WatcherWrapper将ChangeWatcher包装起来,所有之前对ChangeWatcher的调用都通过WatcherWrapper完成
  • 这里onSpanChanged就对ImageSpan特殊处理了,直接返回不调用ChangeWatcher.onSpanChanged
  • 覆盖SpannableStringBuilder的相关方法
  • 对和Span相关的方法特殊处理

贴上WatcherWrapper 的代码,自定义SpannableStringBuilder代码就不贴了,大家可以去项目里找com.sunhapper.spedittool.view.SpannableBuilder自己看

private static class WatcherWrapper implements TextWatcher, SpanWatcher {

private final Object mObject;
private final AtomicInteger mBlockCalls = new AtomicInteger(0);

WatcherWrapper(Object object) {
this.mObject = object;
}

@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
((TextWatcher) mObject).beforeTextChanged(s, start, count, after);
}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
((TextWatcher) mObject).onTextChanged(s, start, before, count);
}

@Override
public void afterTextChanged(Editable s) {
((TextWatcher) mObject).afterTextChanged(s);
}

@Override
public void onSpanAdded(Spannable text, Object what, int start, int end) {
if (mBlockCalls.get() > 0 && isImageSpan(what)) {
return;
}
((SpanWatcher) mObject).onSpanAdded(text, what, start, end);
}

@Override
public void onSpanRemoved(Spannable text, Object what, int start, int end) {
if (mBlockCalls.get() > 0 && isImageSpan(what)) {
return;
}
((SpanWatcher) mObject).onSpanRemoved(text, what, start, end);
}

@Override
public void onSpanChanged(Spannable text, Object what, int ostart, int oend, int nstart,
int nend) {
if (mBlockCalls.get() > 0 && isImageSpan(what)) {
return;
}
((SpanWatcher) mObject).onSpanChanged(text, what, ostart, oend, nstart, nend);
}

final void blockCalls() {
mBlockCalls.incrementAndGet();
}

final void unblockCalls() {
mBlockCalls.decrementAndGet();
}

private boolean isImageSpan(final Object span) {
return span instanceof ImageSpan;
}
}

设置EditText的EditableFactory

setEditableFactory(ImageEditableFactory.getInstance());

自己的demo一跑果然无论从哪个位置删都不会卡顿了

总结

  • 性能分析工具可以帮助自己快速定位问题,对于android sdk这种不太好调试的代码更是事半功倍
  • 解决问题的时候不要一味死磕,特别对于自己不熟悉的东西,有可能思路本身就是错的
  • 对于一些私有的方法,用反射可以实现很多风骚操作~

完整代码

最后

如果你觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

希望读到这的您能转发分享和关注一下我,以后还会更新技术干货,谢谢您的支持!

转发+点赞+关注,第一时间获取最新知识点

Android架构师之路很漫长,一起共勉吧!

以下墙裂推荐阅读!!!

最后祝大家生活愉快~

Android进阶资料

以下的资料是近年来,我和一些朋友面试收集整理了很多大厂的面试真题和资料,还有来自如阿里、小米、爱奇艺等一线大厂的大牛整理的架构进阶资料。希望可以帮助到大家。

Android进阶核心笔记

百万年薪必刷面试题

最全Android进阶学习视频

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

cvd5vTGa-1715472229231)]

最全Android进阶学习视频

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 13
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值